From 60d550a4f97591a315ed86c835a6526c9ed4999d Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 6 Jul 2025 20:08:17 +0200 Subject: [PATCH 001/224] =?UTF-8?q?=F0=9F=93=9D=20(config):=20Ampl=C3=ADa?= =?UTF-8?q?=20y=20corrige=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- helpers/pagetop-macros/src/lib.rs | 4 +- src/config.rs | 119 ++++++++++++++++++++++++++---- src/lib.rs | 2 +- 4 files changed, 108 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7293f88d..159aabe0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HT La aplicación más sencilla de `PageTop` se ve así: -```rust#ignore +```rust use pagetop::prelude::*; #[pagetop::main] diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 1ed29c5b..31d8dcdd 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -21,7 +21,7 @@ use quote::quote; /// /// # Ejemplos /// -/// ```rust#ignore +/// ```rust,ignore /// #[pagetop::main] /// async fn main() { /// async { println!("Hello world!"); }.await @@ -42,7 +42,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { /// /// # Ejemplos /// -/// ```rust#ignore +/// ```rust,ignore /// #[pagetop::test] /// async fn test() { /// assert_eq!(async { "Hello world" }.await, "Hello world"); diff --git a/src/config.rs b/src/config.rs index aa5790ca..eae182fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,7 +55,7 @@ //! Y usa la macro [`include_config!`](crate::include_config) para inicializar tus ajustes en una //! estructura con tipos seguros. Por ejemplo: //! -//! ```rust#ignore +//! ```rust,no_run //! use pagetop::prelude::*; //! use serde::Deserialize; //! @@ -94,7 +94,7 @@ //! //! # Usando tus opciones de configuración //! -//! ```rust#ignore +//! ```rust,ignore //! use pagetop::prelude::*; //! use crate::config; //! @@ -160,20 +160,109 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( .expect("Failed to set application run mode") }); +/// Incluye los ajustes necesarios de la configuración anticipando valores por defecto. +/// +/// ### Sintaxis +/// +/// Hay que añadir en nuestra librería el siguiente código: +/// +/// ```rust,ignore +/// include_config!(SETTINGS: Settings => [ +/// "ruta.clave" => valor, +/// // … +/// ]); +/// ``` +/// +/// donde: +/// +/// * **`SETTINGS_NAME`** es el nombre de la variable global que se usará para referenciar los +/// ajustes. Se recomienda usar `SETTINGS`, aunque no es obligatorio. +/// * **`Settings_Type`** es la referencia a la estructura que define los tipos para deserializar la +/// configuración. Debe implementar `Deserialize` (derivable con `#[derive(Deserialize)]`). +/// * **Lista de pares** con las claves TOML que requieran valores por defecto. Siguen la notación +/// `"seccion.subclave"` para coincidir con el árbol TOML. +/// +/// ### Ejemplo básico +/// +/// ```rust,no_run +/// use pagetop::prelude::*; +/// use serde::Deserialize; +/// +/// include_config!(SETTINGS: BlogSettings => [ +/// // [blog] +/// "blog.title" => "Mi Blog", +/// "blog.port" => 8080, +/// ]); +/// +/// #[derive(Debug, Deserialize)] +/// pub struct BlogSettings { +/// pub blog: Blog, +/// } +/// +/// #[derive(Debug, Deserialize)] +/// pub struct Blog { +/// pub title: String, +/// pub description: Option, +/// pub port: u16, +/// } +/// +/// fn print_title() { +/// // Lectura en tiempo de ejecución. +/// println!("Título: {}", SETTINGS.blog.title); +/// } +/// ``` +/// +/// ### Buenas prácticas +/// +/// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves +/// opcionales pueden ser `Option`. +/// +/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.e. `[blog]`) para +/// evitar colisiones con otras librerías. +/// +/// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para +/// configurar distintos entornos (*dev*, *staging*, *prod*) usa los archivos TOML descritos en la +/// documentación de [`config`](crate::config). +/// +/// * **Errores explícitos**. Si la deserialización falla, la macro lanzará un `panic!` con un +/// mensaje que indica la estructura problemática, facilitando la depuración. +/// +/// ### Requisitos +/// +/// * Dependencia `serde` con la *feature* `derive`. +/// * Las claves deben coincidir con los campos (*snake case*) de tu estructura `Settings_Type`. +/// +/// ```toml +/// [dependencies] +/// serde = { version = "1.0", features = ["derive"] } +/// ``` + #[macro_export] macro_rules! include_config { - ( $SETTINGS:ident : $Settings:ty => [ $( $key:expr => $value:expr ),* $(,)? ] ) => { - /// Valores asignados o predefinidos para la configuración de [`$Settings`]. - pub static $SETTINGS: std::sync::LazyLock<$Settings> = std::sync::LazyLock::new(|| { - let mut settings = $crate::config::CONFIG_VALUES.clone(); - $( - settings = settings.set_default($key, $value).unwrap(); - )* - settings - .build() - .expect(concat!("Failed to build config for ", stringify!($Settings))) - .try_deserialize::<$Settings>() - .expect(concat!("Error parsing settings for ", stringify!($Settings))) - }); + ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { + #[doc = concat!( + "Referencia a los ajustes de configuración deserializados de [`", + stringify!($Settings_Type), + "`]." + )] + #[doc = ""] + #[doc = "Valores por defecto:"] + #[doc = "```text"] + $( + #[doc = concat!($k, " = ", stringify!($v))] + )* + #[doc = "```"] + pub static $SETTINGS_NAME: std::sync::LazyLock<$Settings_Type> = + std::sync::LazyLock::new(|| { + let mut settings = $crate::config::CONFIG_VALUES.clone(); + $( + settings = settings.set_default($k, $v).unwrap(); + )* + settings + .build() + .expect(concat!("Failed to build config for ", stringify!($Settings_Type))) + .try_deserialize::<$Settings_Type>() + .expect(concat!("Error parsing settings for ", stringify!($Settings_Type))) + }); }; } diff --git a/src/lib.rs b/src/lib.rs index 82f9ebaa..81e7bc6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ //! //! La aplicación más sencilla de `PageTop` se ve así: //! -//! ```rust#ignore +//! ```rust,no_run //! use pagetop::prelude::*; //! //! #[pagetop::main] From 84e48a3357705070fab06504d73bc919c77c91cc Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 6 Jul 2025 23:03:21 +0200 Subject: [PATCH 002/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20gesti=C3=B3n?= =?UTF-8?q?=20de=20trazas=20y=20registro=20de=20eventos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reexporta macros esenciales de `tracing` para el registro de eventos. - Inicializa el gestor de trazas en el servidor web. --- .gitignore | 3 + Cargo.lock | 324 ++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 7 +- src/app.rs | 23 ++-- src/global.rs | 51 +++++--- src/lib.rs | 2 + src/prelude.rs | 2 + src/trace.rs | 85 +++++++++++++ 8 files changed, 467 insertions(+), 30 deletions(-) create mode 100644 src/trace.rs diff --git a/.gitignore b/.gitignore index 56ee30e4..65db440e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Ignora directorios de compilación **/target +# Archivos de log +**/log/*.log* + # Archivos de configuración locales **/local.*.toml **/local.toml diff --git a/Cargo.lock b/Cargo.lock index 045a72fd..43eb2107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" @@ -369,6 +375,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -737,12 +758,28 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.174" @@ -794,6 +831,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.5" @@ -827,6 +873,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mutually_exclusive_features" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -848,9 +910,15 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pagetop" -version = "0.0.2" +version = "0.0.3" dependencies = [ "actix-web", "colored", @@ -860,6 +928,10 @@ dependencies = [ "serde", "substring", "terminal_size", + "tracing", + "tracing-actix-web", + "tracing-appender", + "tracing-subscriber", ] [[package]] @@ -904,6 +976,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1016,8 +1108,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -1028,7 +1129,7 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] @@ -1037,6 +1138,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -1062,6 +1169,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ryu" version = "1.0.20" @@ -1138,6 +1251,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1222,6 +1344,35 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.41" @@ -1339,6 +1490,31 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-actix-web" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "pin-project", + "tracing", + "uuid", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.30" @@ -1357,6 +1533,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -1394,6 +1613,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -1415,6 +1651,86 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index a844f527..f5948d56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.2" +version = "0.0.3" edition = "2021" description = """\ @@ -22,6 +22,11 @@ serde.workspace = true substring = "1.4.5" terminal_size = "0.4.2" +tracing = "0.1.41" +tracing-appender = "0.2.3" +tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } +tracing-actix-web = "0.7.18" + actix-web = "4.11.0" pagetop-macros.workspace = true diff --git a/src/app.rs b/src/app.rs index bac74821..d21c08a0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,11 +2,12 @@ mod figfont; -use crate::{global, service}; +use crate::{global, service, trace}; use substring::Substring; use std::io::Error; +use std::sync::LazyLock; pub struct Application; @@ -15,6 +16,10 @@ impl Application { pub fn new() -> Self { // Al arrancar muestra una cabecera para la aplicación. Self::show_banner(); + + // Inicia gestión de trazas y registro de eventos (logging). + LazyLock::force(&trace::TRACING); + Self } @@ -62,13 +67,15 @@ impl Application { /// Ejecuta el servidor web de la aplicación. pub fn run(self) -> Result { // Prepara el servidor web. - Ok(service::HttpServer::new(move || Self::service_app()) - .bind(format!( - "{}:{}", - &global::SETTINGS.server.bind_address, - &global::SETTINGS.server.bind_port - ))? - .run()) + Ok(service::HttpServer::new(move || { + Self::service_app().wrap(tracing_actix_web::TracingLogger::default()) + }) + .bind(format!( + "{}:{}", + &global::SETTINGS.server.bind_address, + &global::SETTINGS.server.bind_port + ))? + .run()) } /// Prepara el servidor web de la aplicación para pruebas. diff --git a/src/global.rs b/src/global.rs index 5e3d52f2..8432032a 100644 --- a/src/global.rs +++ b/src/global.rs @@ -10,48 +10,65 @@ include_config!(SETTINGS: Settings => [ "app.description" => "Developed with the amazing PageTop framework.", "app.startup_banner" => "Slant", + // [log] + "log.tracing" => "Info", + "log.rolling" => "Stdout", + "log.path" => "log", + "log.prefix" => "tracing.log", + "log.format" => "Full", + // [server] "server.bind_address" => "localhost", "server.bind_port" => 8080, ]); #[derive(Debug, Deserialize)] -/// Ajustes de configuración para las secciones globales [`[app]`](App) y [`[server]`](Server). -/// Consulta [`SETTINGS`] para los valores por defecto. +/// Ajustes para las secciones globales [`[app]`](App), [`[log]`](Log) y [`[server]`](Server) de +/// [`SETTINGS`]. pub struct Settings { pub app: App, + pub log: Log, pub server: Server, } #[derive(Debug, Deserialize)] -/// Sección `[app]` de la configuración. -/// -/// Forma parte de [`Settings`]. +/// Sección `[app]` de la configuración. Forma parte de [`Settings`]. pub struct App { /// Nombre de la aplicación. - /// Valor por defecto: *"Sample"*. pub name: String, /// Breve descripción de la aplicación. - /// Valor por defecto: *"Developed with the amazing PageTop framework."*. pub description: String, - /// ASCII banner printed at startup: *"Off"*, *"Slant"*, *"Small"*, *"Speed"*, or *"Starwars"*. - /// Default: *"Slant"*. + /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o + /// *"Starwars"*. pub startup_banner: String, - /// Modo de ejecución. - /// Valor por defecto: el definido por la variable de entorno - /// `PAGETOP_RUN_MODE`, o *"default"* si no está establecida. + /// Modo de ejecución, dado por la variable de entorno `PAGETOP_RUN_MODE`, o *"default"* si no + /// está definido. pub run_mode: String, } #[derive(Debug, Deserialize)] -/// Sección `[server]` de la configuración. -/// -/// Forma parte de [`Settings`]. +/// Sección `[log]` de la configuración. Forma parte de [`Settings`]. +pub struct Log { + /// Opciones, o combinación de opciones separadas por comas, para filtrar las trazas: *"Error"*, + /// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*. + /// Ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug". + pub tracing: String, + /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o las registra en archivos con + /// rotación: *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. + pub rolling: String, + /// Directorio para los archivos de traza (si `rolling` ≠ *"Stdout"*). + pub path: String, + /// Prefijo para los archivos de traza (si `rolling` ≠ *"Stdout"*). + pub prefix: String, + /// Formato de salida de las trazas. Opciones: *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*. + pub format: String, +} + +#[derive(Debug, Deserialize)] +/// Sección `[server]` de la configuración. Forma parte de [`Settings`]. pub struct Server { /// Dirección de enlace para el servidor web. - /// Valor por defecto: *"localhost"*. pub bind_address: String, /// Puerto de escucha del servidor web. - /// Valor por defecto: *8088*. pub bind_port: u16, } diff --git a/src/lib.rs b/src/lib.rs index 81e7bc6b..308c1633 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ pub use pagetop_macros::{main, test}; pub mod config; // Opciones de configuración globales. pub mod global; +// Gestión de trazas y registro de eventos de la aplicación. +pub mod trace; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/prelude.rs b/src/prelude.rs index bee222e8..5d5851fc 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,6 +13,8 @@ pub use crate::include_config; pub use crate::global; +pub use crate::trace; + pub use crate::service; pub use crate::app::Application; diff --git a/src/trace.rs b/src/trace.rs new file mode 100644 index 00000000..93a8ff2d --- /dev/null +++ b/src/trace.rs @@ -0,0 +1,85 @@ +//! Gestión de trazas y registro de eventos de la aplicación. +//! +//! `PageTop` recopila información de diagnóstico de la aplicación de forma estructurada y basada en +//! eventos. +//! +//! En los sistemas asíncronos, interpretar los mensajes de log tradicionales suele volverse +//! complicado. Las tareas individuales se multiplexan en el mismo hilo y los eventos y registros +//! asociados se entremezclan, lo que dificulta seguir la secuencia lógica. +//! +//! `PageTop` usa [`tracing`](https://docs.rs/tracing) para registrar eventos estructurados y con +//! información adicional sobre la *temporalidad* y la *causalidad*. A diferencia de un mensaje de +//! log, un *span* (intervalo) tiene un momento de inicio y de fin, puede entrar y salir del flujo +//! de ejecución y puede existir dentro de un árbol anidado de *spans* similares. Además, estos +//! *spans* son estructurados, con la capacidad de registrar tipos de datos y mensajes de texto. + +use crate::global; + +pub use tracing::{debug, error, info, trace, warn}; +pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span}; + +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::EnvFilter; + +use std::sync::LazyLock; + +/// Trazado y registro de eventos de la aplicación. +/// +/// Para aumentar el rendimiento, un hilo dedicado utiliza un sistema de escritura no bloqueante que +/// actúa de forma periódica en lugar de enviar cada traza o evento al instante. Si el programa +/// termina abruptamente (por ejemplo, debido a un `panic!` o a una llamada a `std::process::exit`), +/// es posible que algunas trazas o eventos no se envíen. +/// +/// Dado que las trazas o eventos registrados poco antes de un fallo suelen ser cruciales para +/// diagnosticar la causa, `Lazy` garantiza que todos los registros almacenados se +/// envíen antes de finalizar la ejecución. + +#[rustfmt::skip] +pub(crate) static TRACING: LazyLock = LazyLock::new(|| { + let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) + .unwrap_or_else(|_| EnvFilter::new("Info")); + + let rolling = global::SETTINGS.log.rolling.to_lowercase(); + + let (non_blocking, guard) = match rolling.as_str() { + "stdout" => tracing_appender::non_blocking(std::io::stdout()), + _ => tracing_appender::non_blocking({ + let path = &global::SETTINGS.log.path; + let prefix = &global::SETTINGS.log.prefix; + match rolling.as_str() { + "daily" => tracing_appender::rolling::daily(path, prefix), + "hourly" => tracing_appender::rolling::hourly(path, prefix), + "minutely" => tracing_appender::rolling::minutely(path, prefix), + "endless" => tracing_appender::rolling::never(path, prefix), + _ => { + println!( + "Rolling value \"{}\" not valid. Using \"daily\". Check the settings file.", + global::SETTINGS.log.rolling, + ); + tracing_appender::rolling::daily(path, prefix) + } + } + }), + }; + + let subscriber = tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_writer(non_blocking) + .with_ansi(rolling.as_str() == "stdout"); + + match global::SETTINGS.log.format.to_lowercase().as_str() { + "json" => subscriber.json().init(), + "full" => subscriber.init(), + "compact" => subscriber.compact().init(), + "pretty" => subscriber.pretty().init(), + _ => { + println!( + "Tracing format \"{}\" not valid. Using \"Full\". Check the settings file.", + global::SETTINGS.log.format, + ); + subscriber.init(); + } + } + + guard +}); From 1fb8e937d161cfb1a894d0482d1cb9ea6bf29d2d Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 7 Jul 2025 18:21:00 +0200 Subject: [PATCH 003/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20macro=20para?= =?UTF-8?q?=20componer=20HTML=20en=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incorpora el código de la versión 0.25.0 de `maud_macros` creado por Chris Wong. - Y reexporta los elementos esenciales desde la librería principal. --- Cargo.lock | 80 ++- Cargo.toml | 3 +- helpers/pagetop-macros/Cargo.toml | 4 + helpers/pagetop-macros/README.md | 7 + helpers/pagetop-macros/src/lib.rs | 9 + helpers/pagetop-macros/src/maud.rs | 39 + helpers/pagetop-macros/src/maud/ast.rs | 221 ++++++ helpers/pagetop-macros/src/maud/escape.rs | 34 + helpers/pagetop-macros/src/maud/generate.rs | 308 ++++++++ helpers/pagetop-macros/src/maud/parse.rs | 752 ++++++++++++++++++++ src/html.rs | 4 + src/html/maud.rs | 350 +++++++++ src/html/maud/escape.rs | 34 + src/lib.rs | 4 +- src/prelude.rs | 4 +- 15 files changed, 1834 insertions(+), 19 deletions(-) create mode 100644 helpers/pagetop-macros/src/maud.rs create mode 100644 helpers/pagetop-macros/src/maud/ast.rs create mode 100644 helpers/pagetop-macros/src/maud/escape.rs create mode 100644 helpers/pagetop-macros/src/maud/generate.rs create mode 100644 helpers/pagetop-macros/src/maud/parse.rs create mode 100644 src/html.rs create mode 100644 src/html/maud.rs create mode 100644 src/html/maud/escape.rs diff --git a/Cargo.lock b/Cargo.lock index 43eb2107..d53ffd7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -182,7 +182,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -426,7 +426,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "unicode-xid", ] @@ -448,7 +448,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -918,12 +918,13 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.3" +version = "0.0.4" dependencies = [ "actix-web", "colored", "config", "figlet-rs", + "itoa", "pagetop-macros", "serde", "substring", @@ -938,7 +939,11 @@ dependencies = [ name = "pagetop-macros" version = "0.0.1" dependencies = [ + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", "quote", + "syn 2.0.104", ] [[package]] @@ -993,7 +998,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1038,6 +1043,39 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1204,7 +1242,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1312,6 +1350,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -1331,7 +1379,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1361,7 +1409,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1523,7 +1571,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1673,7 +1721,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -1695,7 +1743,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1857,7 +1905,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -1878,7 +1926,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1898,7 +1946,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -1932,7 +1980,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f5948d56..b8b3f5fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.3" +version = "0.0.4" edition = "2021" description = """\ @@ -18,6 +18,7 @@ authors.workspace = true colored = "3.0.0" config = { version = "0.15.11", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" +itoa = "1.0.15" serde.workspace = true substring = "1.4.5" terminal_size = "0.4.2" diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 7a4d30e1..80413821 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -18,4 +18,8 @@ authors.workspace = true proc-macro = true [dependencies] +proc-macro2 = "1.0.95" +proc-macro-crate = "3.3.0" +proc-macro-error = "1.0.4" quote = "1.0.40" +syn = { version = "2.0.104", features = ["full"] } diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 8bdc94c2..06799668 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -8,6 +8,13 @@ +## Descripción general + +Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros) +([0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros)) de +[Chris Wong](https://crates.io/users/lambda-fairy) para no tener que referenciar `maud` en las +dependencias del archivo `Cargo.toml` de cada proyecto `PageTop`. + ## Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 31d8dcdd..65badb04 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -14,9 +14,18 @@ //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles //! y configurables, basadas en HTML, CSS y JavaScript. +mod maud; + use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; use quote::quote; +#[proc_macro] +#[proc_macro_error] +pub fn html(input: TokenStream) -> TokenStream { + maud::expand(input.into()).into() +} + /// Define una función `main` asíncrona como punto de entrada de `PageTop`. /// /// # Ejemplos diff --git a/helpers/pagetop-macros/src/maud.rs b/helpers/pagetop-macros/src/maud.rs new file mode 100644 index 00000000..a4e7873f --- /dev/null +++ b/helpers/pagetop-macros/src/maud.rs @@ -0,0 +1,39 @@ +// #![doc(html_root_url = "https://docs.rs/maud_macros/0.25.0")] +// TokenStream values are reference counted, and the mental overhead of tracking +// lifetimes outweighs the marginal gains from explicit borrowing +// #![allow(clippy::needless_pass_by_value)] + +mod ast; +mod escape; +mod generate; +mod parse; + +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::quote; + +pub fn expand(input: TokenStream) -> TokenStream { + let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); + // Heuristic: the size of the resulting markup tends to correlate with the + // code size of the template itself + let size_hint = input.to_string().len(); + let markups = parse::parse(input); + let stmts = generate::generate(markups, output_ident.clone()); + + let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`"); + let pre_escaped = match found_crate { + FoundCrate::Itself => quote!( + crate::html::PreEscaped(#output_ident) + ), + _ => quote!( + pagetop::html::PreEscaped(#output_ident) + ), + }; + + quote!({ + extern crate alloc; + let mut #output_ident = alloc::string::String::with_capacity(#size_hint); + #stmts + #pre_escaped + }) +} diff --git a/helpers/pagetop-macros/src/maud/ast.rs b/helpers/pagetop-macros/src/maud/ast.rs new file mode 100644 index 00000000..cd8a2cef --- /dev/null +++ b/helpers/pagetop-macros/src/maud/ast.rs @@ -0,0 +1,221 @@ +use proc_macro2::{TokenStream, TokenTree}; +use proc_macro_error::SpanRange; + +#[derive(Debug)] +pub enum Markup { + /// Used as a placeholder value on parse error. + ParseError { + span: SpanRange, + }, + Block(Block), + Literal { + content: String, + span: SpanRange, + }, + Symbol { + symbol: TokenStream, + }, + Splice { + expr: TokenStream, + outer_span: SpanRange, + }, + Element { + name: TokenStream, + attrs: Vec, + body: ElementBody, + }, + Let { + at_span: SpanRange, + tokens: TokenStream, + }, + Special { + segments: Vec, + }, + Match { + at_span: SpanRange, + head: TokenStream, + arms: Vec, + arms_span: SpanRange, + }, +} + +impl Markup { + pub fn span(&self) -> SpanRange { + match *self { + Markup::ParseError { span } => span, + Markup::Block(ref block) => block.span(), + Markup::Literal { span, .. } => span, + Markup::Symbol { ref symbol } => span_tokens(symbol.clone()), + Markup::Splice { outer_span, .. } => outer_span, + Markup::Element { + ref name, ref body, .. + } => { + let name_span = span_tokens(name.clone()); + name_span.join_range(body.span()) + } + Markup::Let { + at_span, + ref tokens, + } => at_span.join_range(span_tokens(tokens.clone())), + Markup::Special { ref segments } => join_ranges(segments.iter().map(Special::span)), + Markup::Match { + at_span, arms_span, .. + } => at_span.join_range(arms_span), + } + } +} + +#[derive(Debug)] +pub enum Attr { + Class { + dot_span: SpanRange, + name: Markup, + toggler: Option, + }, + Id { + hash_span: SpanRange, + name: Markup, + }, + Named { + named_attr: NamedAttr, + }, +} + +impl Attr { + pub fn span(&self) -> SpanRange { + match *self { + Attr::Class { + dot_span, + ref name, + ref toggler, + } => { + let name_span = name.span(); + let dot_name_span = dot_span.join_range(name_span); + if let Some(toggler) = toggler { + dot_name_span.join_range(toggler.cond_span) + } else { + dot_name_span + } + } + Attr::Id { + hash_span, + ref name, + } => { + let name_span = name.span(); + hash_span.join_range(name_span) + } + Attr::Named { ref named_attr } => named_attr.span(), + } + } +} + +#[derive(Debug)] +pub enum ElementBody { + Void { semi_span: SpanRange }, + Block { block: Block }, +} + +impl ElementBody { + pub fn span(&self) -> SpanRange { + match *self { + ElementBody::Void { semi_span } => semi_span, + ElementBody::Block { ref block } => block.span(), + } + } +} + +#[derive(Debug)] +pub struct Block { + pub markups: Vec, + pub outer_span: SpanRange, +} + +impl Block { + pub fn span(&self) -> SpanRange { + self.outer_span + } +} + +#[derive(Debug)] +pub struct Special { + pub at_span: SpanRange, + pub head: TokenStream, + pub body: Block, +} + +impl Special { + pub fn span(&self) -> SpanRange { + let body_span = self.body.span(); + self.at_span.join_range(body_span) + } +} + +#[derive(Debug)] +pub struct NamedAttr { + pub name: TokenStream, + pub attr_type: AttrType, +} + +impl NamedAttr { + fn span(&self) -> SpanRange { + let name_span = span_tokens(self.name.clone()); + if let Some(attr_type_span) = self.attr_type.span() { + name_span.join_range(attr_type_span) + } else { + name_span + } + } +} + +#[derive(Debug)] +pub enum AttrType { + Normal { value: Markup }, + Optional { toggler: Toggler }, + Empty { toggler: Option }, +} + +impl AttrType { + fn span(&self) -> Option { + match *self { + AttrType::Normal { ref value } => Some(value.span()), + AttrType::Optional { ref toggler } => Some(toggler.span()), + AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span), + } + } +} + +#[derive(Debug)] +pub struct Toggler { + pub cond: TokenStream, + pub cond_span: SpanRange, +} + +impl Toggler { + fn span(&self) -> SpanRange { + self.cond_span + } +} + +#[derive(Debug)] +pub struct MatchArm { + pub head: TokenStream, + pub body: Block, +} + +pub fn span_tokens>(tokens: I) -> SpanRange { + join_ranges(tokens.into_iter().map(|s| SpanRange::single_span(s.span()))) +} + +pub fn join_ranges>(ranges: I) -> SpanRange { + let mut iter = ranges.into_iter(); + let first = match iter.next() { + Some(span) => span, + None => return SpanRange::call_site(), + }; + let last = iter.last().unwrap_or(first); + first.join_range(last) +} + +pub fn name_to_string(name: TokenStream) -> String { + name.into_iter().map(|token| token.to_string()).collect() +} diff --git a/helpers/pagetop-macros/src/maud/escape.rs b/helpers/pagetop-macros/src/maud/escape.rs new file mode 100644 index 00000000..49ece776 --- /dev/null +++ b/helpers/pagetop-macros/src/maud/escape.rs @@ -0,0 +1,34 @@ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!! PLEASE KEEP THIS IN SYNC WITH `maud/src/escape.rs` !!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +extern crate alloc; + +use alloc::string::String; + +pub fn escape_to_string(input: &str, output: &mut String) { + for b in input.bytes() { + match b { + b'&' => output.push_str("&"), + b'<' => output.push_str("<"), + b'>' => output.push_str(">"), + b'"' => output.push_str("""), + _ => unsafe { output.as_mut_vec().push(b) }, + } + } +} + +#[cfg(test)] +mod test { + extern crate alloc; + + use super::escape_to_string; + use alloc::string::String; + + #[test] + fn it_works() { + let mut s = String::new(); + escape_to_string("", &mut s); + assert_eq!(s, "<script>launchMissiles()</script>"); + } +} diff --git a/helpers/pagetop-macros/src/maud/generate.rs b/helpers/pagetop-macros/src/maud/generate.rs new file mode 100644 index 00000000..be7946d0 --- /dev/null +++ b/helpers/pagetop-macros/src/maud/generate.rs @@ -0,0 +1,308 @@ +use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree}; +use proc_macro_error::SpanRange; +use quote::quote; + +use crate::maud::{ast::*, escape}; + +use proc_macro_crate::{crate_name, FoundCrate}; + +pub fn generate(markups: Vec, output_ident: TokenTree) -> TokenStream { + let mut build = Builder::new(output_ident.clone()); + Generator::new(output_ident).markups(markups, &mut build); + build.finish() +} + +struct Generator { + output_ident: TokenTree, +} + +impl Generator { + fn new(output_ident: TokenTree) -> Generator { + Generator { output_ident } + } + + fn builder(&self) -> Builder { + Builder::new(self.output_ident.clone()) + } + + fn markups(&self, markups: Vec, build: &mut Builder) { + for markup in markups { + self.markup(markup, build); + } + } + + fn markup(&self, markup: Markup, build: &mut Builder) { + match markup { + Markup::ParseError { .. } => {} + Markup::Block(Block { + markups, + outer_span, + }) => { + if markups + .iter() + .any(|markup| matches!(*markup, Markup::Let { .. })) + { + self.block( + Block { + markups, + outer_span, + }, + build, + ); + } else { + self.markups(markups, build); + } + } + Markup::Literal { content, .. } => build.push_escaped(&content), + Markup::Symbol { symbol } => self.name(symbol, build), + Markup::Splice { expr, .. } => self.splice(expr, build), + Markup::Element { name, attrs, body } => self.element(name, attrs, body, build), + Markup::Let { tokens, .. } => build.push_tokens(tokens), + Markup::Special { segments } => { + for Special { head, body, .. } in segments { + build.push_tokens(head); + self.block(body, build); + } + } + Markup::Match { + head, + arms, + arms_span, + .. + } => { + let body = { + let mut build = self.builder(); + for MatchArm { head, body } in arms { + build.push_tokens(head); + self.block(body, &mut build); + } + build.finish() + }; + let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body)); + body.set_span(arms_span.collapse()); + build.push_tokens(quote!(#head #body)); + } + } + } + + fn block( + &self, + Block { + markups, + outer_span, + }: Block, + build: &mut Builder, + ) { + let block = { + let mut build = self.builder(); + self.markups(markups, &mut build); + build.finish() + }; + let mut block = TokenTree::Group(Group::new(Delimiter::Brace, block)); + block.set_span(outer_span.collapse()); + build.push_tokens(TokenStream::from(block)); + } + + fn splice(&self, expr: TokenStream, build: &mut Builder) { + let output_ident = self.output_ident.clone(); + + let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`"); + build.push_tokens(match found_crate { + FoundCrate::Itself => quote!( + crate::html::html_private::render_to!(&#expr, &mut #output_ident); + ), + _ => quote!( + pagetop::html::html_private::render_to!(&#expr, &mut #output_ident); + ), + }); + } + + fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut Builder) { + build.push_str("<"); + self.name(name.clone(), build); + self.attrs(attrs, build); + build.push_str(">"); + if let ElementBody::Block { block } = body { + self.markups(block.markups, build); + build.push_str(""); + } + } + + fn name(&self, name: TokenStream, build: &mut Builder) { + build.push_escaped(&name_to_string(name)); + } + + fn attrs(&self, attrs: Vec, build: &mut Builder) { + for NamedAttr { name, attr_type } in desugar_attrs(attrs) { + match attr_type { + AttrType::Normal { value } => { + build.push_str(" "); + self.name(name, build); + build.push_str("=\""); + self.markup(value, build); + build.push_str("\""); + } + AttrType::Optional { + toggler: Toggler { cond, .. }, + } => { + let inner_value = quote!(inner_value); + let body = { + let mut build = self.builder(); + build.push_str(" "); + self.name(name, &mut build); + build.push_str("=\""); + self.splice(inner_value.clone(), &mut build); + build.push_str("\""); + build.finish() + }; + build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body })); + } + AttrType::Empty { toggler: None } => { + build.push_str(" "); + self.name(name, build); + } + AttrType::Empty { + toggler: Some(Toggler { cond, .. }), + } => { + let body = { + let mut build = self.builder(); + build.push_str(" "); + self.name(name, &mut build); + build.finish() + }; + build.push_tokens(quote!(if (#cond) { #body })); + } + } + } + } +} + +//////////////////////////////////////////////////////// + +fn desugar_attrs(attrs: Vec) -> Vec { + let mut classes_static = vec![]; + let mut classes_toggled = vec![]; + let mut ids = vec![]; + let mut named_attrs = vec![]; + for attr in attrs { + match attr { + Attr::Class { + name, + toggler: Some(toggler), + .. + } => classes_toggled.push((name, toggler)), + Attr::Class { + name, + toggler: None, + .. + } => classes_static.push(name), + Attr::Id { name, .. } => ids.push(name), + Attr::Named { named_attr } => named_attrs.push(named_attr), + } + } + let classes = desugar_classes_or_ids("class", classes_static, classes_toggled); + let ids = desugar_classes_or_ids("id", ids, vec![]); + classes.into_iter().chain(ids).chain(named_attrs).collect() +} + +fn desugar_classes_or_ids( + attr_name: &'static str, + values_static: Vec, + values_toggled: Vec<(Markup, Toggler)>, +) -> Option { + if values_static.is_empty() && values_toggled.is_empty() { + return None; + } + let mut markups = Vec::new(); + let mut leading_space = false; + for name in values_static { + markups.extend(prepend_leading_space(name, &mut leading_space)); + } + for (name, Toggler { cond, cond_span }) in values_toggled { + let body = Block { + markups: prepend_leading_space(name, &mut leading_space), + // TODO: is this correct? + outer_span: cond_span, + }; + markups.push(Markup::Special { + segments: vec![Special { + at_span: SpanRange::call_site(), + head: quote!(if (#cond)), + body, + }], + }); + } + Some(NamedAttr { + name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))), + attr_type: AttrType::Normal { + value: Markup::Block(Block { + markups, + outer_span: SpanRange::call_site(), + }), + }, + }) +} + +fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec { + let mut markups = Vec::new(); + if *leading_space { + markups.push(Markup::Literal { + content: " ".to_owned(), + span: name.span(), + }); + } + *leading_space = true; + markups.push(name); + markups +} + +//////////////////////////////////////////////////////// + +struct Builder { + output_ident: TokenTree, + tokens: Vec, + tail: String, +} + +impl Builder { + fn new(output_ident: TokenTree) -> Builder { + Builder { + output_ident, + tokens: Vec::new(), + tail: String::new(), + } + } + + fn push_str(&mut self, string: &str) { + self.tail.push_str(string); + } + + fn push_escaped(&mut self, string: &str) { + escape::escape_to_string(string, &mut self.tail); + } + + fn push_tokens(&mut self, tokens: TokenStream) { + self.cut(); + self.tokens.extend(tokens); + } + + fn cut(&mut self) { + if self.tail.is_empty() { + return; + } + let push_str_expr = { + let output_ident = self.output_ident.clone(); + let string = TokenTree::Literal(Literal::string(&self.tail)); + quote!(#output_ident.push_str(#string);) + }; + self.tail.clear(); + self.tokens.extend(push_str_expr); + } + + fn finish(mut self) -> TokenStream { + self.cut(); + self.tokens.into_iter().collect() + } +} diff --git a/helpers/pagetop-macros/src/maud/parse.rs b/helpers/pagetop-macros/src/maud/parse.rs new file mode 100644 index 00000000..d24cea6e --- /dev/null +++ b/helpers/pagetop-macros/src/maud/parse.rs @@ -0,0 +1,752 @@ +use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree}; +use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange}; +use std::collections::HashMap; + +use syn::Lit; + +use crate::maud::ast; + +pub fn parse(input: TokenStream) -> Vec { + Parser::new(input).markups() +} + +#[derive(Clone)] +struct Parser { + /// If we're inside an attribute, then this contains the attribute name. + current_attr: Option, + input: ::IntoIter, +} + +impl Iterator for Parser { + type Item = TokenTree; + + fn next(&mut self) -> Option { + self.input.next() + } +} + +impl Parser { + fn new(input: TokenStream) -> Parser { + Parser { + current_attr: None, + input: input.into_iter(), + } + } + + fn with_input(&self, input: TokenStream) -> Parser { + Parser { + current_attr: self.current_attr.clone(), + input: input.into_iter(), + } + } + + /// Returns the next token in the stream without consuming it. + fn peek(&mut self) -> Option { + self.clone().next() + } + + /// Returns the next two tokens in the stream without consuming them. + fn peek2(&mut self) -> Option<(TokenTree, Option)> { + let mut clone = self.clone(); + clone.next().map(|first| (first, clone.next())) + } + + /// Advances the cursor by one step. + fn advance(&mut self) { + self.next(); + } + + /// Advances the cursor by two steps. + fn advance2(&mut self) { + self.next(); + self.next(); + } + + /// Parses multiple blocks of markup. + fn markups(&mut self) -> Vec { + let mut result = Vec::new(); + loop { + match self.peek2() { + None => break, + Some((TokenTree::Punct(ref punct), _)) if punct.as_char() == ';' => self.advance(), + Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref ident)))) + if punct.as_char() == '@' && *ident == "let" => + { + self.advance2(); + let keyword = TokenTree::Ident(ident.clone()); + result.push(self.let_expr(punct.span(), keyword)); + } + _ => result.push(self.markup()), + } + } + result + } + + /// Parses a single block of markup. + fn markup(&mut self) -> ast::Markup { + let token = match self.peek() { + Some(token) => token, + None => { + abort_call_site!("unexpected end of input"); + } + }; + let markup = match token { + // Literal + TokenTree::Literal(literal) => { + self.advance(); + self.literal(literal) + } + // Special form + TokenTree::Punct(ref punct) if punct.as_char() == '@' => { + self.advance(); + let at_span = punct.span(); + match self.next() { + Some(TokenTree::Ident(ident)) => { + let keyword = TokenTree::Ident(ident.clone()); + match ident.to_string().as_str() { + "if" => { + let mut segments = Vec::new(); + self.if_expr(at_span, vec![keyword], &mut segments); + ast::Markup::Special { segments } + } + "while" => self.while_expr(at_span, keyword), + "for" => self.for_expr(at_span, keyword), + "match" => self.match_expr(at_span, keyword), + "let" => { + let span = SpanRange { + first: at_span, + last: ident.span(), + }; + abort!(span, "`@let` only works inside a block"); + } + other => { + let span = SpanRange { + first: at_span, + last: ident.span(), + }; + abort!(span, "unknown keyword `@{}`", other); + } + } + } + _ => { + abort!(at_span, "expected keyword after `@`"); + } + } + } + // Element + TokenTree::Ident(ident) => { + let ident_string = ident.to_string(); + match ident_string.as_str() { + "if" | "while" | "for" | "match" | "let" => { + abort!( + ident, + "found keyword `{}`", ident_string; + help = "should this be a `@{}`?", ident_string + ); + } + "true" | "false" => { + if let Some(attr_name) = &self.current_attr { + emit_error!( + ident, + r#"attribute value must be a string"#; + help = "to declare an empty attribute, omit the equals sign: `{}`", + attr_name; + help = "to toggle the attribute, use square brackets: `{}[some_boolean_flag]`", + attr_name; + ); + return ast::Markup::ParseError { + span: SpanRange::single_span(ident.span()), + }; + } + } + _ => {} + } + + // `.try_namespaced_name()` should never fail as we've + // already seen an `Ident` + let name = self.try_namespaced_name().expect("identifier"); + self.element(name) + } + // Div element shorthand + TokenTree::Punct(ref punct) if punct.as_char() == '.' || punct.as_char() == '#' => { + let name = TokenTree::Ident(Ident::new("div", punct.span())); + self.element(name.into()) + } + // Splice + TokenTree::Group(ref group) if group.delimiter() == Delimiter::Parenthesis => { + self.advance(); + ast::Markup::Splice { + expr: group.stream(), + outer_span: SpanRange::single_span(group.span()), + } + } + // Block + TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => { + self.advance(); + ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span()))) + } + // ??? + token => { + abort!(token, "invalid syntax"); + } + }; + markup + } + + /// Parses a literal string. + fn literal(&mut self, literal: Literal) -> ast::Markup { + match Lit::new(literal.clone()) { + Lit::Str(lit_str) => { + return ast::Markup::Literal { + content: lit_str.value(), + span: SpanRange::single_span(literal.span()), + } + } + // Boolean literals are idents, so `Lit::Bool` is handled in + // `markup`, not here. + Lit::Int(..) | Lit::Float(..) => { + emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal); + } + Lit::Char(lit_char) => { + emit_error!( + literal, + r#"literal must be double-quoted: `"{}"`"#, + lit_char.value(), + ); + } + _ => { + emit_error!(literal, "expected string"); + } + } + ast::Markup::ParseError { + span: SpanRange::single_span(literal.span()), + } + } + + /// Parses an `@if` expression. + /// + /// The leading `@if` should already be consumed. + fn if_expr(&mut self, at_span: Span, prefix: Vec, segments: &mut Vec) { + let mut head = prefix; + let body = loop { + match self.next() { + Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { + break self.block(block.stream(), SpanRange::single_span(block.span())); + } + Some(token) => head.push(token), + None => { + let mut span = ast::span_tokens(head); + span.first = at_span; + abort!(span, "expected body for this `@if`"); + } + } + }; + segments.push(ast::Special { + at_span: SpanRange::single_span(at_span), + head: head.into_iter().collect(), + body, + }); + self.else_if_expr(segments) + } + + /// Parses an optional `@else if` or `@else`. + /// + /// The leading `@else if` or `@else` should *not* already be consumed. + fn else_if_expr(&mut self, segments: &mut Vec) { + match self.peek2() { + Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref else_keyword)))) + if punct.as_char() == '@' && *else_keyword == "else" => + { + self.advance2(); + let at_span = punct.span(); + let else_keyword = TokenTree::Ident(else_keyword.clone()); + match self.peek() { + // `@else if` + Some(TokenTree::Ident(ref if_keyword)) if *if_keyword == "if" => { + self.advance(); + let if_keyword = TokenTree::Ident(if_keyword.clone()); + self.if_expr(at_span, vec![else_keyword, if_keyword], segments) + } + // Just an `@else` + _ => match self.next() { + Some(TokenTree::Group(ref group)) + if group.delimiter() == Delimiter::Brace => + { + let body = + self.block(group.stream(), SpanRange::single_span(group.span())); + segments.push(ast::Special { + at_span: SpanRange::single_span(at_span), + head: vec![else_keyword].into_iter().collect(), + body, + }); + } + _ => { + let span = SpanRange { + first: at_span, + last: else_keyword.span(), + }; + abort!(span, "expected body for this `@else`"); + } + }, + } + } + // We didn't find an `@else`; stop + _ => {} + } + } + + /// Parses an `@while` expression. + /// + /// The leading `@while` should already be consumed. + fn while_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); + let mut head = vec![keyword]; + let body = loop { + match self.next() { + Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { + break self.block(block.stream(), SpanRange::single_span(block.span())); + } + Some(token) => head.push(token), + None => { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@while`"); + } + } + }; + ast::Markup::Special { + segments: vec![ast::Special { + at_span: SpanRange::single_span(at_span), + head: head.into_iter().collect(), + body, + }], + } + } + + /// Parses a `@for` expression. + /// + /// The leading `@for` should already be consumed. + fn for_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); + let mut head = vec![keyword]; + loop { + match self.next() { + Some(TokenTree::Ident(ref in_keyword)) if *in_keyword == "in" => { + head.push(TokenTree::Ident(in_keyword.clone())); + break; + } + Some(token) => head.push(token), + None => { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "missing `in` in `@for` loop"); + } + } + } + let body = loop { + match self.next() { + Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { + break self.block(block.stream(), SpanRange::single_span(block.span())); + } + Some(token) => head.push(token), + None => { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@for`"); + } + } + }; + ast::Markup::Special { + segments: vec![ast::Special { + at_span: SpanRange::single_span(at_span), + head: head.into_iter().collect(), + body, + }], + } + } + + /// Parses a `@match` expression. + /// + /// The leading `@match` should already be consumed. + fn match_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); + let mut head = vec![keyword]; + let (arms, arms_span) = loop { + match self.next() { + Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { + let span = SpanRange::single_span(body.span()); + break (self.with_input(body.stream()).match_arms(), span); + } + Some(token) => head.push(token), + None => { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@match`"); + } + } + }; + ast::Markup::Match { + at_span: SpanRange::single_span(at_span), + head: head.into_iter().collect(), + arms, + arms_span, + } + } + + fn match_arms(&mut self) -> Vec { + let mut arms = Vec::new(); + while let Some(arm) = self.match_arm() { + arms.push(arm); + } + arms + } + + fn match_arm(&mut self) -> Option { + let mut head = Vec::new(); + loop { + match self.peek2() { + Some((TokenTree::Punct(ref eq), Some(TokenTree::Punct(ref gt)))) + if eq.as_char() == '=' + && gt.as_char() == '>' + && eq.spacing() == Spacing::Joint => + { + self.advance2(); + head.push(TokenTree::Punct(eq.clone())); + head.push(TokenTree::Punct(gt.clone())); + break; + } + Some((token, _)) => { + self.advance(); + head.push(token); + } + None => { + if head.is_empty() { + return None; + } else { + let head_span = ast::span_tokens(head); + abort!(head_span, "unexpected end of @match pattern"); + } + } + } + } + let body = match self.next() { + // $pat => { $stmts } + Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { + let body = self.block(body.stream(), SpanRange::single_span(body.span())); + // Trailing commas are optional if the match arm is a braced block + if let Some(TokenTree::Punct(ref punct)) = self.peek() { + if punct.as_char() == ',' { + self.advance(); + } + } + body + } + // $pat => $expr + Some(first_token) => { + let mut span = SpanRange::single_span(first_token.span()); + let mut body = vec![first_token]; + loop { + match self.next() { + Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => break, + Some(token) => { + span.last = token.span(); + body.push(token); + } + None => break, + } + } + self.block(body.into_iter().collect(), span) + } + None => { + let span = ast::span_tokens(head); + abort!(span, "unexpected end of @match arm"); + } + }; + Some(ast::MatchArm { + head: head.into_iter().collect(), + body, + }) + } + + /// Parses a `@let` expression. + /// + /// The leading `@let` should already be consumed. + fn let_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let mut tokens = vec![keyword]; + loop { + match self.next() { + Some(token) => match token { + TokenTree::Punct(ref punct) if punct.as_char() == '=' => { + tokens.push(token.clone()); + break; + } + _ => tokens.push(token), + }, + None => { + let mut span = ast::span_tokens(tokens); + span.first = at_span; + abort!(span, "unexpected end of `@let` expression"); + } + } + } + loop { + match self.next() { + Some(token) => match token { + TokenTree::Punct(ref punct) if punct.as_char() == ';' => { + tokens.push(token.clone()); + break; + } + _ => tokens.push(token), + }, + None => { + let mut span = ast::span_tokens(tokens); + span.first = at_span; + abort!( + span, + "unexpected end of `@let` expression"; + help = "are you missing a semicolon?" + ); + } + } + } + ast::Markup::Let { + at_span: SpanRange::single_span(at_span), + tokens: tokens.into_iter().collect(), + } + } + + /// Parses an element node. + /// + /// The element name should already be consumed. + fn element(&mut self, name: TokenStream) -> ast::Markup { + if self.current_attr.is_some() { + let span = ast::span_tokens(name); + abort!(span, "unexpected element"); + } + let attrs = self.attrs(); + let body = match self.peek() { + Some(TokenTree::Punct(ref punct)) + if punct.as_char() == ';' || punct.as_char() == '/' => + { + // Void element + self.advance(); + if punct.as_char() == '/' { + emit_error!( + punct, + "void elements must use `;`, not `/`"; + help = "change this to `;`"; + help = "see https://github.com/lambda-fairy/maud/pull/315 for details"; + ); + } + ast::ElementBody::Void { + semi_span: SpanRange::single_span(punct.span()), + } + } + Some(_) => match self.markup() { + ast::Markup::Block(block) => ast::ElementBody::Block { block }, + markup => { + let markup_span = markup.span(); + abort!( + markup_span, + "element body must be wrapped in braces"; + help = "see https://github.com/lambda-fairy/maud/pull/137 for details" + ); + } + }, + None => abort_call_site!("expected `;`, found end of macro"), + }; + ast::Markup::Element { name, attrs, body } + } + + /// Parses the attributes of an element. + fn attrs(&mut self) -> Vec { + let mut attrs = Vec::new(); + loop { + if let Some(name) = self.try_namespaced_name() { + // Attribute + match self.peek() { + // Non-empty attribute + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => { + self.advance(); + // Parse a value under an attribute context + assert!(self.current_attr.is_none()); + self.current_attr = Some(ast::name_to_string(name.clone())); + let attr_type = match self.attr_toggler() { + Some(toggler) => ast::AttrType::Optional { toggler }, + None => { + let value = self.markup(); + ast::AttrType::Normal { value } + } + }; + self.current_attr = None; + attrs.push(ast::Attr::Named { + named_attr: ast::NamedAttr { name, attr_type }, + }); + } + // Empty attribute (legacy syntax) + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '?' => { + self.advance(); + let toggler = self.attr_toggler(); + attrs.push(ast::Attr::Named { + named_attr: ast::NamedAttr { + name: name.clone(), + attr_type: ast::AttrType::Empty { toggler }, + }, + }); + } + // Empty attribute (new syntax) + _ => { + let toggler = self.attr_toggler(); + attrs.push(ast::Attr::Named { + named_attr: ast::NamedAttr { + name: name.clone(), + attr_type: ast::AttrType::Empty { toggler }, + }, + }); + } + } + } else { + match self.peek() { + // Class shorthand + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '.' => { + self.advance(); + let name = self.class_or_id_name(); + let toggler = self.attr_toggler(); + attrs.push(ast::Attr::Class { + dot_span: SpanRange::single_span(punct.span()), + name, + toggler, + }); + } + // ID shorthand + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '#' => { + self.advance(); + let name = self.class_or_id_name(); + attrs.push(ast::Attr::Id { + hash_span: SpanRange::single_span(punct.span()), + name, + }); + } + // If it's not a valid attribute, backtrack and bail out + _ => break, + } + } + } + + let mut attr_map: HashMap> = HashMap::new(); + let mut has_class = false; + for attr in &attrs { + let name = match attr { + ast::Attr::Class { .. } => { + if has_class { + // Only check the first class to avoid spurious duplicates + continue; + } + has_class = true; + "class".to_string() + } + ast::Attr::Id { .. } => "id".to_string(), + ast::Attr::Named { named_attr } => named_attr + .name + .clone() + .into_iter() + .map(|token| token.to_string()) + .collect(), + }; + let entry = attr_map.entry(name).or_default(); + entry.push(attr.span()); + } + + for (name, spans) in attr_map { + if spans.len() > 1 { + let mut spans = spans.into_iter(); + let first_span = spans.next().expect("spans should be non-empty"); + abort!(first_span, "duplicate attribute `{}`", name); + } + } + + attrs + } + + /// Parses the name of a class or ID. + fn class_or_id_name(&mut self) -> ast::Markup { + if let Some(symbol) = self.try_name() { + ast::Markup::Symbol { symbol } + } else { + self.markup() + } + } + + /// Parses the `[cond]` syntax after an empty attribute or class shorthand. + fn attr_toggler(&mut self) -> Option { + match self.peek() { + Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Bracket => { + self.advance(); + Some(ast::Toggler { + cond: group.stream(), + cond_span: SpanRange::single_span(group.span()), + }) + } + _ => None, + } + } + + /// Parses an identifier, without dealing with namespaces. + fn try_name(&mut self) -> Option { + let mut result = Vec::new(); + if let Some(token @ TokenTree::Ident(_)) = self.peek() { + self.advance(); + result.push(token); + } else { + return None; + } + let mut expect_ident = false; + loop { + expect_ident = match self.peek() { + Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => { + self.advance(); + result.push(TokenTree::Punct(punct.clone())); + true + } + Some(TokenTree::Ident(ref ident)) if expect_ident => { + self.advance(); + result.push(TokenTree::Ident(ident.clone())); + false + } + _ => break, + }; + } + Some(result.into_iter().collect()) + } + + /// Parses a HTML element or attribute name, along with a namespace + /// if necessary. + fn try_namespaced_name(&mut self) -> Option { + let mut result = vec![self.try_name()?]; + if let Some(TokenTree::Punct(ref punct)) = self.peek() { + if punct.as_char() == ':' { + self.advance(); + result.push(TokenStream::from(TokenTree::Punct(punct.clone()))); + result.push(self.try_name()?); + } + } + Some(result.into_iter().collect()) + } + + /// Parses the given token stream as a Maud expression. + fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block { + let markups = self.with_input(body).markups(); + ast::Block { + markups, + outer_span, + } + } +} diff --git a/src/html.rs b/src/html.rs new file mode 100644 index 00000000..1f24f630 --- /dev/null +++ b/src/html.rs @@ -0,0 +1,4 @@ +//! HTML en código. + +mod maud; +pub use maud::{html, html_private, Markup, PreEscaped, DOCTYPE}; diff --git a/src/html/maud.rs b/src/html/maud.rs new file mode 100644 index 00000000..db9308af --- /dev/null +++ b/src/html/maud.rs @@ -0,0 +1,350 @@ +//#![no_std] + +//! A macro for writing HTML templates. +//! +//! This documentation only describes the runtime API. For a general +//! guide, check out the [book] instead. +//! +//! [book]: https://maud.lambda.xyz/ + +//#![doc(html_root_url = "https://docs.rs/maud/0.25.0")] + +extern crate alloc; + +use alloc::{borrow::Cow, boxed::Box, string::String}; +use core::fmt::{self, Arguments, Display, Write}; + +pub use pagetop_macros::html; + +mod escape; + +/// An adapter that escapes HTML special characters. +/// +/// The following characters are escaped: +/// +/// * `&` is escaped as `&` +/// * `<` is escaped as `<` +/// * `>` is escaped as `>` +/// * `"` is escaped as `"` +/// +/// All other characters are passed through unchanged. +/// +/// **Note:** In versions prior to 0.13, the single quote (`'`) was +/// escaped as well. +/// +/// # Example +/// +/// ```rust#ignore +/// use maud::Escaper; +/// use std::fmt::Write; +/// let mut s = String::new(); +/// write!(Escaper::new(&mut s), "").unwrap(); +/// assert_eq!(s, "<script>launchMissiles()</script>"); +/// ``` +pub struct Escaper<'a>(&'a mut String); + +impl<'a> Escaper<'a> { + /// Creates an `Escaper` from a `String`. + pub fn new(buffer: &'a mut String) -> Escaper<'a> { + Escaper(buffer) + } +} + +impl<'a> fmt::Write for Escaper<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + escape::escape_to_string(s, self.0); + Ok(()) + } +} + +/// Represents a type that can be rendered as HTML. +/// +/// To implement this for your own type, override either the `.render()` +/// or `.render_to()` methods; since each is defined in terms of the +/// other, you only need to implement one of them. See the example below. +/// +/// # Minimal implementation +/// +/// An implementation of this trait must override at least one of +/// `.render()` or `.render_to()`. Since the default definitions of +/// these methods call each other, not doing this will result in +/// infinite recursion. +/// +/// # Example +/// +/// ```rust#ignore +/// use maud::{html, Markup, Render}; +/// +/// /// Provides a shorthand for linking to a CSS stylesheet. +/// pub struct Stylesheet(&'static str); +/// +/// impl Render for Stylesheet { +/// fn render(&self) -> Markup { +/// html! { +/// link rel="stylesheet" type="text/css" href=(self.0); +/// } +/// } +/// } +/// ``` +pub trait Render { + /// Renders `self` as a block of `Markup`. + fn render(&self) -> Markup { + let mut buffer = String::new(); + self.render_to(&mut buffer); + PreEscaped(buffer) + } + + /// Appends a representation of `self` to the given buffer. + /// + /// Its default implementation just calls `.render()`, but you may + /// override it with something more efficient. + /// + /// Note that no further escaping is performed on data written to + /// the buffer. If you override this method, you must make sure that + /// any data written is properly escaped, whether by hand or using + /// the [`Escaper`](struct.Escaper.html) wrapper struct. + fn render_to(&self, buffer: &mut String) { + buffer.push_str(&self.render().into_string()); + } +} + +impl Render for str { + fn render_to(&self, w: &mut String) { + escape::escape_to_string(self, w); + } +} + +impl Render for String { + fn render_to(&self, w: &mut String) { + str::render_to(self, w); + } +} + +impl<'a> Render for Cow<'a, str> { + fn render_to(&self, w: &mut String) { + str::render_to(self, w); + } +} + +impl<'a> Render for Arguments<'a> { + fn render_to(&self, w: &mut String) { + let _ = Escaper::new(w).write_fmt(*self); + } +} + +impl<'a, T: Render + ?Sized> Render for &'a T { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); + } +} + +impl<'a, T: Render + ?Sized> Render for &'a mut T { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); + } +} + +impl Render for Box { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); + } +} + +macro_rules! impl_render_with_display { + ($($ty:ty)*) => { + $( + impl Render for $ty { + fn render_to(&self, w: &mut String) { + // TODO: remove the explicit arg when Rust 1.58 is released + format_args!("{self}", self = self).render_to(w); + } + } + )* + }; +} + +impl_render_with_display! { + char f32 f64 +} + +macro_rules! impl_render_with_itoa { + ($($ty:ty)*) => { + $( + impl Render for $ty { + fn render_to(&self, w: &mut String) { + w.push_str(itoa::Buffer::new().format(*self)); + } + } + )* + }; +} + +impl_render_with_itoa! { + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize +} + +/// Renders a value using its [`Display`] impl. +/// +/// # Example +/// +/// ```rust#ignore +/// use maud::html; +/// use std::net::Ipv4Addr; +/// +/// let ip_address = Ipv4Addr::new(127, 0, 0, 1); +/// +/// let markup = html! { +/// "My IP address is: " +/// (maud::display(ip_address)) +/// }; +/// +/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1"); +/// ``` +pub fn display(value: impl Display) -> impl Render { + struct DisplayWrapper(T); + + impl Render for DisplayWrapper { + fn render_to(&self, w: &mut String) { + format_args!("{0}", self.0).render_to(w); + } + } + + DisplayWrapper(value) +} + +/// A wrapper that renders the inner value without escaping. +#[derive(Debug, Clone, Copy)] +pub struct PreEscaped>(pub T); + +impl> Render for PreEscaped { + fn render_to(&self, w: &mut String) { + w.push_str(self.0.as_ref()); + } +} + +/// A block of markup is a string that does not need to be escaped. +/// +/// The `html!` macro expands to an expression of this type. +pub type Markup = PreEscaped; + +impl Markup { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl + Into> PreEscaped { + /// Converts the inner value to a string. + pub fn into_string(self) -> String { + self.0.into() + } +} + +impl + Into> From> for String { + fn from(value: PreEscaped) -> String { + value.into_string() + } +} + +impl + Default> Default for PreEscaped { + fn default() -> Self { + Self(Default::default()) + } +} + +/// The literal string ``. +/// +/// # Example +/// +/// A minimal web page: +/// +/// ```rust#ignore +/// use maud::{DOCTYPE, html}; +/// +/// let markup = html! { +/// (DOCTYPE) +/// html { +/// head { +/// meta charset="utf-8"; +/// title { "Test page" } +/// } +/// body { +/// p { "Hello, world!" } +/// } +/// } +/// }; +/// ``` +pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped(""); + +mod actix_support { + extern crate alloc; + + use crate::html::PreEscaped; + use actix_web::{http::header, HttpRequest, HttpResponse, Responder}; + use alloc::string::String; + + impl Responder for PreEscaped { + type Body = String; + + fn respond_to(self, _req: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(header::ContentType::html()) + .message_body(self.0) + .unwrap() + } + } +} + +#[doc(hidden)] +pub mod html_private { + extern crate alloc; + + use super::{display, Render}; + use alloc::string::String; + use core::fmt::Display; + + #[doc(hidden)] + #[macro_export] + macro_rules! render_to { + ($x:expr, $buffer:expr) => {{ + use $crate::html::html_private::*; + match ChooseRenderOrDisplay($x) { + x => (&&x).implements_render_or_display().render_to(x.0, $buffer), + } + }}; + } + + pub use render_to; + + pub struct ChooseRenderOrDisplay(pub T); + + pub struct ViaRenderTag; + pub struct ViaDisplayTag; + + pub trait ViaRender { + fn implements_render_or_display(&self) -> ViaRenderTag { + ViaRenderTag + } + } + pub trait ViaDisplay { + fn implements_render_or_display(&self) -> ViaDisplayTag { + ViaDisplayTag + } + } + + impl ViaRender for &ChooseRenderOrDisplay {} + impl ViaDisplay for ChooseRenderOrDisplay {} + + impl ViaRenderTag { + pub fn render_to(self, value: &T, buffer: &mut String) { + value.render_to(buffer); + } + } + + impl ViaDisplayTag { + pub fn render_to(self, value: &T, buffer: &mut String) { + display(value).render_to(buffer); + } + } +} diff --git a/src/html/maud/escape.rs b/src/html/maud/escape.rs new file mode 100644 index 00000000..94cdeec1 --- /dev/null +++ b/src/html/maud/escape.rs @@ -0,0 +1,34 @@ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!! PLEASE KEEP THIS IN SYNC WITH `maud_macros/src/escape.rs` !!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +extern crate alloc; + +use alloc::string::String; + +pub fn escape_to_string(input: &str, output: &mut String) { + for b in input.bytes() { + match b { + b'&' => output.push_str("&"), + b'<' => output.push_str("<"), + b'>' => output.push_str(">"), + b'"' => output.push_str("""), + _ => unsafe { output.as_mut_vec().push(b) }, + } + } +} + +#[cfg(test)] +mod test { + extern crate alloc; + + use super::escape_to_string; + use alloc::string::String; + + #[test] + fn it_works() { + let mut s = String::new(); + escape_to_string("", &mut s); + assert_eq!(s, "<script>launchMissiles()</script>"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 308c1633..2f973518 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ // RE-EXPORTED ************************************************************************************* -pub use pagetop_macros::{main, test}; +pub use pagetop_macros::{html, main, test}; // API ********************************************************************************************* @@ -42,6 +42,8 @@ pub mod config; pub mod global; // Gestión de trazas y registro de eventos de la aplicación. pub mod trace; +// HTML en código. +pub mod html; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/prelude.rs b/src/prelude.rs index 5d5851fc..eb59c4f2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,7 @@ // RE-EXPORTED. -pub use crate::{main, test}; +pub use crate::{html, main, test}; // MACROS. @@ -15,6 +15,8 @@ pub use crate::global; pub use crate::trace; +pub use crate::html::*; + pub use crate::service; pub use crate::app::Application; From 6944938910d3ae48208ba7b50163a234e422b9c1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 7 Jul 2025 20:31:00 +0200 Subject: [PATCH 004/224] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Actualiza=20el=20c?= =?UTF-8?q?=C3=B3digo=20de=20maud=20a=20la=20versi=C3=B3n=200.27.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 82 +- helpers/pagetop-macros/Cargo.toml | 4 +- helpers/pagetop-macros/src/lib.rs | 3 +- helpers/pagetop-macros/src/maud.rs | 53 +- helpers/pagetop-macros/src/maud/ast.rs | 1222 ++++++++++++++++--- helpers/pagetop-macros/src/maud/escape.rs | 7 - helpers/pagetop-macros/src/maud/generate.rs | 478 +++++--- helpers/pagetop-macros/src/maud/parse.rs | 752 ------------ src/html.rs | 2 +- src/html/maud.rs | 75 +- src/service.rs | 2 +- 11 files changed, 1459 insertions(+), 1221 deletions(-) delete mode 100644 helpers/pagetop-macros/src/maud/parse.rs diff --git a/Cargo.lock b/Cargo.lock index d53ffd7c..cf974c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -182,7 +182,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -426,7 +426,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "unicode-xid", ] @@ -448,7 +448,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -937,13 +937,13 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.1" +version = "0.0.2" dependencies = [ "proc-macro-crate", - "proc-macro-error", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -998,7 +998,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1052,30 +1052,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -1085,6 +1061,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + [[package]] name = "quote" version = "1.0.40" @@ -1242,7 +1230,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1350,16 +1338,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.104" @@ -1379,7 +1357,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1409,7 +1387,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1571,7 +1549,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1721,7 +1699,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-shared", ] @@ -1743,7 +1721,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1905,7 +1883,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -1926,7 +1904,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1946,7 +1924,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -1980,7 +1958,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 80413821..ea6404b9 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.1" +version = "0.0.2" edition = "2021" description = """\ @@ -19,7 +19,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.95" +proc-macro2-diagnostics = { version = "0.10.1", default-features = false } proc-macro-crate = "3.3.0" -proc-macro-error = "1.0.4" quote = "1.0.40" syn = { version = "2.0.104", features = ["full"] } diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 65badb04..e0f3badd 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -17,11 +17,10 @@ mod maud; use proc_macro::TokenStream; -use proc_macro_error::proc_macro_error; use quote::quote; +/// Macro para escribir plantillas HTML ([Maud](https://docs.rs/maud)). #[proc_macro] -#[proc_macro_error] pub fn html(input: TokenStream) -> TokenStream { maud::expand(input.into()).into() } diff --git a/helpers/pagetop-macros/src/maud.rs b/helpers/pagetop-macros/src/maud.rs index a4e7873f..9077dbb9 100644 --- a/helpers/pagetop-macros/src/maud.rs +++ b/helpers/pagetop-macros/src/maud.rs @@ -1,4 +1,4 @@ -// #![doc(html_root_url = "https://docs.rs/maud_macros/0.25.0")] +// #![doc(html_root_url = "https://docs.rs/maud_macros/0.27.0")] // TokenStream values are reference counted, and the mental overhead of tracking // lifetimes outweighs the marginal gains from explicit borrowing // #![allow(clippy::needless_pass_by_value)] @@ -6,34 +6,55 @@ mod ast; mod escape; mod generate; -mod parse; -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use ast::DiagnosticParse; +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2_diagnostics::Diagnostic; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; +use syn::parse::{ParseStream, Parser}; pub fn expand(input: TokenStream) -> TokenStream { - let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); // Heuristic: the size of the resulting markup tends to correlate with the // code size of the template itself let size_hint = input.to_string().len(); - let markups = parse::parse(input); - let stmts = generate::generate(markups, output_ident.clone()); - let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`"); - let pre_escaped = match found_crate { - FoundCrate::Itself => quote!( - crate::html::PreEscaped(#output_ident) - ), - _ => quote!( - pagetop::html::PreEscaped(#output_ident) - ), + let mut diagnostics = Vec::new(); + let markups = match Parser::parse2( + |input: ParseStream| ast::Markups::diagnostic_parse(input, &mut diagnostics), + input, + ) { + Ok(data) => data, + Err(err) => { + let err = err.to_compile_error(); + let diag_tokens = diagnostics.into_iter().map(Diagnostic::emit_as_expr_tokens); + + return quote! {{ + #err + #(#diag_tokens)* + }}; + } }; - quote!({ + let diag_tokens = diagnostics.into_iter().map(Diagnostic::emit_as_expr_tokens); + + let output_ident = Ident::new("__maud_output", Span::mixed_site()); + let stmts = generate::generate(markups, output_ident.clone()); + + let found_crate = crate_name("pagetop").expect("pagetop must be in Cargo.toml"); + let crate_ident = match found_crate { + FoundCrate::Itself => Ident::new("pagetop", Span::call_site()), + FoundCrate::Name(ref name) => Ident::new(name, Span::call_site()), + }; + let pre_escaped = quote! { + #crate_ident::html::PreEscaped(#output_ident) + }; + + quote! {{ extern crate alloc; let mut #output_ident = alloc::string::String::with_capacity(#size_hint); #stmts + #(#diag_tokens)* #pre_escaped - }) + }} } diff --git a/helpers/pagetop-macros/src/maud/ast.rs b/helpers/pagetop-macros/src/maud/ast.rs index cd8a2cef..fd499ae2 100644 --- a/helpers/pagetop-macros/src/maud/ast.rs +++ b/helpers/pagetop-macros/src/maud/ast.rs @@ -1,221 +1,1105 @@ -use proc_macro2::{TokenStream, TokenTree}; -use proc_macro_error::SpanRange; +use std::fmt::{self, Display, Formatter}; -#[derive(Debug)] -pub enum Markup { - /// Used as a placeholder value on parse error. - ParseError { - span: SpanRange, - }, - Block(Block), - Literal { - content: String, - span: SpanRange, - }, - Symbol { - symbol: TokenStream, - }, - Splice { - expr: TokenStream, - outer_span: SpanRange, - }, - Element { - name: TokenStream, - attrs: Vec, - body: ElementBody, - }, - Let { - at_span: SpanRange, - tokens: TokenStream, - }, - Special { - segments: Vec, - }, - Match { - at_span: SpanRange, - head: TokenStream, - arms: Vec, - arms_span: SpanRange, +use proc_macro2::TokenStream; +use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt}; +use quote::ToTokens; +use syn::{ + braced, bracketed, + ext::IdentExt, + parenthesized, + parse::{Lookahead1, Parse, ParseStream}, + punctuated::{Pair, Punctuated}, + spanned::Spanned, + token::{ + At, Brace, Bracket, Colon, Comma, Dot, Else, Eq, FatArrow, For, If, In, Let, Match, Minus, + Paren, Pound, Question, Semi, Slash, While, }, + Error, Expr, Ident, Lit, LitBool, LitInt, LitStr, Local, Pat, Stmt, +}; + +#[derive(Debug, Clone)] +pub struct Markups { + pub markups: Vec>, } -impl Markup { - pub fn span(&self) -> SpanRange { - match *self { - Markup::ParseError { span } => span, - Markup::Block(ref block) => block.span(), - Markup::Literal { span, .. } => span, - Markup::Symbol { ref symbol } => span_tokens(symbol.clone()), - Markup::Splice { outer_span, .. } => outer_span, - Markup::Element { - ref name, ref body, .. - } => { - let name_span = span_tokens(name.clone()); - name_span.join_range(body.span()) - } - Markup::Let { - at_span, - ref tokens, - } => at_span.join_range(span_tokens(tokens.clone())), - Markup::Special { ref segments } => join_ranges(segments.iter().map(Special::span)), - Markup::Match { - at_span, arms_span, .. - } => at_span.join_range(arms_span), +impl DiagnosticParse for Markups { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let mut markups = Vec::new(); + while !input.is_empty() { + markups.push(Markup::diagnostic_parse_in_block(input, diagnostics)?) + } + Ok(Self { markups }) + } +} + +impl ToTokens for Markups { + fn to_tokens(&self, tokens: &mut TokenStream) { + for markup in &self.markups { + markup.to_tokens(tokens); } } } -#[derive(Debug)] -pub enum Attr { +#[derive(Debug, Clone)] +pub enum Markup { + Block(Block), + Lit(HtmlLit), + Splice { paren_token: Paren, expr: Expr }, + Element(E), + ControlFlow(ControlFlow), + Semi(Semi), +} + +impl Markup { + pub fn diagnostic_parse_in_block( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + if input.peek(Let) + || input.peek(If) + || input.peek(Else) + || input.peek(For) + || input.peek(While) + || input.peek(Match) + { + let kw = input.call(Ident::parse_any)?; + diagnostics.push( + kw.span() + .error(format!("found keyword `{kw}`")) + .help(format!("should this be `@{kw}`?")), + ); + } + + let lookahead = input.lookahead1(); + + if lookahead.peek(Brace) { + input.diagnostic_parse(diagnostics).map(Self::Block) + } else if lookahead.peek(Lit) { + input.diagnostic_parse(diagnostics).map(Self::Lit) + } else if lookahead.peek(Paren) { + let content; + Ok(Self::Splice { + paren_token: parenthesized!(content in input), + expr: content.parse()?, + }) + } else if let Some(parse_element) = E::should_parse(&lookahead) { + parse_element(input, diagnostics).map(Self::Element) + } else if lookahead.peek(At) { + input.diagnostic_parse(diagnostics).map(Self::ControlFlow) + } else if lookahead.peek(Semi) { + input.parse().map(Self::Semi) + } else { + Err(lookahead.error()) + } + } +} + +impl DiagnosticParse for Markup { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let markup = Self::diagnostic_parse_in_block(input, diagnostics)?; + + if let Self::ControlFlow(ControlFlow { + kind: ControlFlowKind::Let(_), + .. + }) = &markup + { + diagnostics.push( + markup + .span() + .error("`@let` bindings are only allowed inside blocks"), + ) + } + + Ok(markup) + } +} + +impl ToTokens for Markup { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Block(block) => block.to_tokens(tokens), + Self::Lit(lit) => lit.to_tokens(tokens), + Self::Splice { paren_token, expr } => { + paren_token.surround(tokens, |tokens| { + expr.to_tokens(tokens); + }); + } + Self::Element(element) => element.to_tokens(tokens), + Self::ControlFlow(control_flow) => control_flow.to_tokens(tokens), + Self::Semi(semi) => semi.to_tokens(tokens), + } + } +} + +/// Represents a context that may or may not allow elements. +/// +/// An attribute accepts almost the same syntax as an element body, except child elements aren't +/// allowed. To enable code reuse, introduce a trait that abstracts over whether an element is +/// allowed or not. +pub trait MaybeElement: Sized + ToTokens { + /// If an element can be parsed here, returns `Some` with a parser for the rest of the element. + fn should_parse(lookahead: &Lookahead1<'_>) -> Option>; +} + +/// An implementation of `DiagnosticParse::diagnostic_parse`. +pub type DiagnosticParseFn = fn(ParseStream, &mut Vec) -> syn::Result; + +/// Represents an attribute context, where elements are disallowed. +#[derive(Debug, Clone)] +pub enum NoElement {} + +impl MaybeElement for NoElement { + fn should_parse( + _lookahead: &Lookahead1<'_>, + ) -> Option) -> syn::Result> { + None + } +} + +impl ToTokens for NoElement { + fn to_tokens(&self, _tokens: &mut TokenStream) { + match *self {} + } +} + +#[derive(Debug, Clone)] +pub struct Element { + pub name: Option, + pub attrs: Vec, + pub body: ElementBody, +} + +impl From for Element { + fn from(value: NoElement) -> Self { + match value {} + } +} + +impl MaybeElement for Element { + fn should_parse( + lookahead: &Lookahead1<'_>, + ) -> Option) -> syn::Result> { + if lookahead.peek(Ident::peek_any) || lookahead.peek(Dot) || lookahead.peek(Pound) { + Some(Element::diagnostic_parse) + } else { + None + } + } +} + +impl DiagnosticParse for Element { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + name: if input.peek(Ident::peek_any) { + Some(input.diagnostic_parse(diagnostics)?) + } else { + None + }, + attrs: { + let mut id_pushed = false; + let mut attrs = Vec::new(); + + while input.peek(Ident::peek_any) + || input.peek(Lit) + || input.peek(Dot) + || input.peek(Pound) + { + let attr = input.diagnostic_parse(diagnostics)?; + + if let Attribute::Id { .. } = attr { + if id_pushed { + return Err(Error::new_spanned( + attr, + "duplicate id (`#`) attribute specified", + )); + } + id_pushed = true; + } + + attrs.push(attr); + } + + if !(input.peek(Brace) || input.peek(Semi) || input.peek(Slash)) { + let lookahead = input.lookahead1(); + + lookahead.peek(Ident::peek_any); + lookahead.peek(Lit); + lookahead.peek(Dot); + lookahead.peek(Pound); + + lookahead.peek(Brace); + lookahead.peek(Semi); + + return Err(lookahead.error()); + } + + attrs + }, + body: input.diagnostic_parse(diagnostics)?, + }) + } +} + +impl ToTokens for Element { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(name) = &self.name { + name.to_tokens(tokens); + } + for attr in &self.attrs { + attr.to_tokens(tokens); + } + self.body.to_tokens(tokens); + } +} + +#[derive(Debug, Clone)] +pub enum ElementBody { + Void(Semi), + Block(Block), +} + +impl DiagnosticParse for ElementBody { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Semi) { + input.parse().map(Self::Void) + } else if lookahead.peek(Brace) { + input.diagnostic_parse(diagnostics).map(Self::Block) + } else if lookahead.peek(Slash) { + diagnostics.push( + input + .parse::()? + .span() + .error("void elements must use `;`, not `/`") + .help("change this to `;`") + .help("see https://github.com/lambda-fairy/maud/pull/315 for details"), + ); + + Ok(Self::Void(::default())) + } else { + Err(lookahead.error()) + } + } +} + +impl ToTokens for ElementBody { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Void(semi) => semi.to_tokens(tokens), + Self::Block(block) => block.to_tokens(tokens), + } + } +} + +#[derive(Debug, Clone)] +pub struct Block { + pub brace_token: Brace, + pub markups: Markups, +} + +impl DiagnosticParse for Block { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let content; + Ok(Self { + brace_token: braced!(content in input), + markups: content.diagnostic_parse(diagnostics)?, + }) + } +} + +impl ToTokens for Block { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.brace_token.surround(tokens, |tokens| { + self.markups.to_tokens(tokens); + }); + } +} + +#[derive(Debug, Clone)] +pub enum Attribute { Class { - dot_span: SpanRange, - name: Markup, + dot_token: Dot, + name: HtmlNameOrMarkup, toggler: Option, }, Id { - hash_span: SpanRange, - name: Markup, + pound_token: Pound, + name: HtmlNameOrMarkup, }, Named { - named_attr: NamedAttr, + name: HtmlName, + attr_type: AttributeType, }, } -impl Attr { - pub fn span(&self) -> SpanRange { - match *self { - Attr::Class { - dot_span, - ref name, - ref toggler, +impl DiagnosticParse for Attribute { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Dot) { + Ok(Self::Class { + dot_token: input.parse()?, + name: input.diagnostic_parse(diagnostics)?, + toggler: { + let lookahead = input.lookahead1(); + + if lookahead.peek(Bracket) { + Some(input.diagnostic_parse(diagnostics)?) + } else { + None + } + }, + }) + } else if lookahead.peek(Pound) { + Ok(Self::Id { + pound_token: input.parse()?, + name: input.diagnostic_parse(diagnostics)?, + }) + } else { + let name = input.diagnostic_parse::(diagnostics)?; + + if input.peek(Question) { + input.parse::()?; + } + + let fork = input.fork(); + + let attr = Self::Named { + name: name.clone(), + attr_type: input.diagnostic_parse(diagnostics)?, + }; + + if fork.peek(Eq) && fork.peek2(LitBool) { + diagnostics.push( + attr.span() + .error("attribute value must be a string") + .help(format!("to declare an empty attribute, omit the equals sign: `{name}`")) + .help(format!("to toggle the attribute, use square brackets: `{name}[some_boolean_flag]`")) + ); + } + + Ok(attr) + } + } +} + +impl ToTokens for Attribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Class { + dot_token, + name, + toggler, } => { - let name_span = name.span(); - let dot_name_span = dot_span.join_range(name_span); + dot_token.to_tokens(tokens); + name.to_tokens(tokens); if let Some(toggler) = toggler { - dot_name_span.join_range(toggler.cond_span) - } else { - dot_name_span + toggler.to_tokens(tokens); } } - Attr::Id { - hash_span, - ref name, - } => { - let name_span = name.span(); - hash_span.join_range(name_span) + Self::Id { pound_token, name } => { + pound_token.to_tokens(tokens); + name.to_tokens(tokens); + } + Self::Named { name, attr_type } => { + name.to_tokens(tokens); + attr_type.to_tokens(tokens); } - Attr::Named { ref named_attr } => named_attr.span(), } } } -#[derive(Debug)] -pub enum ElementBody { - Void { semi_span: SpanRange }, - Block { block: Block }, +#[derive(Debug, Clone)] +pub enum HtmlNameOrMarkup { + HtmlName(HtmlName), + Markup(Markup), } -impl ElementBody { - pub fn span(&self) -> SpanRange { - match *self { - ElementBody::Void { semi_span } => semi_span, - ElementBody::Block { ref block } => block.span(), - } - } -} - -#[derive(Debug)] -pub struct Block { - pub markups: Vec, - pub outer_span: SpanRange, -} - -impl Block { - pub fn span(&self) -> SpanRange { - self.outer_span - } -} - -#[derive(Debug)] -pub struct Special { - pub at_span: SpanRange, - pub head: TokenStream, - pub body: Block, -} - -impl Special { - pub fn span(&self) -> SpanRange { - let body_span = self.body.span(); - self.at_span.join_range(body_span) - } -} - -#[derive(Debug)] -pub struct NamedAttr { - pub name: TokenStream, - pub attr_type: AttrType, -} - -impl NamedAttr { - fn span(&self) -> SpanRange { - let name_span = span_tokens(self.name.clone()); - if let Some(attr_type_span) = self.attr_type.span() { - name_span.join_range(attr_type_span) +impl DiagnosticParse for HtmlNameOrMarkup { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + if input.peek(Ident::peek_any) || input.peek(Lit) { + input.diagnostic_parse(diagnostics).map(Self::HtmlName) } else { - name_span + input.diagnostic_parse(diagnostics).map(Self::Markup) } } } -#[derive(Debug)] -pub enum AttrType { - Normal { value: Markup }, - Optional { toggler: Toggler }, - Empty { toggler: Option }, +impl Parse for HtmlNameOrMarkup { + fn parse(input: ParseStream) -> syn::Result { + Self::diagnostic_parse(input, &mut Vec::new()) + } } -impl AttrType { - fn span(&self) -> Option { - match *self { - AttrType::Normal { ref value } => Some(value.span()), - AttrType::Optional { ref toggler } => Some(toggler.span()), - AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span), +impl ToTokens for HtmlNameOrMarkup { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::HtmlName(name) => name.to_tokens(tokens), + Self::Markup(markup) => markup.to_tokens(tokens), } } } -#[derive(Debug)] +impl Display for HtmlNameOrMarkup { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::HtmlName(name) => name.fmt(f), + Self::Markup(markup) => markup.to_token_stream().fmt(f), + } + } +} + +#[derive(Debug, Clone)] +pub enum AttributeType { + Normal { + eq_token: Eq, + value: Markup, + }, + Optional { + eq_token: Eq, + toggler: Toggler, + }, + Empty(Option), +} + +impl DiagnosticParse for AttributeType { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Eq) { + let eq_token = input.parse()?; + + if input.peek(Bracket) { + Ok(Self::Optional { + eq_token, + toggler: input.diagnostic_parse(diagnostics)?, + }) + } else { + Ok(Self::Normal { + eq_token, + value: input.diagnostic_parse(diagnostics)?, + }) + } + } else if lookahead.peek(Bracket) { + Ok(Self::Empty(Some(input.diagnostic_parse(diagnostics)?))) + } else { + Ok(Self::Empty(None)) + } + } +} + +impl ToTokens for AttributeType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Normal { eq_token, value } => { + eq_token.to_tokens(tokens); + value.to_tokens(tokens); + } + Self::Optional { eq_token, toggler } => { + eq_token.to_tokens(tokens); + toggler.to_tokens(tokens); + } + Self::Empty(toggler) => { + if let Some(toggler) = toggler { + toggler.to_tokens(tokens); + } + } + } + } +} + +#[derive(Debug, Clone)] +pub struct HtmlName { + pub name: Punctuated, +} + +impl DiagnosticParse for HtmlName { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + name: { + let mut punctuated = Punctuated::new(); + + loop { + punctuated.push_value(input.diagnostic_parse(diagnostics)?); + + if !(input.peek(Minus) || input.peek(Colon)) { + break; + } + + let punct = input.diagnostic_parse(diagnostics)?; + punctuated.push_punct(punct); + } + + punctuated + }, + }) + } +} + +impl Parse for HtmlName { + fn parse(input: ParseStream) -> syn::Result { + Self::diagnostic_parse(input, &mut Vec::new()) + } +} + +impl ToTokens for HtmlName { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.name.to_tokens(tokens); + } +} + +impl Display for HtmlName { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for pair in self.name.pairs() { + match pair { + Pair::Punctuated(fragment, punct) => { + fragment.fmt(f)?; + punct.fmt(f)?; + } + Pair::End(fragment) => { + fragment.fmt(f)?; + } + } + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub enum HtmlNameFragment { + Ident(Ident), + LitInt(LitInt), + LitStr(LitStr), + Empty, +} + +impl DiagnosticParse for HtmlNameFragment { + fn diagnostic_parse( + input: ParseStream, + _diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Ident::peek_any) { + input.call(Ident::parse_any).map(Self::Ident) + } else if lookahead.peek(LitInt) { + input.parse().map(Self::LitInt) + } else if lookahead.peek(LitStr) { + input.parse().map(Self::LitStr) + } else if lookahead.peek(Minus) || lookahead.peek(Colon) { + Ok(Self::Empty) + } else { + Err(lookahead.error()) + } + } +} + +impl ToTokens for HtmlNameFragment { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Ident(ident) => ident.to_tokens(tokens), + Self::LitInt(lit) => lit.to_tokens(tokens), + Self::LitStr(lit) => lit.to_tokens(tokens), + Self::Empty => {} + } + } +} + +impl Display for HtmlNameFragment { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Ident(ident) => ident.fmt(f), + Self::LitInt(lit) => lit.fmt(f), + Self::LitStr(lit) => lit.value().fmt(f), + Self::Empty => Ok(()), + } + } +} + +#[derive(Debug, Clone)] +pub struct HtmlLit { + pub lit: LitStr, +} + +impl DiagnosticParse for HtmlLit { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Lit) { + let lit = input.parse()?; + match lit { + Lit::Str(lit) => Ok(Self { lit }), + Lit::Int(lit) => { + diagnostics.push( + lit.span() + .error(format!(r#"literal must be double-quoted: `"{lit}"`"#)), + ); + Ok(Self { + lit: LitStr::new("", lit.span()), + }) + } + Lit::Float(lit) => { + diagnostics.push( + lit.span() + .error(format!(r#"literal must be double-quoted: `"{lit}"`"#)), + ); + Ok(Self { + lit: LitStr::new("", lit.span()), + }) + } + Lit::Char(lit) => { + diagnostics.push(lit.span().error(format!( + r#"literal must be double-quoted: `"{}"`"#, + lit.value() + ))); + Ok(Self { + lit: LitStr::new("", lit.span()), + }) + } + Lit::Bool(_) => { + // diagnostic handled earlier with more information + Ok(Self { + lit: LitStr::new("", lit.span()), + }) + } + _ => { + diagnostics.push(lit.span().error("expected string")); + Ok(Self { + lit: LitStr::new("", lit.span()), + }) + } + } + } else { + Err(lookahead.error()) + } + } +} + +impl ToTokens for HtmlLit { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lit.to_tokens(tokens); + } +} + +impl Display for HtmlLit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.lit.value().fmt(f) + } +} + +#[derive(Debug, Clone)] +pub enum HtmlNamePunct { + Colon(Colon), + Hyphen(Minus), +} + +impl DiagnosticParse for HtmlNamePunct { + fn diagnostic_parse(input: ParseStream, _: &mut Vec) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Colon) { + input.parse().map(Self::Colon) + } else if lookahead.peek(Minus) { + input.parse().map(Self::Hyphen) + } else { + Err(lookahead.error()) + } + } +} + +impl ToTokens for HtmlNamePunct { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Colon(token) => token.to_tokens(tokens), + Self::Hyphen(token) => token.to_tokens(tokens), + } + } +} + +impl Display for HtmlNamePunct { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Colon(_) => f.write_str(":"), + Self::Hyphen(_) => f.write_str("-"), + } + } +} + +#[derive(Debug, Clone)] pub struct Toggler { - pub cond: TokenStream, - pub cond_span: SpanRange, + pub bracket_token: Bracket, + pub cond: Expr, } -impl Toggler { - fn span(&self) -> SpanRange { - self.cond_span +impl DiagnosticParse for Toggler { + fn diagnostic_parse(input: ParseStream, _: &mut Vec) -> syn::Result { + let content; + Ok(Self { + bracket_token: bracketed!(content in input), + cond: content.parse()?, + }) } } -#[derive(Debug)] -pub struct MatchArm { - pub head: TokenStream, - pub body: Block, +impl ToTokens for Toggler { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.bracket_token.surround(tokens, |tokens| { + self.cond.to_tokens(tokens); + }); + } } -pub fn span_tokens>(tokens: I) -> SpanRange { - join_ranges(tokens.into_iter().map(|s| SpanRange::single_span(s.span()))) +#[derive(Debug, Clone)] +pub struct ControlFlow { + pub at_token: At, + pub kind: ControlFlowKind, } -pub fn join_ranges>(ranges: I) -> SpanRange { - let mut iter = ranges.into_iter(); - let first = match iter.next() { - Some(span) => span, - None => return SpanRange::call_site(), - }; - let last = iter.last().unwrap_or(first); - first.join_range(last) +impl DiagnosticParse for ControlFlow { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + at_token: input.parse()?, + kind: { + let lookahead = input.lookahead1(); + + if lookahead.peek(If) { + ControlFlowKind::If(input.diagnostic_parse(diagnostics)?) + } else if lookahead.peek(For) { + ControlFlowKind::For(input.diagnostic_parse(diagnostics)?) + } else if lookahead.peek(While) { + ControlFlowKind::While(input.diagnostic_parse(diagnostics)?) + } else if lookahead.peek(Match) { + ControlFlowKind::Match(input.diagnostic_parse(diagnostics)?) + } else if lookahead.peek(Let) { + let Stmt::Local(local) = input.parse()? else { + unreachable!() + }; + + ControlFlowKind::Let(local) + } else { + return Err(lookahead.error()); + } + }, + }) + } } -pub fn name_to_string(name: TokenStream) -> String { - name.into_iter().map(|token| token.to_string()).collect() +impl ToTokens for ControlFlow { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.at_token.to_tokens(tokens); + match &self.kind { + ControlFlowKind::Let(local) => local.to_tokens(tokens), + ControlFlowKind::If(if_) => if_.to_tokens(tokens), + ControlFlowKind::For(for_) => for_.to_tokens(tokens), + ControlFlowKind::While(while_) => while_.to_tokens(tokens), + ControlFlowKind::Match(match_) => match_.to_tokens(tokens), + } + } +} + +#[derive(Debug, Clone)] +pub enum ControlFlowKind { + Let(Local), + If(IfExpr), + For(ForExpr), + While(WhileExpr), + Match(MatchExpr), +} + +#[derive(Debug, Clone)] +pub struct IfExpr { + pub if_token: If, + pub cond: Expr, + pub then_branch: Block, + pub else_branch: Option<(At, Else, Box>)>, +} + +impl DiagnosticParse for IfExpr { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + if_token: input.parse()?, + cond: input.call(Expr::parse_without_eager_brace)?, + then_branch: input.diagnostic_parse(diagnostics)?, + else_branch: { + if input.peek(At) && input.peek2(Else) { + Some(( + input.parse()?, + input.parse()?, + input.diagnostic_parse(diagnostics)?, + )) + } else { + None + } + }, + }) + } +} + +impl ToTokens for IfExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.if_token.to_tokens(tokens); + self.cond.to_tokens(tokens); + self.then_branch.to_tokens(tokens); + if let Some((at_token, else_token, else_branch)) = &self.else_branch { + at_token.to_tokens(tokens); + else_token.to_tokens(tokens); + else_branch.to_tokens(tokens); + } + } +} + +#[derive(Debug, Clone)] +pub enum IfOrBlock { + If(IfExpr), + Block(Block), +} + +impl DiagnosticParse for IfOrBlock { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(If) { + input.diagnostic_parse(diagnostics).map(Self::If) + } else if lookahead.peek(Brace) { + input.diagnostic_parse(diagnostics).map(Self::Block) + } else { + Err(lookahead.error()) + } + } +} + +impl ToTokens for IfOrBlock { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::If(if_) => if_.to_tokens(tokens), + Self::Block(block) => block.to_tokens(tokens), + } + } +} + +#[derive(Debug, Clone)] +pub struct ForExpr { + pub for_token: For, + pub pat: Pat, + pub in_token: In, + pub expr: Expr, + pub body: Block, +} + +impl DiagnosticParse for ForExpr { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + for_token: input.parse()?, + pat: input.call(Pat::parse_multi_with_leading_vert)?, + in_token: input.parse()?, + expr: input.call(Expr::parse_without_eager_brace)?, + body: input.diagnostic_parse(diagnostics)?, + }) + } +} + +impl ToTokens for ForExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.for_token.to_tokens(tokens); + self.pat.to_tokens(tokens); + self.in_token.to_tokens(tokens); + self.expr.to_tokens(tokens); + self.body.to_tokens(tokens); + } +} + +#[derive(Debug, Clone)] +pub struct WhileExpr { + pub while_token: While, + pub cond: Expr, + pub body: Block, +} + +impl DiagnosticParse for WhileExpr { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + while_token: input.parse()?, + cond: input.call(Expr::parse_without_eager_brace)?, + body: input.diagnostic_parse(diagnostics)?, + }) + } +} + +impl ToTokens for WhileExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.while_token.to_tokens(tokens); + self.cond.to_tokens(tokens); + self.body.to_tokens(tokens); + } +} + +#[derive(Debug, Clone)] +pub struct MatchExpr { + pub match_token: Match, + pub expr: Expr, + pub brace_token: Brace, + pub arms: Vec>, +} + +impl DiagnosticParse for MatchExpr { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + let match_token = input.parse()?; + let expr = input.call(Expr::parse_without_eager_brace)?; + + let content; + let brace_token = braced!(content in input); + + let mut arms = Vec::new(); + while !content.is_empty() { + arms.push(content.diagnostic_parse(diagnostics)?); + } + + Ok(Self { + match_token, + expr, + brace_token, + arms, + }) + } +} + +impl ToTokens for MatchExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.match_token.to_tokens(tokens); + self.expr.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + for arm in &self.arms { + arm.to_tokens(tokens); + } + }); + } +} + +#[derive(Debug, Clone)] +pub struct MatchArm { + pub pat: Pat, + pub guard: Option<(If, Expr)>, + pub fat_arrow_token: FatArrow, + pub body: Markup, + pub comma_token: Option, +} + +impl DiagnosticParse for MatchArm { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Self { + pat: Pat::parse_multi_with_leading_vert(input)?, + guard: { + if input.peek(If) { + Some((input.parse()?, input.parse()?)) + } else { + None + } + }, + fat_arrow_token: input.parse()?, + body: Markup::diagnostic_parse_in_block(input, diagnostics)?, + comma_token: if input.peek(Comma) { + Some(input.parse()?) + } else { + None + }, + }) + } +} + +impl ToTokens for MatchArm { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.pat.to_tokens(tokens); + if let Some((if_token, guard)) = &self.guard { + if_token.to_tokens(tokens); + guard.to_tokens(tokens); + } + self.fat_arrow_token.to_tokens(tokens); + self.body.to_tokens(tokens); + if let Some(comma_token) = &self.comma_token { + comma_token.to_tokens(tokens); + } + } +} + +pub trait DiagnosticParse: Sized { + fn diagnostic_parse(input: ParseStream, diagnostics: &mut Vec) + -> syn::Result; +} + +impl DiagnosticParse for Box { + fn diagnostic_parse( + input: ParseStream, + diagnostics: &mut Vec, + ) -> syn::Result { + Ok(Box::new(input.diagnostic_parse(diagnostics)?)) + } +} + +trait DiagonsticParseExt: Sized { + fn diagnostic_parse( + self, + diagnostics: &mut Vec, + ) -> syn::Result; +} + +impl DiagonsticParseExt for ParseStream<'_> { + fn diagnostic_parse(self, diagnostics: &mut Vec) -> syn::Result + where + T: DiagnosticParse, + { + T::diagnostic_parse(self, diagnostics) + } } diff --git a/helpers/pagetop-macros/src/maud/escape.rs b/helpers/pagetop-macros/src/maud/escape.rs index 49ece776..786d8c77 100644 --- a/helpers/pagetop-macros/src/maud/escape.rs +++ b/helpers/pagetop-macros/src/maud/escape.rs @@ -2,10 +2,6 @@ // !!!!!!!! PLEASE KEEP THIS IN SYNC WITH `maud/src/escape.rs` !!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -extern crate alloc; - -use alloc::string::String; - pub fn escape_to_string(input: &str, output: &mut String) { for b in input.bytes() { match b { @@ -20,10 +16,7 @@ pub fn escape_to_string(input: &str, output: &mut String) { #[cfg(test)] mod test { - extern crate alloc; - use super::escape_to_string; - use alloc::string::String; #[test] fn it_works() { diff --git a/helpers/pagetop-macros/src/maud/generate.rs b/helpers/pagetop-macros/src/maud/generate.rs index be7946d0..1f825783 100644 --- a/helpers/pagetop-macros/src/maud/generate.rs +++ b/helpers/pagetop-macros/src/maud/generate.rs @@ -1,23 +1,23 @@ -use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree}; -use proc_macro_error::SpanRange; -use quote::quote; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{parse_quote, token::Brace, Expr, Local}; use crate::maud::{ast::*, escape}; use proc_macro_crate::{crate_name, FoundCrate}; -pub fn generate(markups: Vec, output_ident: TokenTree) -> TokenStream { +pub fn generate(markups: Markups, output_ident: Ident) -> TokenStream { let mut build = Builder::new(output_ident.clone()); Generator::new(output_ident).markups(markups, &mut build); build.finish() } struct Generator { - output_ident: TokenTree, + output_ident: Ident, } impl Generator { - fn new(output_ident: TokenTree) -> Generator { + fn new(output_ident: Ident) -> Generator { Generator { output_ident } } @@ -25,257 +25,341 @@ impl Generator { Builder::new(self.output_ident.clone()) } - fn markups(&self, markups: Vec, build: &mut Builder) { - for markup in markups { + fn markups>(&self, markups: Markups, build: &mut Builder) { + for markup in markups.markups { self.markup(markup, build); } } - fn markup(&self, markup: Markup, build: &mut Builder) { + fn markup>(&self, markup: Markup, build: &mut Builder) { match markup { - Markup::ParseError { .. } => {} - Markup::Block(Block { - markups, - outer_span, - }) => { - if markups - .iter() - .any(|markup| matches!(*markup, Markup::Let { .. })) - { - self.block( - Block { - markups, - outer_span, - }, - build, - ); + Markup::Block(block) => { + if block.markups.markups.iter().any(|markup| { + matches!( + *markup, + Markup::ControlFlow(ControlFlow { + kind: ControlFlowKind::Let(_), + .. + }) + ) + }) { + self.block(block, build); } else { - self.markups(markups, build); + self.markups(block.markups, build); } } - Markup::Literal { content, .. } => build.push_escaped(&content), - Markup::Symbol { symbol } => self.name(symbol, build), + Markup::Lit(lit) => build.push_escaped(&lit.to_string()), Markup::Splice { expr, .. } => self.splice(expr, build), - Markup::Element { name, attrs, body } => self.element(name, attrs, body, build), - Markup::Let { tokens, .. } => build.push_tokens(tokens), - Markup::Special { segments } => { - for Special { head, body, .. } in segments { - build.push_tokens(head); - self.block(body, build); - } - } - Markup::Match { - head, - arms, - arms_span, - .. - } => { - let body = { - let mut build = self.builder(); - for MatchArm { head, body } in arms { - build.push_tokens(head); - self.block(body, &mut build); - } - build.finish() - }; - let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body)); - body.set_span(arms_span.collapse()); - build.push_tokens(quote!(#head #body)); - } + Markup::Element(element) => self.element(element.into(), build), + Markup::ControlFlow(control_flow) => self.control_flow(control_flow, build), + Markup::Semi(_) => {} } } - fn block( - &self, - Block { - markups, - outer_span, - }: Block, - build: &mut Builder, - ) { - let block = { + fn block>(&self, block: Block, build: &mut Builder) { + let markups = { let mut build = self.builder(); - self.markups(markups, &mut build); + self.markups(block.markups, &mut build); build.finish() }; - let mut block = TokenTree::Group(Group::new(Delimiter::Brace, block)); - block.set_span(outer_span.collapse()); - build.push_tokens(TokenStream::from(block)); + + build.push_tokens(quote!({ #markups })); } - fn splice(&self, expr: TokenStream, build: &mut Builder) { - let output_ident = self.output_ident.clone(); + fn splice(&self, expr: Expr, build: &mut Builder) { + let output_ident = &self.output_ident; - let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`"); - build.push_tokens(match found_crate { - FoundCrate::Itself => quote!( - crate::html::html_private::render_to!(&#expr, &mut #output_ident); - ), - _ => quote!( - pagetop::html::html_private::render_to!(&#expr, &mut #output_ident); - ), + let found_crate = crate_name("pagetop").expect("pagetop debe existir en Cargo.toml"); + let crate_ident = match found_crate { + FoundCrate::Itself => Ident::new("pagetop", Span::call_site()), + FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), + }; + build.push_tokens(quote! { + #crate_ident::html::html_private::render_to!(&(#expr), &mut #output_ident); }); } - fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut Builder) { + fn element(&self, element: Element, build: &mut Builder) { + let element_name = element.name.clone().unwrap_or_else(|| parse_quote!(div)); build.push_str("<"); - self.name(name.clone(), build); - self.attrs(attrs, build); + self.name(element_name.clone(), build); + self.attrs(element.attrs, build); build.push_str(">"); - if let ElementBody::Block { block } = body { + if let ElementBody::Block(block) = element.body { self.markups(block.markups, build); build.push_str(""); } } - fn name(&self, name: TokenStream, build: &mut Builder) { - build.push_escaped(&name_to_string(name)); + fn name(&self, name: HtmlName, build: &mut Builder) { + build.push_escaped(&name.to_string()); } - fn attrs(&self, attrs: Vec, build: &mut Builder) { - for NamedAttr { name, attr_type } in desugar_attrs(attrs) { - match attr_type { - AttrType::Normal { value } => { + fn name_or_markup(&self, name: HtmlNameOrMarkup, build: &mut Builder) { + match name { + HtmlNameOrMarkup::HtmlName(name) => self.name(name, build), + HtmlNameOrMarkup::Markup(markup) => self.markup(markup, build), + } + } + + fn attr(&self, name: HtmlName, value: AttributeType, build: &mut Builder) { + match value { + AttributeType::Normal { value, .. } => { + build.push_str(" "); + self.name(name, build); + build.push_str("=\""); + self.markup(value, build); + build.push_str("\""); + } + AttributeType::Optional { + toggler: Toggler { cond, .. }, + .. + } => { + let inner_value: Expr = parse_quote!(inner_value); + + let body = { + let mut build = self.builder(); build.push_str(" "); - self.name(name, build); + self.name(name, &mut build); build.push_str("=\""); - self.markup(value, build); + self.splice(inner_value.clone(), &mut build); build.push_str("\""); - } - AttrType::Optional { - toggler: Toggler { cond, .. }, - } => { - let inner_value = quote!(inner_value); - let body = { - let mut build = self.builder(); - build.push_str(" "); - self.name(name, &mut build); - build.push_str("=\""); - self.splice(inner_value.clone(), &mut build); - build.push_str("\""); - build.finish() - }; - build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body })); - } - AttrType::Empty { toggler: None } => { + build.finish() + }; + build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body })); + } + AttributeType::Empty(None) => { + build.push_str(" "); + self.name(name, build); + } + AttributeType::Empty(Some(Toggler { cond, .. })) => { + let body = { + let mut build = self.builder(); build.push_str(" "); - self.name(name, build); - } - AttrType::Empty { - toggler: Some(Toggler { cond, .. }), - } => { - let body = { - let mut build = self.builder(); - build.push_str(" "); - self.name(name, &mut build); - build.finish() - }; - build.push_tokens(quote!(if (#cond) { #body })); - } + self.name(name, &mut build); + build.finish() + }; + build.push_tokens(quote!(if (#cond) { #body })); } } } + + fn attrs(&self, attrs: Vec, build: &mut Builder) { + let (classes, id, named_attrs) = split_attrs(attrs); + + if !classes.is_empty() { + let mut toggle_class_exprs = vec![]; + + build.push_str(" "); + self.name(parse_quote!(class), build); + build.push_str("=\""); + for (i, (name, toggler)) in classes.into_iter().enumerate() { + if let Some(toggler) = toggler { + toggle_class_exprs.push((i > 0, name, toggler)); + } else { + if i > 0 { + build.push_str(" "); + } + self.name_or_markup(name, build); + } + } + + for (not_first, name, toggler) in toggle_class_exprs { + let body = { + let mut build = self.builder(); + if not_first { + build.push_str(" "); + } + self.name_or_markup(name, &mut build); + build.finish() + }; + build.push_tokens(quote!(if (#toggler) { #body })); + } + + build.push_str("\""); + } + + if let Some(id) = id { + build.push_str(" "); + self.name(parse_quote!(id), build); + build.push_str("=\""); + self.name_or_markup(id, build); + build.push_str("\""); + } + + for (name, attr_type) in named_attrs { + self.attr(name, attr_type, build); + } + } + + fn control_flow>(&self, control_flow: ControlFlow, build: &mut Builder) { + match control_flow.kind { + ControlFlowKind::If(if_) => self.control_flow_if(if_, build), + ControlFlowKind::Let(let_) => self.control_flow_let(let_, build), + ControlFlowKind::For(for_) => self.control_flow_for(for_, build), + ControlFlowKind::While(while_) => self.control_flow_while(while_, build), + ControlFlowKind::Match(match_) => self.control_flow_match(match_, build), + } + } + + fn control_flow_if>( + &self, + IfExpr { + if_token, + cond, + then_branch, + else_branch, + }: IfExpr, + build: &mut Builder, + ) { + build.push_tokens(quote!(#if_token #cond)); + self.block(then_branch, build); + + if let Some((_, else_token, if_or_block)) = else_branch { + build.push_tokens(quote!(#else_token)); + self.control_flow_if_or_block(*if_or_block, build); + } + } + + fn control_flow_if_or_block>( + &self, + if_or_block: IfOrBlock, + build: &mut Builder, + ) { + match if_or_block { + IfOrBlock::If(if_) => self.control_flow_if(if_, build), + IfOrBlock::Block(block) => self.block(block, build), + } + } + + fn control_flow_let(&self, let_: Local, build: &mut Builder) { + build.push_tokens(let_.to_token_stream()); + } + + fn control_flow_for>( + &self, + ForExpr { + for_token, + pat, + in_token, + expr, + body, + }: ForExpr, + build: &mut Builder, + ) { + build.push_tokens(quote!(#for_token #pat #in_token (#expr))); + self.block(body, build); + } + + fn control_flow_while>( + &self, + WhileExpr { + while_token, + cond, + body, + }: WhileExpr, + build: &mut Builder, + ) { + build.push_tokens(quote!(#while_token #cond)); + self.block(body, build); + } + + fn control_flow_match>( + &self, + MatchExpr { + match_token, + expr, + brace_token, + arms, + }: MatchExpr, + build: &mut Builder, + ) { + let arms = { + let mut build = self.builder(); + for MatchArm { + pat, + guard, + fat_arrow_token, + body, + comma_token, + } in arms + { + build.push_tokens(quote!(#pat)); + if let Some((if_token, cond)) = guard { + build.push_tokens(quote!(#if_token #cond)); + } + build.push_tokens(quote!(#fat_arrow_token)); + self.block( + Block { + brace_token: Brace(Span::call_site()), + markups: Markups { + markups: vec![body], + }, + }, + &mut build, + ); + build.push_tokens(quote!(#comma_token)); + } + build.finish() + }; + + let mut arm_block = TokenStream::new(); + + brace_token.surround(&mut arm_block, |tokens| { + arms.to_tokens(tokens); + }); + + build.push_tokens(quote!(#match_token #expr #arm_block)); + } } //////////////////////////////////////////////////////// -fn desugar_attrs(attrs: Vec) -> Vec { - let mut classes_static = vec![]; - let mut classes_toggled = vec![]; - let mut ids = vec![]; +#[allow(clippy::type_complexity)] +fn split_attrs( + attrs: Vec, +) -> ( + Vec<(HtmlNameOrMarkup, Option)>, + Option, + Vec<(HtmlName, AttributeType)>, +) { + let mut classes = vec![]; + let mut id = None; let mut named_attrs = vec![]; + for attr in attrs { match attr { - Attr::Class { - name, - toggler: Some(toggler), - .. - } => classes_toggled.push((name, toggler)), - Attr::Class { - name, - toggler: None, - .. - } => classes_static.push(name), - Attr::Id { name, .. } => ids.push(name), - Attr::Named { named_attr } => named_attrs.push(named_attr), + Attribute::Class { name, toggler, .. } => { + classes.push((name, toggler.map(|toggler| toggler.cond))) + } + Attribute::Id { name, .. } => id = Some(name), + Attribute::Named { name, attr_type } => named_attrs.push((name, attr_type)), } } - let classes = desugar_classes_or_ids("class", classes_static, classes_toggled); - let ids = desugar_classes_or_ids("id", ids, vec![]); - classes.into_iter().chain(ids).chain(named_attrs).collect() -} -fn desugar_classes_or_ids( - attr_name: &'static str, - values_static: Vec, - values_toggled: Vec<(Markup, Toggler)>, -) -> Option { - if values_static.is_empty() && values_toggled.is_empty() { - return None; - } - let mut markups = Vec::new(); - let mut leading_space = false; - for name in values_static { - markups.extend(prepend_leading_space(name, &mut leading_space)); - } - for (name, Toggler { cond, cond_span }) in values_toggled { - let body = Block { - markups: prepend_leading_space(name, &mut leading_space), - // TODO: is this correct? - outer_span: cond_span, - }; - markups.push(Markup::Special { - segments: vec![Special { - at_span: SpanRange::call_site(), - head: quote!(if (#cond)), - body, - }], - }); - } - Some(NamedAttr { - name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))), - attr_type: AttrType::Normal { - value: Markup::Block(Block { - markups, - outer_span: SpanRange::call_site(), - }), - }, - }) -} - -fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec { - let mut markups = Vec::new(); - if *leading_space { - markups.push(Markup::Literal { - content: " ".to_owned(), - span: name.span(), - }); - } - *leading_space = true; - markups.push(name); - markups + (classes, id, named_attrs) } //////////////////////////////////////////////////////// struct Builder { - output_ident: TokenTree, - tokens: Vec, + output_ident: Ident, + tokens: TokenStream, tail: String, } impl Builder { - fn new(output_ident: TokenTree) -> Builder { + fn new(output_ident: Ident) -> Builder { Builder { output_ident, - tokens: Vec::new(), + tokens: TokenStream::new(), tail: String::new(), } } - fn push_str(&mut self, string: &str) { + fn push_str(&mut self, string: &'static str) { self.tail.push_str(string); } @@ -294,8 +378,8 @@ impl Builder { } let push_str_expr = { let output_ident = self.output_ident.clone(); - let string = TokenTree::Literal(Literal::string(&self.tail)); - quote!(#output_ident.push_str(#string);) + let tail = &self.tail; + quote!(#output_ident.push_str(#tail);) }; self.tail.clear(); self.tokens.extend(push_str_expr); @@ -303,6 +387,6 @@ impl Builder { fn finish(mut self) -> TokenStream { self.cut(); - self.tokens.into_iter().collect() + self.tokens } } diff --git a/helpers/pagetop-macros/src/maud/parse.rs b/helpers/pagetop-macros/src/maud/parse.rs deleted file mode 100644 index d24cea6e..00000000 --- a/helpers/pagetop-macros/src/maud/parse.rs +++ /dev/null @@ -1,752 +0,0 @@ -use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree}; -use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange}; -use std::collections::HashMap; - -use syn::Lit; - -use crate::maud::ast; - -pub fn parse(input: TokenStream) -> Vec { - Parser::new(input).markups() -} - -#[derive(Clone)] -struct Parser { - /// If we're inside an attribute, then this contains the attribute name. - current_attr: Option, - input: ::IntoIter, -} - -impl Iterator for Parser { - type Item = TokenTree; - - fn next(&mut self) -> Option { - self.input.next() - } -} - -impl Parser { - fn new(input: TokenStream) -> Parser { - Parser { - current_attr: None, - input: input.into_iter(), - } - } - - fn with_input(&self, input: TokenStream) -> Parser { - Parser { - current_attr: self.current_attr.clone(), - input: input.into_iter(), - } - } - - /// Returns the next token in the stream without consuming it. - fn peek(&mut self) -> Option { - self.clone().next() - } - - /// Returns the next two tokens in the stream without consuming them. - fn peek2(&mut self) -> Option<(TokenTree, Option)> { - let mut clone = self.clone(); - clone.next().map(|first| (first, clone.next())) - } - - /// Advances the cursor by one step. - fn advance(&mut self) { - self.next(); - } - - /// Advances the cursor by two steps. - fn advance2(&mut self) { - self.next(); - self.next(); - } - - /// Parses multiple blocks of markup. - fn markups(&mut self) -> Vec { - let mut result = Vec::new(); - loop { - match self.peek2() { - None => break, - Some((TokenTree::Punct(ref punct), _)) if punct.as_char() == ';' => self.advance(), - Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref ident)))) - if punct.as_char() == '@' && *ident == "let" => - { - self.advance2(); - let keyword = TokenTree::Ident(ident.clone()); - result.push(self.let_expr(punct.span(), keyword)); - } - _ => result.push(self.markup()), - } - } - result - } - - /// Parses a single block of markup. - fn markup(&mut self) -> ast::Markup { - let token = match self.peek() { - Some(token) => token, - None => { - abort_call_site!("unexpected end of input"); - } - }; - let markup = match token { - // Literal - TokenTree::Literal(literal) => { - self.advance(); - self.literal(literal) - } - // Special form - TokenTree::Punct(ref punct) if punct.as_char() == '@' => { - self.advance(); - let at_span = punct.span(); - match self.next() { - Some(TokenTree::Ident(ident)) => { - let keyword = TokenTree::Ident(ident.clone()); - match ident.to_string().as_str() { - "if" => { - let mut segments = Vec::new(); - self.if_expr(at_span, vec![keyword], &mut segments); - ast::Markup::Special { segments } - } - "while" => self.while_expr(at_span, keyword), - "for" => self.for_expr(at_span, keyword), - "match" => self.match_expr(at_span, keyword), - "let" => { - let span = SpanRange { - first: at_span, - last: ident.span(), - }; - abort!(span, "`@let` only works inside a block"); - } - other => { - let span = SpanRange { - first: at_span, - last: ident.span(), - }; - abort!(span, "unknown keyword `@{}`", other); - } - } - } - _ => { - abort!(at_span, "expected keyword after `@`"); - } - } - } - // Element - TokenTree::Ident(ident) => { - let ident_string = ident.to_string(); - match ident_string.as_str() { - "if" | "while" | "for" | "match" | "let" => { - abort!( - ident, - "found keyword `{}`", ident_string; - help = "should this be a `@{}`?", ident_string - ); - } - "true" | "false" => { - if let Some(attr_name) = &self.current_attr { - emit_error!( - ident, - r#"attribute value must be a string"#; - help = "to declare an empty attribute, omit the equals sign: `{}`", - attr_name; - help = "to toggle the attribute, use square brackets: `{}[some_boolean_flag]`", - attr_name; - ); - return ast::Markup::ParseError { - span: SpanRange::single_span(ident.span()), - }; - } - } - _ => {} - } - - // `.try_namespaced_name()` should never fail as we've - // already seen an `Ident` - let name = self.try_namespaced_name().expect("identifier"); - self.element(name) - } - // Div element shorthand - TokenTree::Punct(ref punct) if punct.as_char() == '.' || punct.as_char() == '#' => { - let name = TokenTree::Ident(Ident::new("div", punct.span())); - self.element(name.into()) - } - // Splice - TokenTree::Group(ref group) if group.delimiter() == Delimiter::Parenthesis => { - self.advance(); - ast::Markup::Splice { - expr: group.stream(), - outer_span: SpanRange::single_span(group.span()), - } - } - // Block - TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => { - self.advance(); - ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span()))) - } - // ??? - token => { - abort!(token, "invalid syntax"); - } - }; - markup - } - - /// Parses a literal string. - fn literal(&mut self, literal: Literal) -> ast::Markup { - match Lit::new(literal.clone()) { - Lit::Str(lit_str) => { - return ast::Markup::Literal { - content: lit_str.value(), - span: SpanRange::single_span(literal.span()), - } - } - // Boolean literals are idents, so `Lit::Bool` is handled in - // `markup`, not here. - Lit::Int(..) | Lit::Float(..) => { - emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal); - } - Lit::Char(lit_char) => { - emit_error!( - literal, - r#"literal must be double-quoted: `"{}"`"#, - lit_char.value(), - ); - } - _ => { - emit_error!(literal, "expected string"); - } - } - ast::Markup::ParseError { - span: SpanRange::single_span(literal.span()), - } - } - - /// Parses an `@if` expression. - /// - /// The leading `@if` should already be consumed. - fn if_expr(&mut self, at_span: Span, prefix: Vec, segments: &mut Vec) { - let mut head = prefix; - let body = loop { - match self.next() { - Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), SpanRange::single_span(block.span())); - } - Some(token) => head.push(token), - None => { - let mut span = ast::span_tokens(head); - span.first = at_span; - abort!(span, "expected body for this `@if`"); - } - } - }; - segments.push(ast::Special { - at_span: SpanRange::single_span(at_span), - head: head.into_iter().collect(), - body, - }); - self.else_if_expr(segments) - } - - /// Parses an optional `@else if` or `@else`. - /// - /// The leading `@else if` or `@else` should *not* already be consumed. - fn else_if_expr(&mut self, segments: &mut Vec) { - match self.peek2() { - Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref else_keyword)))) - if punct.as_char() == '@' && *else_keyword == "else" => - { - self.advance2(); - let at_span = punct.span(); - let else_keyword = TokenTree::Ident(else_keyword.clone()); - match self.peek() { - // `@else if` - Some(TokenTree::Ident(ref if_keyword)) if *if_keyword == "if" => { - self.advance(); - let if_keyword = TokenTree::Ident(if_keyword.clone()); - self.if_expr(at_span, vec![else_keyword, if_keyword], segments) - } - // Just an `@else` - _ => match self.next() { - Some(TokenTree::Group(ref group)) - if group.delimiter() == Delimiter::Brace => - { - let body = - self.block(group.stream(), SpanRange::single_span(group.span())); - segments.push(ast::Special { - at_span: SpanRange::single_span(at_span), - head: vec![else_keyword].into_iter().collect(), - body, - }); - } - _ => { - let span = SpanRange { - first: at_span, - last: else_keyword.span(), - }; - abort!(span, "expected body for this `@else`"); - } - }, - } - } - // We didn't find an `@else`; stop - _ => {} - } - } - - /// Parses an `@while` expression. - /// - /// The leading `@while` should already be consumed. - fn while_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); - let mut head = vec![keyword]; - let body = loop { - match self.next() { - Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), SpanRange::single_span(block.span())); - } - Some(token) => head.push(token), - None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@while`"); - } - } - }; - ast::Markup::Special { - segments: vec![ast::Special { - at_span: SpanRange::single_span(at_span), - head: head.into_iter().collect(), - body, - }], - } - } - - /// Parses a `@for` expression. - /// - /// The leading `@for` should already be consumed. - fn for_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); - let mut head = vec![keyword]; - loop { - match self.next() { - Some(TokenTree::Ident(ref in_keyword)) if *in_keyword == "in" => { - head.push(TokenTree::Ident(in_keyword.clone())); - break; - } - Some(token) => head.push(token), - None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "missing `in` in `@for` loop"); - } - } - } - let body = loop { - match self.next() { - Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), SpanRange::single_span(block.span())); - } - Some(token) => head.push(token), - None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@for`"); - } - } - }; - ast::Markup::Special { - segments: vec![ast::Special { - at_span: SpanRange::single_span(at_span), - head: head.into_iter().collect(), - body, - }], - } - } - - /// Parses a `@match` expression. - /// - /// The leading `@match` should already be consumed. - fn match_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); - let mut head = vec![keyword]; - let (arms, arms_span) = loop { - match self.next() { - Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { - let span = SpanRange::single_span(body.span()); - break (self.with_input(body.stream()).match_arms(), span); - } - Some(token) => head.push(token), - None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@match`"); - } - } - }; - ast::Markup::Match { - at_span: SpanRange::single_span(at_span), - head: head.into_iter().collect(), - arms, - arms_span, - } - } - - fn match_arms(&mut self) -> Vec { - let mut arms = Vec::new(); - while let Some(arm) = self.match_arm() { - arms.push(arm); - } - arms - } - - fn match_arm(&mut self) -> Option { - let mut head = Vec::new(); - loop { - match self.peek2() { - Some((TokenTree::Punct(ref eq), Some(TokenTree::Punct(ref gt)))) - if eq.as_char() == '=' - && gt.as_char() == '>' - && eq.spacing() == Spacing::Joint => - { - self.advance2(); - head.push(TokenTree::Punct(eq.clone())); - head.push(TokenTree::Punct(gt.clone())); - break; - } - Some((token, _)) => { - self.advance(); - head.push(token); - } - None => { - if head.is_empty() { - return None; - } else { - let head_span = ast::span_tokens(head); - abort!(head_span, "unexpected end of @match pattern"); - } - } - } - } - let body = match self.next() { - // $pat => { $stmts } - Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { - let body = self.block(body.stream(), SpanRange::single_span(body.span())); - // Trailing commas are optional if the match arm is a braced block - if let Some(TokenTree::Punct(ref punct)) = self.peek() { - if punct.as_char() == ',' { - self.advance(); - } - } - body - } - // $pat => $expr - Some(first_token) => { - let mut span = SpanRange::single_span(first_token.span()); - let mut body = vec![first_token]; - loop { - match self.next() { - Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => break, - Some(token) => { - span.last = token.span(); - body.push(token); - } - None => break, - } - } - self.block(body.into_iter().collect(), span) - } - None => { - let span = ast::span_tokens(head); - abort!(span, "unexpected end of @match arm"); - } - }; - Some(ast::MatchArm { - head: head.into_iter().collect(), - body, - }) - } - - /// Parses a `@let` expression. - /// - /// The leading `@let` should already be consumed. - fn let_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let mut tokens = vec![keyword]; - loop { - match self.next() { - Some(token) => match token { - TokenTree::Punct(ref punct) if punct.as_char() == '=' => { - tokens.push(token.clone()); - break; - } - _ => tokens.push(token), - }, - None => { - let mut span = ast::span_tokens(tokens); - span.first = at_span; - abort!(span, "unexpected end of `@let` expression"); - } - } - } - loop { - match self.next() { - Some(token) => match token { - TokenTree::Punct(ref punct) if punct.as_char() == ';' => { - tokens.push(token.clone()); - break; - } - _ => tokens.push(token), - }, - None => { - let mut span = ast::span_tokens(tokens); - span.first = at_span; - abort!( - span, - "unexpected end of `@let` expression"; - help = "are you missing a semicolon?" - ); - } - } - } - ast::Markup::Let { - at_span: SpanRange::single_span(at_span), - tokens: tokens.into_iter().collect(), - } - } - - /// Parses an element node. - /// - /// The element name should already be consumed. - fn element(&mut self, name: TokenStream) -> ast::Markup { - if self.current_attr.is_some() { - let span = ast::span_tokens(name); - abort!(span, "unexpected element"); - } - let attrs = self.attrs(); - let body = match self.peek() { - Some(TokenTree::Punct(ref punct)) - if punct.as_char() == ';' || punct.as_char() == '/' => - { - // Void element - self.advance(); - if punct.as_char() == '/' { - emit_error!( - punct, - "void elements must use `;`, not `/`"; - help = "change this to `;`"; - help = "see https://github.com/lambda-fairy/maud/pull/315 for details"; - ); - } - ast::ElementBody::Void { - semi_span: SpanRange::single_span(punct.span()), - } - } - Some(_) => match self.markup() { - ast::Markup::Block(block) => ast::ElementBody::Block { block }, - markup => { - let markup_span = markup.span(); - abort!( - markup_span, - "element body must be wrapped in braces"; - help = "see https://github.com/lambda-fairy/maud/pull/137 for details" - ); - } - }, - None => abort_call_site!("expected `;`, found end of macro"), - }; - ast::Markup::Element { name, attrs, body } - } - - /// Parses the attributes of an element. - fn attrs(&mut self) -> Vec { - let mut attrs = Vec::new(); - loop { - if let Some(name) = self.try_namespaced_name() { - // Attribute - match self.peek() { - // Non-empty attribute - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => { - self.advance(); - // Parse a value under an attribute context - assert!(self.current_attr.is_none()); - self.current_attr = Some(ast::name_to_string(name.clone())); - let attr_type = match self.attr_toggler() { - Some(toggler) => ast::AttrType::Optional { toggler }, - None => { - let value = self.markup(); - ast::AttrType::Normal { value } - } - }; - self.current_attr = None; - attrs.push(ast::Attr::Named { - named_attr: ast::NamedAttr { name, attr_type }, - }); - } - // Empty attribute (legacy syntax) - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '?' => { - self.advance(); - let toggler = self.attr_toggler(); - attrs.push(ast::Attr::Named { - named_attr: ast::NamedAttr { - name: name.clone(), - attr_type: ast::AttrType::Empty { toggler }, - }, - }); - } - // Empty attribute (new syntax) - _ => { - let toggler = self.attr_toggler(); - attrs.push(ast::Attr::Named { - named_attr: ast::NamedAttr { - name: name.clone(), - attr_type: ast::AttrType::Empty { toggler }, - }, - }); - } - } - } else { - match self.peek() { - // Class shorthand - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '.' => { - self.advance(); - let name = self.class_or_id_name(); - let toggler = self.attr_toggler(); - attrs.push(ast::Attr::Class { - dot_span: SpanRange::single_span(punct.span()), - name, - toggler, - }); - } - // ID shorthand - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '#' => { - self.advance(); - let name = self.class_or_id_name(); - attrs.push(ast::Attr::Id { - hash_span: SpanRange::single_span(punct.span()), - name, - }); - } - // If it's not a valid attribute, backtrack and bail out - _ => break, - } - } - } - - let mut attr_map: HashMap> = HashMap::new(); - let mut has_class = false; - for attr in &attrs { - let name = match attr { - ast::Attr::Class { .. } => { - if has_class { - // Only check the first class to avoid spurious duplicates - continue; - } - has_class = true; - "class".to_string() - } - ast::Attr::Id { .. } => "id".to_string(), - ast::Attr::Named { named_attr } => named_attr - .name - .clone() - .into_iter() - .map(|token| token.to_string()) - .collect(), - }; - let entry = attr_map.entry(name).or_default(); - entry.push(attr.span()); - } - - for (name, spans) in attr_map { - if spans.len() > 1 { - let mut spans = spans.into_iter(); - let first_span = spans.next().expect("spans should be non-empty"); - abort!(first_span, "duplicate attribute `{}`", name); - } - } - - attrs - } - - /// Parses the name of a class or ID. - fn class_or_id_name(&mut self) -> ast::Markup { - if let Some(symbol) = self.try_name() { - ast::Markup::Symbol { symbol } - } else { - self.markup() - } - } - - /// Parses the `[cond]` syntax after an empty attribute or class shorthand. - fn attr_toggler(&mut self) -> Option { - match self.peek() { - Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Bracket => { - self.advance(); - Some(ast::Toggler { - cond: group.stream(), - cond_span: SpanRange::single_span(group.span()), - }) - } - _ => None, - } - } - - /// Parses an identifier, without dealing with namespaces. - fn try_name(&mut self) -> Option { - let mut result = Vec::new(); - if let Some(token @ TokenTree::Ident(_)) = self.peek() { - self.advance(); - result.push(token); - } else { - return None; - } - let mut expect_ident = false; - loop { - expect_ident = match self.peek() { - Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => { - self.advance(); - result.push(TokenTree::Punct(punct.clone())); - true - } - Some(TokenTree::Ident(ref ident)) if expect_ident => { - self.advance(); - result.push(TokenTree::Ident(ident.clone())); - false - } - _ => break, - }; - } - Some(result.into_iter().collect()) - } - - /// Parses a HTML element or attribute name, along with a namespace - /// if necessary. - fn try_namespaced_name(&mut self) -> Option { - let mut result = vec![self.try_name()?]; - if let Some(TokenTree::Punct(ref punct)) = self.peek() { - if punct.as_char() == ':' { - self.advance(); - result.push(TokenStream::from(TokenTree::Punct(punct.clone()))); - result.push(self.try_name()?); - } - } - Some(result.into_iter().collect()) - } - - /// Parses the given token stream as a Maud expression. - fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block { - let markups = self.with_input(body).markups(); - ast::Block { - markups, - outer_span, - } - } -} diff --git a/src/html.rs b/src/html.rs index 1f24f630..14e72b94 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,4 +1,4 @@ //! HTML en código. mod maud; -pub use maud::{html, html_private, Markup, PreEscaped, DOCTYPE}; +pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, Render, DOCTYPE}; diff --git a/src/html/maud.rs b/src/html/maud.rs index db9308af..19429869 100644 --- a/src/html/maud.rs +++ b/src/html/maud.rs @@ -1,4 +1,4 @@ -//#![no_std] +// #![no_std] //! A macro for writing HTML templates. //! @@ -7,11 +7,11 @@ //! //! [book]: https://maud.lambda.xyz/ -//#![doc(html_root_url = "https://docs.rs/maud/0.25.0")] +// #![doc(html_root_url = "https://docs.rs/maud/0.27.0")] extern crate alloc; -use alloc::{borrow::Cow, boxed::Box, string::String}; +use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc}; use core::fmt::{self, Arguments, Display, Write}; pub use pagetop_macros::html; @@ -34,8 +34,8 @@ mod escape; /// /// # Example /// -/// ```rust#ignore -/// use maud::Escaper; +/// ```rust +/// use pagetop::html::Escaper; /// use std::fmt::Write; /// let mut s = String::new(); /// write!(Escaper::new(&mut s), "").unwrap(); @@ -50,7 +50,7 @@ impl<'a> Escaper<'a> { } } -impl<'a> fmt::Write for Escaper<'a> { +impl fmt::Write for Escaper<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { escape::escape_to_string(s, self.0); Ok(()) @@ -72,8 +72,8 @@ impl<'a> fmt::Write for Escaper<'a> { /// /// # Example /// -/// ```rust#ignore -/// use maud::{html, Markup, Render}; +/// ```rust +/// use pagetop::html::{html, Markup, Render}; /// /// /// Provides a shorthand for linking to a CSS stylesheet. /// pub struct Stylesheet(&'static str); @@ -120,25 +120,25 @@ impl Render for String { } } -impl<'a> Render for Cow<'a, str> { +impl Render for Cow<'_, str> { fn render_to(&self, w: &mut String) { str::render_to(self, w); } } -impl<'a> Render for Arguments<'a> { +impl Render for Arguments<'_> { fn render_to(&self, w: &mut String) { let _ = Escaper::new(w).write_fmt(*self); } } -impl<'a, T: Render + ?Sized> Render for &'a T { +impl Render for &T { fn render_to(&self, w: &mut String) { T::render_to(self, w); } } -impl<'a, T: Render + ?Sized> Render for &'a mut T { +impl Render for &mut T { fn render_to(&self, w: &mut String) { T::render_to(self, w); } @@ -150,6 +150,12 @@ impl Render for Box { } } +impl Render for Arc { + fn render_to(&self, w: &mut String) { + T::render_to(self, w); + } +} + macro_rules! impl_render_with_display { ($($ty:ty)*) => { $( @@ -188,15 +194,15 @@ impl_render_with_itoa! { /// /// # Example /// -/// ```rust#ignore -/// use maud::html; +/// ```rust +/// use pagetop::html::{display, html}; /// use std::net::Ipv4Addr; /// /// let ip_address = Ipv4Addr::new(127, 0, 0, 1); /// /// let markup = html! { /// "My IP address is: " -/// (maud::display(ip_address)) +/// (display(ip_address)) /// }; /// /// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1"); @@ -215,7 +221,7 @@ pub fn display(value: impl Display) -> impl Render { /// A wrapper that renders the inner value without escaping. #[derive(Debug, Clone, Copy)] -pub struct PreEscaped>(pub T); +pub struct PreEscaped(pub T); impl> Render for PreEscaped { fn render_to(&self, w: &mut String) { @@ -234,20 +240,20 @@ impl Markup { } } -impl + Into> PreEscaped { +impl> PreEscaped { /// Converts the inner value to a string. pub fn into_string(self) -> String { self.0.into() } } -impl + Into> From> for String { +impl> From> for String { fn from(value: PreEscaped) -> String { value.into_string() } } -impl + Default> Default for PreEscaped { +impl Default for PreEscaped { fn default() -> Self { Self(Default::default()) } @@ -259,8 +265,8 @@ impl + Default> Default for PreEscaped { /// /// A minimal web page: /// -/// ```rust#ignore -/// use maud::{DOCTYPE, html}; +/// ```rust +/// use pagetop::html::{DOCTYPE, html}; /// /// let markup = html! { /// (DOCTYPE) @@ -280,10 +286,35 @@ pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped(""); mod actix_support { extern crate alloc; + use core::{ + pin::Pin, + task::{Context, Poll}, + }; + use crate::html::PreEscaped; - use actix_web::{http::header, HttpRequest, HttpResponse, Responder}; + use actix_web::{ + body::{BodySize, MessageBody}, + http::header, + web::Bytes, + HttpRequest, HttpResponse, Responder, + }; use alloc::string::String; + impl MessageBody for PreEscaped { + type Error = ::Error; + + fn size(&self) -> BodySize { + self.0.size() + } + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + Pin::new(&mut self.0).poll_next(cx) + } + } + impl Responder for PreEscaped { type Body = String; diff --git a/src/service.rs b/src/service.rs index 9fe2450e..e6904b82 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,4 @@ -//! Gestión del servidor y servicios web ([actix-web](https://docs.rs/actix-web)). +//! Gestión del servidor y servicios web ([Actix Web](https://docs.rs/actix-web)). pub use actix_web::body::BoxBody; pub use actix_web::dev::Server; From 7a0d02d57e4c8e7ad1a0445dfc14f781bc154d30 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 7 Jul 2025 21:23:06 +0200 Subject: [PATCH 005/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20AutoDefault=20?= =?UTF-8?q?para=20derivar=20Default=20avanzado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-macros/Cargo.toml | 2 +- helpers/pagetop-macros/README.md | 9 +- helpers/pagetop-macros/src/lib.rs | 20 ++- helpers/pagetop-macros/src/smart_default.rs | 4 + .../src/smart_default/body_impl.rs | 158 ++++++++++++++++++ .../src/smart_default/default_attr.rs | 89 ++++++++++ .../pagetop-macros/src/smart_default/util.rs | 21 +++ src/lib.rs | 2 +- src/prelude.rs | 2 + src/service.rs | 2 +- 11 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 helpers/pagetop-macros/src/smart_default.rs create mode 100644 helpers/pagetop-macros/src/smart_default/body_impl.rs create mode 100644 helpers/pagetop-macros/src/smart_default/default_attr.rs create mode 100644 helpers/pagetop-macros/src/smart_default/util.rs diff --git a/Cargo.lock b/Cargo.lock index cf974c87..3fe4d512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -937,7 +937,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.2" +version = "0.0.3" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index ea6404b9..9f914bfd 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.2" +version = "0.0.3" edition = "2021" description = """\ diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 06799668..e41dd0ec 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -11,9 +11,12 @@ ## Descripción general Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros) -([0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros)) de -[Chris Wong](https://crates.io/users/lambda-fairy) para no tener que referenciar `maud` en las -dependencias del archivo `Cargo.toml` de cada proyecto `PageTop`. +([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de +[Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de +[SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de +[Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la +necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de +cada proyecto `PageTop`. ## Sobre PageTop diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index e0f3badd..eecebed3 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -15,16 +15,34 @@ //! y configurables, basadas en HTML, CSS y JavaScript. mod maud; +mod smart_default; use proc_macro::TokenStream; use quote::quote; +use syn::{parse_macro_input, DeriveInput}; -/// Macro para escribir plantillas HTML ([Maud](https://docs.rs/maud)). +/// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)). #[proc_macro] pub fn html(input: TokenStream) -> TokenStream { maud::expand(input.into()).into() } +/// Deriva [`Default`] con atributos personalizados (basada en +/// [SmartDefault](https://docs.rs/smart-default)). +/// +/// Al derivar una estructura con *AutoDefault* se genera automáticamente la implementación de +/// [`Default`]. Aunque, a diferencia de un simple `#[derive(Default)]`, el atributo +/// `#[derive(AutoDefault)]` permite usar anotaciones en los campos como `#[default = "..."]`, +/// funcionando incluso en estructuras con campos que no implementan [`Default`] o en *enums*. +#[proc_macro_derive(AutoDefault, attributes(default))] +pub fn derive_auto_default(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match smart_default::body_impl::impl_my_derive(&input) { + Ok(output) => output.into(), + Err(error) => error.to_compile_error().into(), + } +} + /// Define una función `main` asíncrona como punto de entrada de `PageTop`. /// /// # Ejemplos diff --git a/helpers/pagetop-macros/src/smart_default.rs b/helpers/pagetop-macros/src/smart_default.rs new file mode 100644 index 00000000..87177dca --- /dev/null +++ b/helpers/pagetop-macros/src/smart_default.rs @@ -0,0 +1,4 @@ +pub mod body_impl; + +mod default_attr; +mod util; diff --git a/helpers/pagetop-macros/src/smart_default/body_impl.rs b/helpers/pagetop-macros/src/smart_default/body_impl.rs new file mode 100644 index 00000000..6a76f904 --- /dev/null +++ b/helpers/pagetop-macros/src/smart_default/body_impl.rs @@ -0,0 +1,158 @@ +use proc_macro2::TokenStream; + +use quote::quote; +use syn::parse::Error; +use syn::spanned::Spanned; +use syn::DeriveInput; + +use crate::smart_default::default_attr::{ConversionStrategy, DefaultAttr}; +use crate::smart_default::util::find_only; + +pub fn impl_my_derive(input: &DeriveInput) -> Result { + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let (default_expr, doc) = match input.data { + syn::Data::Struct(ref body) => { + let (body_assignment, _doc) = default_body_tt(&body.fields)?; + ( + quote! { + #name #body_assignment + }, + format!("Returns a `{}` default.", name), + ) + } + syn::Data::Enum(ref body) => { + let default_variant = find_only(body.variants.iter(), |variant| { + if let Some(meta) = DefaultAttr::find_in_attributes(&variant.attrs)? { + if meta.code.is_none() { + Ok(true) + } else { + Err(Error::new( + meta.code.span(), + "Attribute #[default] on variants should have no value", + )) + } + } else { + Ok(false) + } + })? + .ok_or_else(|| Error::new(input.span(), "No default variant"))?; + let default_variant_name = &default_variant.ident; + let (body_assignment, _doc) = default_body_tt(&default_variant.fields)?; + ( + quote! { + #name :: #default_variant_name #body_assignment + }, + format!("Returns a `{}::{}` default.", name, default_variant_name), + ) + } + syn::Data::Union(_) => { + panic!() + } + }; + Ok(quote! { + #[automatically_derived] + impl #impl_generics Default for #name #ty_generics #where_clause { + #[doc = #doc] + fn default() -> Self { + #default_expr + } + } + }) +} + +/// Return a token-tree for the default "body" - the part after the name that contains the values. +/// That is, the `{ ... }` part for structs, the `(...)` part for tuples, and nothing for units. +fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> { + let mut doc = String::new(); + use std::fmt::Write; + let body_tt = match body { + syn::Fields::Named(ref fields) => { + doc.push_str(" {"); + let result = { + let field_assignments = fields + .named + .iter() + .map(|field| { + let field_name = field.ident.as_ref(); + let (default_value, default_doc) = field_default_expr_and_doc(field)?; + write!( + &mut doc, + "\n {}: {},", + field_name.expect("field value in struct is empty"), + default_doc + ) + .unwrap(); + // let default_value = default_value.into_token_stream(); + Ok(quote! { #field_name : #default_value }) + }) + .collect::, Error>>()?; + quote! { + { + #( #field_assignments ),* + } + } + }; + if doc.ends_with(',') { + doc.pop(); + doc.push('\n'); + }; + doc.push('}'); + result + } + syn::Fields::Unnamed(ref fields) => { + doc.push('('); + let result = { + let field_assignments = fields + .unnamed + .iter() + .map(|field| { + let (default_value, default_doc) = field_default_expr_and_doc(field)?; + write!(&mut doc, "{}, ", default_doc).unwrap(); + Ok(default_value) + }) + .collect::, Error>>()?; + quote! { + ( + #( #field_assignments ),* + ) + } + }; + if doc.ends_with(", ") { + doc.pop(); + doc.pop(); + }; + doc.push(')'); + result + } + &syn::Fields::Unit => quote! {}, + }; + Ok((body_tt, doc)) +} + +/// Return a default expression for a field based on it's `#[default = "..."]` attribute. Panic +/// if there is more than one, of if there is a `#[default]` attribute without value. +fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String), Error> { + if let Some(default_attr) = DefaultAttr::find_in_attributes(&field.attrs)? { + let conversion_strategy = default_attr.conversion_strategy(); + let field_value = default_attr.code.ok_or_else(|| { + Error::new(field.span(), "Expected #[default = ...] or #[default(...)]") + })?; + + let field_value = match conversion_strategy { + ConversionStrategy::NoConversion => field_value, + ConversionStrategy::Into => quote!((#field_value).into()), + }; + + let field_doc = format!("{}", field_value); + Ok((field_value, field_doc)) + } else { + Ok(( + quote! { + Default::default() + }, + "Default::default()".to_owned(), + )) + } +} diff --git a/helpers/pagetop-macros/src/smart_default/default_attr.rs b/helpers/pagetop-macros/src/smart_default/default_attr.rs new file mode 100644 index 00000000..8487fc06 --- /dev/null +++ b/helpers/pagetop-macros/src/smart_default/default_attr.rs @@ -0,0 +1,89 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{parse::Error, MetaNameValue}; + +use crate::smart_default::util::find_only; + +#[derive(Debug, Clone, Copy)] +pub enum ConversionStrategy { + NoConversion, + Into, +} + +pub struct DefaultAttr { + pub code: Option, + conversion_strategy: Option, +} + +impl DefaultAttr { + pub fn find_in_attributes(attrs: &[syn::Attribute]) -> Result, Error> { + if let Some(default_attr) = + find_only(attrs.iter(), |attr| Ok(attr.path().is_ident("default")))? + { + match &default_attr.meta { + syn::Meta::Path(_) => Ok(Some(Self { + code: None, + conversion_strategy: None, + })), + syn::Meta::List(meta) => { + // If the meta contains exactly (_code = "...") take the string literal as the + // expression + if let Ok(ParseCodeHack(code_hack)) = syn::parse(meta.tokens.clone().into()) { + Ok(Some(Self { + code: Some(code_hack), + conversion_strategy: Some(ConversionStrategy::NoConversion), + })) + } else { + Ok(Some(Self { + code: Some(meta.tokens.clone()), + conversion_strategy: None, + })) + } + } + syn::Meta::NameValue(MetaNameValue { value, .. }) => Ok(Some(Self { + code: Some(value.into_token_stream()), + conversion_strategy: None, + })), + } + } else { + Ok(None) + } + } + + pub fn conversion_strategy(&self) -> ConversionStrategy { + if let Some(conversion_strategy) = self.conversion_strategy { + // Conversion strategy already set + return conversion_strategy; + } + let code = if let Some(code) = &self.code { + code + } else { + // #[default] - so no conversion (`Default::default()` already has the correct type) + return ConversionStrategy::NoConversion; + }; + match syn::parse::(code.clone().into()) { + Ok(syn::Lit::Str(_)) | Ok(syn::Lit::ByteStr(_)) => { + // A string literal - so we need a conversion in case we need to make it a `String` + return ConversionStrategy::Into; + } + _ => {} + } + // Not handled by one of the rules, so we don't convert it to avoid causing trouble + ConversionStrategy::NoConversion + } +} + +struct ParseCodeHack(TokenStream); + +impl syn::parse::Parse for ParseCodeHack { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident: syn::Ident = input.parse()?; + if ident != "_code" { + return Err(Error::new(ident.span(), "Expected `_code`")); + } + input.parse::()?; + let code: syn::LitStr = input.parse()?; + let code: TokenStream = code.parse()?; + Ok(ParseCodeHack(code)) + } +} diff --git a/helpers/pagetop-macros/src/smart_default/util.rs b/helpers/pagetop-macros/src/smart_default/util.rs new file mode 100644 index 00000000..0d4b247b --- /dev/null +++ b/helpers/pagetop-macros/src/smart_default/util.rs @@ -0,0 +1,21 @@ +use syn::parse::Error; +use syn::spanned::Spanned; + +/// Return the value that fulfills the predicate if there is one in the slice. Panic if there is +/// more than one. +pub fn find_only(iter: impl Iterator, pred: F) -> Result, Error> +where + T: Spanned, + F: Fn(&T) -> Result, +{ + let mut result = None; + for item in iter { + if pred(&item)? { + if result.is_some() { + return Err(Error::new(item.span(), "Multiple defaults")); + } + result = Some(item); + } + } + Ok(result) +} diff --git a/src/lib.rs b/src/lib.rs index 2f973518..b9b2a4bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ // RE-EXPORTED ************************************************************************************* -pub use pagetop_macros::{html, main, test}; +pub use pagetop_macros::{html, main, test, AutoDefault}; // API ********************************************************************************************* diff --git a/src/prelude.rs b/src/prelude.rs index eb59c4f2..872447aa 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,6 +4,8 @@ pub use crate::{html, main, test}; +pub use crate::AutoDefault; + // MACROS. // crate::config diff --git a/src/service.rs b/src/service.rs index e6904b82..90b13758 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,4 @@ -//! Gestión del servidor y servicios web ([Actix Web](https://docs.rs/actix-web)). +//! Gestión del servidor y servicios web (con [Actix Web](https://docs.rs/actix-web)). pub use actix_web::body::BoxBody; pub use actix_web::dev::Server; From 7e03d926bba7a420b0e1433eed01ae91e49a299f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 8 Jul 2025 20:57:39 +0200 Subject: [PATCH 006/224] =?UTF-8?q?=F0=9F=9A=A8=20Aplica=20retoques=20por?= =?UTF-8?q?=20ejecuci=C3=B3n=20de=20clippy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 6 ++++++ src/config.rs | 13 ++++++------- src/trace.rs | 1 - 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index d21c08a0..082419ab 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,12 @@ use std::sync::LazyLock; pub struct Application; +impl Default for Application { + fn default() -> Self { + Self::new() + } +} + impl Application { /// Crea una instancia de la aplicación. pub fn new() -> Self { diff --git a/src/config.rs b/src/config.rs index eae182fc..d532ba14 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ //! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y `PageTop` intentará //! cargar *config/default.toml* si el archivo existe. //! -//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.e. +//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.ej. //! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, //! URLs o ajustes de rendimiento, sin afectar a los demás. //! @@ -126,7 +126,7 @@ const DEFAULT_RUN_MODE: &str = "default"; /// Valores originales cargados desde los archivos de configuración como pares `clave = valor`. pub static CONFIG_VALUES: LazyLock> = LazyLock::new(|| { // Determina el directorio de configuración: - // - Usa CONFIG_DIR si está definido en el entorno (p.e.: CONFIG_DIR=/etc/myapp ./myapp). + // - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp). // - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR). // - Si nada de esto aplica, entonces usa DEFAULT_CONFIG_DIR relativo al ejecutable. let config_dir: PathBuf = if let Ok(env_dir) = env::var("CONFIG_DIR") { @@ -143,15 +143,15 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( }; // Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa - // DEFAULT_RUN_MODE si no está definida (p.e.: PAGETOP_RUN_MODE=production ./myapp). + // DEFAULT_RUN_MODE si no está definida (p.ej.: PAGETOP_RUN_MODE=production ./myapp). let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); Config::builder() // 1. Configuración común para todos los entornos (common.toml). .add_source(File::from(config_dir.join("common.toml")).required(false)) - // 2. Configuración específica del entorno (p.e.: default.toml, production.toml). + // 2. Configuración específica del entorno (p.ej.: default.toml, production.toml). .add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false)) - // 3. Configuración local reservada para cada entorno (p.e.: local.default.toml). + // 3. Configuración local reservada para cada entorno (p.ej.: local.default.toml). .add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false)) // 4. Configuración local común (local.toml). .add_source(File::from(config_dir.join("local.toml")).required(false)) @@ -217,7 +217,7 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( /// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves /// opcionales pueden ser `Option`. /// -/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.e. `[blog]`) para +/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.ej. `[blog]`) para /// evitar colisiones con otras librerías. /// /// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para @@ -236,7 +236,6 @@ pub static CONFIG_VALUES: LazyLock> = LazyLock::new( /// [dependencies] /// serde = { version = "1.0", features = ["derive"] } /// ``` - #[macro_export] macro_rules! include_config { ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { diff --git a/src/trace.rs b/src/trace.rs index 93a8ff2d..bd8ce56d 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -33,7 +33,6 @@ use std::sync::LazyLock; /// Dado que las trazas o eventos registrados poco antes de un fallo suelen ser cruciales para /// diagnosticar la causa, `Lazy` garantiza que todos los registros almacenados se /// envíen antes de finalizar la ejecución. - #[rustfmt::skip] pub(crate) static TRACING: LazyLock = LazyLock::new(|| { let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) From 896b066ca9f53adf82b622e86916ac55fe936e68 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 9 Jul 2025 20:39:39 +0200 Subject: [PATCH 007/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20para?= =?UTF-8?q?=20localizaci=C3=B3n=20y=20traducci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incluye recursos Fluent básicos y pruebas asociadas. - Nueva variable de configuración global para definir el idioma predeterminado. --- Cargo.lock | 278 ++++++++++++++++++++++++- Cargo.toml | 5 +- src/app.rs | 5 +- src/global.rs | 3 + src/lib.rs | 4 + src/locale.rs | 364 +++++++++++++++++++++++++++++++++ src/locale/en-US/languages.ftl | 5 + src/locale/en-US/test.ftl | 11 + src/locale/es-ES/languages.ftl | 5 + src/locale/es-ES/test.ftl | 11 + src/prelude.rs | 8 + src/util.rs | 26 +++ tests/locale.rs | 58 ++++++ 13 files changed, 780 insertions(+), 3 deletions(-) create mode 100644 src/locale.rs create mode 100644 src/locale/en-US/languages.ftl create mode 100644 src/locale/en-US/test.ftl create mode 100644 src/locale/es-ES/languages.ftl create mode 100644 src/locale/es-ES/test.ftl create mode 100644 src/util.rs create mode 100644 tests/locale.rs diff --git a/Cargo.lock b/Cargo.lock index 3fe4d512..64073ea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -384,6 +394,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -492,6 +521,81 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash 1.1.0", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror", +] + +[[package]] +name = "fluent-template-macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed02449601d0dacdc05cb5e13db5dab8f2b98d773aff5c53b62fad43a1b19a1" +dependencies = [ + "flume", + "ignore", + "proc-macro2", + "quote", + "syn", + "unic-langid", +] + +[[package]] +name = "fluent-templates" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69855a5fe87629495efca79aec72adfa97954f1006f928e3a2ec750cb3e85386" +dependencies = [ + "fluent-bundle", + "fluent-langneg", + "fluent-syntax", + "fluent-template-macros", + "flume", + "ignore", + "intl-memoizer", + "log", + "thiserror", + "unic-langid", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -571,6 +675,19 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "h2" version = "0.3.26" @@ -726,6 +843,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.9", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "impl-more" version = "0.1.9" @@ -742,6 +875,25 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "intl-memoizer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + [[package]] name = "itoa" version = "1.0.15" @@ -918,12 +1070,13 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.4" +version = "0.0.5" dependencies = [ "actix-web", "colored", "config", "figlet-rs", + "fluent-templates", "itoa", "pagetop-macros", "serde", @@ -933,6 +1086,7 @@ dependencies = [ "tracing-actix-web", "tracing-appender", "tracing-subscriber", + "unic-langid", ] [[package]] @@ -1052,6 +1206,12 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1182,6 +1342,18 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.0.7" @@ -1207,12 +1379,36 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.2.0", +] + +[[package]] +name = "self_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" + [[package]] name = "serde" version = "1.0.219" @@ -1323,6 +1519,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1604,12 +1809,64 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5957eb82e346d7add14182a3315a7e298f04e1ba4baac36f7f0dbfedba5fc25" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1662,6 +1919,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1751,6 +2018,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index b8b3f5fa..bcf1fd50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.4" +version = "0.0.5" edition = "2021" description = """\ @@ -28,6 +28,9 @@ tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } tracing-actix-web = "0.7.18" +fluent-templates = "0.13.0" +unic-langid = { version = "0.9.6", features = ["macros"] } + actix-web = "4.11.0" pagetop-macros.workspace = true diff --git a/src/app.rs b/src/app.rs index 082419ab..b02b294a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ mod figfont; -use crate::{global, service, trace}; +use crate::{global, locale, service, trace}; use substring::Substring; @@ -26,6 +26,9 @@ impl Application { // Inicia gestión de trazas y registro de eventos (logging). LazyLock::force(&trace::TRACING); + // Valida el identificador de idioma por defecto. + LazyLock::force(&locale::DEFAULT_LANGID); + Self } diff --git a/src/global.rs b/src/global.rs index 8432032a..a90aa86c 100644 --- a/src/global.rs +++ b/src/global.rs @@ -8,6 +8,7 @@ include_config!(SETTINGS: Settings => [ // [app] "app.name" => "Sample", "app.description" => "Developed with the amazing PageTop framework.", + "app.language" => "en-US", "app.startup_banner" => "Slant", // [log] @@ -38,6 +39,8 @@ pub struct App { pub name: String, /// Breve descripción de la aplicación. pub description: String, + /// Idioma predeterminado (localización). + pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. pub startup_banner: String, diff --git a/src/lib.rs b/src/lib.rs index b9b2a4bc..07b99347 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,8 @@ pub use pagetop_macros::{html, main, test, AutoDefault}; // API ********************************************************************************************* +// Funciones y macros útiles. +pub mod util; // Carga las opciones de configuración. pub mod config; // Opciones de configuración globales. @@ -44,6 +46,8 @@ pub mod global; pub mod trace; // HTML en código. pub mod html; +// Localización. +pub mod locale; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/locale.rs b/src/locale.rs new file mode 100644 index 00000000..064481f0 --- /dev/null +++ b/src/locale.rs @@ -0,0 +1,364 @@ +//! Localización (L10n). +//! +//! `PageTop` utiliza las especificaciones de [Fluent](https://www.projectfluent.org/) para la +//! localización de aplicaciones, y aprovecha [fluent-templates](https://docs.rs/fluent-templates/) +//! para integrar los recursos de traducción directamente en el binario de la aplicación. +//! +//! # Sintaxis Fluent (FTL) +//! +//! El formato empleado para describir los recursos de traducción se denomina +//! [FTL](https://www.projectfluent.org/fluent/guide/). Está diseñado para ser legible y expresivo, +//! permitiendo representar construcciones complejas del lenguaje natural como el género, el plural +//! o las conjugaciones verbales. +//! +//! # Recursos Fluent +//! +//! Por defecto, las traducciones se organizan en el directorio *src/locale*, con subdirectorios +//! para cada [Identificador de Idioma Unicode](https://docs.rs/unic-langid/) válido. Podríamos +//! tener una estructura como esta: +//! +//! ```text +//! src/locale/ +//! ├── common.ftl +//! ├── en-US/ +//! │ ├── default.ftl +//! │ └── main.ftl +//! ├── es-ES/ +//! │ ├── default.ftl +//! │ └── main.ftl +//! ├── es-MX/ +//! │ ├── default.ftl +//! │ └── main.ftl +//! └── fr/ +//! ├── default.ftl +//! └── main.ftl +//! ``` +//! +//! Ejemplo de un archivo *src/locale/en-US/main.ftl*: +//! +//! ```text +//! hello-world = Hello world! +//! hello-user = Hello, {$userName}! +//! shared-photos = +//! {$userName} {$photoCount -> +//! [one] added a new photo +//! *[other] added {$photoCount} new photos +//! } of {$userGender -> +//! [male] him and his family +//! [female] her and her family +//! *[other] the family +//! }. +//! ``` +//! +//! Y su archivo equivalente para español *src/locale/es-ES/main.ftl*: +//! +//! ```text +//! hello-world = Hola mundo! +//! hello-user = ¡Hola, {$userName}! +//! shared-photos = +//! {$userName} {$photoCount -> +//! [one] ha añadido una nueva foto +//! *[other] ha añadido {$photoCount} nuevas fotos +//! } de {$userGender -> +//! [male] él y su familia +//! [female] ella y su familia +//! *[other] la familia +//! }. +//! ``` +//! +//! +//! # Cómo aplicar la localización en tu código +//! +//! Una vez creado el directorio con los recursos FTL, basta con utilizar la macro +//! [`include_locales!`](crate::include_locales) para integrarlos en la aplicación. +//! +//! Si los recursos se encuentran en el directorio `"src/locale"`, sólo hay que declarar: +//! +//! ```rust +//! use pagetop::prelude::*; +//! +//! include_locales!(LOCALES_SAMPLE); +//! ``` +//! +//! Pero si están ubicados en otro directorio, entonces se pueden incluir usando: +//! +//! ```rust,ignore +//! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); +//! ``` + +use crate::html::{Markup, PreEscaped}; +use crate::{global, hm, AutoDefault}; + +pub use fluent_templates; +pub use unic_langid::{CharacterDirection, LanguageIdentifier}; + +use unic_langid::langid; + +use fluent_templates::Loader; +use fluent_templates::StaticLoader as Locales; + +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::LazyLock; + +use std::fmt; + +// Asocia cada código de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la clave +// en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. +static LANGUAGES: LazyLock> = LazyLock::new(|| { + hm![ + "en" => ( langid!("en-US"), "english" ), + "en-GB" => ( langid!("en-GB"), "english_british" ), + "en-US" => ( langid!("en-US"), "english_united_states" ), + "es" => ( langid!("es-ES"), "spanish" ), + "es-ES" => ( langid!("es-ES"), "spanish_spain" ), + ] +}); + +// Identificador del idioma de **respaldo** (predefinido a `en-US`). +// +// Se usa cuando el valor del código de idioma en las traducciones no corresponde con ningún idioma +// soportado por la aplicación. +static FALLBACK_LANGID: LazyLock = LazyLock::new(|| langid!("en-US")); + +// Identificador del idioma **por defecto** para la aplicación. +// +// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el código de +// idioma configurado no es válido o no está disponible entonces resuelve como [`FALLBACK_LANGID`]. +pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = + LazyLock::new(|| LangMatch::langid_or_fallback(&global::SETTINGS.app.language)); + +/// Comprueba si el idioma está soportado por `PageTop`. +/// +/// Útil para transformar un código de idioma en un [`LanguageIdentifier`] válido para `PageTop`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum LangMatch { + /// Cuando el código del idioma es una cadena vacía. + Empty, + /// Si encuentra un [`LanguageIdentifier`] que coincide exactamente o retrocediendo al idioma + /// base. + Found(&'static LanguageIdentifier), + /// Si el código del idioma no está entre los soportados por `PageTop`. + Unknown(String), +} + +impl LangMatch { + /// Resuelve `language` y devuelve el [`LangMatch`] apropiado. + pub fn resolve(language: impl AsRef) -> Self { + let language = language.as_ref().trim(); + + // Rechaza cadenas vacías. + if language.is_empty() { + return Self::Empty; + } + + // Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). + if let Some(langid) = LANGUAGES.get(language).map(|(langid, _)| langid) { + return Self::Found(langid); + } + + // Si la variante regional no existe, retrocede al idioma base (p.ej. "es"). + if let Some((base_lang, _)) = language.split_once('-') { + if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { + return Self::Found(langid); + } + } + + // Devuelve desconocido si el idioma no está soportado. + Self::Unknown(language.to_string()) + } + + /// Devuelve siempre un [`LanguageIdentifier`] válido. + /// + /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). + #[inline] + pub fn langid_or_fallback(language: impl AsRef) -> &'static LanguageIdentifier { + match Self::resolve(language) { + Self::Found(l) => l, + _ => &FALLBACK_LANGID, + } + } + + /// Devuelve un [`LanguageIdentifier`] válido para la instancia. + /// + /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). + #[inline] + pub fn as_langid(&self) -> &'static LanguageIdentifier { + match self { + LangMatch::Found(l) => l, + _ => &FALLBACK_LANGID, + } + } +} + +#[macro_export] +/// Define un conjunto de elementos de localización y textos de traducción local. +macro_rules! include_locales { + // Se eliminan las marcas de aislamiento Unicode en los argumentos para mejorar la legibilidad y + // la compatibilidad en ciertos contextos de renderizado. + ( $LOCALES:ident $(, $core_locales:literal)? ) => { + $crate::locale::fluent_templates::static_loader! { + static $LOCALES = { + locales: "src/locale", + $( core_locales: $core_locales, )? + fallback_language: "en-US", + // Elimina marcas de aislamiento Unicode en los argumentos. + customise: |bundle| bundle.set_use_isolating(false), + }; + } + }; + ( $LOCALES:ident from $dir_locales:literal $(, $core_locales:literal)? ) => { + $crate::locale::fluent_templates::static_loader! { + static $LOCALES = { + locales: $dir_locales, + $( core_locales: $core_locales, )? + fallback_language: "en-US", + // Elimina marcas de aislamiento Unicode en los argumentos. + customise: |bundle| bundle.set_use_isolating(false), + }; + } + }; +} + +include_locales!(LOCALES_PAGETOP); + +// Operación de localización a realizar. +// +// * `None` - No se aplica ninguna localización. +// * `Text` - Con una cadena literal que se devolverá tal cual. +// * `Translate` - Con la clave a resolver en el `Locales` indicado. +#[derive(AutoDefault)] +enum L10nOp { + #[default] + None, + Text(String), + Translate(String), +} + +/// Crea instancias para traducir textos localizados. +/// +/// Cada instancia puede representar: +/// +/// - Un texto puro (`n()`) que no requiere traducción. +/// - Una clave para traducir un texto de las traducciones por defecto de `PageTop` (`l()`). +/// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). +/// +/// Los argumentos dinámicos se añaden mediante `with_arg()` o `with_args()`. +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Texto literal sin traducción. +/// let raw = L10n::n("© 2025 PageTop").get(); +/// +/// // Traducción simple con clave y argumentos. +/// let hello = L10n::l("greeting") +/// .with_arg("name", "Manuel") +/// .markup(); +/// ``` +/// +/// También para traducciones a idiomas concretos. +/// +/// ```rust,ignore +/// // Traducción con clave, conjunto de traducciones y código de idioma a usar. +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_fallback("it")); +/// ``` +#[derive(AutoDefault)] +pub struct L10n { + op: L10nOp, + #[default(&LOCALES_PAGETOP)] + locales: &'static Locales, + args: HashMap, +} + +impl L10n { + /// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción. + pub fn n(text: impl Into) -> Self { + L10n { + op: L10nOp::Text(text.into()), + ..Default::default() + } + } + + /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave de la tabla de + /// traducciones por defecto. + pub fn l(key: impl Into) -> Self { + L10n { + op: L10nOp::Translate(key.into()), + ..Default::default() + } + } + + /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de una tabla de + /// traducciones específica. + pub fn t(key: impl Into, locales: &'static Locales) -> Self { + L10n { + op: L10nOp::Translate(key.into()), + locales, + ..Default::default() + } + } + + /// Añade un argumento `{$arg}` → `value` a la traducción. + pub fn with_arg(mut self, arg: impl Into, value: impl Into) -> Self { + self.args.insert(arg.into(), value.into()); + self + } + + /// Añade varios argumentos a la traducción de una sola vez (p.ej. usando la macro [`hm!`], + /// también vec![("k", "v")], incluso un array de duplas u otras colecciones). + pub fn with_args(mut self, args: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.args + .extend(args.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } + + /// Resuelve la traducción usando el idioma por defecto de la aplicación. Devuelve `None` si no + /// aplica o no encuentra una traducción. + pub fn get(&self) -> Option { + self.using(&DEFAULT_LANGID) + } + + /// Resuelve la traducción usando el [`LanguageIdentifier`] indicado. Devuelve `None` si no + /// aplica o no encuentra una traducción. + pub fn using(&self, langid: &LanguageIdentifier) -> Option { + match &self.op { + L10nOp::None => None, + L10nOp::Text(text) => Some(text.to_owned()), + L10nOp::Translate(key) => self.locales.try_lookup_with_args( + langid, + key, + &self.args.iter().fold(HashMap::new(), |mut arg, (k, v)| { + arg.insert(Cow::Owned(k.clone()), v.to_owned().into()); + arg + }), + ), + } + } + + /// Traduce y escapa con el idioma por defecto, devolviendo [`Markup`]. + pub fn markup(&self) -> Markup { + PreEscaped(self.get().unwrap_or_default()) + } + + /// Traduce y escapa con el [`LanguageIdentifier`] indicado, devolviendo [`Markup`]. + pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { + PreEscaped(self.using(langid).unwrap_or_default()) + } +} + +impl fmt::Display for L10n { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let content = match &self.op { + L10nOp::None => String::new(), + L10nOp::Text(text) => text.clone(), + L10nOp::Translate(key) => self.get().unwrap_or_else(|| format!("??<{}>", key)), + }; + write!(f, "{content}") + } +} diff --git a/src/locale/en-US/languages.ftl b/src/locale/en-US/languages.ftl new file mode 100644 index 00000000..1e816605 --- /dev/null +++ b/src/locale/en-US/languages.ftl @@ -0,0 +1,5 @@ +english = English +english_british = English (British) +english_united_states = English (United States) +spanish = Spanish +spanish_spain = Spanish (Spain) diff --git a/src/locale/en-US/test.ftl b/src/locale/en-US/test.ftl new file mode 100644 index 00000000..3c317fcb --- /dev/null +++ b/src/locale/en-US/test.ftl @@ -0,0 +1,11 @@ +test-hello-world = Hello world! +test-hello-user = Hello, { $userName }! +test-shared-photos = + { $userName } { $photoCount -> + [one] added a new photo + *[other] added { $photoCount } new photos + } of { $userGender -> + [male] him and his family + [female] her and her family + *[other] their family + }. diff --git a/src/locale/es-ES/languages.ftl b/src/locale/es-ES/languages.ftl new file mode 100644 index 00000000..ee74ec26 --- /dev/null +++ b/src/locale/es-ES/languages.ftl @@ -0,0 +1,5 @@ +english = Inglés +english_british = Inglés (Gran Bretaña) +english_united_states = Inglés (Estados Unidos) +spanish = Español +spanish_spain = Español (España) diff --git a/src/locale/es-ES/test.ftl b/src/locale/es-ES/test.ftl new file mode 100644 index 00000000..02cd22e3 --- /dev/null +++ b/src/locale/es-ES/test.ftl @@ -0,0 +1,11 @@ +test-hello-world = ¡Hola mundo! +test-hello-user = ¡Hola, { $userName }! +test-shared-photos = + { $userName } { $photoCount -> + [one] ha añadido una nueva foto + *[other] ha añadido { $photoCount } nuevas fotos + } de { $userGender -> + [male] él y su familia + [female] ella y su familia + *[other] la familia + }. diff --git a/src/prelude.rs b/src/prelude.rs index 872447aa..af29d376 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,17 +8,25 @@ pub use crate::AutoDefault; // MACROS. +// crate::util +pub use crate::hm; // crate::config pub use crate::include_config; +// crate::locale +pub use crate::include_locales; // API. +pub use crate::util; + pub use crate::global; pub use crate::trace; pub use crate::html::*; +pub use crate::locale::*; + pub use crate::service; pub use crate::app::Application; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..07a6abdb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,26 @@ +//! Funciones y macros útiles. + +// MACROS ÚTILES *********************************************************************************** + +#[macro_export] +/// Macro para construir una colección de pares clave-valor. +/// +/// ```rust +/// use pagetop::hm; +/// use std::collections::HashMap; +/// +/// let args:HashMap<&str, String> = hm![ +/// "userName" => "Roberto", +/// "photoCount" => "3", +/// "userGender" => "male", +/// ]; +/// ``` +macro_rules! hm { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut a = std::collections::HashMap::new(); + $( + a.insert($key.into(), $value.into()); + )* + a + }}; +} diff --git a/tests/locale.rs b/tests/locale.rs new file mode 100644 index 00000000..c723cd46 --- /dev/null +++ b/tests/locale.rs @@ -0,0 +1,58 @@ +use pagetop::prelude::*; + +#[pagetop::test] +async fn literal_text() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::n("© 2025 PageTop"); + assert_eq!(l10n.get(), Some("© 2025 PageTop".to_string())); +} + +#[pagetop::test] +async fn translation_without_args() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::l("test-hello-world"); + let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")); + assert_eq!(translation, Some("¡Hola mundo!".to_string())); +} + +#[pagetop::test] +async fn translation_with_args() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::l("test-hello-user").with_arg("userName", "Manuel"); + let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")); + assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); +} + +#[pagetop::test] +async fn translation_with_plural_and_select() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::l("test-shared-photos").with_args(vec![ + ("userName", "Roberto"), + ("photoCount", "3"), + ("userGender", "male"), + ]); + let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")).unwrap(); + assert!(translation.contains("añadido 3 nuevas fotos de él")); +} + +#[pagetop::test] +async fn check_fallback_language() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::l("test-hello-world"); + let translation = l10n.using(LangMatch::langid_or_fallback("xx-YY")); // Fallback a "en-US". + assert_eq!(translation, Some("Hello world!".to_string())); +} + +#[pagetop::test] +async fn check_unknown_key() { + let _app = service::test::init_service(Application::new().test()).await; + + let l10n = L10n::l("non-existent-key"); + let translation = l10n.using(LangMatch::langid_or_fallback("en-US")); + assert_eq!(translation, None); +} From 22faea7d403d2ec531f3f94e709d37d470f958ec Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 9 Jul 2025 20:51:43 +0200 Subject: [PATCH 008/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20para?= =?UTF-8?q?=20manejo=20de=20fechas=20y=20horas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 166 ++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 5 +- src/datetime.rs | 4 ++ src/lib.rs | 2 + src/prelude.rs | 2 + 5 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 src/datetime.rs diff --git a/Cargo.lock b/Cargo.lock index 64073ea1..50660b36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -335,6 +350,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "colored" version = "3.0.0" @@ -346,9 +375,9 @@ dependencies = [ [[package]] name = "config" -version = "0.15.11" +version = "0.15.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" +checksum = "5b1eb4fb07bc7f012422df02766c7bd5971effb894f573865642f06fa3265440" dependencies = [ "pathdiff", "serde", @@ -367,6 +396,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -736,6 +771,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -1047,6 +1106,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -1070,9 +1138,10 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.5" +version = "0.0.6" dependencies = [ "actix-web", + "chrono", "colored", "config", "figlet-rs", @@ -1443,9 +1512,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -1677,14 +1746,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.7.0", + "toml_parser", + "winnow", ] [[package]] @@ -1692,6 +1762,12 @@ name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] @@ -1703,9 +1779,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", - "serde", - "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e" +dependencies = [ "winnow", ] @@ -2033,6 +2116,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index bcf1fd50..c2cfbd59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.5" +version = "0.0.6" edition = "2021" description = """\ @@ -15,8 +15,9 @@ license.workspace = true authors.workspace = true [dependencies] +chrono = "0.4.41" colored = "3.0.0" -config = { version = "0.15.11", default-features = false, features = ["toml"] } +config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" itoa = "1.0.15" serde.workspace = true diff --git a/src/datetime.rs b/src/datetime.rs new file mode 100644 index 00000000..2e8b6229 --- /dev/null +++ b/src/datetime.rs @@ -0,0 +1,4 @@ +//! Soporte a fechas y horas según estándar [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) +//! (basado en [chrono](https://docs.rs/chrono)). + +pub use chrono::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index 07b99347..d7a97e25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,8 @@ pub mod trace; pub mod html; // Localización. pub mod locale; +// Soporte a fechas y horas. +pub mod datetime; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/prelude.rs b/src/prelude.rs index af29d376..cc38877a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -27,6 +27,8 @@ pub use crate::html::*; pub use crate::locale::*; +pub use crate::datetime::*; + pub use crate::service; pub use crate::app::Application; From 835aacf1fa52569a4269562adf2c086e1ee77685 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 9 Jul 2025 21:59:09 +0200 Subject: [PATCH 009/224] =?UTF-8?q?=F0=9F=8D=B1=20A=C3=B1ade=20el=20banner?= =?UTF-8?q?=20de=20PageTop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/banner.png | Bin 0 -> 74026 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/banner.png diff --git a/static/banner.png b/static/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..422e493e0060ac723a13b5e13b8a9d64b2b257d1 GIT binary patch literal 74026 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 BY>i2P1)3d{|~Du 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 z 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 literal 0 HcmV?d00001 From 412524018a6551360b7d29a16eefb0b2e38981b1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 9 Jul 2025 22:05:42 +0200 Subject: [PATCH 010/224] =?UTF-8?q?=F0=9F=93=9D=20Actualiza=20el=20banner?= =?UTF-8?q?=20en=20la=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 159aabe0..94062a0e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@

+ +

PageTop

Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.

@@ -26,6 +28,13 @@ async fn main() -> std::io::Result<()> { ``` +# 🚧 Advertencia + +`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + # 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/src/lib.rs b/src/lib.rs index d7a97e25..4519c440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //!
//! -//! +//! //! //!

PageTop

//! From 8bc1d00cb9b9a74f6a80e8e6768f9ff4f3872233 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 10 Jul 2025 22:51:47 +0200 Subject: [PATCH 011/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20est=C3=A1ticos?= =?UTF-8?q?=20y=20SCSS=20compilados=20para=20binarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Módulo auxiliar para ejecutar durante la compilación de proyectos de PageTop para incluir archivos estáticos o archivos SCSS compilados en los binarios de los proyectos. --- Cargo.toml | 15 +- helpers/pagetop-build/Cargo.toml | 20 +++ helpers/pagetop-build/README.md | 36 +++++ helpers/pagetop-build/src/lib.rs | 259 +++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 helpers/pagetop-build/Cargo.toml create mode 100644 helpers/pagetop-build/README.md create mode 100644 helpers/pagetop-build/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c2cfbd59..a91964bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.6" +version = "0.0.7" edition = "2021" description = """\ @@ -20,7 +20,7 @@ colored = "3.0.0" config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" itoa = "1.0.15" -serde.workspace = true +paste = { package = "pastey", version = "0.1.0" } substring = "1.4.5" terminal_size = "0.4.2" @@ -33,14 +33,18 @@ fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } actix-web = "4.11.0" +static-files.workspace = true -pagetop-macros.workspace = true +serde = { version = "1.0", features = ["derive"] } + +pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } [workspace] resolver = "2" members = [ + "helpers/pagetop-build", "helpers/pagetop-macros", ] @@ -51,7 +55,4 @@ license = "MIT OR Apache-2.0" authors = ["Manuel Cillero "] [workspace.dependencies] -serde = { version = "1.0", features = ["derive"] } - -# Helpers -pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } +static-files = "0.2.5" diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml new file mode 100644 index 00000000..4b947b3e --- /dev/null +++ b/helpers/pagetop-build/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pagetop-build" +version = "0.0.1" +edition = "2021" + +description = """\ + Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el \ + binario de un proyecto PageTop.\ +""" +categories = ["development-tools::build-utils", "web-programming"] +keywords = ["pagetop", "build", "assets", "resources", "static"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +grass = "0.13.4" +static-files.workspace = true diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md new file mode 100644 index 00000000..9f912e99 --- /dev/null +++ b/helpers/pagetop-build/README.md @@ -0,0 +1,36 @@ +
+ +

PageTop Build

+ +

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) + +
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# 🚧 Advertencia + +`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +# 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs new file mode 100644 index 00000000..c48b21fc --- /dev/null +++ b/helpers/pagetop-build/src/lib.rs @@ -0,0 +1,259 @@ +//!
+//! +//!

PageTop Build

+//! +//!

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

+//! +//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! +//!
+//! +//! ## Sobre PageTop +//! +//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la +//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles +//! y configurables, basadas en HTML, CSS y JavaScript. +//! +//! +//! # ⚡️ Guía rápida +//! +//! Añadir en el archivo `Cargo.toml` del proyecto: +//! +//! ```toml +//! [build-dependencies] +//! pagetop-build = { ... } +//! ``` +//! +//! Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +//! archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: +//! +//! ## Incluir archivos estáticos desde un directorio +//! +//! Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +//! ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: +//! +//! ```rust,no_run +//! use pagetop_build::StaticFilesBundle; +//! +//! fn main() -> std::io::Result<()> { +//! StaticFilesBundle::from_dir("./static", None) +//! .with_name("guides") +//! .build() +//! } +//! ``` +//! +//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, +//! por ejemplo: +//! +//! ```rust,no_run +//! use pagetop_build::StaticFilesBundle; +//! use std::path::Path; +//! +//! fn main() -> std::io::Result<()> { +//! fn only_pdf_files(path: &Path) -> bool { +//! // Selecciona únicamente los archivos con extensión `.pdf`. +//! path.extension().map_or(false, |ext| ext == "pdf") +//! } +//! +//! StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) +//! .with_name("guides") +//! .build() +//! } +//! ``` +//! +//! ## Compilar archivos SCSS a CSS +//! +//! Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso +//! con el archivo CSS minificado obtenido. Por ejemplo: +//! +//! ```rust,no_run +//! use pagetop_build::StaticFilesBundle; +//! +//! fn main() -> std::io::Result<()> { +//! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") +//! .with_name("main_styles") +//! .build() +//! } +//! ``` +//! +//! Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un +//! recurso llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. +//! +//! +//! # 📦 Módulos generados +//! +//! Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +//! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +//! donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para +//! `with_name("guides")` se crea un archivo llamado `guides.rs`. +//! +//! No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. +//! +//! Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con +//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con +//! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) +//! configurar un servicio web para servir los recursos desde la ruta indicada: +//! +//! ```rust,ignore +//! use pagetop::prelude::*; +//! +//! include_files!(guides); +//! +//! pub struct MyExtension; +//! +//! impl ExtensionTrait for MyExtension { +//! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. +//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { +//! include_files_service!(scfg, guides => "/ruta/a/guides"); +//! } +//! } +//! ``` +//! +//! También se puede acceder al conjunto de recursos declarando un `HashMap` estático global: +//! +//! ```rust,ignore +//! include_files!(HM_GUIDES => guides); +//! ``` + +use grass::{from_path, Options, OutputStyle}; +use static_files::{resource_dir, ResourceDir}; + +use std::fs::{create_dir_all, remove_dir_all, File}; +use std::io::Write; +use std::path::Path; + +/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto utilizando +/// [static_files](https://docs.rs/static-files/). +pub struct StaticFilesBundle { + resource_dir: ResourceDir, +} + +impl StaticFilesBundle { + /// Prepara el conjunto de recursos con los archivos de un directorio. Opcionalmente se puede + /// aplicar un filtro para seleccionar un subconjunto de los archivos. + /// + /// # Argumentos + /// + /// * `dir` - Directorio que contiene los archivos. + /// * `filter` - Una función opcional para aceptar o no un archivo según su ruta. + /// + /// # Ejemplo + /// + /// ```rust,no_run + /// use pagetop_build::StaticFilesBundle; + /// use std::path::Path; + /// + /// fn main() -> std::io::Result<()> { + /// fn only_images(path: &Path) -> bool { + /// matches!( + /// path.extension().and_then(|ext| ext.to_str()), + /// Some("jpg" | "png" | "gif") + /// ) + /// } + /// + /// StaticFilesBundle::from_dir("./static", Some(only_images)) + /// .with_name("images") + /// .build() + /// } + /// ``` + pub fn from_dir(dir: &'static str, filter: Option bool>) -> Self { + let mut resource_dir = resource_dir(dir); + + // Aplica el filtro si está definido. + if let Some(f) = filter { + resource_dir.with_filter(f); + } + + // Identifica el directorio temporal de recursos. + StaticFilesBundle { resource_dir } + } + + /// Prepara un recurso CSS minimizado a partir de la compilación de un archivo SCSS (que puede a + /// su vez importar otros archivos SCSS). + /// + /// # Argumentos + /// + /// * `path` - Archivo SCSS a compilar. + /// * `target_name` - Nombre para el archivo CSS. + /// + /// # Ejemplo + /// + /// ```rust,no_run + /// use pagetop_build::StaticFilesBundle; + /// + /// fn main() -> std::io::Result<()> { + /// StaticFilesBundle::from_scss("./bootstrap/scss/main.scss", "bootstrap.min.css") + /// .with_name("bootstrap_css") + /// .build() + /// } + /// ``` + pub fn from_scss

(path: P, target_name: &str) -> Self + where + P: AsRef, + { + // Crea un directorio temporal para el archivo CSS. + let out_dir = std::env::var("OUT_DIR").unwrap(); + let temp_dir = Path::new(&out_dir).join("from_scss_files"); + + // Limpia el directorio temporal de ejecuciones previas, si existe. + if temp_dir.exists() { + remove_dir_all(&temp_dir).unwrap_or_else(|e| { + panic!( + "Failed to clean temporary directory `{}`: {e}", + temp_dir.display() + ); + }); + } + create_dir_all(&temp_dir).unwrap_or_else(|e| { + panic!( + "Failed to create temporary directory `{}`: {e}", + temp_dir.display() + ); + }); + + // Compila SCSS a CSS. + let css_content = from_path( + path.as_ref(), + &Options::default().style(OutputStyle::Compressed), + ) + .unwrap_or_else(|e| { + panic!( + "Failed to compile SCSS file `{}`: {e}", + path.as_ref().display(), + ) + }); + + // Guarda el archivo CSS compilado en el directorio temporal. + let css_path = temp_dir.join(target_name); + File::create(&css_path) + .expect(&format!( + "Failed to create CSS file `{}`", + css_path.display() + )) + .write_all(css_content.as_bytes()) + .expect(&format!( + "Failed to write CSS content to `{}`", + css_path.display() + )); + + // Identifica el directorio temporal de recursos. + StaticFilesBundle { + resource_dir: resource_dir(temp_dir.to_str().unwrap()), + } + } + + /// Asigna un nombre al conjunto de recursos. + pub fn with_name(mut self, name: &'static str) -> Self { + let out_dir = std::env::var("OUT_DIR").unwrap(); + let filename = Path::new(&out_dir).join(format!("{name}.rs")); + self.resource_dir.with_generated_filename(filename); + self.resource_dir.with_module_name(format!("bundle_{name}")); + self.resource_dir.with_generated_fn(name); + self + } + + /// Contruye finalmente el conjunto de recursos para incluir en el binario de la aplicación. + pub fn build(self) -> std::io::Result<()> { + self.resource_dir.build() + } +} From f37a128044ef33fe0d2c479e5b1af76d621bb18c Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 11 Jul 2025 22:17:59 +0200 Subject: [PATCH 012/224] =?UTF-8?q?=F0=9F=9A=A9=20A=C3=B1ade=20feature=20"?= =?UTF-8?q?testing"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Permite desactivar trazas y registro de eventos al ejecutar tests. - Añade opción de configuración para activar o desactivar las trazas. --- .cargo/config.toml | 3 + Cargo.lock | 385 +++++++++++++++++++++++++++++- Cargo.toml | 9 +- README.md | 16 ++ helpers/pagetop-macros/src/lib.rs | 4 +- src/global.rs | 3 + src/trace.rs | 8 + 7 files changed, 416 insertions(+), 12 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..d29b0de3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +ts = ["test", "--features", "testing"] # cargo ts +tw = ["test", "--workspace", "--features", "testing"] # cargo tw diff --git a/Cargo.lock b/Cargo.lock index 50660b36..24c9616f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.9.1", "sha1", "smallvec", "tokio", @@ -200,6 +200,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -224,6 +236,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -239,6 +257,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -350,6 +418,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "change-detection" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a" +dependencies = [ + "path-matchers", + "path-slash", +] + [[package]] name = "chrono" version = "0.4.41" @@ -364,6 +442,45 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "codemap" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "colored" version = "3.0.0" @@ -515,6 +632,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -540,6 +663,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "figlet-rs" version = "0.1.5" @@ -692,6 +821,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -710,6 +852,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "globset" version = "0.4.16" @@ -723,6 +871,31 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "grass" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94" +dependencies = [ + "clap", + "getrandom 0.2.16", + "grass_compiler", +] + +[[package]] +name = "grass_compiler" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6" +dependencies = [ + "codemap", + "indexmap", + "lasso", + "once_cell", + "phf", + "rand 0.8.5", +] + [[package]] name = "h2" version = "0.3.26" @@ -742,6 +915,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -931,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -953,6 +1136,12 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.15" @@ -965,7 +1154,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom", + "getrandom 0.3.3", "libc", ] @@ -985,6 +1174,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lasso" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1063,6 +1261,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1130,6 +1338,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "overload" version = "0.1.1" @@ -1138,18 +1352,22 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.6" +version = "0.0.8" dependencies = [ "actix-web", "chrono", "colored", "config", + "dunce", "figlet-rs", "fluent-templates", "itoa", "pagetop-macros", + "pastey", "serde", + "static-files", "substring", + "tempfile", "terminal_size", "tracing", "tracing-actix-web", @@ -1158,6 +1376,14 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "pagetop-build" +version = "0.0.1" +dependencies = [ + "grass", + "static-files", +] + [[package]] name = "pagetop-macros" version = "0.0.3" @@ -1192,6 +1418,27 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pastey" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8cb46bdc156b1c90460339ae6bfd45ba0394e5effbaa640badb4987fdc261" + +[[package]] +name = "path-matchers" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b" +dependencies = [ + "glob", +] + +[[package]] +name = "path-slash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1204,6 +1451,48 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1317,14 +1606,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -1334,7 +1644,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -1343,7 +1662,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", ] [[package]] @@ -1566,6 +1885,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.10" @@ -1603,6 +1928,23 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static-files" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c425c07353535ef55b45420f5a8b0a397cd9bc3d7e5236497ca0d90604aa9b" +dependencies = [ + "change-detection", + "mime_guess", + "path-slash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "substring" version = "1.4.5" @@ -1634,6 +1976,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "terminal_size" version = "0.4.2" @@ -1950,6 +2305,12 @@ dependencies = [ "unic-langid-impl", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1979,13 +2340,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom", + "getrandom 0.3.3", "js-sys", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index a91964bb..3ef84687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.7" +version = "0.0.8" edition = "2021" description = """\ @@ -39,6 +39,13 @@ serde = { version = "1.0", features = ["derive"] } pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } +[features] +default = [] +testing = [] + +[dev-dependencies] +tempfile = "3.20.0" + [workspace] diff --git a/README.md b/README.md index 94062a0e..6b4aff0c 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,22 @@ async fn main() -> std::io::Result<()> { ``` +# 🧪 Pruebas + +Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de Cargo** declarados en +`.cargo/config.toml`. Basta con ejecutarlos desde la raíz del proyecto: + +| Comando | Descripción | +| ------- | ----------- | +| `cargo ts` | Ejecuta los tests de `pagetop` (*unit + integration*) con la *feature* `testing`. | +| `cargo ts --test util` | Lanza sólo las pruebas de integración del módulo `util`. | +| `cargo tw` | Ejecuta los tests de **todos los paquetes** del *workspace*. | + +> **Nota** +> Estos alias ya compilan con la configuración adecuada. No requieren `--no-default-features`. +> Si quieres **activar** las trazas del registro de eventos entonces usa simplemente `cargo test`. + + # 🚧 Advertencia `PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index eecebed3..776903c0 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -45,7 +45,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// Define una función `main` asíncrona como punto de entrada de `PageTop`. /// -/// # Ejemplos +/// # Ejemplo /// /// ```rust,ignore /// #[pagetop::main] @@ -66,7 +66,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { /// Define funciones de prueba asíncronas para usar con `PageTop`. /// -/// # Ejemplos +/// # Ejemplo /// /// ```rust,ignore /// #[pagetop::test] diff --git a/src/global.rs b/src/global.rs index a90aa86c..c3332d39 100644 --- a/src/global.rs +++ b/src/global.rs @@ -12,6 +12,7 @@ include_config!(SETTINGS: Settings => [ "app.startup_banner" => "Slant", // [log] + "log.enabled" => true, "log.tracing" => "Info", "log.rolling" => "Stdout", "log.path" => "log", @@ -52,6 +53,8 @@ pub struct App { #[derive(Debug, Deserialize)] /// Sección `[log]` de la configuración. Forma parte de [`Settings`]. pub struct Log { + /// Gestión de trazas y registro de eventos activado (`true`) o desactivado (`false`). + pub enabled: bool, /// Opciones, o combinación de opciones separadas por comas, para filtrar las trazas: *"Error"*, /// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*. /// Ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug". diff --git a/src/trace.rs b/src/trace.rs index bd8ce56d..c57c6a3c 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -35,6 +35,14 @@ use std::sync::LazyLock; /// envíen antes de finalizar la ejecución. #[rustfmt::skip] pub(crate) static TRACING: LazyLock = LazyLock::new(|| { + if !global::SETTINGS.log.enabled || cfg!(test) || cfg!(feature = "testing") { + // Tracing desactivado, se instala un subscriber nulo. + tracing::subscriber::set_global_default(tracing::subscriber::NoSubscriber::default()) + .expect("Failed to install global NoSubscriber (tracing disabled)"); + let (_, guard) = tracing_appender::non_blocking(std::io::sink()); + return guard; + } + let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) .unwrap_or_else(|_| EnvFilter::new("Info")); From 4eadc8b257a0951bc327f27a6368e9ab5ebbecb0 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 12 Jul 2025 06:35:09 +0200 Subject: [PATCH 013/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20gesti=C3=B3n?= =?UTF-8?q?=20de=20recursos=20en=20binario=20o=20de=20disco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Añade dosnuevas macros `include_files` y `include_files_service` para incluir los recursos preparados con `pagetop-build`. - Añade nueva función útil `absolute_dir` y sus tests correspondientes. --- Cargo.lock | 97 +++++++++++++++++++++++++++++++++---- Cargo.toml | 4 +- src/lib.rs | 3 ++ src/prelude.rs | 4 +- src/service.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++++- src/util.rs | 49 +++++++++++++++++++ tests/util.rs | 70 +++++++++++++++++++++++++++ 7 files changed, 340 insertions(+), 13 deletions(-) create mode 100644 tests/util.rs diff --git a/Cargo.lock b/Cargo.lock index 24c9616f..659ed18e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-files" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags", + "bytes", + "derive_more 0.99.20", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + [[package]] name = "actix-http" version = "3.11.0" @@ -34,7 +57,7 @@ dependencies = [ "brotli", "bytes", "bytestring", - "derive_more", + "derive_more 2.0.1", "encoding_rs", "flate2", "foldhash", @@ -149,7 +172,7 @@ dependencies = [ "bytestring", "cfg-if", "cookie", - "derive_more", + "derive_more 2.0.1", "encoding_rs", "foldhash", "futures-core", @@ -185,6 +208,18 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-web-static-files" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f" +dependencies = [ + "actix-web", + "derive_more 0.99.20", + "futures-util", + "static-files", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -502,6 +537,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.16.2" @@ -590,6 +631,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -632,12 +686,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -809,6 +857,7 @@ dependencies = [ "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -942,6 +991,12 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -1352,13 +1407,14 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.8" +version = "0.0.9" dependencies = [ + "actix-files", "actix-web", + "actix-web-static-files", "chrono", "colored", "config", - "dunce", "figlet-rs", "fluent-templates", "itoa", @@ -1742,6 +1798,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -1797,6 +1862,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -2357,6 +2428,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 3ef84687..851c620d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.8" +version = "0.0.9" edition = "2021" description = """\ @@ -33,6 +33,8 @@ fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } actix-web = "4.11.0" +actix-web-files = { package = "actix-files", version = "0.6.6" } +actix-web-static-files = "4.0.1" static-files.workspace = true serde = { version = "1.0", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index 4519c440..800f7830 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ pub use pagetop_macros::{html, main, test, AutoDefault}; +/// Representa un conjunto de recursos asociados a `$STATIC` en [`include_files!`]. +pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>; + // API ********************************************************************************************* // Funciones y macros útiles. diff --git a/src/prelude.rs b/src/prelude.rs index cc38877a..c27b997e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::{html, main, test}; -pub use crate::AutoDefault; +pub use crate::{AutoDefault, StaticResources}; // MACROS. @@ -14,6 +14,8 @@ pub use crate::hm; pub use crate::include_config; // crate::locale pub use crate::include_locales; +// crate::service +pub use crate::{include_files, include_files_service}; // API. diff --git a/src/service.rs b/src/service.rs index 90b13758..1adaead8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -5,5 +5,129 @@ pub use actix_web::dev::Server; pub use actix_web::dev::ServiceFactory as Factory; pub use actix_web::dev::ServiceRequest as Request; pub use actix_web::dev::ServiceResponse as Response; -pub use actix_web::{http, rt, test}; +pub use actix_web::{http, rt, web}; pub use actix_web::{App, Error, HttpServer}; + +#[doc(hidden)] +pub use actix_web::test; + +/// Incluye en código un conjunto de recursos previamente preparado con `build.rs`. +/// +/// # Formas de uso +/// +/// * `include_files!(media)` - Incluye el conjunto de recursos llamado `media`. Normalmente se +/// usará esta forma. +/// +/// * `include_files!(BLOG_HM => blog)` - Asigna a la variable estática `BLOG_HM` un conjunto de +/// recursos llamado `blog`. +/// +/// # Argumentos +/// +/// * `$bundle` – Nombre del conjunto de recursos generado por `build.rs` (consultar +/// [`pagetop_build`](https://docs.rs/pagetop-build)). +/// * `$STATIC` – Identificador para la variable estática de tipo +/// [`StaticResources`](`crate::StaticResources`). +/// +/// # Ejemplos +/// +/// ```rust,ignore +/// include_files!(assets); // Uso habitual. +/// +/// include_files!(STATIC_ASSETS => assets); +/// ``` +#[macro_export] +macro_rules! include_files { + // Forma 1: incluye un conjunto de recursos por nombre. + ( $bundle:ident ) => { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + } + }; + // Forma 2: asigna a una variable estática $STATIC un conjunto de recursos. + ( $STATIC:ident => $bundle:ident ) => { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + pub static $STATIC: std::sync::LazyLock = std::sync::LazyLock::new( + []::$bundle + ); + } + }; +} + +/// Configura un servicio web para publicar los recursos embebidos con [`include_files!`]. +/// +/// El código expandido de la macro decide durante el arranque de la aplicación si debe servir los +/// archivos de los recursos embebidos o directamente desde el sistema de ficheros, si se ha +/// indicado una ruta válida a un directorio de recursos. +/// +/// # Argumentos +/// +/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la +/// configuración del servicio web. +/// * `$bundle` – Nombre del conjunto de recursos incluido con [`include_files!`]. +/// * `$route` – Ruta URL de origen desde la que se servirán los archivos. +/// * `[ $root, $relative ]` *(opcional)* – Directorio raíz y ruta relativa para construir la ruta +/// absoluta donde buscar los archivos en el sistema de ficheros (ver +/// [`absolute_dir`](crate::util::absolute_dir)). Si no existe, se usarán los recursos embebidos. +/// +/// # Ejemplos +/// +/// ```rust,ignore +/// use pagetop::prelude::*; +/// +/// include_files!(assets); +/// +/// pub struct MyExtension; +/// +/// impl ExtensionTrait for MyExtension { +/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { +/// include_files_service!(scfg, assets => "/public"); +/// } +/// } +/// ``` +/// +/// Y para buscar los recursos en el sistema de ficheros (si existe la ruta absoluta): +/// +/// ```rust,ignore +/// include_files_service!(cfg, assets => "/public", ["/var/www", "assets"]); +/// +/// // También desde el directorio actual de ejecución. +/// include_files_service!(cfg, assets => "/public", ["", "static"]); +/// ``` +#[macro_export] +macro_rules! include_files_service { + ( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{ + $crate::util::paste! { + let span = $crate::trace::debug_span!("Configuring static files ", path = $route); + let _ = span.in_scope(|| { + // Determina si se sirven recursos embebidos (`true`) o desde disco (`false`). + #[allow(unused_mut)] + let mut serve_embedded:bool = true; + $( + // Si `$root` y `$relative` no están vacíos, se comprueba la ruta absoluta. + if !$root.is_empty() && !$relative.is_empty() { + if let Ok(absolute) = $crate::util::absolute_dir($root, $relative) { + // Servimos directamente desde el sistema de ficheros. + $scfg.service($crate::service::ActixFiles::new( + $route, + absolute, + ).show_files_listing()); + serve_embedded = false + } + } + )? + // Si no se localiza el directorio, se exponen entonces los recursos embebidos. + if serve_embedded { + $scfg.service($crate::service::ResourceFiles::new( + $route, + []::$bundle(), + )); + } + }); + } + }}; +} diff --git a/src/util.rs b/src/util.rs index 07a6abdb..7a48001f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,56 @@ //! Funciones y macros útiles. +use crate::trace; + +use std::io; +use std::path::{Path, PathBuf}; + + +/// Devuelve la ruta absoluta a un directorio existente. +/// +/// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`. +/// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual. +/// * Devuelve error si la ruta no existe o no es un directorio. +/// +/// # Ejemplo +/// +/// ```rust,no_run +/// use pagetop::prelude::*; +/// +/// let root = "/home/user"; +/// let rel = "documents"; +/// println!("{:#?}", util::absolute_dir(root, rel)); +/// ``` +pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result +where + P: AsRef, + Q: AsRef, +{ + // Une ambas rutas: + // - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`. + // - Si el resultado es aún relativo, lo será respecto al directorio actual. + let candidate = root_path.as_ref().join(relative_path.as_ref()); + + // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. + let absolute_dir = candidate.canonicalize()?; + + // Asegura que realmente es un directorio existente. + if absolute_dir.is_dir() { + Ok(absolute_dir) + } else { + Err({ + let msg = format!("Path \"{}\" is not a directory", absolute_dir.display()); + trace::warn!(msg); + io::Error::new(io::ErrorKind::InvalidInput, msg) + }) + } +} + // MACROS ÚTILES *********************************************************************************** +#[doc(hidden)] +pub use paste::paste; + #[macro_export] /// Macro para construir una colección de pares clave-valor. /// diff --git a/tests/util.rs b/tests/util.rs new file mode 100644 index 00000000..2d52064e --- /dev/null +++ b/tests/util.rs @@ -0,0 +1,70 @@ +use pagetop::prelude::*; + +use std::{fs, io}; +use tempfile::TempDir; + +#[cfg(unix)] +mod unix { + use super::*; + + #[pagetop::test] + async fn ok_absolute_dir() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + // /tmp//sub + let td = TempDir::new()?; + let root = td.path(); + let sub = root.join("sub"); + fs::create_dir(&sub)?; + + let abs = util::absolute_dir(root, "sub")?; + assert_eq!(abs, std::fs::canonicalize(&sub)?); + Ok(()) + } + + #[pagetop::test] + async fn error_not_a_directory() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let file = td.path().join("foo.txt"); + fs::write(&file, b"data")?; + + let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + Ok(()) + } +} + +#[cfg(windows)] +mod windows { + use super::*; + + #[pagetop::test] + async fn ok_absolute_dir() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + // C:\Users\…\Temp\… + let td = TempDir::new()?; + let root = td.path(); + let sub = root.join("sub"); + fs::create_dir(&sub)?; + + let abs = util::absolute_dir(root, sub.as_path())?; + assert_eq!(abs, std::fs::canonicalize(&sub)?); + Ok(()) + } + + #[pagetop::test] + async fn error_not_a_directory() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let file = td.path().join("foo.txt"); + fs::write(&file, b"data")?; + + let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + Ok(()) + } +} From 2b1b31fbf004e227e76162f5b05bb657fde568ca Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 12 Jul 2025 12:04:37 +0200 Subject: [PATCH 014/224] =?UTF-8?q?=E2=9C=A8=20(macros):=20A=C3=B1ade=20ma?= =?UTF-8?q?cro=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 4 +- Cargo.toml | 2 +- helpers/pagetop-macros/Cargo.toml | 2 +- helpers/pagetop-macros/src/lib.rs | 157 +++++++++++++++++++++++++++++- src/lib.rs | 2 +- src/prelude.rs | 2 +- 6 files changed, 161 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 659ed18e..49fe0074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1407,7 +1407,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.9" +version = "0.0.10" dependencies = [ "actix-files", "actix-web", @@ -1442,7 +1442,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.3" +version = "0.0.4" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 851c620d..8212f62b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.9" +version = "0.0.10" edition = "2021" description = """\ diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 9f914bfd..070e7784 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.3" +version = "0.0.4" edition = "2021" description = """\ diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 776903c0..25105c2f 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -18,8 +18,8 @@ mod maud; mod smart_default; use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; +use quote::{quote, quote_spanned}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, ItemFn}; /// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)). #[proc_macro] @@ -43,6 +43,159 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { } } +/// Macro (*attribute*) que asocia un método *builder* `with_` con un método `alter_`. +/// +/// La macro añade automáticamente un método `alter_` para modificar la instancia actual usando +/// `&mut self`, y redefine el método *builder* `with_`, que consume la instancia (`mut self`), para +/// delegar la lógica de la modificación al nuevo método `alter_`, reutilizando así la misma +/// implementación. +/// +/// Esta macro emitirá un error en tiempo de compilación si la función anotada no cumple con la +/// firma esperada para el método *builder*: `pub fn with_...(mut self, ...) -> Self`. +/// +/// # Ejemplos +/// +/// Si defines un método `with_` como este: +/// +/// ```rust,ignore +/// #[builder_fn] +/// pub fn with_example(mut self, value: impl Into) -> Self { +/// self.value = Some(value.into()); +/// self +/// } +/// ``` +/// +/// la macro generará automáticamente el siguiente método `alter_`: +/// +/// ```rust,ignore +/// pub fn alter_example(&mut self, value: impl Into) -> &mut Self { +/// self.value = Some(value.into()); +/// self +/// } +/// ``` +/// +/// y reescribirá el método `with_` para delegar la modificación al método `alter_`: +/// +/// ```rust,ignore +/// pub fn with_example(mut self, value: impl Into) -> Self { +/// self.alter_example(value); +/// self +/// } +/// ``` +/// +/// Así, cada método *builder* `with_...()` generará automáticamente su correspondiente método +/// `alter_...()`, que permitirá más adelante modificar instancias existentes. +#[proc_macro_attribute] +pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { + let fn_with = parse_macro_input!(item as ItemFn); + let fn_with_name = fn_with.sig.ident.clone(); + let fn_with_name_str = fn_with.sig.ident.to_string(); + + // Valida el nombre del método. + if !fn_with_name_str.starts_with("with_") { + let expanded = quote_spanned! { + fn_with.sig.ident.span() => + compile_error!("expected a \"pub fn with_...(mut self, ...) -> Self\" method"); + }; + return expanded.into(); + } + // Valida que el método sea público. + if !matches!(fn_with.vis, syn::Visibility::Public(_)) { + return quote_spanned! { + fn_with.sig.ident.span() => compile_error!("expected method to be `pub`"); + } + .into(); + } + // Valida que el método devuelva el tipo `Self`. + if let syn::ReturnType::Type(_, ty) = &fn_with.sig.output { + if let syn::Type::Path(type_path) = &**ty { + let ident = &type_path.path.segments.last().unwrap().ident; + if ident != "Self" { + return quote_spanned! { + fn_with.sig.output.span() => compile_error!("expected return type to be `Self`"); + }.into(); + } + } + } else { + return quote_spanned! { + fn_with.sig.output.span() => compile_error!("expected method to return `Self`"); + } + .into(); + } + // Valida que el primer argumento sea `mut self`. + if let Some(syn::FnArg::Receiver(receiver)) = fn_with.sig.inputs.first() { + if receiver.mutability.is_none() { + return quote_spanned! { + receiver.span() => compile_error!("expected `mut self` as the first argument"); + } + .into(); + } + } else { + return quote_spanned! { + fn_with.sig.ident.span() => compile_error!("expected `mut self` as the first argument"); + } + .into(); + } + + // Genera el nombre del método alter_...(). + let fn_alter_name_str = fn_with_name_str.replace("with_", "alter_"); + let fn_alter_name = syn::Ident::new(&fn_alter_name_str, fn_with.sig.ident.span()); + + // Extrae genéricos y cláusulas where. + let fn_generics = &fn_with.sig.generics; + let where_clause = &fn_with.sig.generics.where_clause; + + // Extrae argumentos y parámetros de llamada. + let args: Vec<_> = fn_with.sig.inputs.iter().skip(1).collect(); + let params: Vec<_> = fn_with + .sig + .inputs + .iter() + .skip(1) + .map(|arg| match arg { + syn::FnArg::Typed(pat) => &pat.pat, + _ => panic!("unexpected argument type"), + }) + .collect(); + + // Extrae bloque del método. + let fn_with_block = &fn_with.block; + + // Extrae documentación y otros atributos del método. + let fn_with_attrs = &fn_with.attrs; + + // Genera el método alter_...() con el código del método with_...(). + let fn_alter_doc = format!( + "Modifica la instancia en los mismos términos que para el patrón builder hace el \ + método asociado `{}()`.", + fn_with_name_str, + ); + let fn_alter = quote! { + #[doc = #fn_alter_doc] + pub fn #fn_alter_name #fn_generics(&mut self, #(#args),*) -> &mut Self #where_clause { + #fn_with_block + } + }; + + // Redefine el método with_...() para que llame a alter_...(). + let fn_with = quote! { + #(#fn_with_attrs)* + #[inline] + pub fn #fn_with_name #fn_generics(mut self, #(#args),*) -> Self #where_clause { + self.#fn_alter_name(#(#params),*); + self + } + }; + + // Genera el código final. + let expanded = quote! { + #fn_with + #[inline] + #fn_alter + }; + expanded.into() +} + /// Define una función `main` asíncrona como punto de entrada de `PageTop`. /// /// # Ejemplo diff --git a/src/lib.rs b/src/lib.rs index 800f7830..d64cf55c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ // RE-EXPORTED ************************************************************************************* -pub use pagetop_macros::{html, main, test, AutoDefault}; +pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; /// Representa un conjunto de recursos asociados a `$STATIC` en [`include_files!`]. pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>; diff --git a/src/prelude.rs b/src/prelude.rs index c27b997e..e4aabf55 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,7 @@ // RE-EXPORTED. -pub use crate::{html, main, test}; +pub use crate::{builder_fn, html, main, test}; pub use crate::{AutoDefault, StaticResources}; From f6b4cb936ca7a9695460c82d0195647e906b327b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 13 Jul 2025 11:10:06 +0200 Subject: [PATCH 015/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20definici=C3=B3n?= =?UTF-8?q?=20encapsulando=20uso=20de=20recursos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-build/src/lib.rs | 4 ++-- src/lib.rs | 25 +++++++++++++++++++++++-- src/service.rs | 17 +++++++++-------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index c48b21fc..98c8ef87 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -109,10 +109,10 @@ //! } //! ``` //! -//! También se puede acceder al conjunto de recursos declarando un `HashMap` estático global: +//! También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: //! //! ```rust,ignore -//! include_files!(HM_GUIDES => guides); +//! include_files!(GUIDES => guides); //! ``` use grass::{from_path, Options, OutputStyle}; diff --git a/src/lib.rs b/src/lib.rs index d64cf55c..1ae35895 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,12 +30,33 @@ #![cfg_attr(docsrs, feature(doc_cfg))] +use std::collections::HashMap; +use std::ops::Deref; + // RE-EXPORTED ************************************************************************************* pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; -/// Representa un conjunto de recursos asociados a `$STATIC` en [`include_files!`]. -pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>; +/// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files). +pub struct StaticResources { + bundle: HashMap<&'static str, static_files::Resource>, +} + +impl StaticResources { + /// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar + /// [`pagetop_build`](https://docs.rs/pagetop-build)). + pub fn new(bundle: HashMap<&'static str, static_files::Resource>) -> Self { + Self { bundle } + } +} + +impl Deref for StaticResources { + type Target = HashMap<&'static str, static_files::Resource>; + + fn deref(&self) -> &Self::Target { + &self.bundle + } +} // API ********************************************************************************************* diff --git a/src/service.rs b/src/service.rs index 1adaead8..9e506fae 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,18 +15,18 @@ pub use actix_web::test; /// /// # Formas de uso /// -/// * `include_files!(media)` - Incluye el conjunto de recursos llamado `media`. Normalmente se +/// * `include_files!(media)` - Para incluir un conjunto de recursos llamado `media`. Normalmente se /// usará esta forma. /// -/// * `include_files!(BLOG_HM => blog)` - Asigna a la variable estática `BLOG_HM` un conjunto de -/// recursos llamado `blog`. +/// * `include_files!(BLOG => media)` - También se puede asignar el conjunto de recursos a una +/// variable global; p.ej. `BLOG`. /// /// # Argumentos /// /// * `$bundle` – Nombre del conjunto de recursos generado por `build.rs` (consultar /// [`pagetop_build`](https://docs.rs/pagetop-build)). -/// * `$STATIC` – Identificador para la variable estática de tipo -/// [`StaticResources`](`crate::StaticResources`). +/// * `$STATIC` – Asigna el conjunto de recursos a una variable global de tipo +/// [`StaticResources`](crate::StaticResources). /// /// # Ejemplos /// @@ -51,9 +51,10 @@ macro_rules! include_files { mod [] { include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); } - pub static $STATIC: std::sync::LazyLock = std::sync::LazyLock::new( - []::$bundle - ); + pub static $STATIC: std::sync::LazyLock<$crate::StaticResources> = + std::sync::LazyLock::new( + $crate::StaticResources::new([]::$bundle) + ); } }; } From 5f60bc4845ba591ecf35bac46e134f4b6840b17f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 15 Jul 2025 20:12:15 +0200 Subject: [PATCH 016/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20API=20para=20e?= =?UTF-8?q?xtensiones=20con=20funcionalidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade el interfaz común que debe implementar cualquier extensión de PageTop para añadir nuevas funcionalidades a la aplicación en forma de servicios web y API de uso. --- src/app.rs | 37 +++++- src/core.rs | 205 +++++++++++++++++++++++++++++++ src/core/extension.rs | 9 ++ src/core/extension/all.rs | 104 ++++++++++++++++ src/core/extension/definition.rs | 77 ++++++++++++ src/lib.rs | 2 + src/prelude.rs | 4 + 7 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 src/core.rs create mode 100644 src/core/extension.rs create mode 100644 src/core/extension/all.rs create mode 100644 src/core/extension/definition.rs diff --git a/src/app.rs b/src/app.rs index b02b294a..0bcece8e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,6 +2,7 @@ mod figfont; +use crate::core::{extension, extension::ExtensionRef}; use crate::{global, locale, service, trace}; use substring::Substring; @@ -9,6 +10,12 @@ use substring::Substring; use std::io::Error; use std::sync::LazyLock; +/// Punto de entrada de una aplicación `PageTop`. +/// +/// No almacena datos, pero **encapsula** el ciclo completo de configuración y puesta en marcha. +/// Para instanciarla se puede usar [`new`](Application::new) o [`prepare`](Application::prepare). +/// Después sólo hay que llamar a [`run`](Application::run) (o a [`test`](Application::test) si se +/// está preparando un entorno de pruebas). pub struct Application; impl Default for Application { @@ -20,6 +27,23 @@ impl Default for Application { impl Application { /// Crea una instancia de la aplicación. pub fn new() -> Self { + Self::internal_prepare(None) + } + + /// Prepara una instancia de la aplicación a partir de una extensión raíz. + /// + /// Esa extensión suele declarar: + /// + /// - Sus propias dependencias (que se habilitarán automáticamente). + /// - Una lista de extensiones que deben deshabilitarse si estuvieran activadas. + /// + /// Esto simplifica el arranque en escenarios complejos. + pub fn prepare(root_extension: ExtensionRef) -> Self { + Self::internal_prepare(Some(root_extension)) + } + + // Método interno para preparar la aplicación, opcionalmente con una extensión. + fn internal_prepare(root_extension: Option) -> Self { // Al arrancar muestra una cabecera para la aplicación. Self::show_banner(); @@ -29,6 +53,12 @@ impl Application { // Valida el identificador de idioma por defecto. LazyLock::force(&locale::DEFAULT_LANGID); + // Registra las extensiones de la aplicación. + extension::all::register_extensions(root_extension); + + // Inicializa las extensiones. + extension::all::initialize_extensions(); + Self } @@ -73,7 +103,10 @@ impl Application { } } - /// Ejecuta el servidor web de la aplicación. + /// Arranca el servidor web de la aplicación. + /// + /// Devuelve [`std::io::Error`] si el *socket* no puede enlazarse (por puerto en uso, permisos, + /// etc.). pub fn run(self) -> Result { // Prepara el servidor web. Ok(service::HttpServer::new(move || { @@ -112,6 +145,6 @@ impl Application { InitError = (), >, > { - service::App::new() + service::App::new().configure(extension::all::configure_services) } } diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 00000000..5aebd1fa --- /dev/null +++ b/src/core.rs @@ -0,0 +1,205 @@ +//! Tipos y funciones esenciales para crear extensiones. + +use std::any::Any; + +/// Selector para identificar segmentos de la ruta de un tipo. +#[derive(Clone, Copy, Debug)] +pub enum TypeInfo { + /// Ruta completa tal y como la devuelve [`core::any::type_name`]. + FullName, + /// Último segmento de la ruta – por ejemplo `Vec` en lugar de `alloc::vec::Vec`. + ShortName, + /// Conserva todo **desde** `start` inclusive hasta el final. + NameFrom(isize), + /// Conserva todo **hasta e incluyendo** `end`. + NameTo(isize), + /// Conserva la subruta comprendida entre `start` y `end` (ambos inclusive). + PartialName(isize, isize), +} + +impl TypeInfo { + /// Devuelve el segmento solicitado de la ruta para el tipo `T`. + pub fn of(&self) -> &'static str { + let type_name = std::any::type_name::(); + match self { + TypeInfo::FullName => type_name, + TypeInfo::ShortName => Self::partial(type_name, -1, None), + TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None), + TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)), + TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)), + } + } + + // Extrae un rango de segmentos de `type_name` (tokens separados por `::`). + // + // Los argumentos `start` y `end` identifican los índices de los segmentos teniendo en cuenta: + // + // * Los índices positivos cuentan **desde la izquierda**, empezando en `0`. + // * Los índices negativos cuentan **desde la derecha**, `-1` es el último. + // * Si `end` es `None`, el corte llega hasta el último segmento. + // * Si la selección resulta vacía por índices desordenados o segmento inexistente, se devuelve + // la cadena vacía. + // + // Ejemplos (con `type_name = "alloc::vec::Vec"`): + // + // | Llamada | Resultado | + // |------------------------------|--------------------------| + // | `partial(..., 0, None)` | `"alloc::vec::Vec"` | + // | `partial(..., 1, None)` | `"vec::Vec"` | + // | `partial(..., -1, None)` | `"Vec"` | + // | `partial(..., 0, Some(-2))` | `"alloc::vec"` | + // | `partial(..., -5, None)` | `"alloc::vec::Vec"` | + // + // La porción devuelta vive tanto como `'static` porque `type_name` es `'static` y sólo se + // presta. + fn partial(type_name: &'static str, start: isize, end: Option) -> &'static str { + let maxlen = type_name.len(); + + // Localiza los límites de cada segmento a nivel 0 de `<…>`. + let mut segments = Vec::new(); + let mut segment_start = 0; // Posición inicial del segmento actual. + let mut angle_brackets = 0; // Profundidad dentro de '<…>'. + let mut previous_char = '\0'; // Se inicializa a carácter nulo, no hay aún carácter previo. + + for (idx, c) in type_name.char_indices() { + match c { + ':' if angle_brackets == 0 => { + if previous_char == ':' { + if segment_start < idx - 1 { + segments.push((segment_start, idx - 1)); // No incluye último '::'. + } + segment_start = idx + 1; // Nuevo segmento tras '::'. + } + } + '<' => angle_brackets += 1, + '>' => angle_brackets -= 1, + _ => {} + } + previous_char = c; + } + + // Incluye el último segmento si lo hubiese. + if segment_start < maxlen { + segments.push((segment_start, maxlen)); + } + + // Calcula la posición inicial. + let start_pos = segments + .get(if start >= 0 { + start as usize + } else { + segments.len().saturating_sub(start.unsigned_abs()) + }) + .map_or(0, |&(s, _)| s); + + // Calcula la posición final. + let end_pos = segments + .get(if let Some(end) = end { + if end >= 0 { + end as usize + } else { + segments.len().saturating_sub(end.unsigned_abs()) + } + } else { + segments.len() - 1 + }) + .map_or(maxlen, |&(_, e)| e); + + // Devuelve la cadena parcial basada en las posiciones calculadas. + if start_pos >= end_pos { + return ""; + } + &type_name[start_pos..end_pos] + } +} + +/// Proporciona información de tipo en tiempo de ejecución y conversión dinámica de tipos. +/// +/// Este *trait* se implementa automáticamente para **todos** los tipos que implementen [`Any`], de +/// modo que basta con traer [`AnyInfo`] al ámbito (`use crate::AnyInfo;`) para disponer de estos +/// métodos adicionales, o usar el [`prelude`](crate::prelude) de `PageTop`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let n = 3u32; +/// assert_eq!(n.type_name(), "u32"); +/// ``` +pub trait AnyInfo: Any { + /// Devuelve el nombre totalmente cualificado del tipo. + fn type_name(&self) -> &'static str; + + /// Devuelve el nombre corto del tipo (último segmento del nombre). + fn short_name(&self) -> &'static str; + + /// Devuelve una referencia a `dyn Any` para la conversión dinámica de tipos. + fn as_any_ref(&self) -> &dyn Any; + + /// Devuelve una referencia mutable a `dyn Any` para la conversión dinámica de tipos. + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl AnyInfo for T { + #[inline] + fn type_name(&self) -> &'static str { + TypeInfo::FullName.of::() + } + + #[inline] + fn short_name(&self) -> &'static str { + TypeInfo::ShortName.of::() + } + + #[inline] + fn as_any_ref(&self) -> &dyn Any { + self + } + + #[inline] + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +/// Extiende [`AnyInfo`] con utilidades de *downcasting* para conversión de tipos. +/// +/// Preferible a usar directamente `Any::downcast_ref` porque conserva el *trait bound* [`AnyInfo`], +/// lo que permite seguir llamando a `type_name`, etc. +pub trait AnyCast: AnyInfo { + /// Comprueba si la instancia subyacente es de tipo `T`. + #[inline] + fn is(&self) -> bool + where + T: AnyInfo, + { + self.as_any_ref().is::() + } + + /// Intenta hacer *downcast* de un objeto para obtener una referencia de tipo `T`. + #[inline] + #[must_use] + fn downcast_ref(&self) -> Option<&T> + where + T: AnyInfo, + { + self.as_any_ref().downcast_ref() + } + + /// Intenta hacer *downcast* de un objeto para obtener una referencia mutable de tipo `T`. + #[inline] + #[must_use] + fn downcast_mut(&mut self) -> Option<&mut T> + where + T: AnyInfo, + { + self.as_any_mut().downcast_mut() + } +} + +/// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`]. +impl AnyCast for T {} + +// Infraestructura para ampliar funcionalidades mediante extensiones. +pub mod extension; diff --git a/src/core/extension.rs b/src/core/extension.rs new file mode 100644 index 00000000..9eb3bd24 --- /dev/null +++ b/src/core/extension.rs @@ -0,0 +1,9 @@ +//! Infraestructura para ampliar funcionalidades mediante extensiones. +//! +//! Cada funcionalidad adicional que quiera incorporarse a una aplicación `PageTop` se debe modelar +//! como una **extensión**. Todas comparten la misma interfaz declarada en [`ExtensionTrait`]. + +mod definition; +pub use definition::{ExtensionRef, ExtensionTrait}; + +pub(crate) mod all; diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs new file mode 100644 index 00000000..d5bebdad --- /dev/null +++ b/src/core/extension/all.rs @@ -0,0 +1,104 @@ +use crate::core::extension::ExtensionRef; +use crate::{service, trace}; + +use std::sync::{LazyLock, RwLock}; + +// EXTENSIONES ************************************************************************************* + +static ENABLED_EXTENSIONS: LazyLock>> = + LazyLock::new(|| RwLock::new(Vec::new())); + +static DROPPED_EXTENSIONS: LazyLock>> = + LazyLock::new(|| RwLock::new(Vec::new())); + +// REGISTRO DE LAS EXTENSIONES ********************************************************************* + +pub fn register_extensions(root_extension: Option) { + // Prepara la lista de extensiones habilitadas. + let mut enabled_list: Vec = Vec::new(); + + // Si se proporciona una extensión raíz inicial, se añade a la lista de extensiones habilitadas. + if let Some(extension) = root_extension { + add_to_enabled(&mut enabled_list, extension); + } + + // Guarda la lista final de extensiones habilitadas. + ENABLED_EXTENSIONS + .write() + .unwrap() + .append(&mut enabled_list); + + // Prepara una lista de extensiones deshabilitadas. + let mut dropped_list: Vec = Vec::new(); + + // Si se proporciona una extensión raíz, analiza su lista de dependencias. + if let Some(extension) = root_extension { + add_to_dropped(&mut dropped_list, extension); + } + + // Guarda la lista final de extensiones deshabilitadas. + DROPPED_EXTENSIONS + .write() + .unwrap() + .append(&mut dropped_list); +} + +fn add_to_enabled(list: &mut Vec, extension: ExtensionRef) { + // Verifica que la extensión no esté en la lista para evitar duplicados. + if !list.iter().any(|e| e.type_id() == extension.type_id()) { + // Añade primero (en orden inverso) las dependencias de la extensión. + for d in extension.dependencies().iter().rev() { + add_to_enabled(list, *d); + } + + // Añade la propia extensión a la lista. + list.push(extension); + } +} + +fn add_to_dropped(list: &mut Vec, extension: ExtensionRef) { + // Recorre las extensiones que la actual recomienda deshabilitar. + for d in &extension.drop_extensions() { + // Verifica que la extensión no esté ya en la lista. + if !list.iter().any(|e| e.type_id() == d.type_id()) { + // Comprueba si la extensión está habilitada. Si es así, registra una advertencia. + if ENABLED_EXTENSIONS + .read() + .unwrap() + .iter() + .any(|e| e.type_id() == extension.type_id()) + { + trace::warn!( + "Trying to drop \"{}\" extension which is enabled", + extension.short_name() + ); + } else { + // Si la extensión no está habilitada, se añade a la lista y registra la acción. + list.push(*d); + trace::debug!("Extension \"{}\" dropped", d.short_name()); + // Añade recursivamente las dependencias de la extensión eliminada. + // De este modo, todas las dependencias se tienen en cuenta para ser deshabilitadas. + for dependency in &extension.dependencies() { + add_to_dropped(list, *dependency); + } + } + } + } +} + +// INICIALIZA LAS EXTENSIONES ********************************************************************** + +pub fn initialize_extensions() { + trace::info!("Calling application bootstrap"); + for extension in ENABLED_EXTENSIONS.read().unwrap().iter() { + extension.initialize(); + } +} + +// CONFIGURA LOS SERVICIOS ************************************************************************* + +pub fn configure_services(scfg: &mut service::web::ServiceConfig) { + for extension in ENABLED_EXTENSIONS.read().unwrap().iter() { + extension.configure_service(scfg); + } +} diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs new file mode 100644 index 00000000..761fceea --- /dev/null +++ b/src/core/extension/definition.rs @@ -0,0 +1,77 @@ +use crate::core::AnyInfo; +use crate::locale::L10n; +use crate::service; + +/// Representa una referencia a una extensión. +/// +/// Las extensiones se definen como instancias estáticas globales para poder acceder a ellas desde +/// cualquier hilo de la ejecución sin necesidad de sincronización adicional. +pub type ExtensionRef = &'static dyn ExtensionTrait; + +/// Interfaz común que debe implementar cualquier extensión de `PageTop`. +/// +/// Este *trait* es fácil de implementar, basta con declarar la estructura de la extensión y +/// sobreescribir los métodos que sea necesario. +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// pub struct Blog; +/// +/// impl ExtensionTrait for Blog { +/// fn name(&self) -> L10n { L10n::n("Blog") } +/// fn description(&self) -> L10n { L10n::n("Sistema de blogs") } +/// } +/// ``` +pub trait ExtensionTrait: AnyInfo + Send + Sync { + /// Nombre legible para el usuario. + /// + /// Predeterminado por el [`short_name`](AnyInfo::short_name) del tipo asociado a la extensión. + fn name(&self) -> L10n { + L10n::n(self.short_name()) + } + + /// Descripción corta para paneles, listados, etc. + fn description(&self) -> L10n { + L10n::default() + } + + /// Otras extensiones que deben habilitarse **antes** de esta. + /// + /// `PageTop` las resolverá automáticamente respetando el orden durante el arranque de la + /// aplicación. + fn dependencies(&self) -> Vec { + vec![] + } + + /// Inicializa la extensión durante la lógica de arranque de la aplicación. + /// + /// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de + /// aceptar cualquier petición HTTP. + fn initialize(&self) {} + + /// Configura los servicios web de la extensión, como rutas, *middleware*, acceso a ficheros + /// estáticos, etc., usando [`ServiceConfig`](crate::service::web::ServiceConfig). + /// + /// ```rust,ignore + /// use pagetop::prelude::*; + /// + /// pub struct ExtensionSample; + /// + /// impl ExtensionTrait for ExtensionSample { + /// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + /// scfg.route("/sample", web::get().to(route_sample)); + /// } + /// } + /// ``` + #[allow(unused_variables)] + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {} + + /// Permite crear extensiones para deshabilitar y desinstalar los recursos de otras extensiones + /// utilizadas en versiones anteriores de la aplicación. + /// + /// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones. + fn drop_extensions(&self) -> Vec { + vec![] + } +} diff --git a/src/lib.rs b/src/lib.rs index 1ae35895..04a99397 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,8 @@ pub mod html; pub mod locale; // Soporte a fechas y horas. pub mod datetime; +// Tipos y funciones esenciales para crear extensiones. +pub mod core; // Gestión del servidor y servicios web. pub mod service; // Prepara y ejecuta la aplicación. diff --git a/src/prelude.rs b/src/prelude.rs index e4aabf55..c391de8c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -33,4 +33,8 @@ pub use crate::datetime::*; pub use crate::service; +pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; + +pub use crate::core::extension::*; + pub use crate::app::Application; From 4b281a1e96b71facf0a3992d90c1598c536314be Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 16 Jul 2025 00:45:05 +0200 Subject: [PATCH 017/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20A?= =?UTF-8?q?=C3=B1ade=20macros=20para=20concatenar=20cadenas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 9 ++- Cargo.toml | 3 +- src/prelude.rs | 2 +- src/util.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49fe0074..cde64db7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "concat-string" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609" + [[package]] name = "config" version = "0.15.13" @@ -1407,13 +1413,14 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.10" +version = "0.0.11" dependencies = [ "actix-files", "actix-web", "actix-web-static-files", "chrono", "colored", + "concat-string", "config", "figlet-rs", "fluent-templates", diff --git a/Cargo.toml b/Cargo.toml index 8212f62b..b6f844f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.10" +version = "0.0.11" edition = "2021" description = """\ @@ -17,6 +17,7 @@ authors.workspace = true [dependencies] chrono = "0.4.41" colored = "3.0.0" +concat-string = "1.0.1" config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" itoa = "1.0.15" diff --git a/src/prelude.rs b/src/prelude.rs index c391de8c..6506aaf9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,7 @@ pub use crate::{AutoDefault, StaticResources}; // MACROS. // crate::util -pub use crate::hm; +pub use crate::{hm, join, join_opt, join_pair, join_strict}; // crate::config pub use crate::include_config; // crate::locale diff --git a/src/util.rs b/src/util.rs index 7a48001f..b605125f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,6 +5,7 @@ use crate::trace; use std::io; use std::path::{Path, PathBuf}; +// FUNCIONES ÚTILES ******************************************************************************** /// Devuelve la ruta absoluta a un directorio existente. /// @@ -51,6 +52,9 @@ where #[doc(hidden)] pub use paste::paste; +#[doc(hidden)] +pub use concat_string::concat_string; + #[macro_export] /// Macro para construir una colección de pares clave-valor. /// @@ -73,3 +77,160 @@ macro_rules! hm { a }}; } + +/// Concatena eficientemente varios fragmentos en un [`String`]. +/// +/// Esta macro exporta [`concat_string!`](https://docs.rs/concat-string). Acepta cualquier número de +/// fragmentos que implementen [`AsRef`] y construye un [`String`] con el tamaño óptimo, de +/// forma eficiente y evitando el uso de cadenas de formato que penalicen el rendimiento. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Concatena todos los fragmentos directamente. +/// let result = join!("Hello", " ", "World"); +/// assert_eq!(result, String::from("Hello World")); +/// +/// // También funciona con valores vacíos. +/// let result_with_empty = join!("Hello", "", "World"); +/// assert_eq!(result_with_empty, String::from("HelloWorld")); +/// +/// // Un único fragmento devuelve el mismo valor. +/// let single_result = join!("Hello"); +/// assert_eq!(single_result, String::from("Hello")); +/// ``` +#[macro_export] +macro_rules! join { + ($($arg:tt)*) => { + $crate::util::concat_string!($($arg)*) + }; +} + +/// Concatena los fragmentos no vacíos en un [`Option`] con un separador opcional. +/// +/// Esta macro acepta cualquier número de fragmentos que implementen [`AsRef`] para concatenar +/// todos los fragmentos no vacíos usando opcionalmente un separador. +/// +/// Si todos los fragmentos están vacíos, devuelve [`None`]. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Concatena los fragmentos no vacíos con un espacio como separador. +/// let result_with_separator = join_opt!(["Hello", "", "World"]; " "); +/// assert_eq!(result_with_separator, Some(String::from("Hello World"))); +/// +/// // Concatena los fragmentos no vacíos sin un separador. +/// let result_without_separator = join_opt!(["Hello", "", "World"]); +/// assert_eq!(result_without_separator, Some(String::from("HelloWorld"))); +/// +/// // Devuelve `None` si todos los fragmentos están vacíos. +/// let result_empty = join_opt!(["", "", ""]); +/// assert_eq!(result_empty, None); +/// ``` +#[macro_export] +macro_rules! join_opt { + ([$($arg:expr),* $(,)?]) => {{ + let s = $crate::util::concat_string!($($arg),*); + (!s.is_empty()).then_some(s) + }}; + ([$($arg:expr),* $(,)?]; $separator:expr) => {{ + let s = [$($arg),*] + .iter() + .filter(|&item| !item.is_empty()) + .cloned() + .collect::>() + .join($separator); + (!s.is_empty()).then_some(s) + }}; +} + +/// Concatena dos fragmentos en un [`String`] usando un separador. +/// +/// Une los dos fragmentos, que deben implementar [`AsRef`], usando el separador proporcionado. +/// Si uno de ellos está vacío, devuelve directamente el otro; y si ambos están vacíos devuelve un +/// [`String`] vacío. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let first = "Hello"; +/// let separator = "-"; +/// let second = "World"; +/// +/// // Concatena los dos fragmentos cuando ambos no están vacíos. +/// let result = join_pair!(first, separator, second); +/// assert_eq!(result, String::from("Hello-World")); +/// +/// // Si el primer fragmento está vacío, devuelve el segundo. +/// let result_empty_first = join_pair!("", separator, second); +/// assert_eq!(result_empty_first, String::from("World")); +/// +/// // Si el segundo fragmento está vacío, devuelve el primero. +/// let result_empty_second = join_pair!(first, separator, ""); +/// assert_eq!(result_empty_second, String::from("Hello")); +/// +/// // Si ambos fragmentos están vacíos, devuelve una cadena vacía. +/// let result_both_empty = join_pair!("", separator, ""); +/// assert_eq!(result_both_empty, String::from("")); +/// ``` +#[macro_export] +macro_rules! join_pair { + ($first:expr, $separator:expr, $second:expr) => {{ + if $first.is_empty() { + String::from($second) + } else if $second.is_empty() { + String::from($first) + } else { + $crate::util::concat_string!($first, $separator, $second) + } + }}; +} + +/// Concatena varios fragmentos en un [`Option`] si ninguno está vacío. +/// +/// Si alguno de los fragmentos, que deben implementar [`AsRef`], está vacío, devuelve +/// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Concatena los fragmentos. +/// let result = join_strict!(["Hello", "World"]); +/// assert_eq!(result, Some(String::from("HelloWorld"))); +/// +/// // Concatena los fragmentos con un separador. +/// let result_with_separator = join_strict!(["Hello", "World"]; " "); +/// assert_eq!(result_with_separator, Some(String::from("Hello World"))); +/// +/// // Devuelve `None` si alguno de los fragmentos está vacío. +/// let result_with_empty = join_strict!(["Hello", "", "World"]); +/// assert_eq!(result_with_empty, None); +/// ``` +#[macro_export] +macro_rules! join_strict { + ([$($arg:expr),* $(,)?]) => {{ + let fragments = [$($arg),*]; + if fragments.iter().any(|&item| item.is_empty()) { + None + } else { + Some(fragments.concat()) + } + }}; + ([$($arg:expr),* $(,)?]; $separator:expr) => {{ + let fragments = [$($arg),*]; + if fragments.iter().any(|&item| item.is_empty()) { + None + } else { + Some(fragments.join($separator)) + } + }}; +} From 9bfdf2e9ac49ebbf3983d4e4424c042d8bbf9f67 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 16 Jul 2025 01:04:36 +0200 Subject: [PATCH 018/224] =?UTF-8?q?=F0=9F=93=9D=20Actualiza=20documentaci?= =?UTF-8?q?=C3=B3n=20con=20favicon=20propio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-build/src/lib.rs | 4 ++++ helpers/pagetop-macros/src/lib.rs | 4 ++++ src/lib.rs | 4 ++++ static/favicon.ico | Bin 0 -> 10206 bytes 4 files changed, 12 insertions(+) create mode 100644 static/favicon.ico diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 98c8ef87..3816c658 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -115,6 +115,10 @@ //! include_files!(GUIDES => guides); //! ``` +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + use grass::{from_path, Options, OutputStyle}; use static_files::{resource_dir, ResourceDir}; diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 25105c2f..62b6e886 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -14,6 +14,10 @@ //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles //! y configurables, basadas en HTML, CSS y JavaScript. +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + mod maud; mod smart_default; diff --git a/src/lib.rs b/src/lib.rs index 04a99397..0fd3250c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,10 @@ #![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + use std::collections::HashMap; use std::ops::Deref; diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..95e1affa748f69b77f95490649836e9861036325 GIT binary patch literal 10206 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 Yo2E{c#pzo8UZDq&kyMnZ6f+L`AAO+n{{R30 literal 0 HcmV?d00001 From 17f52efb7274bc64c680c27f7d9981f02697fc77 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 16 Jul 2025 02:53:49 +0200 Subject: [PATCH 019/224] =?UTF-8?q?=F0=9F=9A=91=20(macros):=20Corrige=20ru?= =?UTF-8?q?tas=20absolutas=20en=20html!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mantiene fijas las rutas absolutas en las funciones de la macro html!. - Introduce un alias interno del crate (`extern crate self as pagetop;`) en `src/lib.rs` para que las expansiones funcionen uniformemente en tests, ejemplos y dependencias externas. --- Cargo.lock | 31 ++------------------- helpers/pagetop-macros/Cargo.toml | 3 +- helpers/pagetop-macros/src/maud.rs | 12 +------- helpers/pagetop-macros/src/maud/generate.rs | 14 ++-------- src/html/maud.rs | 6 ++-- src/lib.rs | 4 +++ 6 files changed, 14 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cde64db7..ab7b20a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1449,9 +1449,8 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.4" +version = "0.0.5" dependencies = [ - "proc-macro-crate", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -1618,15 +1617,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -2185,17 +2175,11 @@ checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.7.0", + "toml_datetime", "toml_parser", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - [[package]] name = "toml_datetime" version = "0.7.0" @@ -2205,17 +2189,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime 0.6.11", - "winnow", -] - [[package]] name = "toml_parser" version = "1.0.0" diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 070e7784..3a7fd950 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.4" +version = "0.0.5" edition = "2021" description = """\ @@ -20,6 +20,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.95" proc-macro2-diagnostics = { version = "0.10.1", default-features = false } -proc-macro-crate = "3.3.0" quote = "1.0.40" syn = { version = "2.0.104", features = ["full"] } diff --git a/helpers/pagetop-macros/src/maud.rs b/helpers/pagetop-macros/src/maud.rs index 9077dbb9..2c763cd0 100644 --- a/helpers/pagetop-macros/src/maud.rs +++ b/helpers/pagetop-macros/src/maud.rs @@ -10,7 +10,6 @@ mod generate; use ast::DiagnosticParse; use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2_diagnostics::Diagnostic; -use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; use syn::parse::{ParseStream, Parser}; @@ -41,20 +40,11 @@ pub fn expand(input: TokenStream) -> TokenStream { let output_ident = Ident::new("__maud_output", Span::mixed_site()); let stmts = generate::generate(markups, output_ident.clone()); - let found_crate = crate_name("pagetop").expect("pagetop must be in Cargo.toml"); - let crate_ident = match found_crate { - FoundCrate::Itself => Ident::new("pagetop", Span::call_site()), - FoundCrate::Name(ref name) => Ident::new(name, Span::call_site()), - }; - let pre_escaped = quote! { - #crate_ident::html::PreEscaped(#output_ident) - }; - quote! {{ extern crate alloc; let mut #output_ident = alloc::string::String::with_capacity(#size_hint); #stmts #(#diag_tokens)* - #pre_escaped + pagetop::html::PreEscaped(#output_ident) }} } diff --git a/helpers/pagetop-macros/src/maud/generate.rs b/helpers/pagetop-macros/src/maud/generate.rs index 1f825783..19ff3d76 100644 --- a/helpers/pagetop-macros/src/maud/generate.rs +++ b/helpers/pagetop-macros/src/maud/generate.rs @@ -4,8 +4,6 @@ use syn::{parse_quote, token::Brace, Expr, Local}; use crate::maud::{ast::*, escape}; -use proc_macro_crate::{crate_name, FoundCrate}; - pub fn generate(markups: Markups, output_ident: Ident) -> TokenStream { let mut build = Builder::new(output_ident.clone()); Generator::new(output_ident).markups(markups, &mut build); @@ -68,15 +66,9 @@ impl Generator { fn splice(&self, expr: Expr, build: &mut Builder) { let output_ident = &self.output_ident; - - let found_crate = crate_name("pagetop").expect("pagetop debe existir en Cargo.toml"); - let crate_ident = match found_crate { - FoundCrate::Itself => Ident::new("pagetop", Span::call_site()), - FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), - }; - build.push_tokens(quote! { - #crate_ident::html::html_private::render_to!(&(#expr), &mut #output_ident); - }); + build.push_tokens( + quote!(pagetop::html::html_private::render_to!(&(#expr), &mut #output_ident);), + ); } fn element(&self, element: Element, build: &mut Builder) { diff --git a/src/html/maud.rs b/src/html/maud.rs index 19429869..39cb2c20 100644 --- a/src/html/maud.rs +++ b/src/html/maud.rs @@ -73,7 +73,7 @@ impl fmt::Write for Escaper<'_> { /// # Example /// /// ```rust -/// use pagetop::html::{html, Markup, Render}; +/// use pagetop::prelude::*; /// /// /// Provides a shorthand for linking to a CSS stylesheet. /// pub struct Stylesheet(&'static str); @@ -195,7 +195,7 @@ impl_render_with_itoa! { /// # Example /// /// ```rust -/// use pagetop::html::{display, html}; +/// use pagetop::prelude::*; /// use std::net::Ipv4Addr; /// /// let ip_address = Ipv4Addr::new(127, 0, 0, 1); @@ -266,7 +266,7 @@ impl Default for PreEscaped { /// A minimal web page: /// /// ```rust -/// use pagetop::html::{DOCTYPE, html}; +/// use pagetop::prelude::*; /// /// let markup = html! { /// (DOCTYPE) diff --git a/src/lib.rs b/src/lib.rs index 0fd3250c..248ba6d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,10 @@ html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] +// Alias para que las rutas absolutas `::pagetop::…` generadas por las macros funcionen en el propio +// *crate*, en *crates* externos y en *doctests*. +extern crate self as pagetop; + use std::collections::HashMap; use std::ops::Deref; From de37724835863ffb028d990dc1a7095e6931ee9c Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 19 Jul 2025 00:13:49 +0200 Subject: [PATCH 020/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Mej?= =?UTF-8?q?ora=20uso=20y=20doc.=20de=20la=20API=20de=20localizaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locale.rs | 127 +++++++++++++++++++++++++++++++++++------------- tests/locale.rs | 10 ++-- 2 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/locale.rs b/src/locale.rs index 064481f0..ab1f5efc 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -13,9 +13,9 @@ //! //! # Recursos Fluent //! -//! Por defecto, las traducciones se organizan en el directorio *src/locale*, con subdirectorios -//! para cada [Identificador de Idioma Unicode](https://docs.rs/unic-langid/) válido. Podríamos -//! tener una estructura como esta: +//! Por defecto las traducciones están en el directorio `src/locale`, con subdirectorios para cada +//! [Identificador de Idioma Unicode](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier) +//! válido. Podríamos tener una estructura como esta: //! //! ```text //! src/locale/ @@ -29,12 +29,12 @@ //! ├── es-MX/ //! │ ├── default.ftl //! │ └── main.ftl -//! └── fr/ +//! └── fr-FR/ //! ├── default.ftl //! └── main.ftl //! ``` //! -//! Ejemplo de un archivo *src/locale/en-US/main.ftl*: +//! Ejemplo de un archivo en `src/locale/en-US/main.ftl` //! //! ```text //! hello-world = Hello world! @@ -50,7 +50,7 @@ //! }. //! ``` //! -//! Y su archivo equivalente para español *src/locale/es-ES/main.ftl*: +//! Y su archivo equivalente para español en `src/locale/es-ES/main.ftl`: //! //! ```text //! hello-world = Hola mundo! @@ -69,10 +69,11 @@ //! //! # Cómo aplicar la localización en tu código //! -//! Una vez creado el directorio con los recursos FTL, basta con utilizar la macro +//! Una vez creado el directorio con los recursos FTL, basta con usar la macro //! [`include_locales!`](crate::include_locales) para integrarlos en la aplicación. //! -//! Si los recursos se encuentran en el directorio `"src/locale"`, sólo hay que declarar: +//! Si los recursos se encuentran en el directorio por defecto `src/locale` del *crate*, sólo hay +//! que declarar: //! //! ```rust //! use pagetop::prelude::*; @@ -80,11 +81,14 @@ //! include_locales!(LOCALES_SAMPLE); //! ``` //! -//! Pero si están ubicados en otro directorio, entonces se pueden incluir usando: +//! Si están ubicados en otro directorio se puede usar la forma: //! //! ```rust,ignore //! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); //! ``` +//! +//! Y *voilà*, sólo queda operar con los idiomas soportados por `PageTop` usando [`LangMatch`] y +//! traducir textos con [`L10n`]. use crate::html::{Markup, PreEscaped}; use crate::{global, hm, AutoDefault}; @@ -128,28 +132,69 @@ static FALLBACK_LANGID: LazyLock = LazyLock::new(|| langid!( pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = LazyLock::new(|| LangMatch::langid_or_fallback(&global::SETTINGS.app.language)); -/// Comprueba si el idioma está soportado por `PageTop`. +/// Operaciones con los idiomas soportados por `PageTop`. /// -/// Útil para transformar un código de idioma en un [`LanguageIdentifier`] válido para `PageTop`. +/// Utiliza [`LangMatch`] para transformar un código de idioma en un [`LanguageIdentifier`] +/// soportado por `PageTop`. +/// +/// # Ejemplos +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Coincidencia exacta. +/// let lang = LangMatch::resolve("es-ES"); +/// assert_eq!(lang.as_langid().to_string(), "es-ES"); +/// +/// // Coincidencia parcial (con el idioma base). +/// let lang = LangMatch::resolve("es-EC"); +/// assert_eq!(lang.as_langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. +/// +/// // Idioma no especificado. +/// let lang = LangMatch::resolve(""); +/// assert_eq!(lang, LangMatch::Unspecified); +/// +/// // Idioma no soportado. +/// let lang = LangMatch::resolve("ja-JP"); +/// assert_eq!(lang, LangMatch::Unsupported("ja-JP".to_string())); +/// ``` +/// +/// Las siguientes instrucciones devuelven siempre un [`LanguageIdentifier`] válido, ya sea porque +/// resuelven un idioma soportado o porque aplican el idioma por defecto o de respaldo: +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Idioma por defecto si no resuelve. +/// let lang = LangMatch::resolve("it-IT"); +/// let langid = lang.as_langid(); +/// +/// // Idioma por defecto si no se encuentra. +/// let langid = LangMatch::langid_or_default("es-MX"); +/// +/// // Idioma de respaldo ("en-US") si no se encuentra. +/// let langid = LangMatch::langid_or_fallback("es-MX"); +/// ``` #[derive(Clone, Debug, Eq, PartialEq)] pub enum LangMatch { /// Cuando el código del idioma es una cadena vacía. - Empty, - /// Si encuentra un [`LanguageIdentifier`] que coincide exactamente o retrocediendo al idioma - /// base. + Unspecified, + /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por `PageTop` que + /// coincide exactamente con el código del idioma (p.ej. "es-ES"), o con el código del idioma + /// base (p.ej. "es"). Found(&'static LanguageIdentifier), /// Si el código del idioma no está entre los soportados por `PageTop`. - Unknown(String), + Unsupported(String), } impl LangMatch { - /// Resuelve `language` y devuelve el [`LangMatch`] apropiado. + /// Resuelve `language` y devuelve la variante [`LangMatch`] apropiada. pub fn resolve(language: impl AsRef) -> Self { let language = language.as_ref().trim(); // Rechaza cadenas vacías. if language.is_empty() { - return Self::Empty; + return Self::Unspecified; } // Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). @@ -164,28 +209,40 @@ impl LangMatch { } } - // Devuelve desconocido si el idioma no está soportado. - Self::Unknown(language.to_string()) + // En otro caso indica que el idioma no está soportado. + Self::Unsupported(language.to_string()) } - /// Devuelve siempre un [`LanguageIdentifier`] válido. + /// Devuelve el idioma de la variante de la instancia, o el idioma por defecto si no está + /// soportado. /// - /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). - #[inline] - pub fn langid_or_fallback(language: impl AsRef) -> &'static LanguageIdentifier { - match Self::resolve(language) { - Self::Found(l) => l, - _ => &FALLBACK_LANGID, - } - } - - /// Devuelve un [`LanguageIdentifier`] válido para la instancia. - /// - /// Si `language` está vacío o es desconocido, devuelve el idioma de respaldo ("en-US"). + /// Siempre devuelve un [`LanguageIdentifier`] válido. #[inline] pub fn as_langid(&self) -> &'static LanguageIdentifier { match self { LangMatch::Found(l) => l, + _ => &DEFAULT_LANGID, + } + } + + /// Si `language` está vacío o no está soportado, devuelve el idioma por defecto. + /// + /// Siempre devuelve un [`LanguageIdentifier`] válido. + #[inline] + pub fn langid_or_default(language: impl AsRef) -> &'static LanguageIdentifier { + match Self::resolve(language) { + Self::Found(l) => l, + _ => &DEFAULT_LANGID, + } + } + + /// Si `language` está vacío o no está soportado, devuelve el idioma de respaldo ("en-US"). + /// + /// Siempre devuelve un [`LanguageIdentifier`] válido. + #[inline] + pub fn langid_or_fallback(language: impl AsRef) -> &'static LanguageIdentifier { + match Self::resolve(language) { + Self::Found(l) => l, _ => &FALLBACK_LANGID, } } @@ -243,7 +300,9 @@ enum L10nOp { /// - Una clave para traducir un texto de las traducciones por defecto de `PageTop` (`l()`). /// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). /// -/// Los argumentos dinámicos se añaden mediante `with_arg()` o `with_args()`. +/// # Ejemplo +/// +/// Los argumentos dinámicos se añaden usando `with_arg()` o `with_args()`. /// /// ```rust /// use pagetop::prelude::*; @@ -261,7 +320,7 @@ enum L10nOp { /// /// ```rust,ignore /// // Traducción con clave, conjunto de traducciones y código de idioma a usar. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_fallback("it")); +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_default("it")); /// ``` #[derive(AutoDefault)] pub struct L10n { diff --git a/tests/locale.rs b/tests/locale.rs index c723cd46..420914d8 100644 --- a/tests/locale.rs +++ b/tests/locale.rs @@ -13,7 +13,7 @@ async fn translation_without_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")); + let translation = l10n.using(LangMatch::langid_or_default("es-ES")); assert_eq!(translation, Some("¡Hola mundo!".to_string())); } @@ -22,7 +22,7 @@ async fn translation_with_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-user").with_arg("userName", "Manuel"); - let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")); + let translation = l10n.using(LangMatch::langid_or_default("es-ES")); assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); } @@ -35,7 +35,7 @@ async fn translation_with_plural_and_select() { ("photoCount", "3"), ("userGender", "male"), ]); - let translation = l10n.using(LangMatch::langid_or_fallback("es-ES")).unwrap(); + let translation = l10n.using(LangMatch::langid_or_default("es-ES")).unwrap(); assert!(translation.contains("añadido 3 nuevas fotos de él")); } @@ -44,7 +44,7 @@ async fn check_fallback_language() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(LangMatch::langid_or_fallback("xx-YY")); // Fallback a "en-US". + let translation = l10n.using(LangMatch::langid_or_fallback("xx-YY")); // Retrocede a "en-US". assert_eq!(translation, Some("Hello world!".to_string())); } @@ -53,6 +53,6 @@ async fn check_unknown_key() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("non-existent-key"); - let translation = l10n.using(LangMatch::langid_or_fallback("en-US")); + let translation = l10n.using(LangMatch::langid_or_default("en-US")); assert_eq!(translation, None); } From a19587c39c073591ed0770840f6b8f6c80d97547 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 20 Jul 2025 11:58:17 +0200 Subject: [PATCH 021/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20(bu?= =?UTF-8?q?ild):=20M=C3=A1s=20opciones=20de=20tipo=20para=20funciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cambia parámetros de tipo en las funciones `from_dir` y `with_name` para aceptar referencias a cadenas. --- helpers/pagetop-build/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 3816c658..e125c591 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -160,8 +160,8 @@ impl StaticFilesBundle { /// .build() /// } /// ``` - pub fn from_dir(dir: &'static str, filter: Option bool>) -> Self { - let mut resource_dir = resource_dir(dir); + pub fn from_dir(dir: impl AsRef, filter: Option bool>) -> Self { + let mut resource_dir = resource_dir(dir.as_ref()); // Aplica el filtro si está definido. if let Some(f) = filter { @@ -247,7 +247,8 @@ impl StaticFilesBundle { } /// Asigna un nombre al conjunto de recursos. - pub fn with_name(mut self, name: &'static str) -> Self { + pub fn with_name(mut self, name: impl AsRef) -> Self { + let name = name.as_ref(); let out_dir = std::env::var("OUT_DIR").unwrap(); let filename = Path::new(&out_dir).join(format!("{name}.rs")); self.resource_dir.with_generated_filename(filename); From b3ed8e07af1d1b6318701eedd43b86f4471fae3a Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 20 Jul 2025 14:24:19 +0200 Subject: [PATCH 022/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20para?= =?UTF-8?q?=20recursos=20en=20documentos=20HTML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incluye los recursos favicon, hojas de estilo y scripts JavaScript. - Se introduce una estructura de contexto que, además de gestionar el idioma y el uso de parámetros contextuales, permite administrar estos recursos en documentos HTML. --- src/html.rs | 74 ++++++++++++ src/html/assets.rs | 63 ++++++++++ src/html/assets/favicon.rs | 165 ++++++++++++++++++++++++++ src/html/assets/javascript.rs | 178 ++++++++++++++++++++++++++++ src/html/assets/stylesheet.rs | 174 +++++++++++++++++++++++++++ src/html/context.rs | 216 ++++++++++++++++++++++++++++++++++ src/html/maud.rs | 12 +- src/lib.rs | 6 + src/prelude.rs | 5 +- src/service.rs | 2 +- tests/html.rs | 17 +++ 11 files changed, 904 insertions(+), 8 deletions(-) create mode 100644 src/html/assets.rs create mode 100644 src/html/assets/favicon.rs create mode 100644 src/html/assets/javascript.rs create mode 100644 src/html/assets/stylesheet.rs create mode 100644 src/html/context.rs create mode 100644 tests/html.rs diff --git a/src/html.rs b/src/html.rs index 14e72b94..14a89edd 100644 --- a/src/html.rs +++ b/src/html.rs @@ -2,3 +2,77 @@ mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, Render, DOCTYPE}; + +mod assets; +pub use assets::favicon::Favicon; +pub use assets::javascript::JavaScript; +pub use assets::stylesheet::{StyleSheet, TargetMedia}; +pub(crate) use assets::Assets; + +mod context; +pub use context::{Context, ErrorParam}; + +use crate::AutoDefault; + +/// Prepara contenido HTML para su conversión a [`Markup`]. +/// +/// Este tipo encapsula distintos orígenes de contenido HTML (texto plano, HTML escapado o marcado +/// ya procesado) para renderizar de forma homogénea en plantillas sin interferir con el uso +/// estándar de [`Markup`]. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let fragment = PrepareMarkup::Text(String::from("Hola mundo")); +/// assert_eq!(fragment.render().into_string(), "Hola <b>mundo</b>"); +/// +/// let raw_html = PrepareMarkup::Escaped(String::from("negrita")); +/// assert_eq!(raw_html.render().into_string(), "negrita"); +/// +/// let prepared = PrepareMarkup::With(html! { +/// h2 { "Título de ejemplo" } +/// p { "Este es un párrafo con contenido dinámico." } +/// }); +/// assert_eq!( +/// prepared.render().into_string(), +/// "

Título de ejemplo

Este es un párrafo con contenido dinámico.

" +/// ); +/// ``` +#[derive(AutoDefault)] +pub enum PrepareMarkup { + /// No se genera contenido HTML (devuelve `html! {}`). + #[default] + None, + /// Texto estático que se escapará automáticamente para no ser interpretado como HTML. + Text(String), + /// Contenido sin escapado adicional, útil para HTML generado externamente. + Escaped(String), + /// Fragmento HTML ya preparado como [`Markup`], listo para insertarse directamente. + With(Markup), +} + +impl PrepareMarkup { + /// Devuelve `true` si el contenido está vacío y no generará HTML al renderizar. + pub fn is_empty(&self) -> bool { + match self { + PrepareMarkup::None => true, + PrepareMarkup::Text(text) => text.is_empty(), + PrepareMarkup::Escaped(string) => string.is_empty(), + PrepareMarkup::With(markup) => markup.is_empty(), + } + } +} + +impl Render for PrepareMarkup { + /// Integra el renderizado fácilmente en la macro [`html!`]. + fn render(&self) -> Markup { + match self { + PrepareMarkup::None => html! {}, + PrepareMarkup::Text(text) => html! { (text) }, + PrepareMarkup::Escaped(string) => html! { (PreEscaped(string)) }, + PrepareMarkup::With(markup) => html! { (markup) }, + } + } +} diff --git a/src/html/assets.rs b/src/html/assets.rs new file mode 100644 index 00000000..894b7e84 --- /dev/null +++ b/src/html/assets.rs @@ -0,0 +1,63 @@ +pub mod favicon; +pub mod javascript; +pub mod stylesheet; + +use crate::html::{html, Markup, Render}; +use crate::{AutoDefault, Weight}; + +pub trait AssetsTrait: Render { + // Devuelve el nombre del recurso, utilizado como clave única. + fn name(&self) -> &str; + + // Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. + fn weight(&self) -> Weight; +} + +#[derive(AutoDefault)] +pub(crate) struct Assets(Vec); + +impl Assets { + pub fn new() -> Self { + Assets::(Vec::::new()) + } + + pub fn add(&mut self, asset: T) -> bool { + match self.0.iter().position(|x| x.name() == asset.name()) { + Some(index) => { + if self.0[index].weight() > asset.weight() { + self.0.remove(index); + self.0.push(asset); + true + } else { + false + } + } + _ => { + self.0.push(asset); + true + } + } + } + + pub fn remove(&mut self, name: impl AsRef) -> bool { + if let Some(index) = self.0.iter().position(|x| x.name() == name.as_ref()) { + self.0.remove(index); + true + } else { + false + } + } +} + +impl Render for Assets { + fn render(&self) -> Markup { + let mut assets = self.0.iter().collect::>(); + assets.sort_by_key(|a| a.weight()); + + html! { + @for a in assets { + (a.render()) + } + } + } +} diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs new file mode 100644 index 00000000..97a44e86 --- /dev/null +++ b/src/html/assets/favicon.rs @@ -0,0 +1,165 @@ +use crate::html::{html, Markup, Render}; +use crate::AutoDefault; + +/// Un **Favicon** es un recurso gráfico que usa el navegador como icono asociado al sitio. +/// +/// Es universalmente aceptado para mostrar el icono del sitio (`.ico`, `.png`, `.svg`, …) en +/// pestañas, marcadores o accesos directos. +/// +/// Este tipo permite construir de forma fluida las distintas variantes de un *favicon*, ya sea un +/// icono estándar, un icono Apple para la pantalla de inicio, o un icono para Safari con color. +/// También puede aplicar colores al tema o configuraciones específicas para *tiles* de Windows. +/// +/// > **Nota** +/// > Los archivos de los iconos deben estar disponibles en el servidor web de la aplicación. Pueden +/// > incluirse en el proyecto utilizando [`include_files`](crate::include_files) y servirse con +/// > [`include_files_service`](crate::include_files_service). +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let favicon = Favicon::new() +/// // Estándar de facto admitido por todos los navegadores. +/// .with_icon("/icons/favicon.ico") +/// +/// // Variante del favicon con tamaños explícitos: 32×32 y 16×16. +/// .with_icon_for_sizes("/icons/favicon-32.png", "32x32") +/// .with_icon_for_sizes("/icons/favicon-16.png", "16x16") +/// +/// // Icono específico para accesos directos en la pantalla de inicio de iOS. +/// .with_apple_touch_icon("/icons/apple-touch-icon.png", "180x180") +/// +/// // Icono vectorial con color dinámico para pestañas ancladas en Safari. +/// .with_mask_icon("/icons/safari-pinned-tab.svg", "#5bbad5") +/// +/// // Personaliza la barra superior del navegador en Android Chrome (y soportado en otros). +/// .with_theme_color("#ffffff") +/// +/// // Personalizaciones específicas para "tiles" en Windows. +/// .with_ms_tile_color("#da532c") +/// .with_ms_tile_image("/icons/mstile-144x144.png"); +/// ``` +#[derive(AutoDefault)] +pub struct Favicon(Vec); + +impl Favicon { + /// Crea un nuevo `Favicon` vacío. + /// + /// Equivalente a `Favicon::default()`. Se recomienda iniciar la secuencia de configuración + /// desde aquí. + pub fn new() -> Self { + Favicon::default() + } + + // Favicon BUILDER ***************************************************************************** + + /// Le añade un icono genérico apuntando a `image`. El tipo MIME se infiere automáticamente a + /// partir de la extensión. + pub fn with_icon(self, image: impl Into) -> Self { + self.add_icon_item("icon", image.into(), None, None) + } + + /// Le añade un icono genérico con atributo `sizes`, útil para indicar resoluciones específicas. + /// + /// El atributo `sizes` informa al navegador de las dimensiones de la imagen para que seleccione + /// el recurso más adecuado. Puede enumerar varias dimensiones separadas por espacios, p.ej. + /// `"16x16 32x32 48x48"` o usar `any` para iconos escalables (SVG). + /// + /// No es imprescindible, pero puede mejorar la selección del icono más adecuado. + pub fn with_icon_for_sizes(self, image: impl Into, sizes: impl Into) -> Self { + self.add_icon_item("icon", image.into(), Some(sizes.into()), None) + } + + /// Le añade un *Apple Touch Icon*, usado por dispositivos iOS para las pantallas de inicio. + /// + /// Se recomienda indicar también el tamaño, p.ej. `"256x256"`. + pub fn with_apple_touch_icon(self, image: impl Into, sizes: impl Into) -> Self { + self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None) + } + + /// Le añade un icono para el navegador Safari, con un color dinámico. + /// + /// El atributo `color` lo usa Safari para colorear el trazado SVG cuando el icono se muestra en + /// modo *Pinned Tab*. Aunque Safari 12+ acepta *favicons normales*, este método garantiza + /// compatibilidad con versiones anteriores. + pub fn with_mask_icon(self, image: impl Into, color: impl Into) -> Self { + self.add_icon_item("mask-icon", image.into(), None, Some(color.into())) + } + + /// Define el color del tema (``). + /// + /// Lo usan algunos navegadores para colorear la barra de direcciones o interfaces. + pub fn with_theme_color(mut self, color: impl Into) -> Self { + self.0.push(html! { + meta name="theme-color" content=(color.into()); + }); + self + } + + /// Define el color del *tile* en Windows (``). + pub fn with_ms_tile_color(mut self, color: impl Into) -> Self { + self.0.push(html! { + meta name="msapplication-TileColor" content=(color.into()); + }); + self + } + + /// Define la imagen del *tile* en Windows (``). + pub fn with_ms_tile_image(mut self, image: impl Into) -> Self { + self.0.push(html! { + meta name="msapplication-TileImage" content=(image.into()); + }); + self + } + + // Función interna que centraliza la creación de las etiquetas ``. + // + // - `icon_rel`: indica el tipo de recurso (`"icon"`, `"apple-touch-icon"`, etc.). + // - `icon_source`: URL del recurso. + // - `icon_sizes`: tamaños opcionales. + // - `icon_color`: color opcional (solo relevante para `mask-icon`). + // + // También infiere automáticamente el tipo MIME (`type`) según la extensión del archivo. + fn add_icon_item( + mut self, + icon_rel: &str, + icon_source: String, + icon_sizes: Option, + icon_color: Option, + ) -> Self { + let icon_type = match icon_source.rfind('.') { + Some(i) => match icon_source[i..].to_owned().to_lowercase().as_str() { + ".avif" => Some("image/avif"), + ".gif" => Some("image/gif"), + ".ico" => Some("image/x-icon"), + ".jpg" | ".jpeg" => Some("image/jpeg"), + ".png" => Some("image/png"), + ".svg" => Some("image/svg+xml"), + ".webp" => Some("image/webp"), + _ => None, + }, + _ => None, + }; + self.0.push(html! { + link + rel=(icon_rel) + type=[(icon_type)] + sizes=[(icon_sizes)] + color=[(icon_color)] + href=(icon_source); + }); + self + } +} + +impl Render for Favicon { + fn render(&self) -> Markup { + html! { + @for item in &self.0 { + (item) + } + } + } +} diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs new file mode 100644 index 00000000..fb0a1b64 --- /dev/null +++ b/src/html/assets/javascript.rs @@ -0,0 +1,178 @@ +use crate::html::assets::AssetsTrait; +use crate::html::{html, Markup, Render}; +use crate::{join, join_pair, AutoDefault, Weight}; + +// Define el origen del recurso JavaScript y cómo debe cargarse en el navegador. +// +// Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del +// script. +// +// - [`From`] – Carga el script de forma estándar con la etiqueta ``. El parámetro `name` se usa como identificador interno del + /// *script*. + pub fn inline(name: impl Into, script: impl Into) -> Self { + JavaScript { + source: Source::Inline(name.into(), script.into()), + ..Default::default() + } + } + + /// Crea un **script embebido** que se ejecuta automáticamente al terminar de cargarse el + /// documento HTML. + /// + /// El código se envuelve automáticamente en un `addEventListener('DOMContentLoaded', ...)`. El + /// parámetro `name` se usa como identificador interno del *script*. + pub fn on_load(name: impl Into, script: impl Into) -> Self { + JavaScript { + source: Source::OnLoad(name.into(), script.into()), + ..Default::default() + } + } + + // JavaScript BUILDER ************************************************************************** + + /// Asocia una versión al recurso (usada para control de la caché del navegador). + /// + /// Si `version` está vacío, no se añade ningún parámetro a la URL. + pub fn with_version(mut self, version: impl Into) -> Self { + self.version = version.into(); + self + } + + /// Modifica el peso del recurso. + /// + /// Los recursos se renderizan de menor a mayor peso. Por defecto es `0`, que respeta el orden + /// de creación. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } +} + +impl AssetsTrait for JavaScript { + // Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador. + fn name(&self) -> &str { + match &self.source { + Source::From(path) => path, + Source::Defer(path) => path, + Source::Async(path) => path, + Source::Inline(name, _) => name, + Source::OnLoad(name, _) => name, + } + } + + fn weight(&self) -> Weight { + self.weight + } +} + +impl Render for JavaScript { + fn render(&self) -> Markup { + match &self.source { + Source::From(path) => html! { + script src=(join_pair!(path, "?v=", self.version.as_str())) {}; + }, + Source::Defer(path) => html! { + script src=(join_pair!(path, "?v=", self.version.as_str())) defer {}; + }, + Source::Async(path) => html! { + script src=(join_pair!(path, "?v=", self.version.as_str())) async {}; + }, + Source::Inline(_, code) => html! { + script { (code) }; + }, + Source::OnLoad(_, code) => html! { (join!( + "document.addEventListener('DOMContentLoaded',function(){", code, "});" + )) }, + } + } +} diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs new file mode 100644 index 00000000..7f64d187 --- /dev/null +++ b/src/html/assets/stylesheet.rs @@ -0,0 +1,174 @@ +use crate::html::assets::AssetsTrait; +use crate::html::{html, Markup, PreEscaped, Render}; +use crate::{join_pair, AutoDefault, Weight}; + +// Define el origen del recurso CSS y cómo se incluye en el documento. +// +// Los estilos pueden cargarse desde un archivo externo o estar embebidos directamente en una +// etiqueta ``. El parámetro `name` se usa como identificador interno del + /// recurso. + pub fn inline(name: impl Into, styles: impl Into) -> Self { + StyleSheet { + source: Source::Inline(name.into(), styles.into()), + ..Default::default() + } + } + + // StyleSheet BUILDER ************************************************************************** + + /// Asocia una versión al recurso (usada para control de la caché del navegador). + /// + /// Si `version` está vacío, no se añade ningún parámetro a la URL. + pub fn with_version(mut self, version: impl Into) -> Self { + self.version = version.into(); + self + } + + /// Modifica el peso del recurso. + /// + /// Los recursos se renderizan de menor a mayor peso. Por defecto es `0`, que respeta el orden + /// de creación. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // StyleSheet EXTRAS *************************************************************************** + + /// Especifica el medio donde se aplican los estilos. + /// + /// Según el argumento `media`: + /// + /// - `TargetMedia::Default` - Se aplica en todos los casos (medio por defecto). + /// - `TargetMedia::Print` - Se aplican cuando el documento se imprime. + /// - `TargetMedia::Screen` - Se aplican en pantallas. + /// - `TargetMedia::Speech` - Se aplican en dispositivos que convierten el texto a voz. + pub fn for_media(mut self, media: TargetMedia) -> Self { + self.media = media; + self + } +} + +impl AssetsTrait for StyleSheet { + // Para hojas de estilos externas es la ruta; para las embebidas, un identificador. + fn name(&self) -> &str { + match &self.source { + Source::From(path) => path, + Source::Inline(name, _) => name, + } + } + + fn weight(&self) -> Weight { + self.weight + } +} + +impl Render for StyleSheet { + fn render(&self) -> Markup { + match &self.source { + Source::From(path) => html! { + link + rel="stylesheet" + href=(join_pair!(path, "?v=", self.version.as_str())) + media=[self.media.as_str_opt()]; + }, + Source::Inline(_, code) => html! { + style { (PreEscaped(code)) }; + }, + } + } +} diff --git a/src/html/context.rs b/src/html/context.rs new file mode 100644 index 00000000..23e2be2b --- /dev/null +++ b/src/html/context.rs @@ -0,0 +1,216 @@ +use crate::core::TypeInfo; +use crate::html::{html, Markup, Render}; +use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; +use crate::join; +use crate::locale::{LanguageIdentifier, DEFAULT_LANGID}; +use crate::service::HttpRequest; + +use std::collections::HashMap; +use std::error::Error; +use std::str::FromStr; + +use std::fmt; + +/// Errores de lectura o conversión de parámetros almacenados en el contexto. +#[derive(Debug)] +pub enum ErrorParam { + /// El parámetro solicitado no existe. + NotFound, + /// El valor del parámetro no pudo convertirse al tipo requerido. + ParseError(String), +} + +impl fmt::Display for ErrorParam { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ErrorParam::NotFound => write!(f, "Parameter not found"), + ErrorParam::ParseError(e) => write!(f, "Parse error: {e}"), + } + } +} + +impl Error for ErrorParam {} + +/// Representa el contexto asociado a un documento HTML. +/// +/// Esta estructura se crea internamente para recoger información relativa al documento asociado, +/// como la solicitud HTTP de origen, el idioma y los recursos *favicon* ([`Favicon`]), las hojas de +/// estilo [`StyleSheet`], los *scripts* [`JavaScript`], o parámetros de contexto definidos en +/// tiempo de ejecución. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// fn configure_context(mut ctx: Context) { +/// // Establece el idioma del documento a español. +/// ctx.set_langid(LangMatch::langid_or_default("es-ES")); +/// +/// // Asigna un favicon. +/// ctx.set_favicon(Some(Favicon::new().with_icon("/icons/favicon.ico"))); +/// +/// // Añade una hoja de estilo externa. +/// ctx.add_stylesheet(StyleSheet::from("/css/style.css")); +/// +/// // Añade un script JavaScript. +/// ctx.add_javascript(JavaScript::defer("/js/main.js")); +/// +/// // Añade un parámetro dinámico al contexto. +/// ctx.set_param("usuario_id", 42); +/// +/// // Recupera el parámetro y lo convierte a su tipo original. +/// let id: i32 = ctx.get_param("usuario_id").unwrap(); +/// assert_eq!(id, 42); +/// +/// // Genera un identificador único para un componente de tipo `Menu`. +/// struct Menu; +/// let unique_id = ctx.required_id::(None); +/// assert_eq!(unique_id, "menu-1"); // Si es el primero generado. +/// } +/// ``` +#[rustfmt::skip] +pub struct Context { + request : HttpRequest, // Solicitud HTTP de origen. + langid : &'static LanguageIdentifier, // Identificador del idioma. + favicon : Option, // Favicon, si se ha definido. + stylesheets: Assets, // Hojas de estilo CSS. + javascripts: Assets, // Scripts JavaScript. + params : HashMap, // Parámetros definidos en tiempo de ejecución. + id_counter : usize, // Contador para generar identificadores únicos. +} + +impl Context { + // Crea un nuevo contexto asociado a una solicitud HTTP. + // + // El contexto inicializa el idioma por defecto, sin favicon ni recursos cargados. + #[rustfmt::skip] + pub(crate) fn new(request: HttpRequest) -> Self { + Context { + request, + langid : &DEFAULT_LANGID, + favicon : None, + stylesheets: Assets::::new(), + javascripts: Assets::::new(), + params : HashMap::::new(), + id_counter : 0, + } + } + + /// Modifica el identificador de idioma del documento. + pub fn set_langid(&mut self, langid: &'static LanguageIdentifier) -> &mut Self { + self.langid = langid; + self + } + + /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. + pub fn set_favicon(&mut self, favicon: Option) -> &mut Self { + self.favicon = favicon; + self + } + + /// Define el *favicon* solo si no se ha establecido previamente. + pub fn set_favicon_if_none(&mut self, favicon: Favicon) -> &mut Self { + if self.favicon.is_none() { + self.favicon = Some(favicon); + } + self + } + + /// Añade una hoja de estilos CSS al documento. + pub fn add_stylesheet(&mut self, css: StyleSheet) -> &mut Self { + self.stylesheets.add(css); + self + } + + /// Elimina una hoja de estilos por su ruta o identificador. + pub fn remove_stylesheet(&mut self, name: impl AsRef) -> &mut Self { + self.stylesheets.remove(name); + self + } + + /// Añade un *script* JavaScript al documento. + pub fn add_javascript(&mut self, js: JavaScript) -> &mut Self { + self.javascripts.add(js); + self + } + + /// Elimina un *script* por su ruta o identificador. + pub fn remove_javascript(&mut self, name: impl AsRef) -> &mut Self { + self.javascripts.remove(name); + self + } + + /// Añade o modifica un parámetro del contexto almacenando el valor como [`String`]. + pub fn set_param(&mut self, key: impl AsRef, value: T) -> &mut Self { + self.params + .insert(key.as_ref().to_string(), value.to_string()); + self + } + + // Context GETTERS ***************************************************************************** + + /// Devuelve la solicitud HTTP asociada al documento. + pub fn request(&self) -> &HttpRequest { + &self.request + } + + /// Devuelve el identificador del idioma asociado al documento. + pub fn langid(&self) -> &LanguageIdentifier { + self.langid + } + + /// Recupera un parámetro del contexto convertido al tipo especificado. + /// + /// Devuelve un error si el parámetro no existe ([`ErrorParam::NotFound`]) o la conversión falla + /// ([`ErrorParam::ParseError`]). + pub fn get_param(&self, key: impl AsRef) -> Result { + self.params + .get(key.as_ref()) + .ok_or(ErrorParam::NotFound) + .and_then(|v| T::from_str(v).map_err(|_| ErrorParam::ParseError(v.clone()))) + } + + // Context EXTRAS ****************************************************************************** + + /// Elimina un parámetro del contexto. Devuelve `true` si existía y se eliminó. + pub fn remove_param(&mut self, key: impl AsRef) -> bool { + self.params.remove(key.as_ref()).is_some() + } + + /// Genera un identificador único si no se proporciona uno explícito. + /// + /// Si no se proporciona un `id`, se genera un identificador único en la forma `-` + /// donde `` es el nombre corto del tipo en minúsculas (sin espacios) y `` es un + /// contador interno incremental. + pub fn required_id(&mut self, id: Option) -> String { + if let Some(id) = id { + id + } else { + let prefix = TypeInfo::ShortName + .of::() + .trim() + .replace(' ', "_") + .to_lowercase(); + let prefix = if prefix.is_empty() { + "prefix".to_owned() + } else { + prefix + }; + self.id_counter += 1; + join!(prefix, "-", self.id_counter.to_string()) + } + } +} + +impl Render for Context { + fn render(&self) -> Markup { + html! { + @if let Some(favicon) = &self.favicon { + (favicon) + } + (self.stylesheets) + (self.javascripts) + } + } +} diff --git a/src/html/maud.rs b/src/html/maud.rs index 39cb2c20..9bf179ec 100644 --- a/src/html/maud.rs +++ b/src/html/maud.rs @@ -18,7 +18,7 @@ pub use pagetop_macros::html; mod escape; -/// An adapter that escapes HTML special characters. +/// Adaptador que escapa los caracteres especiales de HTML. /// /// The following characters are escaped: /// @@ -57,7 +57,7 @@ impl fmt::Write for Escaper<'_> { } } -/// Represents a type that can be rendered as HTML. +/// Representa un tipo que puede renderizarse como HTML. /// /// To implement this for your own type, override either the `.render()` /// or `.render_to()` methods; since each is defined in terms of the @@ -190,7 +190,7 @@ impl_render_with_itoa! { u8 u16 u32 u64 u128 usize } -/// Renders a value using its [`Display`] impl. +/// Renderiza un valor usando su implementación de [`Display`]. /// /// # Example /// @@ -219,7 +219,7 @@ pub fn display(value: impl Display) -> impl Render { DisplayWrapper(value) } -/// A wrapper that renders the inner value without escaping. +/// Contenedor que renderiza el valor interno sin escapar. #[derive(Debug, Clone, Copy)] pub struct PreEscaped(pub T); @@ -229,7 +229,7 @@ impl> Render for PreEscaped { } } -/// A block of markup is a string that does not need to be escaped. +/// Un bloque de marcado es una cadena que no necesita ser escapada. /// /// The `html!` macro expands to an expression of this type. pub type Markup = PreEscaped; @@ -259,7 +259,7 @@ impl Default for PreEscaped { } } -/// The literal string ``. +/// La cadena literal ``. /// /// # Example /// diff --git a/src/lib.rs b/src/lib.rs index 248ba6d4..f3126212 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,12 @@ impl Deref for StaticResources { } } +/// Representa el peso lógico de una instancia en una colección ordenada por pesos. +/// +/// Las instancias con pesos **más bajos**, incluyendo valores negativos (`-128..127`), se situarán +/// antes en la ordenación. +pub type Weight = i8; + // API ********************************************************************************************* // Funciones y macros útiles. diff --git a/src/prelude.rs b/src/prelude.rs index 6506aaf9..3a9f4f67 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::{builder_fn, html, main, test}; -pub use crate::{AutoDefault, StaticResources}; +pub use crate::{AutoDefault, StaticResources, Weight}; // MACROS. @@ -16,6 +16,8 @@ pub use crate::include_config; pub use crate::include_locales; // crate::service pub use crate::{include_files, include_files_service}; +// crate::core::action +//pub use crate::actions; // API. @@ -35,6 +37,7 @@ pub use crate::service; pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; +//pub use crate::core::action::*; pub use crate::core::extension::*; pub use crate::app::Application; diff --git a/src/service.rs b/src/service.rs index 9e506fae..47e84224 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ pub use actix_web::dev::ServiceFactory as Factory; pub use actix_web::dev::ServiceRequest as Request; pub use actix_web::dev::ServiceResponse as Response; pub use actix_web::{http, rt, web}; -pub use actix_web::{App, Error, HttpServer}; +pub use actix_web::{App, Error, HttpRequest, HttpServer}; #[doc(hidden)] pub use actix_web::test; diff --git a/tests/html.rs b/tests/html.rs new file mode 100644 index 00000000..315f74ad --- /dev/null +++ b/tests/html.rs @@ -0,0 +1,17 @@ +use pagetop::prelude::*; + +#[pagetop::test] +async fn prepare_markup_is_empty() { + let _app = service::test::init_service(Application::new().test()).await; + + assert!(PrepareMarkup::None.is_empty()); + + assert!(PrepareMarkup::Text(String::from("")).is_empty()); + assert!(!PrepareMarkup::Text(String::from("x")).is_empty()); + + assert!(PrepareMarkup::Escaped(String::new()).is_empty()); + assert!(!PrepareMarkup::Escaped("a".into()).is_empty()); + + assert!(PrepareMarkup::With(html! {}).is_empty()); + assert!(!PrepareMarkup::With(html! { span { "!" } }).is_empty()); +} From 50b704fb6db8ace6fbb89f623b78968ec5845b95 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 20 Jul 2025 14:28:09 +0200 Subject: [PATCH 023/224] =?UTF-8?q?=F0=9F=93=9D=20Retoques=20en=20la=20doc?= =?UTF-8?q?umentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core.rs | 2 +- src/core/extension.rs | 2 +- src/core/extension/definition.rs | 4 ++-- src/lib.rs | 1 - src/locale.rs | 30 +++++++++++++++--------------- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/core.rs b/src/core.rs index 5aebd1fa..4541fae6 100644 --- a/src/core.rs +++ b/src/core.rs @@ -201,5 +201,5 @@ pub trait AnyCast: AnyInfo { /// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`]. impl AnyCast for T {} -// Infraestructura para ampliar funcionalidades mediante extensiones. +// API para añadir nuevas funcionalidades usando extensiones. pub mod extension; diff --git a/src/core/extension.rs b/src/core/extension.rs index 9eb3bd24..c493cf5c 100644 --- a/src/core/extension.rs +++ b/src/core/extension.rs @@ -1,4 +1,4 @@ -//! Infraestructura para ampliar funcionalidades mediante extensiones. +//! API para añadir nuevas funcionalidades usando extensiones. //! //! Cada funcionalidad adicional que quiera incorporarse a una aplicación `PageTop` se debe modelar //! como una **extensión**. Todas comparten la misma interfaz declarada en [`ExtensionTrait`]. diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 761fceea..625beba0 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -10,8 +10,8 @@ pub type ExtensionRef = &'static dyn ExtensionTrait; /// Interfaz común que debe implementar cualquier extensión de `PageTop`. /// -/// Este *trait* es fácil de implementar, basta con declarar la estructura de la extensión y -/// sobreescribir los métodos que sea necesario. +/// Este *trait* es fácil de implementar, basta con declarar una estructura de tamaño cero para la +/// extensión y sobreescribir los métodos que sea necesario. /// /// ```rust /// use pagetop::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index f3126212..edc791cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ //! ``` #![cfg_attr(docsrs, feature(doc_cfg))] - #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] diff --git a/src/locale.rs b/src/locale.rs index ab1f5efc..2dcf27e7 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -107,8 +107,8 @@ use std::sync::LazyLock; use std::fmt; -// Asocia cada código de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la clave -// en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. +// Asocia cada identificador de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la +// clave en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. static LANGUAGES: LazyLock> = LazyLock::new(|| { hm![ "en" => ( langid!("en-US"), "english" ), @@ -121,20 +121,20 @@ static LANGUAGES: LazyLock> = LazyLock // Identificador del idioma de **respaldo** (predefinido a `en-US`). // -// Se usa cuando el valor del código de idioma en las traducciones no corresponde con ningún idioma -// soportado por la aplicación. +// Se usa cuando el valor del identificador de idioma en las traducciones no corresponde con ningún +// idioma soportado por la aplicación. static FALLBACK_LANGID: LazyLock = LazyLock::new(|| langid!("en-US")); // Identificador del idioma **por defecto** para la aplicación. // -// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el código de -// idioma configurado no es válido o no está disponible entonces resuelve como [`FALLBACK_LANGID`]. +// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador +// de idioma no es válido o no está disponible entonces resuelve como [`FALLBACK_LANGID`]. pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = LazyLock::new(|| LangMatch::langid_or_fallback(&global::SETTINGS.app.language)); /// Operaciones con los idiomas soportados por `PageTop`. /// -/// Utiliza [`LangMatch`] para transformar un código de idioma en un [`LanguageIdentifier`] +/// Utiliza [`LangMatch`] para transformar un identificador de idioma en un [`LanguageIdentifier`] /// soportado por `PageTop`. /// /// # Ejemplos @@ -156,10 +156,10 @@ pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = /// /// // Idioma no soportado. /// let lang = LangMatch::resolve("ja-JP"); -/// assert_eq!(lang, LangMatch::Unsupported("ja-JP".to_string())); +/// assert_eq!(lang, LangMatch::Unsupported(String::from("ja-JP"))); /// ``` /// -/// Las siguientes instrucciones devuelven siempre un [`LanguageIdentifier`] válido, ya sea porque +/// Las siguientes líneas devuelven siempre un [`LanguageIdentifier`] válido, ya sea porque /// resuelven un idioma soportado o porque aplican el idioma por defecto o de respaldo: /// /// ```rust @@ -177,13 +177,13 @@ pub(crate) static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = /// ``` #[derive(Clone, Debug, Eq, PartialEq)] pub enum LangMatch { - /// Cuando el código del idioma es una cadena vacía. + /// Cuando el identificador del idioma es una cadena vacía. Unspecified, /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por `PageTop` que - /// coincide exactamente con el código del idioma (p.ej. "es-ES"), o con el código del idioma - /// base (p.ej. "es"). + /// coincide exactamente con el identificador del idioma (p.ej. "es-ES"), o con el identificador + /// del idioma base (p.ej. "es"). Found(&'static LanguageIdentifier), - /// Si el código del idioma no está entre los soportados por `PageTop`. + /// Si el identificador del idioma no está entre los soportados por `PageTop`. Unsupported(String), } @@ -210,7 +210,7 @@ impl LangMatch { } // En otro caso indica que el idioma no está soportado. - Self::Unsupported(language.to_string()) + Self::Unsupported(String::from(language)) } /// Devuelve el idioma de la variante de la instancia, o el idioma por defecto si no está @@ -319,7 +319,7 @@ enum L10nOp { /// También para traducciones a idiomas concretos. /// /// ```rust,ignore -/// // Traducción con clave, conjunto de traducciones y código de idioma a usar. +/// // Traducción con clave, conjunto de traducciones e identificador de idioma a usar. /// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_default("it")); /// ``` #[derive(AutoDefault)] From 861392430a5c4bb073e8de968e600c0ffa5a318a Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 20 Jul 2025 23:51:15 +0200 Subject: [PATCH 024/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20a=20?= =?UTF-8?q?temas=20en=20la=20API=20de=20extensiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incluye una opción de configuración para definir el tema por defecto. - Añade un tema básico predeterminado. --- config/default.toml | 2 ++ src/base.rs | 3 +++ src/base/theme.rs | 4 ++++ src/base/theme/basic.rs | 14 ++++++++++++++ src/core.rs | 5 ++++- src/core/extension/all.rs | 19 +++++++++++++++++++ src/core/extension/definition.rs | 26 ++++++++++++++++++++++++++ src/core/theme.rs | 6 ++++++ src/core/theme/all.rs | 31 +++++++++++++++++++++++++++++++ src/core/theme/definition.rs | 30 ++++++++++++++++++++++++++++++ src/global.rs | 3 +++ src/lib.rs | 4 +++- src/prelude.rs | 6 +++--- 13 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 config/default.toml create mode 100644 src/base.rs create mode 100644 src/base/theme.rs create mode 100644 src/base/theme/basic.rs create mode 100644 src/core/theme.rs create mode 100644 src/core/theme/all.rs create mode 100644 src/core/theme/definition.rs diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 00000000..3e254586 --- /dev/null +++ b/config/default.toml @@ -0,0 +1,2 @@ +[log] +tracing = "Info,pagetop=Debug" diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 00000000..c50cc9e4 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,3 @@ +//! Reúne temas listos para usar. + +pub mod theme; diff --git a/src/base/theme.rs b/src/base/theme.rs new file mode 100644 index 00000000..ea9eeb66 --- /dev/null +++ b/src/base/theme.rs @@ -0,0 +1,4 @@ +//! Temas básicos soportados por `PageTop`. + +mod basic; +pub use basic::Basic; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs new file mode 100644 index 00000000..0fc533c5 --- /dev/null +++ b/src/base/theme/basic.rs @@ -0,0 +1,14 @@ +//! Es el tema básico que incluye `PageTop` por defecto. + +use crate::prelude::*; + +/// Tema básico por defecto. +pub struct Basic; + +impl ExtensionTrait for Basic { + fn theme(&self) -> Option { + Some(&Self) + } +} + +impl ThemeTrait for Basic {} diff --git a/src/core.rs b/src/core.rs index 4541fae6..9f3d7b55 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -//! Tipos y funciones esenciales para crear extensiones. +//! Tipos y funciones esenciales para crear extensiones y temas. use std::any::Any; @@ -203,3 +203,6 @@ impl AnyCast for T {} // API para añadir nuevas funcionalidades usando extensiones. pub mod extension; + +// API para añadir y gestionar nuevos temas. +pub mod theme; diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index d5bebdad..63a178c9 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -1,4 +1,5 @@ use crate::core::extension::ExtensionRef; +use crate::core::theme::all::THEMES; use crate::{service, trace}; use std::sync::{LazyLock, RwLock}; @@ -17,6 +18,9 @@ pub fn register_extensions(root_extension: Option) { // Prepara la lista de extensiones habilitadas. let mut enabled_list: Vec = Vec::new(); + // Primero añade el tema básico a la lista de extensiones habilitadas. + add_to_enabled(&mut enabled_list, &crate::base::theme::Basic); + // Si se proporciona una extensión raíz inicial, se añade a la lista de extensiones habilitadas. if let Some(extension) = root_extension { add_to_enabled(&mut enabled_list, extension); @@ -53,6 +57,21 @@ fn add_to_enabled(list: &mut Vec, extension: ExtensionRef) { // Añade la propia extensión a la lista. list.push(extension); + + // Comprueba si la extensión tiene un tema asociado que deba registrarse. + if let Some(theme) = extension.theme() { + let mut registered_themes = THEMES.write().unwrap(); + // Asegura que el tema no esté ya registrado para evitar duplicados. + if !registered_themes + .iter() + .any(|t| t.type_id() == theme.type_id()) + { + registered_themes.push(theme); + trace::debug!("Enabling \"{}\" theme", theme.short_name()); + } + } else { + trace::debug!("Enabling \"{}\" extension", extension.short_name()); + } } } diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 625beba0..6b963a24 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -1,3 +1,4 @@ +use crate::core::theme::ThemeRef; use crate::core::AnyInfo; use crate::locale::L10n; use crate::service; @@ -36,6 +37,31 @@ pub trait ExtensionTrait: AnyInfo + Send + Sync { L10n::default() } + /// Los temas son extensiones que implementan [`ExtensionTrait`] y también + /// [`ThemeTrait`](crate::core::theme::ThemeTrait). + /// + /// Si la extensión no es un tema, este método devuelve `None` por defecto. + /// + /// En caso contrario, este método debe implementarse para devolver una referencia de sí mismo + /// como tema. Por ejemplo: + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// pub struct MyTheme; + /// + /// impl ExtensionTrait for MyTheme { + /// fn theme(&self) -> Option { + /// Some(&Self) + /// } + /// } + /// + /// impl ThemeTrait for MyTheme {} + /// ``` + fn theme(&self) -> Option { + None + } + /// Otras extensiones que deben habilitarse **antes** de esta. /// /// `PageTop` las resolverá automáticamente respetando el orden durante el arranque de la diff --git a/src/core/theme.rs b/src/core/theme.rs new file mode 100644 index 00000000..d741cf80 --- /dev/null +++ b/src/core/theme.rs @@ -0,0 +1,6 @@ +//! API para añadir y gestionar nuevos temas. + +mod definition; +pub use definition::{ThemeRef, ThemeTrait}; + +pub(crate) mod all; diff --git a/src/core/theme/all.rs b/src/core/theme/all.rs new file mode 100644 index 00000000..3516107e --- /dev/null +++ b/src/core/theme/all.rs @@ -0,0 +1,31 @@ +use crate::core::theme::ThemeRef; +use crate::global; + +use std::sync::{LazyLock, RwLock}; + +// TEMAS ******************************************************************************************* + +pub static THEMES: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); + +// TEMA PREDETERMINADO ***************************************************************************** + +pub static DEFAULT_THEME: LazyLock = + LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) { + Some(theme) => theme, + None => &crate::base::theme::Basic, + }); + +// TEMA POR NOMBRE ********************************************************************************* + +pub fn theme_by_short_name(short_name: impl AsRef) -> Option { + let short_name = short_name.as_ref().to_lowercase(); + match THEMES + .read() + .unwrap() + .iter() + .find(|t| t.short_name().to_lowercase() == short_name) + { + Some(theme) => Some(*theme), + _ => None, + } +} diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs new file mode 100644 index 00000000..13fe2824 --- /dev/null +++ b/src/core/theme/definition.rs @@ -0,0 +1,30 @@ +use crate::core::extension::ExtensionTrait; + +/// Representa una referencia a un tema. +/// +/// Los temas son también extensiones. Por tanto se deben definir igual, es decir, como instancias +/// estáticas globales que implementan [`ThemeTrait`], pero también [`ExtensionTrait`]. +pub type ThemeRef = &'static dyn ThemeTrait; + +/// Interfaz común que debe implementar cualquier tema de `PageTop`. +/// +/// Un tema implementará [`ThemeTrait`] y los métodos que sean necesarios de [`ExtensionTrait`], +/// aunque el único obligatorio es [`theme()`](crate::core::extension::ExtensionTrait#method.theme). +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// pub struct MyTheme; +/// +/// impl ExtensionTrait for MyTheme { +/// fn name(&self) -> L10n { L10n::n("My theme") } +/// fn description(&self) -> L10n { L10n::n("Un tema personal") } +/// +/// fn theme(&self) -> Option { +/// Some(&Self) +/// } +/// } +/// +/// impl ThemeTrait for MyTheme {} +/// ``` +pub trait ThemeTrait: ExtensionTrait + Send + Sync {} diff --git a/src/global.rs b/src/global.rs index c3332d39..e8f50658 100644 --- a/src/global.rs +++ b/src/global.rs @@ -8,6 +8,7 @@ include_config!(SETTINGS: Settings => [ // [app] "app.name" => "Sample", "app.description" => "Developed with the amazing PageTop framework.", + "app.theme" => "Basic", "app.language" => "en-US", "app.startup_banner" => "Slant", @@ -40,6 +41,8 @@ pub struct App { pub name: String, /// Breve descripción de la aplicación. pub description: String, + /// Tema predeterminado. + pub theme: String, /// Idioma predeterminado (localización). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o diff --git a/src/lib.rs b/src/lib.rs index edc791cd..264fdbc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,10 +87,12 @@ pub mod html; pub mod locale; // Soporte a fechas y horas. pub mod datetime; -// Tipos y funciones esenciales para crear extensiones. +// Tipos y funciones esenciales para crear extensiones y temas. pub mod core; // Gestión del servidor y servicios web. pub mod service; +// Reúne temas listos para usar. +pub mod base; // Prepara y ejecuta la aplicación. pub mod app; diff --git a/src/prelude.rs b/src/prelude.rs index 3a9f4f67..7244b912 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,8 +16,6 @@ pub use crate::include_config; pub use crate::include_locales; // crate::service pub use crate::{include_files, include_files_service}; -// crate::core::action -//pub use crate::actions; // API. @@ -37,7 +35,9 @@ pub use crate::service; pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; -//pub use crate::core::action::*; pub use crate::core::extension::*; +pub use crate::core::theme::*; + +pub use crate::base::theme; pub use crate::app::Application; From c89d4bb5fc609be3f0d040a68b2df1727469e0df Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 21 Jul 2025 08:58:09 +0200 Subject: [PATCH 025/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20para?= =?UTF-8?q?=20inyectar=20acciones=20en=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 3 + src/core.rs | 5 +- src/core/action.rs | 66 ++++++++++++++++++++ src/core/action/all.rs | 66 ++++++++++++++++++++ src/core/action/definition.rs | 102 +++++++++++++++++++++++++++++++ src/core/action/list.rs | 43 +++++++++++++ src/core/extension/all.rs | 11 ++++ src/core/extension/definition.rs | 12 +++- src/lib.rs | 7 ++- src/prelude.rs | 5 +- 10 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 src/core/action.rs create mode 100644 src/core/action/all.rs create mode 100644 src/core/action/definition.rs create mode 100644 src/core/action/list.rs diff --git a/src/app.rs b/src/app.rs index 0bcece8e..b6b5a716 100644 --- a/src/app.rs +++ b/src/app.rs @@ -56,6 +56,9 @@ impl Application { // Registra las extensiones de la aplicación. extension::all::register_extensions(root_extension); + // Registra las acciones de las extensiones. + extension::all::register_actions(); + // Inicializa las extensiones. extension::all::initialize_extensions(); diff --git a/src/core.rs b/src/core.rs index 9f3d7b55..4b857fc4 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -//! Tipos y funciones esenciales para crear extensiones y temas. +//! Tipos y funciones esenciales para crear acciones, extensiones y temas. use std::any::Any; @@ -201,6 +201,9 @@ pub trait AnyCast: AnyInfo { /// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`]. impl AnyCast for T {} +// API para definir acciones que alteran el comportamiento predeterminado del código. +pub mod action; + // API para añadir nuevas funcionalidades usando extensiones. pub mod extension; diff --git a/src/core/action.rs b/src/core/action.rs new file mode 100644 index 00000000..8503ae90 --- /dev/null +++ b/src/core/action.rs @@ -0,0 +1,66 @@ +//! API para definir acciones que inyectan código en el flujo de la aplicación. +//! +//! Permite crear acciones en las librerías para que otros *crates* puedan inyectar código usando +//! funciones *ad hoc* que modifican el comportamiento predefinido en puntos concretos del flujo de +//! ejecución de la aplicación. + +mod definition; +pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait}; + +mod list; +use list::ActionsList; + +mod all; +pub(crate) use all::add_action; +pub use all::dispatch_actions; + +/// Crea una lista de acciones para facilitar la implementación del método +/// [`actions`](crate::core::extension::ExtensionTrait#method.actions). +/// +/// Esta macro crea vectores de [`ActionBox`], el tipo dinámico que encapsula cualquier acción que +/// implemente [`ActionTrait`]. Evita escribir repetidamente `Box::new(...)` para cada acción +/// inyectada, manteniendo el código más limpio. +/// +/// # Ejemplos +/// +/// Puede llamarse sin argumentos para crear un vector vacío: +/// +/// ```rust,ignore +/// let my_actions = inject_actions![]; +/// ``` +/// +/// O con una lista de acciones concretas: +/// +/// ```rust,ignore +/// let my_actions = inject_actions![ +/// MyFirstAction::new(), +/// MySecondAction::new().with_weight(10), +/// ]; +/// ``` +/// +/// Internamente, expande a un `vec![Box::new(...), ...]`. +/// +/// # Ejemplo típico en una extensión +/// +/// ```rust,ignore +/// impl ExtensionTrait for MyExtension { +/// fn actions(&self) -> Vec { +/// inject_actions![ +/// CustomizeLoginAction::new(), +/// ModifyHeaderAction::new().with_weight(-5), +/// ] +/// } +/// } +/// ``` +/// +/// Así, `PageTop` podrá registrar todas estas acciones durante la inicialización de la extensión y +/// posteriormente despacharlas según corresponda. +#[macro_export] +macro_rules! inject_actions { + () => { + Vec::::new() + }; + ( $($action:expr),+ $(,)? ) => {{ + vec![$(Box::new($action),)+] + }}; +} diff --git a/src/core/action/all.rs b/src/core/action/all.rs new file mode 100644 index 00000000..4dcb68df --- /dev/null +++ b/src/core/action/all.rs @@ -0,0 +1,66 @@ +use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList}; + +use std::collections::HashMap; +use std::sync::{LazyLock, RwLock}; + +// ACCIONES **************************************************************************************** + +static ACTIONS: LazyLock>> = + LazyLock::new(|| RwLock::new(HashMap::new())); + +// AÑADIR ACCIONES ********************************************************************************* + +// Registra una nueva acción en el sistema. +// +// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la lista existente. Si no, +// se crea una nueva lista. +// +// # Uso típico +// +// Las extensiones llamarán a esta función durante su inicialización para instalar acciones +// personalizadas que modifiquen el comportamiento del núcleo o de otros componentes. +// +// ```rust,ignore +// add_action(Box::new(MyCustomAction::new())); +// ``` +pub fn add_action(action: ActionBox) { + let key = action.key(); + let mut actions = ACTIONS.write().unwrap(); + if let Some(list) = actions.get_mut(&key) { + list.add(action); + } else { + let mut list = ActionsList::new(); + list.add(action); + actions.insert(key, list); + } +} + +// DESPLEGAR ACCIONES ****************************************************************************** + +/// Despacha las funciones asociadas a un [`ActionKey`] y las ejecuta. +/// +/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una +/// acción específica. +/// +/// # Parámetros genéricos +/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionTrait`]. +/// - `F`: Función o cierre que recibe cada acción y devuelve un valor de tipo `B`. +/// +/// # Ejemplo de uso +/// ```rust,ignore +/// dispatch_actions::(&some_key, |action| { +/// action.do_something(); +/// }); +/// ``` +/// +/// Esto permite a PageTop o a otros módulos aplicar lógica específica a las acciones de un contexto +/// determinado, manteniendo la flexibilidad del sistema. +pub fn dispatch_actions(key: &ActionKey, f: F) +where + A: ActionTrait, + F: FnMut(&A) -> B, +{ + if let Some(list) = ACTIONS.read().unwrap().get(key) { + list.iter_map(f); + } +} diff --git a/src/core/action/definition.rs b/src/core/action/definition.rs new file mode 100644 index 00000000..d662b2b6 --- /dev/null +++ b/src/core/action/definition.rs @@ -0,0 +1,102 @@ +use crate::core::AnyInfo; +use crate::{UniqueId, Weight}; + +/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionTrait`]. +pub type ActionBox = Box; + +/// Identifica una acción con una clave que define las condiciones de selección de las funciones +/// asociadas a esa acción. +/// +/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y +/// ejecutar en un punto concreto del flujo de ejecución. +/// +/// # Campos +/// +/// - `action_type_id`: Tipo de la acción. +/// - `theme_type_id`: Opcional, filtra las funciones para un tema dado. +/// - `referer_type_id`: Opcional, filtra las funciones para un tipo dado (p.ej. para un tipo de +/// componente). +/// - `referer_id`: Opcional, filtra las funciones por el identificador de una instancia (p.ej. para +/// un formulario concreto). +#[derive(Eq, PartialEq, Hash)] +pub struct ActionKey { + action_type_id: UniqueId, + theme_type_id: Option, + referer_type_id: Option, + referer_id: Option, +} + +impl ActionKey { + /// Crea una nueva clave para un tipo de acción. + /// + /// Esta clave permite seleccionar las funciones a ejecutar para ese tipo de acción con filtros + /// opcionales por tema, un tipo de referencia, o una instancia concreta según su identificador. + pub fn new( + action_type_id: UniqueId, + theme_type_id: Option, + referer_type_id: Option, + referer_id: Option, + ) -> Self { + ActionKey { + action_type_id, + theme_type_id, + referer_type_id, + referer_id, + } + } +} + +/// Trait base que permite obtener la clave ([`ActionKey`]) asociada a una acción. +/// +/// Implementado automáticamente para cualquier tipo que cumpla [`ActionTrait`]. +pub trait ActionBase { + fn key(&self) -> ActionKey; +} + +/// Interfaz común que deben implementar las acciones del código que pueden ser modificadas. +/// +/// Este trait combina: +/// - [`AnyInfo`] para identificación única del tipo en tiempo de ejecución. +/// - `Send + Sync` para permitir uso concurrente seguro. +/// +/// # Métodos personalizables +/// - `theme_type_id()`: Asocia la acción a un tipo concreto de tema (si aplica). +/// - `referer_type_id()`: Asocia la acción a un tipo de objeto referente (si aplica). +/// - `referer_id()`: Asocia la acción a un identificador concreto. +/// - `weight()`: Controla el orden de aplicación de acciones; valores más bajos se ejecutan antes. +pub trait ActionTrait: ActionBase + AnyInfo + Send + Sync { + /// Especifica el tipo de tema asociado. Por defecto `None`. + fn theme_type_id(&self) -> Option { + None + } + + /// Especifica el tipo del objeto referente. Por defecto `None`. + fn referer_type_id(&self) -> Option { + None + } + + /// Especifica un identificador único del objeto referente. Por defecto `None`. + fn referer_id(&self) -> Option { + None + } + + /// Define el peso lógico de la acción para determinar el orden de aplicación. + /// + /// Acciones con pesos más bajos se aplicarán antes. Se pueden usar valores negativos. Por + /// defecto es `0`. + fn weight(&self) -> Weight { + 0 + } +} + +// Implementación automática que construye la clave `ActionKey` a partir de los métodos definidos. +impl ActionBase for A { + fn key(&self) -> ActionKey { + ActionKey { + action_type_id: self.type_id(), + theme_type_id: self.theme_type_id(), + referer_type_id: self.referer_type_id(), + referer_id: self.referer_id(), + } + } +} diff --git a/src/core/action/list.rs b/src/core/action/list.rs new file mode 100644 index 00000000..ec8149be --- /dev/null +++ b/src/core/action/list.rs @@ -0,0 +1,43 @@ +use crate::core::action::{ActionBox, ActionTrait}; +use crate::core::AnyCast; +use crate::trace; +use crate::AutoDefault; + +use std::sync::RwLock; + +#[derive(AutoDefault)] +pub struct ActionsList(RwLock>); + +impl ActionsList { + pub fn new() -> Self { + ActionsList::default() + } + + pub fn add(&mut self, action: ActionBox) { + let mut list = self.0.write().unwrap(); + list.push(action); + list.sort_by_key(|a| a.weight()); + } + + pub fn iter_map(&self, mut f: F) + where + Self: Sized, + A: ActionTrait, + F: FnMut(&A) -> B, + { + let _: Vec<_> = self + .0 + .read() + .unwrap() + .iter() + .rev() + .map(|a| { + if let Some(action) = (**a).downcast_ref::() { + f(action); + } else { + trace::error!("Failed to downcast action of type {}", (**a).type_name()); + } + }) + .collect(); + } +} diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index 63a178c9..9012234f 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -1,3 +1,4 @@ +use crate::core::action::add_action; use crate::core::extension::ExtensionRef; use crate::core::theme::all::THEMES; use crate::{service, trace}; @@ -105,6 +106,16 @@ fn add_to_dropped(list: &mut Vec, extension: ExtensionRef) { } } +// REGISTRO DE LAS ACCIONES ************************************************************************ + +pub fn register_actions() { + for extension in ENABLED_EXTENSIONS.read().unwrap().iter() { + for a in extension.actions().into_iter() { + add_action(a); + } + } +} + // INICIALIZA LAS EXTENSIONES ********************************************************************** pub fn initialize_extensions() { diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 6b963a24..645afc80 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -1,7 +1,8 @@ +use crate::core::action::ActionBox; use crate::core::theme::ThemeRef; use crate::core::AnyInfo; use crate::locale::L10n; -use crate::service; +use crate::{inject_actions, service}; /// Representa una referencia a una extensión. /// @@ -70,6 +71,15 @@ pub trait ExtensionTrait: AnyInfo + Send + Sync { vec![] } + /// Devuelve la lista de acciones que la extensión va a registrar. + /// + /// Estas [acciones](crate::core::action) se despachan por orden de registro o por + /// [peso](crate::Weight), permitiendo personalizar el comportamiento de la aplicación en puntos + /// específicos. + fn actions(&self) -> Vec { + inject_actions![] + } + /// Inicializa la extensión durante la lógica de arranque de la aplicación. /// /// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de diff --git a/src/lib.rs b/src/lib.rs index 264fdbc4..4ab3c6e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,11 @@ impl Deref for StaticResources { } } +/// Identificador único de un tipo estático durante la ejecución de la aplicación. +/// +/// **Nota:** El valor es único sólo dentro del proceso actual y cambia en cada compilación. +pub type UniqueId = std::any::TypeId; + /// Representa el peso lógico de una instancia en una colección ordenada por pesos. /// /// Las instancias con pesos **más bajos**, incluyendo valores negativos (`-128..127`), se situarán @@ -87,7 +92,7 @@ pub mod html; pub mod locale; // Soporte a fechas y horas. pub mod datetime; -// Tipos y funciones esenciales para crear extensiones y temas. +// Tipos y funciones esenciales para crear acciones, extensiones y temas. pub mod core; // Gestión del servidor y servicios web. pub mod service; diff --git a/src/prelude.rs b/src/prelude.rs index 7244b912..22d9ea0e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,7 +4,7 @@ pub use crate::{builder_fn, html, main, test}; -pub use crate::{AutoDefault, StaticResources, Weight}; +pub use crate::{AutoDefault, StaticResources, UniqueId, Weight}; // MACROS. @@ -16,6 +16,8 @@ pub use crate::include_config; pub use crate::include_locales; // crate::service pub use crate::{include_files, include_files_service}; +// crate::core::action +pub use crate::inject_actions; // API. @@ -35,6 +37,7 @@ pub use crate::service; pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; +pub use crate::core::action::*; pub use crate::core::extension::*; pub use crate::core::theme::*; From 4f56d4441ff37417b9ba53718aaf0d8ad49ba2a5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 21 Jul 2025 20:52:45 +0200 Subject: [PATCH 026/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20tipos=20para?= =?UTF-8?q?=20renderizar=20atributos=20HTML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html.rs | 15 +++++ src/html/opt_classes.rs | 130 +++++++++++++++++++++++++++++++++++++ src/html/opt_id.rs | 57 ++++++++++++++++ src/html/opt_name.rs | 57 ++++++++++++++++ src/html/opt_string.rs | 56 ++++++++++++++++ src/html/opt_translated.rs | 65 +++++++++++++++++++ src/locale.rs | 15 ++++- 7 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 src/html/opt_classes.rs create mode 100644 src/html/opt_id.rs create mode 100644 src/html/opt_name.rs create mode 100644 src/html/opt_string.rs create mode 100644 src/html/opt_translated.rs diff --git a/src/html.rs b/src/html.rs index 14a89edd..4fc9b3e5 100644 --- a/src/html.rs +++ b/src/html.rs @@ -12,6 +12,21 @@ pub(crate) use assets::Assets; mod context; pub use context::{Context, ErrorParam}; +mod opt_id; +pub use opt_id::OptionId; + +mod opt_name; +pub use opt_name::OptionName; + +mod opt_string; +pub use opt_string::OptionString; + +mod opt_translated; +pub use opt_translated::OptionTranslated; + +mod opt_classes; +pub use opt_classes::{ClassesOp, OptionClasses}; + use crate::AutoDefault; /// Prepara contenido HTML para su conversión a [`Markup`]. diff --git a/src/html/opt_classes.rs b/src/html/opt_classes.rs new file mode 100644 index 00000000..bcec9372 --- /dev/null +++ b/src/html/opt_classes.rs @@ -0,0 +1,130 @@ +use crate::{builder_fn, AutoDefault}; + +/// Operaciones disponibles sobre la lista de clases en [`OptionClasses`]. +pub enum ClassesOp { + /// Añade al final (si no existe). + Add, + /// Añade al principio. + Prepend, + /// Elimina coincidencias. + Remove, + /// Sustituye una o varias por las nuevas (`Replace("old other")`). + Replace(String), + /// Alterna presencia/ausencia. + Toggle, + /// Sustituye toda la lista. + Set, +} + +/// Cadena de clases CSS normalizadas para el atributo `class` de HTML. +/// +/// Permite construir y modificar dinámicamente con [`ClassesOp`] una lista de clases CSS +/// normalizadas. +/// +/// ### Normalización +/// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS. +/// - No se permiten clases duplicadas. +/// - Las clases vacías se ignoran. +/// +/// # Ejemplo +/// ```rust +/// use pagetop::prelude::*; +/// +/// let classes = OptionClasses::new("btn btn-primary") +/// .with_value(ClassesOp::Add, "active") +/// .with_value(ClassesOp::Remove, "btn-primary"); +/// +/// assert_eq!(classes.get(), Some(String::from("btn active"))); +/// assert!(classes.contains("active")); +/// ``` +#[derive(AutoDefault, Clone, Debug)] +pub struct OptionClasses(Vec); + +impl OptionClasses { + pub fn new(classes: impl AsRef) -> Self { + OptionClasses::default().with_value(ClassesOp::Prepend, classes) + } + + // OptionClasses BUILDER *********************************************************************** + + #[builder_fn] + pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + let classes: &str = classes.as_ref(); + let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); + + if classes.is_empty() { + return self; + } + + match op { + ClassesOp::Add => { + self.add(&classes, self.0.len()); + } + ClassesOp::Prepend => { + self.add(&classes, 0); + } + ClassesOp::Remove => { + for class in classes { + self.0.retain(|c| c.ne(&class.to_string())); + } + } + ClassesOp::Replace(classes_to_replace) => { + let mut pos = self.0.len(); + let replace: Vec<&str> = classes_to_replace.split_ascii_whitespace().collect(); + for class in replace { + if let Some(replace_pos) = self.0.iter().position(|c| c.eq(class)) { + self.0.remove(replace_pos); + if pos > replace_pos { + pos = replace_pos; + } + } + } + self.add(&classes, pos); + } + ClassesOp::Toggle => { + for class in classes { + if !class.is_empty() { + if let Some(pos) = self.0.iter().position(|c| c.eq(class)) { + self.0.remove(pos); + } else { + self.0.push(class.to_string()); + } + } + } + } + ClassesOp::Set => { + self.0.clear(); + self.add(&classes, 0); + } + } + + self + } + + #[inline] + fn add(&mut self, classes: &[&str], mut pos: usize) { + for &class in classes { + if !class.is_empty() && !self.0.iter().any(|c| c == class) { + self.0.insert(pos, class.to_string()); + pos += 1; + } + } + } + + // OptionClasses GETTERS *********************************************************************** + + /// Devuele la cadena de clases, si existe. + pub fn get(&self) -> Option { + if self.0.is_empty() { + None + } else { + Some(self.0.join(" ")) + } + } + + /// Devuelve `true` si la clase está presente. + pub fn contains(&self, class: impl AsRef) -> bool { + let class = class.as_ref(); + self.0.iter().any(|c| c == class) + } +} diff --git a/src/html/opt_id.rs b/src/html/opt_id.rs new file mode 100644 index 00000000..6f166b95 --- /dev/null +++ b/src/html/opt_id.rs @@ -0,0 +1,57 @@ +use crate::{builder_fn, AutoDefault}; + +/// Identificador normalizado para el atributo `id` o similar de HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. +/// +/// # Normalización +/// - Se eliminan los espacios al principio y al final. +/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let id = OptionId::new("main section"); +/// assert_eq!(id.get(), Some(String::from("main_section"))); +/// +/// let empty = OptionId::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct OptionId(Option); + +impl OptionId { + /// Crea un nuevo [`OptionId`]. + /// + /// El valor se normaliza automáticamente. + pub fn new(value: impl AsRef) -> Self { + OptionId::default().with_value(value) + } + + // OptionId BUILDER **************************************************************************** + + /// Establece un identificador nuevo. + /// + /// El valor se normaliza automáticamente. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim().replace(' ', "_"); + self.0 = (!value.is_empty()).then_some(value); + self + } + + // OptionId GETTERS **************************************************************************** + + /// Devuelve el identificador, si existe. + pub fn get(&self) -> Option { + if let Some(value) = &self.0 { + if !value.is_empty() { + return Some(value.to_owned()); + } + } + None + } +} diff --git a/src/html/opt_name.rs b/src/html/opt_name.rs new file mode 100644 index 00000000..c4e2e2e0 --- /dev/null +++ b/src/html/opt_name.rs @@ -0,0 +1,57 @@ +use crate::{builder_fn, AutoDefault}; + +/// Nombre normalizado para el atributo `name` o similar de HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. +/// +/// # Normalización +/// - Se eliminan los espacios al principio y al final. +/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// ## Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let name = OptionName::new(" display name "); +/// assert_eq!(name.get(), Some(String::from("display_name"))); +/// +/// let empty = OptionName::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct OptionName(Option); + +impl OptionName { + /// Crea un nuevo [`OptionName`]. + /// + /// El valor se normaliza automáticamente. + pub fn new(value: impl AsRef) -> Self { + OptionName::default().with_value(value) + } + + // OptionName BUILDER ************************************************************************** + + /// Establece un nombre nuevo. + /// + /// El valor se normaliza automáticamente. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim().replace(' ', "_"); + self.0 = (!value.is_empty()).then_some(value); + self + } + + // OptionName GETTERS ************************************************************************** + + /// Devuelve el nombre, si existe. + pub fn get(&self) -> Option { + if let Some(value) = &self.0 { + if !value.is_empty() { + return Some(value.to_owned()); + } + } + None + } +} diff --git a/src/html/opt_string.rs b/src/html/opt_string.rs new file mode 100644 index 00000000..5379597e --- /dev/null +++ b/src/html/opt_string.rs @@ -0,0 +1,56 @@ +use crate::{builder_fn, AutoDefault}; + +/// Cadena normalizada para renderizar en atributos HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. +/// +/// # Normalización +/// - Se eliminan los espacios al principio y al final. +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let s = OptionString::new(" a new string "); +/// assert_eq!(s.get(), Some(String::from("a new string"))); +/// +/// let empty = OptionString::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct OptionString(Option); + +impl OptionString { + /// Crea un nuevo [`OptionString`]. + /// + /// El valor se normaliza automáticamente. + pub fn new(value: impl AsRef) -> Self { + OptionString::default().with_value(value) + } + + // OptionString BUILDER ************************************************************************ + + /// Establece una cadena nueva. + /// + /// El valor se normaliza automáticamente. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim().to_owned(); + self.0 = (!value.is_empty()).then_some(value); + self + } + + // OptionString GETTERS ************************************************************************ + + /// Devuelve la cadena, si existe. + pub fn get(&self) -> Option { + if let Some(value) = &self.0 { + if !value.is_empty() { + return Some(value.to_owned()); + } + } + None + } +} diff --git a/src/html/opt_translated.rs b/src/html/opt_translated.rs new file mode 100644 index 00000000..ba60a0f8 --- /dev/null +++ b/src/html/opt_translated.rs @@ -0,0 +1,65 @@ +use crate::html::Markup; +use crate::locale::{L10n, LanguageIdentifier}; +use crate::{builder_fn, AutoDefault}; + +/// Cadena para traducir al renderizar ([`locale`](crate::locale)). +/// +/// Encapsula un tipo [`L10n`] para manejar traducciones de forma segura. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// // Traducción por clave en las locales por defecto de PageTop. +/// let hello = OptionTranslated::new(L10n::l("test-hello-world")); +/// +/// // Español disponible. +/// assert_eq!( +/// hello.using(LangMatch::langid_or_default("es-ES")), +/// Some(String::from("¡Hola mundo!")) +/// ); +/// +/// // Japonés no disponible, traduce al idioma de respaldo ("en-US"). +/// assert_eq!( +/// hello.using(LangMatch::langid_or_fallback("ja-JP")), +/// Some(String::from("Hello world!")) +/// ); +/// +/// // Para incrustar en HTML escapado: +/// let markup = hello.escaped(LangMatch::langid_or_default("es-ES")); +/// assert_eq!(markup.into_string(), "¡Hola mundo!"); +/// ``` +#[derive(AutoDefault, Clone, Debug)] +pub struct OptionTranslated(L10n); + +impl OptionTranslated { + /// Crea una nueva instancia [`OptionTranslated`]. + pub fn new(value: L10n) -> Self { + OptionTranslated(value) + } + + // OptionTranslated BUILDER ******************************************************************** + + /// Establece una traducción nueva. + #[builder_fn] + pub fn with_value(mut self, value: L10n) -> Self { + self.0 = value; + self + } + + // OptionTranslated GETTERS ******************************************************************** + + /// Devuelve la traducción para `langid`, si existe. + pub fn using(&self, langid: &LanguageIdentifier) -> Option { + self.0.using(langid) + } + + /// Devuelve la traducción *escapada* como [`Markup`] para `langid`, si existe. + /// + /// Útil para incrustar el texto directamente en plantillas HTML sin riesgo de inyección de + /// contenido. + pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { + self.0.escaped(langid) + } +} diff --git a/src/locale.rs b/src/locale.rs index 2dcf27e7..9af83f58 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -284,7 +284,7 @@ include_locales!(LOCALES_PAGETOP); // * `None` - No se aplica ninguna localización. // * `Text` - Con una cadena literal que se devolverá tal cual. // * `Translate` - Con la clave a resolver en el `Locales` indicado. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Debug)] enum L10nOp { #[default] None, @@ -322,7 +322,7 @@ enum L10nOp { /// // Traducción con clave, conjunto de traducciones e identificador de idioma a usar. /// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(LangMatch::langid_or_default("it")); /// ``` -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone)] pub struct L10n { op: L10nOp, #[default(&LOCALES_PAGETOP)] @@ -411,6 +411,17 @@ impl L10n { } } +impl fmt::Debug for L10n { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("L10n") + .field("op", &self.op) + .field("args", &self.args) + // No se puede mostrar `locales`. Se representa con un texto fijo. + .field("locales", &"") + .finish() + } +} + impl fmt::Display for L10n { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let content = match &self.op { From 36ddbd7ecf4d8eec677de79da17142272a98aaa9 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 21 Jul 2025 21:17:40 +0200 Subject: [PATCH 027/224] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20A=C3=B1ade=20el?= =?UTF-8?q?=20tema=20para=20renderizar=20en=20contexto=20HTML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/context.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/html/context.rs b/src/html/context.rs index 23e2be2b..b3622501 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -1,3 +1,5 @@ +use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME}; +use crate::core::theme::ThemeRef; use crate::core::TypeInfo; use crate::html::{html, Markup, Render}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; @@ -34,9 +36,9 @@ impl Error for ErrorParam {} /// Representa el contexto asociado a un documento HTML. /// /// Esta estructura se crea internamente para recoger información relativa al documento asociado, -/// como la solicitud HTTP de origen, el idioma y los recursos *favicon* ([`Favicon`]), las hojas de -/// estilo [`StyleSheet`], los *scripts* [`JavaScript`], o parámetros de contexto definidos en -/// tiempo de ejecución. +/// como la solicitud HTTP de origen, el idioma, el tema para renderizar ([`ThemeRef`]), y los +/// recursos *favicon* ([`Favicon`]), las hojas de estilo ([`StyleSheet`]) y los *scripts* +/// ([`JavaScript`]). También admite parámetros de contexto definidos en tiempo de ejecución. /// /// # Ejemplo /// @@ -47,6 +49,9 @@ impl Error for ErrorParam {} /// // Establece el idioma del documento a español. /// ctx.set_langid(LangMatch::langid_or_default("es-ES")); /// +/// // Selecciona un tema (por su nombre corto). +/// ctx.set_theme("aliner"); +/// /// // Asigna un favicon. /// ctx.set_favicon(Some(Favicon::new().with_icon("/icons/favicon.ico"))); /// @@ -63,6 +68,10 @@ impl Error for ErrorParam {} /// let id: i32 = ctx.get_param("usuario_id").unwrap(); /// assert_eq!(id, 42); /// +/// // Recupera el tema seleccionado. +/// let active_theme = ctx.theme(); +/// assert_eq!(active_theme.short_name(), "aliner"); +/// /// // Genera un identificador único para un componente de tipo `Menu`. /// struct Menu; /// let unique_id = ctx.required_id::(None); @@ -73,6 +82,7 @@ impl Error for ErrorParam {} pub struct Context { request : HttpRequest, // Solicitud HTTP de origen. langid : &'static LanguageIdentifier, // Identificador del idioma. + theme : ThemeRef, // Referencia al tema para renderizar. favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. @@ -89,6 +99,7 @@ impl Context { Context { request, langid : &DEFAULT_LANGID, + theme : *DEFAULT_THEME, favicon : None, stylesheets: Assets::::new(), javascripts: Assets::::new(), @@ -103,6 +114,15 @@ impl Context { self } + /// Establece el tema que se usará para renderizar el documento. + /// + /// Localiza el tema por su [`short_name`](crate::core::AnyInfo::short_name), y si no aplica + /// ninguno entonces usará el tema por defecto. + pub fn set_theme(&mut self, short_name: impl AsRef) -> &mut Self { + self.theme = theme_by_short_name(short_name).unwrap_or(*DEFAULT_THEME); + self + } + /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. pub fn set_favicon(&mut self, favicon: Option) -> &mut Self { self.favicon = favicon; @@ -160,6 +180,11 @@ impl Context { self.langid } + /// Devuelve el tema que se usará para renderizar el documento. + pub fn theme(&self) -> ThemeRef { + self.theme + } + /// Recupera un parámetro del contexto convertido al tipo especificado. /// /// Devuelve un error si el parámetro no existe ([`ErrorParam::NotFound`]) o la conversión falla From 8e67065aae51343d8d91b9cebba3827688572f54 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 24 Jul 2025 08:38:17 +0200 Subject: [PATCH 028/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20acciones=20bas?= =?UTF-8?q?e=20y=20renderizado=20de=20componentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Añade acciones BeforeRender y AfterRender para ejecutar código personalizado antes y después de renderizar un componente. - Introduce la acción PrepareRender para personalizar totalmente el renderizado de un componente. - Se actualizan las definiciones de acciones para utilizar el nuevo "trait" ActionDispatcher. - Se crea un nuevo trait ComponentTrait para definir componentes renderizables. - Se implementan las estructuras Children y Child para gestionar componentes hijos dentro de un componente padre. - Se añade OptionComponent para encapsular de forma segura componentes opcionales y poder usarlos en otros componentes. --- Cargo.lock | 7 +- Cargo.toml | 5 +- src/base.rs | 4 +- src/base/action.rs | 15 + src/base/action/component.rs | 10 + .../component/after_render_component.rs | 81 +++++ .../component/before_render_component.rs | 81 +++++ src/base/action/component/is_renderable.rs | 96 +++++ src/base/action/theme.rs | 10 + .../action/theme/after_render_component.rs | 50 +++ .../action/theme/before_render_component.rs | 50 +++ src/base/action/theme/prepare_render.rs | 63 ++++ src/core.rs | 5 +- src/core/action.rs | 56 ++- src/core/action/all.rs | 54 +-- src/core/action/definition.rs | 72 ++-- src/core/action/list.rs | 9 +- src/core/component.rs | 9 + src/core/component/children.rs | 327 ++++++++++++++++++ src/core/component/definition.rs | 121 +++++++ src/core/extension/all.rs | 33 +- src/core/extension/definition.rs | 4 +- src/core/theme/all.rs | 6 +- src/core/theme/definition.rs | 2 +- src/html.rs | 3 + src/html/opt_component.rs | 68 ++++ src/lib.rs | 4 +- src/prelude.rs | 4 +- 28 files changed, 1102 insertions(+), 147 deletions(-) create mode 100644 src/base/action.rs create mode 100644 src/base/action/component.rs create mode 100644 src/base/action/component/after_render_component.rs create mode 100644 src/base/action/component/before_render_component.rs create mode 100644 src/base/action/component/is_renderable.rs create mode 100644 src/base/action/theme.rs create mode 100644 src/base/action/theme/after_render_component.rs create mode 100644 src/base/action/theme/before_render_component.rs create mode 100644 src/base/action/theme/prepare_render.rs create mode 100644 src/core/component.rs create mode 100644 src/core/component/children.rs create mode 100644 src/core/component/definition.rs create mode 100644 src/html/opt_component.rs diff --git a/Cargo.lock b/Cargo.lock index ab7b20a5..b03e9808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1413,7 +1413,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.11" +version = "0.0.12" dependencies = [ "actix-files", "actix-web", @@ -1426,6 +1426,7 @@ dependencies = [ "fluent-templates", "itoa", "pagetop-macros", + "parking_lot", "pastey", "serde", "static-files", @@ -2212,9 +2213,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5" +checksum = "5360edd490ec8dee9fedfc6a9fd83ac2f01b3e1996e3261b9ad18a61971fe064" dependencies = [ "actix-web", "mutually_exclusive_features", diff --git a/Cargo.toml b/Cargo.toml index b6f844f1..049c258c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.11" +version = "0.0.12" edition = "2021" description = """\ @@ -21,6 +21,7 @@ concat-string = "1.0.1" config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" itoa = "1.0.15" +parking_lot = "0.12.4" paste = { package = "pastey", version = "0.1.0" } substring = "1.4.5" terminal_size = "0.4.2" @@ -28,7 +29,7 @@ terminal_size = "0.4.2" tracing = "0.1.41" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } -tracing-actix-web = "0.7.18" +tracing-actix-web = "0.7.19" fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } diff --git a/src/base.rs b/src/base.rs index c50cc9e4..491223e6 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,3 +1,5 @@ -//! Reúne temas listos para usar. +//! Reúne acciones y temas listos para usar. + +pub mod action; pub mod theme; diff --git a/src/base/action.rs b/src/base/action.rs new file mode 100644 index 00000000..119a7eaa --- /dev/null +++ b/src/base/action.rs @@ -0,0 +1,15 @@ +//! Acciones predefinidas para alterar el funcionamiento interno de `PageTop`. + +use crate::prelude::*; + +/// Tipo de función para manipular componentes y su contexto de renderizado. +/// +/// Se usa en acciones definidas en [`component`] y [`theme`] para alterar el comportamiento de los +/// componentes. +/// +/// Recibe referencias mutables (`&mut`) del componente `component` y del contexto `cx`. +pub type FnActionWithComponent = fn(component: &mut C, cx: &mut Context); + +pub mod component; + +pub mod theme; diff --git a/src/base/action/component.rs b/src/base/action/component.rs new file mode 100644 index 00000000..aaef1ce9 --- /dev/null +++ b/src/base/action/component.rs @@ -0,0 +1,10 @@ +//! Acciones que operan sobre componentes. + +mod is_renderable; +pub use is_renderable::*; + +mod before_render_component; +pub use before_render_component::*; + +mod after_render_component; +pub use after_render_component::*; diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs new file mode 100644 index 00000000..3c8ead9a --- /dev/null +++ b/src/base/action/component/after_render_component.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] después de renderizar un componente. +pub struct AfterRender { + f: FnActionWithComponent, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnActionWithComponent`] después de renderizar un componente `C`. +impl ActionDispatcher for AfterRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl AfterRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`]. + pub fn new(f: FnActionWithComponent) -> Self { + AfterRender { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente + /// `C` con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + // Primero despacha las acciones para el tipo de componente. + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + // Y luego despacha las acciones para el tipo de componente con un identificador dado. + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| (action.f)(component, cx), + ); + } + } +} diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs new file mode 100644 index 00000000..0ebe4092 --- /dev/null +++ b/src/base/action/component/before_render_component.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] antes de renderizar el componente. +pub struct BeforeRender { + f: FnActionWithComponent, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnActionWithComponent`] antes de renderizar un componente `C`. +impl ActionDispatcher for BeforeRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl BeforeRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`]. + pub fn new(f: FnActionWithComponent) -> Self { + BeforeRender { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente + /// `C` con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + // Primero despacha las acciones para el tipo de componente. + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + // Y luego despacha las aciones para el tipo de componente con un identificador dado. + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| (action.f)(component, cx), + ); + } + } +} diff --git a/src/base/action/component/is_renderable.rs b/src/base/action/component/is_renderable.rs new file mode 100644 index 00000000..7ba7d53c --- /dev/null +++ b/src/base/action/component/is_renderable.rs @@ -0,0 +1,96 @@ +use crate::prelude::*; + +/// Tipo de función para determinar si un componente se renderiza o no. +/// +/// Se usa en la acción [`IsRenderable`] para controlar dinámicamente la visibilidad del componente +/// `component` según el contexto `cx`. El componente **no se renderiza** en cuanto una de las +/// funciones devuelva `false`. +pub type FnIsRenderable = fn(component: &C, cx: &Context) -> bool; + +/// Con la función [`FnIsRenderable`] se puede decidir si se renderiza o no un componente. +pub struct IsRenderable { + f: FnIsRenderable, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnIsRenderable`] para decidir si se renderiza o no un componente `C`. +impl ActionDispatcher for IsRenderable { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl IsRenderable { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnIsRenderable`]. + pub fn new(f: FnIsRenderable) -> Self { + IsRenderable { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnIsRenderable`] sólo para el componente `C` + /// con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. Se detiene en cuanto una [`FnIsRenderable`] devuelve `false`. + #[inline] + pub(crate) fn dispatch(component: &C, cx: &mut Context) -> bool { + let mut renderable = true; + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| { + if renderable && !(action.f)(component, cx) { + renderable = false; + } + }, + ); + if renderable { + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| { + if renderable && !(action.f)(component, cx) { + renderable = false; + } + }, + ); + } + } + renderable + } +} diff --git a/src/base/action/theme.rs b/src/base/action/theme.rs new file mode 100644 index 00000000..40988574 --- /dev/null +++ b/src/base/action/theme.rs @@ -0,0 +1,10 @@ +//! Acciones lanzadas desde los temas. + +mod before_render_component; +pub use before_render_component::*; + +mod after_render_component; +pub use after_render_component::*; + +mod prepare_render; +pub use prepare_render::*; diff --git a/src/base/action/theme/after_render_component.rs b/src/base/action/theme/after_render_component.rs new file mode 100644 index 00000000..d33e0d2d --- /dev/null +++ b/src/base/action/theme/after_render_component.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] después de que un tema renderice el componente. +pub struct AfterRender { + f: FnActionWithComponent, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnActionWithComponent`] después de que un tema renderice el componente +/// `C`. +impl ActionDispatcher for AfterRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl AfterRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para + /// un tema dado. + pub fn new(theme: ThemeRef, f: FnActionWithComponent) -> Self { + AfterRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + } +} diff --git a/src/base/action/theme/before_render_component.rs b/src/base/action/theme/before_render_component.rs new file mode 100644 index 00000000..76f6cd22 --- /dev/null +++ b/src/base/action/theme/before_render_component.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] antes de que un tema renderice el componente. +pub struct BeforeRender { + f: FnActionWithComponent, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnActionWithComponent`] antes de que un tema renderice el componente +/// `C`. +impl ActionDispatcher for BeforeRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl BeforeRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para + /// un tema dado. + pub fn new(theme: ThemeRef, f: FnActionWithComponent) -> Self { + BeforeRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + } +} diff --git a/src/base/action/theme/prepare_render.rs b/src/base/action/theme/prepare_render.rs new file mode 100644 index 00000000..9d3e2612 --- /dev/null +++ b/src/base/action/theme/prepare_render.rs @@ -0,0 +1,63 @@ +use crate::prelude::*; + +/// Tipo de función para alterar el renderizado de un componente. +/// +/// Permite a un [tema](crate::base::action::theme) sobreescribir el renderizado predeterminado de +/// los componentes. +/// +/// Recibe una referencia al componente `component` y una referencia mutable al contexto `cx`. +pub type FnPrepareRender = fn(component: &C, cx: &mut Context) -> PrepareMarkup; + +/// Ejecuta [`FnPrepareRender`] para preparar el renderizado de un componente. +/// +/// Permite a un tema hacer una implementación nueva del renderizado de un componente. +pub struct PrepareRender { + f: FnPrepareRender, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnPrepareRender`] que modifica el renderizado de un componente `C`. +impl ActionDispatcher for PrepareRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl PrepareRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnPrepareRender`] para un + /// tema dado. + pub fn new(theme: ThemeRef, f: FnPrepareRender) -> Self { + PrepareRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. Se detiene en cuanto una renderiza. + #[inline] + pub(crate) fn dispatch(component: &C, cx: &mut Context) -> PrepareMarkup { + let mut render_component = PrepareMarkup::None; + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| { + if render_component.is_empty() { + render_component = (action.f)(component, cx); + } + }, + ); + render_component + } +} diff --git a/src/core.rs b/src/core.rs index 4b857fc4..2168c932 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -//! Tipos y funciones esenciales para crear acciones, extensiones y temas. +//! Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas. use std::any::Any; @@ -204,6 +204,9 @@ impl AnyCast for T {} // API para definir acciones que alteran el comportamiento predeterminado del código. pub mod action; +// API para construir nuevos componentes. +pub mod component; + // API para añadir nuevas funcionalidades usando extensiones. pub mod extension; diff --git a/src/core/action.rs b/src/core/action.rs index 8503ae90..f5dc11c5 100644 --- a/src/core/action.rs +++ b/src/core/action.rs @@ -1,11 +1,11 @@ //! API para definir acciones que inyectan código en el flujo de la aplicación. //! -//! Permite crear acciones en las librerías para que otros *crates* puedan inyectar código usando -//! funciones *ad hoc* que modifican el comportamiento predefinido en puntos concretos del flujo de -//! ejecución de la aplicación. +//! Permite crear acciones para que otros *crates* puedan inyectar código usando funciones *ad hoc* +//! que modifican el comportamiento predefinido en puntos concretos del flujo de ejecución de la +//! aplicación. mod definition; -pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait}; +pub use definition::{ActionBox, ActionDispatcher, ActionKey}; mod list; use list::ActionsList; @@ -14,49 +14,33 @@ mod all; pub(crate) use all::add_action; pub use all::dispatch_actions; -/// Crea una lista de acciones para facilitar la implementación del método -/// [`actions`](crate::core::extension::ExtensionTrait#method.actions). +/// Facilita la implementación del método +/// [`actions()`](crate::core::extension::ExtensionTrait::actions). /// -/// Esta macro crea vectores de [`ActionBox`], el tipo dinámico que encapsula cualquier acción que -/// implemente [`ActionTrait`]. Evita escribir repetidamente `Box::new(...)` para cada acción -/// inyectada, manteniendo el código más limpio. +/// Evita escribir repetidamente `Box::new(...)` para cada acción de la lista, manteniendo el código +/// más limpio. /// -/// # Ejemplos -/// -/// Puede llamarse sin argumentos para crear un vector vacío: +/// # Ejemplo /// /// ```rust,ignore -/// let my_actions = inject_actions![]; -/// ``` +/// use pagetop::prelude::*; /// -/// O con una lista de acciones concretas: -/// -/// ```rust,ignore -/// let my_actions = inject_actions![ -/// MyFirstAction::new(), -/// MySecondAction::new().with_weight(10), -/// ]; -/// ``` -/// -/// Internamente, expande a un `vec![Box::new(...), ...]`. -/// -/// # Ejemplo típico en una extensión -/// -/// ```rust,ignore -/// impl ExtensionTrait for MyExtension { +/// impl ExtensionTrait for MyTheme { /// fn actions(&self) -> Vec { -/// inject_actions![ -/// CustomizeLoginAction::new(), -/// ModifyHeaderAction::new().with_weight(-5), +/// actions_boxed![ +/// action::theme::BeforeRender::
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. +Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar +según las necesidades de cada proyecto, incluyendo: + + * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su flujo + de ejecución. + * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, + configurables y reutilizables. + * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs + de `PageTop` o de terceros. + * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y + componentes sin comprometer su funcionalidad. # ⚡️ Guía rápida @@ -27,6 +41,55 @@ async fn main() -> std::io::Result<()> { } ``` +Sólo con esto, este código sirve por defecto una página web de bienvenida accesible desde un +navegador en la dirección `http://localhost:8080` con la configuración predeterminada. + +Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: + +```rust +use pagetop::prelude::*; + +struct HelloWorld; + +impl Extension for HelloWorld { + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + scfg.route("/", service::web::get().to(hello_world)); + } +} + +async fn hello_world(request: HttpRequest) -> ResultPage { + Page::new(Some(request)) + .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .render() +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&HelloWorld).run()?.await +} +``` + +Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz +(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. + + +# 📂 Repositorio + +El código se organiza en un *workspace* donde actualmente se incluyen los siguientes subproyectos: + + * **[pagetop](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/src)**, con el código + fuente de la librería principal. Reúne algunos de los *crates* más estables y populares del + ecosistema Rust para proporcionar APIs y recursos para la creación avanzada de soluciones web. + +## Auxiliares + + * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, + permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el + binario de las aplicaciones `PageTop`. + + * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, + proporciona una colección de macros que mejoran la experiencia de desarrollo con `PageTop`. + # 🧪 Pruebas @@ -37,6 +100,7 @@ Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de C | ------- | ----------- | | `cargo ts` | Ejecuta los tests de `pagetop` (*unit + integration*) con la *feature* `testing`. | | `cargo ts --test util` | Lanza sólo las pruebas de integración del módulo `util`. | +| `cargo ts --doc locale` | Lanza las pruebas de la documentación del módulo `locale`. | | `cargo tw` | Ejecuta los tests de **todos los paquetes** del *workspace*. | > **Nota** diff --git a/examples/hello-name.rs b/examples/hello-name.rs new file mode 100644 index 00000000..7a6db545 --- /dev/null +++ b/examples/hello-name.rs @@ -0,0 +1,24 @@ +use pagetop::prelude::*; + +struct HelloName; + +impl Extension for HelloName { + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + scfg.route("/hello/{name}", service::web::get().to(hello_name)); + } +} + +async fn hello_name( + request: HttpRequest, + path: service::web::Path, +) -> ResultPage { + let name = path.into_inner(); + Page::new(Some(request)) + .with_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) + .render() +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&HelloName).run()?.await +} diff --git a/examples/hello-world.rs b/examples/hello-world.rs new file mode 100644 index 00000000..ba268dcd --- /dev/null +++ b/examples/hello-world.rs @@ -0,0 +1,20 @@ +use pagetop::prelude::*; + +struct HelloWorld; + +impl Extension for HelloWorld { + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + scfg.route("/", service::web::get().to(hello_world)); + } +} + +async fn hello_world(request: HttpRequest) -> ResultPage { + Page::new(Some(request)) + .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .render() +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&HelloWorld).run()?.await +} From bc1328546af7e37b54386836738509392ff33258 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 3 Aug 2025 18:00:02 +0200 Subject: [PATCH 053/224] =?UTF-8?q?=F0=9F=9A=A7=20Normaliza=20versiones=20?= =?UTF-8?q?a=20las=20=C3=BAltimas=20publicaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revisa y completa la documentación general. --- Cargo.lock | 4 +-- Cargo.toml | 4 +-- README.md | 4 +-- helpers/pagetop-build/Cargo.toml | 8 ++--- helpers/pagetop-build/README.md | 3 ++ helpers/pagetop-build/src/lib.rs | 3 ++ helpers/pagetop-macros/Cargo.toml | 6 ++-- helpers/pagetop-macros/README.md | 3 ++ helpers/pagetop-macros/src/lib.rs | 3 ++ src/lib.rs | 56 ++++++++++++++++++++++++++++++- 10 files changed, 80 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 906b338c..7cba8b96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.0.1" +version = "0.0.16" dependencies = [ "grass", "static-files", @@ -1608,7 +1608,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.5" +version = "0.0.18" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", diff --git a/Cargo.toml b/Cargo.toml index 6844b7b7..a1a6bfc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ name = "pagetop" version = "0.0.61" edition = "2021" -description = """\ - Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.\ +description = """ + Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables. """ categories = ["web-programming", "gui", "development-tools", "asynchronous"] keywords = ["pagetop", "web", "framework", "frontend", "ssr"] diff --git a/README.md b/README.md index 2619cb66..e671b50b 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ async fn main() -> std::io::Result<()> { } ``` -Sólo con esto, este código sirve por defecto una página web de bienvenida accesible desde un -navegador en la dirección `http://localhost:8080` con la configuración predeterminada. +Este código arranca el servidor de `PageTop` que, con la configuración por defecto, muestra una +página de bienvenida accesible desde un navegador local en `http://localhost:8080`. Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 4b947b3e..c6d9d519 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "pagetop-build" -version = "0.0.1" +version = "0.0.16" edition = "2021" -description = """\ - Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el \ - binario de un proyecto PageTop.\ +description = """ + Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el + binario de un proyecto PageTop. """ categories = ["development-tools::build-utils", "web-programming"] keywords = ["pagetop", "build", "assets", "resources", "static"] diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 9f912e99..27c98147 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -5,6 +5,9 @@

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +[![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) +[![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) +[![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build)

diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index b18cf52f..90898dd3 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -5,6 +5,9 @@ //!

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

//! //! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) +//! [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) +//! [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) //! //! //! diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 3a7fd950..27325b88 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "pagetop-macros" -version = "0.0.5" +version = "0.0.18" edition = "2021" -description = """\ - Una colección de macros que mejoran la experiencia de desarrollo con PageTop.\ +description = """ + Una colección de macros que mejoran la experiencia de desarrollo con PageTop. """ categories = ["development-tools::procedural-macro-helpers", "web-programming"] keywords = ["pagetop", "macros", "proc-macros", "codegen"] diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index e41dd0ec..826698ad 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -5,6 +5,9 @@

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +[![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) +[![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) +[![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index b9be0bea..8a6b98b7 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -5,6 +5,9 @@ //!

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

//! //! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) +//! [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) +//! [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) //! //! //! diff --git a/src/lib.rs b/src/lib.rs index 598c55e6..c0e28d45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,13 +7,26 @@ //!

Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.

//! //! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) +//! [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) +//! [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) //! //!
//! //! //! `PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) //! para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y -//! JavaScript. +//! JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, +//! extender o adaptar según las necesidades de cada proyecto, incluyendo: +//! +//! * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su +//! flujo de ejecución. +//! * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, +//! configurables y reutilizables. +//! * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las +//! APIs de `PageTop` o de terceros. +//! * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y +//! componentes sin comprometer su funcionalidad. //! //! # ⚡️ Guía rápida //! @@ -27,6 +40,47 @@ //! Application::new().run()?.await //! } //! ``` +//! +//! Este código arranca el servidor de `PageTop` que, con la +//! [configuración por defecto](crate::global::SETTINGS), muestra una página de bienvenida accesible +//! desde un navegador local en `http://localhost:8080`. +//! +//! Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: +//! +//! ```rust,no_run +//! use pagetop::prelude::*; +//! +//! struct HelloWorld; +//! +//! impl Extension for HelloWorld { +//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { +//! scfg.route("/", service::web::get().to(hello_world)); +//! } +//! } +//! +//! async fn hello_world(request: HttpRequest) -> ResultPage { +//! Page::new(Some(request)) +//! .with_component(Html::with(move |_| html! { h1 { "Hello world!" } })) +//! .render() +//! } +//! +//! #[pagetop::main] +//! async fn main() -> std::io::Result<()> { +//! Application::prepare(&HelloWorld).run()?.await +//! } +//! ``` +//! +//! Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta +//! raíz (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. +//! +//! # 🧩 Gestión de Dependencias +//! +//! Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro +//! proyecto en Rust. +//! +//! Sin embargo, es fundamental que cada extensión declare explícitamente sus +//! [dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda +//! estructurar e inicializar la aplicación de forma modular. #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( From 11caad11afadd8f65ae35924ce2ecf38ea0bf4d6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 18:49:04 +0200 Subject: [PATCH 054/224] =?UTF-8?q?=F0=9F=93=9D=20Retoques=20menores=20en?= =?UTF-8?q?=20la=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CREDITS.md | 2 +- README.md | 4 ++-- src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 5e89b868..f5c1b0fa 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -10,7 +10,7 @@ como son: de la aplicación. * [Fluent templates](https://github.com/XAMPPRocky/fluent-templates), que integra [Fluent](https://projectfluent.org/) para internacionalizar las aplicaciones. - * Además de otros crates adicionales que se pueden explorar en los archivos `Cargo.toml` de + * Además de otros *crates* adicionales que se pueden explorar en los archivos `Cargo.toml` de `PageTop` y sus extensiones. diff --git a/README.md b/README.md index e671b50b..87f8b8da 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ async fn main() -> std::io::Result<()> { } ``` -Este código arranca el servidor de `PageTop` que, con la configuración por defecto, muestra una -página de bienvenida accesible desde un navegador local en `http://localhost:8080`. +Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página +de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: diff --git a/src/lib.rs b/src/lib.rs index c0e28d45..baa57a69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,9 +41,9 @@ //! } //! ``` //! -//! Este código arranca el servidor de `PageTop` que, con la +//! Este código arranca el servidor de `PageTop`. Con la //! [configuración por defecto](crate::global::SETTINGS), muestra una página de bienvenida accesible -//! desde un navegador local en `http://localhost:8080`. +//! desde un navegador local en la dirección `http://localhost:8080`. //! //! Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: //! From 4ae919ccd78d8863e3fb29431a7ebf087ebc49be Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 18:52:00 +0200 Subject: [PATCH 055/224] =?UTF-8?q?=F0=9F=9A=A8=20Aplica=20cambios=20por?= =?UTF-8?q?=20sugerencias=20de=20clippy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 7 +++---- helpers/pagetop-macros/src/maud/ast.rs | 3 +++ helpers/pagetop-macros/src/smart_default/body_impl.rs | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 8a6b98b7..fedd682f 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -177,10 +177,9 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { let fn_with_attrs = &fn_with.attrs; // Genera el método alter_...() con el código del método with_...(). - let fn_alter_doc = format!( - "Igual que [`Self::{0}()`](Self::{0}), pero sin usar el patrón *builder*.", - fn_with_name_str, - ); + let fn_alter_doc = + format!("Igual que [`Self::{fn_with_name_str}()`], pero sin usar el patrón *builder*."); + let fn_alter = quote! { #[doc = #fn_alter_doc] pub fn #fn_alter_name #fn_generics(&mut self, #(#args),*) -> &mut Self #where_clause { diff --git a/helpers/pagetop-macros/src/maud/ast.rs b/helpers/pagetop-macros/src/maud/ast.rs index fd499ae2..ebd53318 100644 --- a/helpers/pagetop-macros/src/maud/ast.rs +++ b/helpers/pagetop-macros/src/maud/ast.rs @@ -44,6 +44,7 @@ impl ToTokens for Markups { } #[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] pub enum Markup { Block(Block), Lit(HtmlLit), @@ -429,6 +430,7 @@ impl ToTokens for Attribute { } #[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] pub enum HtmlNameOrMarkup { HtmlName(HtmlName), Markup(Markup), @@ -472,6 +474,7 @@ impl Display for HtmlNameOrMarkup { } #[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] pub enum AttributeType { Normal { eq_token: Eq, diff --git a/helpers/pagetop-macros/src/smart_default/body_impl.rs b/helpers/pagetop-macros/src/smart_default/body_impl.rs index 6a76f904..f7d59bb5 100644 --- a/helpers/pagetop-macros/src/smart_default/body_impl.rs +++ b/helpers/pagetop-macros/src/smart_default/body_impl.rs @@ -19,7 +19,7 @@ pub fn impl_my_derive(input: &DeriveInput) -> Result { quote! { #name #body_assignment }, - format!("Returns a `{}` default.", name), + format!("Returns a `{name}` default."), ) } syn::Data::Enum(ref body) => { @@ -44,7 +44,7 @@ pub fn impl_my_derive(input: &DeriveInput) -> Result { quote! { #name :: #default_variant_name #body_assignment }, - format!("Returns a `{}::{}` default.", name, default_variant_name), + format!("Returns a `{name}::{default_variant_name}` default."), ) } syn::Data::Union(_) => { @@ -109,7 +109,7 @@ fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> { .iter() .map(|field| { let (default_value, default_doc) = field_default_expr_and_doc(field)?; - write!(&mut doc, "{}, ", default_doc).unwrap(); + write!(&mut doc, "{default_doc}, ").unwrap(); Ok(default_value) }) .collect::, Error>>()?; @@ -145,7 +145,7 @@ fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String ConversionStrategy::Into => quote!((#field_value).into()), }; - let field_doc = format!("{}", field_value); + let field_doc = format!("{field_value}"); Ok((field_value, field_doc)) } else { Ok(( From b01b12f47245aacbcc9da90b126e268f0976b86e Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 18:53:01 +0200 Subject: [PATCH 056/224] =?UTF-8?q?=F0=9F=94=A8=20(devops):=20Scripts=20pa?= =?UTF-8?q?ra=20la=20publicaci=C3=B3n=20de=20crates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/cliff.toml | 67 +++++++++++++++++++++++++++++ .cargo/release.toml | 45 ++++++++++++++++++++ tools/changelog.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++ tools/release.sh | 48 +++++++++++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 .cargo/cliff.toml create mode 100644 .cargo/release.toml create mode 100755 tools/changelog.sh create mode 100755 tools/release.sh diff --git a/.cargo/cliff.toml b/.cargo/cliff.toml new file mode 100644 index 00000000..ff362c46 --- /dev/null +++ b/.cargo/cliff.toml @@ -0,0 +1,67 @@ +# cliff.toml + +[changelog] +header = """ +# CHANGELOG + +Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado +en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo +las reglas del [Versionado Semántico](https://semver.org/lang/es/). + +Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, +correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o +internos pueden omitirse si no afectan al uso del proyecto. +""" +trim = true +render_always = true + +body = """ +{% if version %} +## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }}) +{% else %} +## Pendiente de publicación +{% endif %}\ +{% set base = "https://git.cillero.es/manuelcillero/pagetop" %}\ +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | upper_first }} +{% for commit in commits %} +{%- set msg = commit.message + | split(pat="\n") + | first + | replace(from="✨ ", to="") + | replace(from="🐛 ", to="") + | replace(from="⬆️ ", to="") + | replace(from="🚧 ", to="") + | replace(from="♻️ ", to="") + | replace(from="✏️ ", to="") + | replace(from="🏷️ ", to="") + | replace(from="🧑‍💻 ", to="") + | replace(from="🍱 ", to="") + | replace(from="📝 ", to="") + | replace(from="💡 ", to="") +-%} +{% set sha7 = commit.id | truncate(length=7, end="") %} +- {{ msg | trim }} ([{{ sha7 }}]({{ base }}/commit/{{ commit.id }}){% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %}) +{% endfor %}{% endfor %} +""" + +[git] +conventional_commits = false +filter_unconventional = false +topo_order_commits = true +sort_commits = "oldest" + +commit_parsers = [ + { message = "^✨", group = "Añadido" }, + { message = "^🐛", group = "Corregido" }, + { message = "^🚧", group = "Cambiado" }, + { message = "^♻️", group = "Cambiado" }, + { message = "^✏️", group = "Cambiado" }, + { message = "^🏷️", group = "Cambiado" }, + { message = "^🧑‍💻", group = "Cambiado" }, + { message = "^🍱", group = "Cambiado" }, + { message = "^⬆️", group = "Dependencias" }, + { message = "^📝", group = "Documentado" }, + { message = "^💡", group = "Documentado" }, + { message = "^.*", group = "Otros cambios" }, +] diff --git a/.cargo/release.toml b/.cargo/release.toml new file mode 100644 index 00000000..9c073794 --- /dev/null +++ b/.cargo/release.toml @@ -0,0 +1,45 @@ +# release.toml + +# Etiqueta por crate: `pagetop-macros-v0.2.0` +tag-prefix = "{{crate_name}}-v" + +# Confirmaciones firmadas (no requeridas) +sign-commit = false +sign-tag = false + +# Empuja etiquetas y commits +push = true + +# Publica en crates.io (puedes desactivarlo para pruebas) +publish = true + +# Actualiza todos los dependientes internos +update-dependencies = true + +# Solo permite publicar estos crates (los que forman parte del workspace) +allow-branch = ["main"] +consolidate-commits = false +consolidate-pushes = true + +# Mensaje personalizado para el commit de versión +pre-release-commit-message = "🔖 Prepara publicación de {{crate_name}} {{version}}" + +[workspace] +# Lista de crates que se pueden publicar dentro del workspace +# Puedes añadir extensiones más adelante +allow-publish = [ + "pagetop", + "pagetop-build", + "pagetop-macros" +] + +# Opcional: ordena la publicación de dependencias internas +publish-order = [ + "pagetop-build", + "pagetop-macros", + "pagetop" +] + +pre-release-hook = [ + "./tools/changelog.sh", "{{crate_name}}", "{{version}}", "--stage" +] diff --git a/tools/changelog.sh b/tools/changelog.sh new file mode 100755 index 00000000..19cbed9e --- /dev/null +++ b/tools/changelog.sh @@ -0,0 +1,101 @@ +#!/bin/bash +set -euo pipefail + +# ------------------------------------------------------------------------------ +# Script para generar el archivo de cambios del crate indicado. +# Uso: +# ./tools/changelog.sh [--stage] +# Ejemplo: +# ./tools/changelog.sh pagetop-macros 0.1.0 # Sólo genera archivo +# ./tools/changelog.sh pagetop 0.1.0 --stage # Prepara archivo para commit +# ------------------------------------------------------------------------------ + +# Configuración +CRATE="${1:-}" +VERSION="${2:-}" +STAGE="${3:-}" +CLIFF_CONFIG=".cargo/cliff.toml" + +# Comprobaciones +if [[ -z "$CRATE" || -z "$VERSION" ]]; then + echo "Usage: $0 [--stage]" >&2 + exit 1 +fi + +# Dependencias +command -v git-cliff >/dev/null || { + echo "Error: git-cliff is not installed. Use: cargo install git-cliff" + exit 1 +} + +# Cambia al directorio del espacio +cd "$(dirname "$0")/.." || exit 1 + +# ------------------------------------------------------------------------------ +# Determina ruta del archivo y ámbito de los archivos afectados para el crate +# ------------------------------------------------------------------------------ +case "$CRATE" in + pagetop) + CHANGELOG_FILE="CHANGELOG.md" + PATH_FLAGS=( + --exclude-path "helpers/pagetop-macros/**/*" + --exclude-path "helpers/pagetop-build/**/*" + ) + ;; + pagetop-macros) + CHANGELOG_FILE="helpers/pagetop-macros/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-macros/**/*") + ;; + pagetop-build) + CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") + ;; + *) + echo "Error: unsupported crate '$CRATE'" >&2 + exit 1 + ;; +esac + +# ------------------------------------------------------------------------------ +# Obtiene la última etiqueta del crate +# ------------------------------------------------------------------------------ +LAST_TAG="$(git tag --list "${CRATE}-v*" --sort=-v:refname | head -n 1)" + +if [[ -n "$LAST_TAG" ]]; then + echo "Generating CHANGELOG for '$CRATE' from last tag '$LAST_TAG'" + CLIFF_ARGS=(--unreleased --tag "$VERSION") +else + echo "Generating initial CHANGELOG for '$CRATE'" + CLIFF_ARGS=(--tag "$VERSION") +fi + +# ------------------------------------------------------------------------------ +# Genera el CHANGELOG para el crate correspondiente +# ------------------------------------------------------------------------------ +git-cliff --config "$CLIFF_CONFIG" "${PATH_FLAGS[@]}" "${CLIFF_ARGS[@]}" -o "$CHANGELOG_FILE" -u +echo "CHANGELOG generated at '$CHANGELOG_FILE'" + +# Pregunta por la revisión del archivo de cambios generado +read -p "Do you want to review the changelog before continuing? (y/n) " -n 1 -r || exit 1 +echo +if [[ "$REPLY" =~ ^[Yy]$ ]]; then + ${EDITOR:-nano} "$CHANGELOG_FILE" +fi +read -p "Do you want to proceed with the release of $CRATE? (y/n) " -n 1 -r || exit 1 +echo +if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then + echo "Aborting release process." >&2 + exit 1 +fi + +# Si hay cambios y procede, añade al stage (cargo-release hará el commit) +if ! git diff --quiet -- "$CHANGELOG_FILE"; then + if [[ "$STAGE" == "--stage" ]]; then + git add "$CHANGELOG_FILE" + echo "Staged $CHANGELOG_FILE for commit" + else + echo "Changes detected in '$CHANGELOG_FILE', but not staged (no --stage flag)" + fi +else + echo "No changes in '$CHANGELOG_FILE', skipping staging" +fi diff --git a/tools/release.sh b/tools/release.sh new file mode 100755 index 00000000..f62993aa --- /dev/null +++ b/tools/release.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -euo pipefail + +# ------------------------------------------------------------------------------ +# Script para publicar un crate individual del workspace con cargo-release. +# Uso: +# ./tools/release.sh [patch|minor|major] [--execute] +# Ejemplos: +# ./tools/release.sh pagetop-macros patch # Dry run (por defecto) +# ./tools/release.sh pagetop minor --execute # Publicación real +# ------------------------------------------------------------------------------ + +# Configuración +CRATE="${1:-}" +LEVEL="${2:-patch}" +EXECUTE="${3:-}" +CONFIG=".cargo/release.toml" + +# Comprobaciones +if [[ -z "$CRATE" ]]; then + echo "Usage: $0 [patch|minor|major] [--execute]" + exit 1 +fi +if [[ ! "$LEVEL" =~ ^(patch|minor|major)$ ]]; then + echo "Error: invalid level '$LEVEL'. Use: patch, minor, or major" + exit 1 +fi + +# Dependencias +command -v cargo-release >/dev/null || { + echo "Error: cargo-release is not installed. Use: cargo install cargo-release" + exit 1 +} + +# Cambia al directorio del espacio +cd "$(dirname "$0")/.." || exit 1 + +# ------------------------------------------------------------------------------ +# DRY-RUN (por defecto) o ejecución real con --execute +# ------------------------------------------------------------------------------ +if [[ "$EXECUTE" != "--execute" ]]; then + echo "Running dry-run (default mode). Add --execute to publish" + CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --dry-run +else + echo "Releasing $CRATE ($LEVEL)…" + CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --execute + echo "Release completed." +fi From 5c0c3090b136484ba9a1672c38013da2488335b9 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 18:53:21 +0200 Subject: [PATCH 057/224] =?UTF-8?q?=F0=9F=93=84=20Cada=20crate=20con=20su?= =?UTF-8?q?=20licencia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-build/LICENSE-APACHE | 201 ++++++++++++++++++++++++++ helpers/pagetop-build/LICENSE-MIT | 21 +++ helpers/pagetop-macros/LICENSE-APACHE | 201 ++++++++++++++++++++++++++ helpers/pagetop-macros/LICENSE-MIT | 21 +++ 4 files changed, 444 insertions(+) create mode 100644 helpers/pagetop-build/LICENSE-APACHE create mode 100644 helpers/pagetop-build/LICENSE-MIT create mode 100644 helpers/pagetop-macros/LICENSE-APACHE create mode 100644 helpers/pagetop-macros/LICENSE-MIT diff --git a/helpers/pagetop-build/LICENSE-APACHE b/helpers/pagetop-build/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/helpers/pagetop-build/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/helpers/pagetop-build/LICENSE-MIT b/helpers/pagetop-build/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/helpers/pagetop-build/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/helpers/pagetop-macros/LICENSE-APACHE b/helpers/pagetop-macros/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/helpers/pagetop-macros/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/helpers/pagetop-macros/LICENSE-MIT b/helpers/pagetop-macros/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/helpers/pagetop-macros/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 4928b0f7a6a8bd1978dca03346deb39a2d1085c7 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 19:02:33 +0200 Subject: [PATCH 058/224] =?UTF-8?q?=F0=9F=A9=B9=20El=20modo=20dry-run=20se?= =?UTF-8?q?=20ejecuta=20por=20defecto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/release.sh b/tools/release.sh index f62993aa..082869f5 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -40,7 +40,7 @@ cd "$(dirname "$0")/.." || exit 1 # ------------------------------------------------------------------------------ if [[ "$EXECUTE" != "--execute" ]]; then echo "Running dry-run (default mode). Add --execute to publish" - CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --dry-run + CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" else echo "Releasing $CRATE ($LEVEL)…" CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --execute From b03cd746c0c4cf95b160c55158c2f4ff39404f51 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 5 Aug 2025 23:11:21 +0200 Subject: [PATCH 059/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cba8b96..1d32d893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.0.16" +version = "0.1.0" dependencies = [ "grass", "static-files", diff --git a/Cargo.toml b/Cargo.toml index a1a6bfc7..dcf191d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,5 +71,5 @@ authors = ["Manuel Cillero "] [workspace.dependencies] static-files = "0.2.5" -pagetop-build = { version = "0.0", path = "helpers/pagetop-build" } +pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index c6d9d519..7bf60489 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.0.16" +version = "0.1.0" edition = "2021" description = """ From c8976edca45ed97f4e5bc13dc171d55f639ebbd8 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 01:54:47 +0200 Subject: [PATCH 060/224] =?UTF-8?q?=F0=9F=90=9B=20Corrige=20edici=C3=B3n?= =?UTF-8?q?=20de=20CHANGELOG=20y=20nuevas=20versiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/release.toml | 22 +-------------------- tools/changelog.sh | 47 +++++++++++++++++++++++---------------------- tools/release.sh | 4 ++-- 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/.cargo/release.toml b/.cargo/release.toml index 9c073794..b0ab9389 100644 --- a/.cargo/release.toml +++ b/.cargo/release.toml @@ -13,33 +13,13 @@ push = true # Publica en crates.io (puedes desactivarlo para pruebas) publish = true -# Actualiza todos los dependientes internos -update-dependencies = true - # Solo permite publicar estos crates (los que forman parte del workspace) allow-branch = ["main"] consolidate-commits = false -consolidate-pushes = true # Mensaje personalizado para el commit de versión pre-release-commit-message = "🔖 Prepara publicación de {{crate_name}} {{version}}" -[workspace] -# Lista de crates que se pueden publicar dentro del workspace -# Puedes añadir extensiones más adelante -allow-publish = [ - "pagetop", - "pagetop-build", - "pagetop-macros" -] - -# Opcional: ordena la publicación de dependencias internas -publish-order = [ - "pagetop-build", - "pagetop-macros", - "pagetop" -] - pre-release-hook = [ - "./tools/changelog.sh", "{{crate_name}}", "{{version}}", "--stage" + "sh", "-c", "ROOT=$(git rev-parse --show-toplevel) && \"$ROOT/tools/changelog.sh\" {{crate_name}} {{version}} --stage" ] diff --git a/tools/changelog.sh b/tools/changelog.sh index 19cbed9e..59f04693 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -35,20 +35,20 @@ cd "$(dirname "$0")/.." || exit 1 # Determina ruta del archivo y ámbito de los archivos afectados para el crate # ------------------------------------------------------------------------------ case "$CRATE" in - pagetop) - CHANGELOG_FILE="CHANGELOG.md" - PATH_FLAGS=( - --exclude-path "helpers/pagetop-macros/**/*" - --exclude-path "helpers/pagetop-build/**/*" - ) + pagetop-build) + CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") ;; pagetop-macros) CHANGELOG_FILE="helpers/pagetop-macros/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-macros/**/*") ;; - pagetop-build) - CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") + pagetop) + CHANGELOG_FILE="CHANGELOG.md" + PATH_FLAGS=( + --exclude-path "helpers/pagetop-build/**/*" + --exclude-path "helpers/pagetop-macros/**/*" + ) ;; *) echo "Error: unsupported crate '$CRATE'" >&2 @@ -56,23 +56,24 @@ case "$CRATE" in ;; esac -# ------------------------------------------------------------------------------ -# Obtiene la última etiqueta del crate -# ------------------------------------------------------------------------------ -LAST_TAG="$(git tag --list "${CRATE}-v*" --sort=-v:refname | head -n 1)" - -if [[ -n "$LAST_TAG" ]]; then - echo "Generating CHANGELOG for '$CRATE' from last tag '$LAST_TAG'" - CLIFF_ARGS=(--unreleased --tag "$VERSION") -else - echo "Generating initial CHANGELOG for '$CRATE'" - CLIFF_ARGS=(--tag "$VERSION") -fi - # ------------------------------------------------------------------------------ # Genera el CHANGELOG para el crate correspondiente # ------------------------------------------------------------------------------ -git-cliff --config "$CLIFF_CONFIG" "${PATH_FLAGS[@]}" "${CLIFF_ARGS[@]}" -o "$CHANGELOG_FILE" -u +COMMON_ARGS=( + --config "$CLIFF_CONFIG" + "${PATH_FLAGS[@]}" + --tag-pattern "^${CRATE}-v" + --tag "$VERSION" + -o "$CHANGELOG_FILE" +) +LAST_TAG="$(git tag --list "${CRATE}-v*" --sort=-v:refname | head -n 1)" +if [[ -n "$LAST_TAG" ]]; then + echo "Generating CHANGELOG for '$CRATE' from tag '$LAST_TAG'" + git-cliff --unreleased "${COMMON_ARGS[@]}" +else + echo "Generating initial CHANGELOG for '$CRATE'" + git-cliff "${COMMON_ARGS[@]}" +fi echo "CHANGELOG generated at '$CHANGELOG_FILE'" # Pregunta por la revisión del archivo de cambios generado diff --git a/tools/release.sh b/tools/release.sh index 082869f5..bb092416 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -40,9 +40,9 @@ cd "$(dirname "$0")/.." || exit 1 # ------------------------------------------------------------------------------ if [[ "$EXECUTE" != "--execute" ]]; then echo "Running dry-run (default mode). Add --execute to publish" - CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" + cargo release --config "$CONFIG" --package "$CRATE" "$LEVEL" else echo "Releasing $CRATE ($LEVEL)…" - CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --execute + cargo release --config "$CONFIG" --package "$CRATE" "$LEVEL" --execute echo "Release completed." fi From 49f5de9498ae1bd758767638637ca7fb7c521d49 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:01:32 +0200 Subject: [PATCH 061/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d32d893..6b894b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 7bf60489..4769631b 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 9aa8e05ed0108fc0b203d23c0b336cf51c2522f6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:07:01 +0200 Subject: [PATCH 062/224] =?UTF-8?q?=F0=9F=90=9B=20Corrige=20comprobaci?= =?UTF-8?q?=C3=B3n=20de=20cambios=20en=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/changelog.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/changelog.sh b/tools/changelog.sh index 59f04693..5c5ec325 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -90,7 +90,7 @@ if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then fi # Si hay cambios y procede, añade al stage (cargo-release hará el commit) -if ! git diff --quiet -- "$CHANGELOG_FILE"; then +if ! git diff --quiet HEAD -- "$CHANGELOG_FILE"; then if [[ "$STAGE" == "--stage" ]]; then git add "$CHANGELOG_FILE" echo "Staged $CHANGELOG_FILE for commit" From bab44422a2787efa0210443bc066c3feee01bf0f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:09:50 +0200 Subject: [PATCH 063/224] =?UTF-8?q?=F0=9F=9A=A7=20Trabajando=20sobre=20la?= =?UTF-8?q?=20publicaci=C3=B3n=20de=20versiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b894b5d..1d32d893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 4769631b..7bf60489 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" edition = "2021" description = """ From 652f5adfef09c370d368abfbb9303e5f4eb97861 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:10:37 +0200 Subject: [PATCH 064/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d32d893..6b894b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 7bf60489..4769631b 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 9abd67f479790e4f8d0568119f269751cf788e20 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:15:29 +0200 Subject: [PATCH 065/224] =?UTF-8?q?=F0=9F=9A=A7=20Validando=20comparaci?= =?UTF-8?q?=C3=B3n=20de=20versiones=20de=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- tools/changelog.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b894b5d..1d32d893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 4769631b..7bf60489 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" edition = "2021" description = """ diff --git a/tools/changelog.sh b/tools/changelog.sh index 5c5ec325..eb4ab7e5 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -90,7 +90,7 @@ if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then fi # Si hay cambios y procede, añade al stage (cargo-release hará el commit) -if ! git diff --quiet HEAD -- "$CHANGELOG_FILE"; then +if git status --porcelain -- "$CHANGELOG_FILE" | grep -qE '^( M|A |??)'; then if [[ "$STAGE" == "--stage" ]]; then git add "$CHANGELOG_FILE" echo "Staged $CHANGELOG_FILE for commit" From 1a3649897ee97b53a22588f1a5f1837bef1403ff Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:16:00 +0200 Subject: [PATCH 066/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d32d893..6b894b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 7bf60489..4769631b 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 7a6bbf9065b795361439b426f1633620a08fab34 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:19:01 +0200 Subject: [PATCH 067/224] =?UTF-8?q?=F0=9F=9A=A7=20Validaci=C3=B3n=20en=20c?= =?UTF-8?q?urso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- tools/changelog.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b894b5d..1d32d893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 4769631b..7bf60489 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.1" +version = "0.1.0" edition = "2021" description = """ diff --git a/tools/changelog.sh b/tools/changelog.sh index eb4ab7e5..09ffcb0b 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -90,7 +90,7 @@ if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then fi # Si hay cambios y procede, añade al stage (cargo-release hará el commit) -if git status --porcelain -- "$CHANGELOG_FILE" | grep -qE '^( M|A |??)'; then +if [[ -n $(git status --porcelain -- "$CHANGELOG_FILE") ]]; then if [[ "$STAGE" == "--stage" ]]; then git add "$CHANGELOG_FILE" echo "Staged $CHANGELOG_FILE for commit" From 806249ea1b4a761ab74d442321fd33b885c40339 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 02:19:25 +0200 Subject: [PATCH 068/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/CHANGELOG.md | 17 +++++++++++++++++ helpers/pagetop-build/Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 helpers/pagetop-build/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 1d32d893..6b894b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" dependencies = [ "grass", "static-files", diff --git a/helpers/pagetop-build/CHANGELOG.md b/helpers/pagetop-build/CHANGELOG.md new file mode 100644 index 00000000..22429694 --- /dev/null +++ b/helpers/pagetop-build/CHANGELOG.md @@ -0,0 +1,17 @@ +# CHANGELOG + +Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado +en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo +las reglas del [Versionado Semántico](https://semver.org/lang/es/). + +Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, +correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o +internos pueden omitirse si no afectan al uso del proyecto. + +## 0.1.1 (2025-08-05) + +- Depura la edición de CHANGELOGs y publicación de nuevas versiones + +## 0.1.0 (2025-08-05) + +- Versión inicial diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 7bf60489..4769631b 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 605473a68368976b9534bff065507b9171b2ce83 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 03:01:19 +0200 Subject: [PATCH 069/224] =?UTF-8?q?=F0=9F=9A=91=20Corrige=20generaci=C3=B3?= =?UTF-8?q?n=20de=20CHANGELOG=20existentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/cliff.toml | 2 ++ .cargo/release.toml | 2 +- tools/changelog.sh | 12 +++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.cargo/cliff.toml b/.cargo/cliff.toml index ff362c46..5e4cfb85 100644 --- a/.cargo/cliff.toml +++ b/.cargo/cliff.toml @@ -30,6 +30,7 @@ body = """ | first | replace(from="✨ ", to="") | replace(from="🐛 ", to="") + | replace(from="🚑 ", to="") | replace(from="⬆️ ", to="") | replace(from="🚧 ", to="") | replace(from="♻️ ", to="") @@ -54,6 +55,7 @@ sort_commits = "oldest" commit_parsers = [ { message = "^✨", group = "Añadido" }, { message = "^🐛", group = "Corregido" }, + { message = "^🚑", group = "Corregido" }, { message = "^🚧", group = "Cambiado" }, { message = "^♻️", group = "Cambiado" }, { message = "^✏️", group = "Cambiado" }, diff --git a/.cargo/release.toml b/.cargo/release.toml index b0ab9389..68f7a9cc 100644 --- a/.cargo/release.toml +++ b/.cargo/release.toml @@ -1,7 +1,7 @@ # release.toml # Etiqueta por crate: `pagetop-macros-v0.2.0` -tag-prefix = "{{crate_name}}-v" +tag-prefix = "{{crate_name}}-" # Confirmaciones firmadas (no requeridas) sign-commit = false diff --git a/tools/changelog.sh b/tools/changelog.sh index 09ffcb0b..13333d4a 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -59,21 +59,27 @@ esac # ------------------------------------------------------------------------------ # Genera el CHANGELOG para el crate correspondiente # ------------------------------------------------------------------------------ +if [[ -f "$CHANGELOG_FILE" ]]; then + # Archivo existe: inserta la nueva sección arriba + OUTPUT_FLAG=(--prepend "$CHANGELOG_FILE") +else + # Primera vez: crea el fichero desde cero + OUTPUT_FLAG=(-o "$CHANGELOG_FILE") +fi COMMON_ARGS=( --config "$CLIFF_CONFIG" "${PATH_FLAGS[@]}" --tag-pattern "^${CRATE}-v" --tag "$VERSION" - -o "$CHANGELOG_FILE" + "${OUTPUT_FLAG[@]}" ) LAST_TAG="$(git tag --list "${CRATE}-v*" --sort=-v:refname | head -n 1)" if [[ -n "$LAST_TAG" ]]; then echo "Generating CHANGELOG for '$CRATE' from tag '$LAST_TAG'" - git-cliff --unreleased "${COMMON_ARGS[@]}" else echo "Generating initial CHANGELOG for '$CRATE'" - git-cliff "${COMMON_ARGS[@]}" fi +git-cliff --unreleased "${COMMON_ARGS[@]}" echo "CHANGELOG generated at '$CHANGELOG_FILE'" # Pregunta por la revisión del archivo de cambios generado From a5f75cdf5bbef09a5ddd9ba0fc41fedadd02b754 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 03:14:25 +0200 Subject: [PATCH 070/224] =?UTF-8?q?=F0=9F=9A=A9=20Asegura=20compilaci?= =?UTF-8?q?=C3=B3n=20para=20publicar=20nueva=20versi=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 27325b88..76db954d 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -21,4 +21,4 @@ proc-macro = true proc-macro2 = "1.0.95" proc-macro2-diagnostics = { version = "0.10.1", default-features = false } quote = "1.0.40" -syn = { version = "2.0.104", features = ["full"] } +syn = { version = "2.0.104", features = ["full", "extra-traits"] } From 1ee9339bca7f621fa9628de4393eb2d7e53661b0 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 03:15:15 +0200 Subject: [PATCH 071/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-macros=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-macros/CHANGELOG.md | 13 +++++++++++++ helpers/pagetop-macros/Cargo.toml | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 helpers/pagetop-macros/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 6b894b5d..baba4a51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1608,7 +1608,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.0.18" +version = "0.1.0" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", diff --git a/Cargo.toml b/Cargo.toml index dcf191d6..860d4ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,4 +72,4 @@ authors = ["Manuel Cillero "] static-files = "0.2.5" pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } -pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } +pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } diff --git a/helpers/pagetop-macros/CHANGELOG.md b/helpers/pagetop-macros/CHANGELOG.md new file mode 100644 index 00000000..b01b9883 --- /dev/null +++ b/helpers/pagetop-macros/CHANGELOG.md @@ -0,0 +1,13 @@ +# CHANGELOG + +Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado +en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo +las reglas del [Versionado Semántico](https://semver.org/lang/es/). + +Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, +correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o +internos pueden omitirse si no afectan al uso del proyecto. + +## 0.1.0 (2025-08-06) + +- Versión inicial diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 76db954d..45ba3283 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.0.18" +version = "0.1.0" edition = "2021" description = """ From 96bc0776166d9c2f1495bacb7b479c8b86f766ec Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 03:22:20 +0200 Subject: [PATCH 072/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 13 +++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..b01b9883 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# CHANGELOG + +Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado +en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo +las reglas del [Versionado Semántico](https://semver.org/lang/es/). + +Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, +correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o +internos pueden omitirse si no afectan al uso del proyecto. + +## 0.1.0 (2025-08-06) + +- Versión inicial diff --git a/Cargo.lock b/Cargo.lock index baba4a51..e9ba9fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1569,7 +1569,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.61" +version = "0.1.0" dependencies = [ "actix-files", "actix-session", diff --git a/Cargo.toml b/Cargo.toml index 860d4ee5..229c989c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.61" +version = "0.1.0" edition = "2021" description = """ From bbc0a596513a9ed90170c6c08d97bbc6917ac0d8 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 11:24:13 +0200 Subject: [PATCH 073/224] =?UTF-8?q?=F0=9F=A9=B9=20Corrige=20enlaces=20de?= =?UTF-8?q?=20licencia=20en=20la=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- helpers/pagetop-build/README.md | 2 +- helpers/pagetop-build/src/lib.rs | 2 +- helpers/pagetop-macros/README.md | 2 +- helpers/pagetop-macros/src/lib.rs | 2 +- src/lib.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 87f8b8da..fe85e665 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.

-[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 27c98147..d9d52571 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -4,7 +4,7 @@

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

-[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 90898dd3..4dcd70c0 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -4,7 +4,7 @@ //! //!

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) //! [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) //! [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) //! [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 826698ad..c5006a87 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -4,7 +4,7 @@

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

-[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index fedd682f..496ce82b 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -4,7 +4,7 @@ //! //!

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) //! [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) //! [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) //! [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) diff --git a/src/lib.rs b/src/lib.rs index baa57a69..e0da3617 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! //!

Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.

//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) +//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) //! [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) //! [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) //! [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) From 53f42f9a778ecd4de9ef4f0937c7fb9a8126f965 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 6 Aug 2025 11:26:56 +0200 Subject: [PATCH 074/224] =?UTF-8?q?=F0=9F=9A=A9=20Afina=20Cargo.toml=20par?= =?UTF-8?q?a=20buscar=20la=20mejor=20categor=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- helpers/pagetop-build/Cargo.toml | 2 +- helpers/pagetop-macros/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 229c989c..3eee24d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" description = """ Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables. """ -categories = ["web-programming", "gui", "development-tools", "asynchronous"] +categories = ["web-programming::http-server"] keywords = ["pagetop", "web", "framework", "frontend", "ssr"] repository.workspace = true diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 4769631b..ba911efc 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -7,7 +7,7 @@ description = """ Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop. """ -categories = ["development-tools::build-utils", "web-programming"] +categories = ["development-tools::build-utils"] keywords = ["pagetop", "build", "assets", "resources", "static"] repository.workspace = true diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 45ba3283..64f1cf4c 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" description = """ Una colección de macros que mejoran la experiencia de desarrollo con PageTop. """ -categories = ["development-tools::procedural-macro-helpers", "web-programming"] +categories = ["development-tools::procedural-macro-helpers"] keywords = ["pagetop", "macros", "proc-macros", "codegen"] repository.workspace = true From 0ed4b6188048955740a8687772aabfb87df49473 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 8 Aug 2025 23:58:07 +0200 Subject: [PATCH 075/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20librer=C3=ADa?= =?UTF-8?q?=20para=20gestionar=20recursos=20est=C3=A1ticos=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.cillero.es/manuelcillero/pagetop/pulls/1 Co-authored-by: Manuel Cillero Co-committed-by: Manuel Cillero --- Cargo.lock | 48 +-- Cargo.toml | 9 +- helpers/pagetop-build/Cargo.toml | 2 +- helpers/pagetop-build/src/lib.rs | 2 +- helpers/pagetop-statics/Cargo.toml | 33 ++ helpers/pagetop-statics/LICENSE-APACHE | 201 +++++++++ helpers/pagetop-statics/LICENSE-MIT | 21 + helpers/pagetop-statics/README.md | 50 +++ helpers/pagetop-statics/build.rs | 43 ++ helpers/pagetop-statics/src/lib.rs | 34 ++ helpers/pagetop-statics/src/resource.rs | 249 +++++++++++ helpers/pagetop-statics/src/resource_dir.rs | 118 ++++++ helpers/pagetop-statics/src/resource_files.rs | 396 ++++++++++++++++++ helpers/pagetop-statics/src/sets.rs | 184 ++++++++ helpers/pagetop-statics/tests/file1.txt | 0 helpers/pagetop-statics/tests/file2.txt | 0 helpers/pagetop-statics/tests/file3.info | 0 helpers/pagetop-statics/tests/index.html | 10 + src/lib.rs | 8 +- src/service.rs | 4 +- 20 files changed, 1371 insertions(+), 41 deletions(-) create mode 100644 helpers/pagetop-statics/Cargo.toml create mode 100644 helpers/pagetop-statics/LICENSE-APACHE create mode 100644 helpers/pagetop-statics/LICENSE-MIT create mode 100644 helpers/pagetop-statics/README.md create mode 100644 helpers/pagetop-statics/build.rs create mode 100644 helpers/pagetop-statics/src/lib.rs create mode 100644 helpers/pagetop-statics/src/resource.rs create mode 100644 helpers/pagetop-statics/src/resource_dir.rs create mode 100644 helpers/pagetop-statics/src/resource_files.rs create mode 100644 helpers/pagetop-statics/src/sets.rs create mode 100644 helpers/pagetop-statics/tests/file1.txt create mode 100644 helpers/pagetop-statics/tests/file2.txt create mode 100644 helpers/pagetop-statics/tests/file3.info create mode 100644 helpers/pagetop-statics/tests/index.html diff --git a/Cargo.lock b/Cargo.lock index e9ba9fb8..2f15a489 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,18 +225,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix-web-static-files" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f" -dependencies = [ - "actix-web", - "derive_more 0.99.20", - "futures-util", - "static-files", -] - [[package]] name = "addr2line" version = "0.24.2" @@ -658,9 +646,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1076,9 +1064,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -1574,7 +1562,6 @@ dependencies = [ "actix-files", "actix-session", "actix-web", - "actix-web-static-files", "chrono", "colored", "concat-string", @@ -1584,10 +1571,10 @@ dependencies = [ "itoa", "pagetop-build", "pagetop-macros", + "pagetop-statics", "parking_lot", "pastey", "serde", - "static-files", "substring", "tempfile", "terminal_size", @@ -1603,7 +1590,7 @@ name = "pagetop-build" version = "0.1.1" dependencies = [ "grass", - "static-files", + "pagetop-statics", ] [[package]] @@ -1616,6 +1603,18 @@ dependencies = [ "syn", ] +[[package]] +name = "pagetop-statics" +version = "0.0.1" +dependencies = [ + "actix-web", + "change-detection", + "derive_more 0.99.20", + "futures-util", + "mime_guess", + "path-slash", +] + [[package]] name = "parking_lot" version = "0.12.4" @@ -2178,17 +2177,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "static-files" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c425c07353535ef55b45420f5a8b0a397cd9bc3d7e5236497ca0d90604aa9b" -dependencies = [ - "change-detection", - "mime_guess", - "path-slash", -] - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 3eee24d6..36007421 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,15 +34,14 @@ tracing-actix-web = "0.7.19" fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } -actix-web = "4.11.0" +actix-web = { workspace = true, default-features = true } actix-session = { version = "0.10.1", features = ["cookie-session"] } actix-web-files = { package = "actix-files", version = "0.6.6" } -actix-web-static-files = "4.0.1" -static-files.workspace = true serde = { version = "1.0", features = ["derive"] } pagetop-macros.workspace = true +pagetop-statics.workspace = true [features] default = [] @@ -60,6 +59,7 @@ resolver = "2" members = [ "helpers/pagetop-build", "helpers/pagetop-macros", + "helpers/pagetop-statics", ] [workspace.package] @@ -69,7 +69,8 @@ license = "MIT OR Apache-2.0" authors = ["Manuel Cillero "] [workspace.dependencies] -static-files = "0.2.5" +actix-web = { version = "4.11.0", default-features = false } pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } +pagetop-statics = { version = "0.0", path = "helpers/pagetop-statics" } diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index ba911efc..41e826e9 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -17,4 +17,4 @@ authors.workspace = true [dependencies] grass = "0.13.4" -static-files.workspace = true +pagetop-statics.workspace = true diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 4dcd70c0..b360377f 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -123,7 +123,7 @@ )] use grass::{from_path, Options, OutputStyle}; -use static_files::{resource_dir, ResourceDir}; +use pagetop_statics::{resource_dir, ResourceDir}; use std::fs::{create_dir_all, remove_dir_all, File}; use std::io::Write; diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml new file mode 100644 index 00000000..f41cec76 --- /dev/null +++ b/helpers/pagetop-statics/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pagetop-statics" +version = "0.0.1" +edition = "2021" + +description = """ + Librería para automatizar la recopilación de recursos estáticos en PageTop. +""" +categories = ["development-tools::build-utils"] +keywords = ["pagetop", "build", "static", "resources", "file"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[features] +default = ["change-detection"] +sort = [] + +[dependencies] +change-detection = { version = "1.2", optional = true } +mime_guess = "2.0" +path-slash = "0.1" + +actix-web.workspace = true +derive_more = "0.99.17" +futures-util = { version = "0.3", default-features = false, features = ["std"] } + +[build-dependencies] +change-detection = { version = "1.2", optional = true } +mime_guess = "2.0" +path-slash = "0.1" diff --git a/helpers/pagetop-statics/LICENSE-APACHE b/helpers/pagetop-statics/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/helpers/pagetop-statics/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/helpers/pagetop-statics/LICENSE-MIT b/helpers/pagetop-statics/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/helpers/pagetop-statics/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md new file mode 100644 index 00000000..c053e955 --- /dev/null +++ b/helpers/pagetop-statics/README.md @@ -0,0 +1,50 @@ +
+ +

PageTop Statics

+ +

Librería para automatizar la recopilación de recursos estáticos en PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) + +
+ +## Descripción general + +Permite a `PageTop` incluir archivos estáticos en el ejecutable de la aplicación para servirlos de +forma eficiente vía web, con detección de cambios que optimiza el tiempo de compilación. + +Para ello, reúne el código de los *crates* [static-files](https://crates.io/crates/static_files) +(versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y +[actix-web-static-files](https://crates.io/crates/actix_web_static_files) (versión +[4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por +[Alexander Korolev](https://crates.io/users/kilork). + +Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar +`static-files` manualmente como dependencia en su `Cargo.toml`. + +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# 🚧 Advertencia + +`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +# 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/helpers/pagetop-statics/build.rs b/helpers/pagetop-statics/build.rs new file mode 100644 index 00000000..fcd009c9 --- /dev/null +++ b/helpers/pagetop-statics/build.rs @@ -0,0 +1,43 @@ +#![allow(dead_code)] +#![doc(html_no_source)] +#![allow(clippy::needless_doctest_main)] + +mod resource { + include!("src/resource.rs"); +} +use resource::generate_resources_mapping; +mod resource_dir { + include!("src/resource_dir.rs"); +} +use resource_dir::resource_dir; +mod sets { + include!("src/sets.rs"); +} +use sets::{generate_resources_sets, SplitByCount}; + +use std::{env, path::Path}; + +fn main() -> std::io::Result<()> { + resource_dir("./tests").build_test()?; + + let out_dir = env::var("OUT_DIR").unwrap(); + + generate_resources_mapping( + "./tests", + None, + Path::new(&out_dir).join("generated_mapping.rs"), + "pagetop_statics", + )?; + + generate_resources_sets( + "./tests", + None, + Path::new(&out_dir).join("generated_sets.rs"), + "sets", + "generate", + &mut SplitByCount::new(2), + "pagetop_statics", + )?; + + Ok(()) +} diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs new file mode 100644 index 00000000..fffd1ae3 --- /dev/null +++ b/helpers/pagetop-statics/src/lib.rs @@ -0,0 +1,34 @@ +//!
+//! +//!

PageTop Statics

+//! +//!

Librería para automatizar la recopilación de recursos estáticos en PageTop.

+//! +//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +//! +//!
+//! +//! ## Sobre PageTop +//! +//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la +//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles +//! y configurables, basadas en HTML, CSS y JavaScript. + +#![doc(test(no_crate_inject))] +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] +#![allow(clippy::needless_doctest_main)] + +/// Resource definition and single module based generation. +pub mod resource; +pub use resource::Resource as StaticResource; + +mod resource_dir; +pub use resource_dir::{resource_dir, ResourceDir}; + +mod resource_files; +pub use resource_files::{ResourceFiles, UriSegmentError}; + +/// Support for module based generations. Use it for large data sets (more than 128 Mb). +pub mod sets; diff --git a/helpers/pagetop-statics/src/resource.rs b/helpers/pagetop-statics/src/resource.rs new file mode 100644 index 00000000..0b81969e --- /dev/null +++ b/helpers/pagetop-statics/src/resource.rs @@ -0,0 +1,249 @@ +use path_slash::PathExt; +use std::{ + fs::{self, File, Metadata}, + io::{self, Write}, + path::{Path, PathBuf}, + time::SystemTime, +}; + +/// Static files resource. +pub struct Resource { + pub data: &'static [u8], + pub modified: u64, + pub mime_type: &'static str, +} + +/// Used internally in generated functions. +#[inline] +pub fn new_resource(data: &'static [u8], modified: u64, mime_type: &'static str) -> Resource { + Resource { + data, + modified, + mime_type, + } +} + +pub(crate) const DEFAULT_VARIABLE_NAME: &str = "r"; + +/// Generate resources for `project_dir` using `filter`. +/// Result saved in `generated_filename` and function named as `fn_name`. +/// +/// in `build.rs`: +/// ```rust +/// use std::{env, path::Path}; +/// use pagetop_statics::resource::generate_resources; +/// +/// fn main() { +/// let out_dir = env::var("OUT_DIR").unwrap(); +/// let generated_filename = Path::new(&out_dir).join("generated.rs"); +/// generate_resources("./tests", None, generated_filename, "generate", "pagetop_statics").unwrap(); +/// } +/// ``` +/// +/// in `main.rs`: +/// ```rust +/// include!(concat!(env!("OUT_DIR"), "/generated.rs")); +/// +/// fn main() { +/// let generated_file = generate(); +/// +/// assert_eq!(generated_file.len(), 4); +/// } +/// ``` +pub fn generate_resources, G: AsRef>( + project_dir: P, + filter: Option bool>, + generated_filename: G, + fn_name: &str, + crate_name: &str, +) -> io::Result<()> { + let resources = collect_resources(&project_dir, filter)?; + + let mut f = File::create(&generated_filename)?; + + generate_function_header(&mut f, fn_name, crate_name)?; + generate_uses(&mut f, crate_name)?; + + generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?; + generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?; + generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?; + + generate_function_end(&mut f)?; + + Ok(()) +} + +/// Generate resource mapping for `project_dir` using `filter`. +/// Result saved in `generated_filename` as anonymous block which returns HashMap<&'static str, Resource>. +/// +/// in `build.rs`: +/// ```rust +/// +/// use std::{env, path::Path}; +/// use pagetop_statics::resource::generate_resources_mapping; +/// +/// fn main() { +/// let out_dir = env::var("OUT_DIR").unwrap(); +/// let generated_filename = Path::new(&out_dir).join("generated_mapping.rs"); +/// generate_resources_mapping("./tests", None, generated_filename, "pagetop_statics").unwrap(); +/// } +/// ``` +/// +/// in `main.rs`: +/// ```rust +/// use std::collections::HashMap; +/// +/// use pagetop_statics::StaticResource; +/// +/// fn generate_mapping() -> HashMap<&'static str, StaticResource> { +/// include!(concat!(env!("OUT_DIR"), "/generated_mapping.rs")) +/// } +/// +/// fn main() { +/// let generated_file = generate_mapping(); +/// +/// assert_eq!(generated_file.len(), 4); +/// +/// } +/// ``` +pub fn generate_resources_mapping, G: AsRef>( + project_dir: P, + filter: Option bool>, + generated_filename: G, + crate_name: &str, +) -> io::Result<()> { + let resources = collect_resources(&project_dir, filter)?; + + let mut f = File::create(&generated_filename)?; + writeln!(f, "{{")?; + + generate_uses(&mut f, crate_name)?; + + generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?; + + generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?; + + generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?; + + writeln!(f, "}}")?; + Ok(()) +} + +#[cfg(not(feature = "sort"))] +pub(crate) fn collect_resources>( + path: P, + filter: Option bool>, +) -> io::Result> { + collect_resources_nested(path, filter) +} + +#[cfg(feature = "sort")] +pub(crate) fn collect_resources>( + path: P, + filter: Option bool>, +) -> io::Result> { + let mut resources = collect_resources_nested(path, filter)?; + resources.sort_by(|a, b| a.0.cmp(&b.0)); + Ok(resources) +} + +#[inline] +fn collect_resources_nested>( + path: P, + filter: Option bool>, +) -> io::Result> { + let mut result = vec![]; + + for entry in fs::read_dir(&path)? { + let entry = entry?; + let path = entry.path(); + + if let Some(ref filter) = filter { + if !filter(path.as_ref()) { + continue; + } + } + + if path.is_dir() { + let nested = collect_resources(path, filter)?; + result.extend(nested); + } else { + result.push((path, entry.metadata()?)); + } + } + + Ok(result) +} + +pub(crate) fn generate_resource_inserts, W: Write>( + f: &mut W, + project_dir: &P, + variable_name: &str, + resources: Vec<(PathBuf, Metadata)>, +) -> io::Result<()> { + for resource in &resources { + generate_resource_insert(f, project_dir, variable_name, resource)?; + } + Ok(()) +} + +#[allow(clippy::unnecessary_debug_formatting)] +pub(crate) fn generate_resource_insert, W: Write>( + f: &mut W, + project_dir: &P, + variable_name: &str, + resource: &(PathBuf, Metadata), +) -> io::Result<()> { + let (path, metadata) = resource; + let abs_path = path.canonicalize()?; + let key_path = path.strip_prefix(project_dir).unwrap().to_slash().unwrap(); + + let modified = if let Ok(Ok(modified)) = metadata + .modified() + .map(|x| x.duration_since(SystemTime::UNIX_EPOCH)) + { + modified.as_secs() + } else { + 0 + }; + let mime_type = mime_guess::MimeGuess::from_path(path).first_or_octet_stream(); + writeln!( + f, + "{}.insert({:?},n(i!({:?}),{:?},{:?}));", + variable_name, &key_path, &abs_path, modified, &mime_type, + ) +} + +pub(crate) fn generate_function_header( + f: &mut F, + fn_name: &str, + crate_name: &str, +) -> io::Result<()> { + writeln!( + f, + "#[allow(clippy::unreadable_literal)] pub fn {fn_name}() -> ::std::collections::HashMap<&'static str, ::{crate_name}::StaticResource> {{", + ) +} + +pub(crate) fn generate_function_end(f: &mut F) -> io::Result<()> { + writeln!(f, "}}") +} + +pub(crate) fn generate_uses(f: &mut F, crate_name: &str) -> io::Result<()> { + writeln!( + f, + "use ::{crate_name}::resource::new_resource as n; +use ::std::include_bytes as i;", + ) +} + +pub(crate) fn generate_variable_header(f: &mut F, variable_name: &str) -> io::Result<()> { + writeln!( + f, + "let mut {variable_name} = ::std::collections::HashMap::new();", + ) +} + +pub(crate) fn generate_variable_return(f: &mut F, variable_name: &str) -> io::Result<()> { + writeln!(f, "{variable_name}") +} diff --git a/helpers/pagetop-statics/src/resource_dir.rs b/helpers/pagetop-statics/src/resource_dir.rs new file mode 100644 index 00000000..805e1ed4 --- /dev/null +++ b/helpers/pagetop-statics/src/resource_dir.rs @@ -0,0 +1,118 @@ +use super::sets::{generate_resources_sets, SplitByCount}; +use std::{ + env, io, + path::{Path, PathBuf}, +}; + +/// Generate resources for `resource_dir`. +/// +/// ```rust,no_run +/// // Generate resources for ./tests dir with file name generated.rs +/// // stored in path defined by OUT_DIR environment variable. +/// // Function name is 'generate' +/// use pagetop_statics::resource_dir; +/// +/// resource_dir("./tests").build().unwrap(); +/// ``` +pub fn resource_dir>(resource_dir: P) -> ResourceDir { + ResourceDir { + resource_dir: resource_dir.as_ref().into(), + ..Default::default() + } +} + +/// Resource dir. +/// +/// A builder structure allows to change default settings for: +/// - file filter +/// - generated file name +/// - generated function name +#[derive(Default)] +pub struct ResourceDir { + pub(crate) resource_dir: PathBuf, + pub(crate) filter: Option bool>, + pub(crate) generated_filename: Option, + pub(crate) generated_fn: Option, + pub(crate) module_name: Option, + pub(crate) count_per_module: Option, +} + +pub const DEFAULT_MODULE_NAME: &str = "sets"; +pub const DEFAULT_COUNT_PER_MODULE: usize = 256; + +impl ResourceDir { + /// Generates resources for current configuration. + pub fn build(self) -> io::Result<()> { + self.internal_build("pagetop") + } + + /// Generates resources for testing current configuration. + #[allow(dead_code)] + pub(crate) fn build_test(self) -> io::Result<()> { + self.internal_build("pagetop_statics") + } + + fn internal_build(self, crate_name: &str) -> io::Result<()> { + let generated_filename = self.generated_filename.unwrap_or_else(|| { + let out_dir = env::var("OUT_DIR").unwrap(); + + Path::new(&out_dir).join("generated.rs") + }); + let generated_fn = self.generated_fn.unwrap_or_else(|| "generate".into()); + + let module_name = self + .module_name + .unwrap_or_else(|| format!("{}_{}", &generated_fn, DEFAULT_MODULE_NAME)); + + let count_per_module = self.count_per_module.unwrap_or(DEFAULT_COUNT_PER_MODULE); + + generate_resources_sets( + &self.resource_dir, + self.filter, + &generated_filename, + module_name.as_str(), + &generated_fn, + &mut SplitByCount::new(count_per_module), + crate_name, + ) + } + + /// Sets the file filter. + pub fn with_filter(&mut self, filter: fn(p: &Path) -> bool) -> &mut Self { + self.filter = Some(filter); + self + } + + /// Sets the generated filename. + pub fn with_generated_filename>(&mut self, generated_filename: P) -> &mut Self { + self.generated_filename = Some(generated_filename.as_ref().into()); + self + } + + /// Sets the generated function name. + pub fn with_generated_fn(&mut self, generated_fn: S) -> &mut Self + where + S: Into, + { + self.generated_fn = Some(generated_fn.into()); + self + } + + /// Sets the generated module name. + /// + /// Default value is based on generated function name and the suffix "sets". + /// Generated module would be overriden by each call. + pub fn with_module_name(&mut self, module_name: S) -> &mut Self + where + S: Into, + { + self.module_name = Some(module_name.into()); + self + } + + /// Sets maximal count of files per module. + pub fn with_count_per_module(&mut self, count_per_module: usize) -> &mut Self { + self.count_per_module = Some(count_per_module); + self + } +} diff --git a/helpers/pagetop-statics/src/resource_files.rs b/helpers/pagetop-statics/src/resource_files.rs new file mode 100644 index 00000000..b487bca9 --- /dev/null +++ b/helpers/pagetop-statics/src/resource_files.rs @@ -0,0 +1,396 @@ +use super::resource::Resource; +use actix_web::{ + dev::{ + always_ready, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, + ServiceRequest, ServiceResponse, + }, + error::Error, + guard::{Guard, GuardContext}, + http::{ + header::{self, ContentType}, + Method, StatusCode, + }, + HttpMessage, HttpRequest, HttpResponse, ResponseError, +}; +use derive_more::{Deref, Display, Error}; +use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use std::{collections::HashMap, ops::Deref, rc::Rc}; + +/// Static resource files handling +/// +/// `ResourceFiles` service must be registered with `App::service` method. +/// +/// ```rust +/// use std::collections::HashMap; +/// +/// use actix_web::App; +/// +/// fn main() { +/// // serve root directory with default options: +/// // - resolve index.html +/// let files: HashMap<&'static str, pagetop_statics::StaticResource> = HashMap::new(); +/// let app = App::new() +/// .service(pagetop_statics::ResourceFiles::new("/", files)); +/// // or subpath with additional option to not resolve index.html +/// let files: HashMap<&'static str, pagetop_statics::StaticResource> = HashMap::new(); +/// let app = App::new() +/// .service(pagetop_statics::ResourceFiles::new("/imgs", files) +/// .do_not_resolve_defaults()); +/// } +/// ``` +#[allow(clippy::needless_doctest_main)] +pub struct ResourceFiles { + not_resolve_defaults: bool, + use_guard: bool, + not_found_resolves_to: Option, + inner: Rc, +} + +pub struct ResourceFilesInner { + path: String, + files: HashMap<&'static str, Resource>, +} + +const INDEX_HTML: &str = "index.html"; + +impl ResourceFiles { + pub fn new(path: &str, files: HashMap<&'static str, Resource>) -> Self { + let inner = ResourceFilesInner { + path: path.into(), + files, + }; + Self { + inner: Rc::new(inner), + not_resolve_defaults: false, + not_found_resolves_to: None, + use_guard: false, + } + } + + /// By default trying to resolve '.../' to '.../index.html' if it exists. + /// Turn off this resolution by calling this function. + pub fn do_not_resolve_defaults(mut self) -> Self { + self.not_resolve_defaults = true; + self + } + + /// Resolves not found references to this path. + /// + /// This can be useful for angular-like applications. + pub fn resolve_not_found_to(mut self, path: S) -> Self { + self.not_found_resolves_to = Some(path.to_string()); + self + } + + /// Resolves not found references to root path. + /// + /// This can be useful for angular-like applications. + pub fn resolve_not_found_to_root(self) -> Self { + self.resolve_not_found_to(INDEX_HTML) + } + + /// If this is called, we will use an [actix_web::guard::Guard] to check if this request should be handled. + /// If set to true, we skip using the handler for files that haven't been found, instead of sending 404s. + /// Would be ignored, if `resolve_not_found_to` or `resolve_not_found_to_root` is used. + /// + /// Can be useful if you want to share files on a (sub)path that's also used by a different route handler. + pub fn skip_handler_when_not_found(mut self) -> Self { + self.use_guard = true; + self + } + + fn select_guard(&self) -> Box { + if self.not_resolve_defaults { + Box::new(NotResolveDefaultsGuard::from(self)) + } else { + Box::new(ResolveDefaultsGuard::from(self)) + } + } +} + +impl Deref for ResourceFiles { + type Target = ResourceFilesInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +struct NotResolveDefaultsGuard { + inner: Rc, +} + +impl Guard for NotResolveDefaultsGuard { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + self.inner + .files + .contains_key(ctx.head().uri.path().trim_start_matches('/')) + } +} + +impl From<&ResourceFiles> for NotResolveDefaultsGuard { + fn from(files: &ResourceFiles) -> Self { + Self { + inner: files.inner.clone(), + } + } +} + +struct ResolveDefaultsGuard { + inner: Rc, +} + +impl Guard for ResolveDefaultsGuard { + fn check(&self, ctx: &GuardContext<'_>) -> bool { + let path = ctx.head().uri.path().trim_start_matches('/'); + self.inner.files.contains_key(path) + || ((path.is_empty() || path.ends_with('/')) + && self + .inner + .files + .contains_key((path.to_string() + INDEX_HTML).as_str())) + } +} + +impl From<&ResourceFiles> for ResolveDefaultsGuard { + fn from(files: &ResourceFiles) -> Self { + Self { + inner: files.inner.clone(), + } + } +} + +impl HttpServiceFactory for ResourceFiles { + fn register(self, config: &mut AppService) { + let prefix = self.path.trim_start_matches('/'); + let rdef = if config.is_root() { + ResourceDef::root_prefix(prefix) + } else { + ResourceDef::prefix(prefix) + }; + let guards = if self.use_guard && self.not_found_resolves_to.is_none() { + Some(vec![self.select_guard()]) + } else { + None + }; + config.register_service(rdef, guards, self, None); + } +} + +impl ServiceFactory for ResourceFiles { + type Config = (); + type Response = ServiceResponse; + type Error = Error; + type Service = ResourceFilesService; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; + + fn new_service(&self, _: ()) -> Self::Future { + ok(ResourceFilesService { + resolve_defaults: !self.not_resolve_defaults, + not_found_resolves_to: self.not_found_resolves_to.clone(), + inner: self.inner.clone(), + }) + .boxed_local() + } +} + +#[derive(Deref)] +pub struct ResourceFilesService { + resolve_defaults: bool, + not_found_resolves_to: Option, + #[deref] + inner: Rc, +} + +impl Service for ResourceFilesService { + type Response = ServiceResponse; + type Error = Error; + type Future = Ready>; + + always_ready!(); + + fn call(&self, req: ServiceRequest) -> Self::Future { + match *req.method() { + Method::HEAD | Method::GET => (), + _ => { + return ok(ServiceResponse::new( + req.into_parts().0, + HttpResponse::MethodNotAllowed() + .insert_header(ContentType::plaintext()) + .insert_header((header::ALLOW, "GET, HEAD")) + .body("This resource only supports GET and HEAD."), + )); + } + } + + let req_path = req.match_info().unprocessed(); + let mut item = self.files.get(req_path); + + if item.is_none() + && self.resolve_defaults + && (req_path.is_empty() || req_path.ends_with('/')) + { + let index_req_path = req_path.to_string() + INDEX_HTML; + item = self.files.get(index_req_path.trim_start_matches('/')); + } + + let (req, response) = if item.is_some() { + let (req, _) = req.into_parts(); + let response = respond_to(&req, item); + (req, response) + } else { + let real_path = match get_pathbuf(req_path) { + Ok(item) => item, + Err(e) => return ok(req.error_response(e)), + }; + + let (req, _) = req.into_parts(); + + let mut item = self.files.get(real_path.as_str()); + + if item.is_none() && self.not_found_resolves_to.is_some() { + let not_found_path = self.not_found_resolves_to.as_ref().unwrap(); + item = self.files.get(not_found_path.as_str()); + } + + let response = respond_to(&req, item); + (req, response) + }; + + ok(ServiceResponse::new(req, response)) + } +} + +fn respond_to(req: &HttpRequest, item: Option<&Resource>) -> HttpResponse { + if let Some(file) = item { + let etag = Some(header::EntityTag::new_strong(format!( + "{:x}:{:x}", + file.data.len(), + file.modified + ))); + + let precondition_failed = !any_match(etag.as_ref(), req); + + let not_modified = !none_match(etag.as_ref(), req); + + let mut resp = HttpResponse::build(StatusCode::OK); + resp.insert_header((header::CONTENT_TYPE, file.mime_type)); + + if let Some(etag) = etag { + resp.insert_header(header::ETag(etag)); + } + + if precondition_failed { + return resp.status(StatusCode::PRECONDITION_FAILED).finish(); + } else if not_modified { + return resp.status(StatusCode::NOT_MODIFIED).finish(); + } + + resp.body(file.data) + } else { + HttpResponse::NotFound().body("Not found") + } +} + +/// Returns true if `req` has no `If-Match` header or one which matches `etag`. +fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + None | Some(header::IfMatch::Any) => true, + Some(header::IfMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.strong_eq(some_etag) { + return true; + } + } + } + false + } + } +} + +/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. +fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + Some(header::IfNoneMatch::Any) => false, + Some(header::IfNoneMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.weak_eq(some_etag) { + return false; + } + } + } + true + } + None => true, + } +} + +/// Error type representing invalid characters in a URI path segment. +/// +/// This enum is used to report specific formatting errors in individual segments of a URI path, +/// such as starting, ending, or containing disallowed characters. Each variant wraps the offending +/// character that caused the error. +#[derive(Debug, PartialEq, Display, Error)] +pub enum UriSegmentError { + /// The segment started with the wrapped invalid character. + #[display(fmt = "The segment started with the wrapped invalid character")] + BadStart(#[error(not(source))] char), + + /// The segment contained the wrapped invalid character. + #[display(fmt = "The segment contained the wrapped invalid character")] + BadChar(#[error(not(source))] char), + + /// The segment ended with the wrapped invalid character. + #[display(fmt = "The segment ended with the wrapped invalid character")] + BadEnd(#[error(not(source))] char), +} + +#[cfg(test)] +mod tests_error_impl { + use super::*; + + fn assert_send_and_sync() {} + + #[test] + fn test_error_impl() { + // ensure backwards compatibility when migrating away from failure + assert_send_and_sync::(); + } +} + +/// Return `BadRequest` for `UriSegmentError` +impl ResponseError for UriSegmentError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST) + } +} + +fn get_pathbuf(path: &str) -> Result { + let mut buf = Vec::new(); + for segment in path.split('/') { + if segment == ".." { + buf.pop(); + } else if segment.starts_with('.') { + return Err(UriSegmentError::BadStart('.')); + } else if segment.starts_with('*') { + return Err(UriSegmentError::BadStart('*')); + } else if segment.ends_with(':') { + return Err(UriSegmentError::BadEnd(':')); + } else if segment.ends_with('>') { + return Err(UriSegmentError::BadEnd('>')); + } else if segment.ends_with('<') { + return Err(UriSegmentError::BadEnd('<')); + } else if segment.is_empty() { + continue; + } else if cfg!(windows) && segment.contains('\\') { + return Err(UriSegmentError::BadChar('\\')); + } else { + buf.push(segment) + } + } + + Ok(buf.join("/")) +} diff --git a/helpers/pagetop-statics/src/sets.rs b/helpers/pagetop-statics/src/sets.rs new file mode 100644 index 00000000..1d9299df --- /dev/null +++ b/helpers/pagetop-statics/src/sets.rs @@ -0,0 +1,184 @@ +use std::{ + fs::{self, File, Metadata}, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +use super::resource::{ + collect_resources, generate_function_end, generate_function_header, generate_resource_insert, + generate_uses, generate_variable_header, generate_variable_return, DEFAULT_VARIABLE_NAME, +}; + +/// Defines the split strategie. +pub trait SetSplitStrategie { + /// Register next file from resources. + fn register(&mut self, path: &Path, metadata: &Metadata); + /// Determine, should we split modules now. + fn should_split(&self) -> bool; + /// Resets internal counters after split. + fn reset(&mut self); +} + +/// Split modules by files count. +pub struct SplitByCount { + current: usize, + max: usize, +} + +impl SplitByCount { + pub fn new(max: usize) -> Self { + Self { current: 0, max } + } +} + +impl SetSplitStrategie for SplitByCount { + fn register(&mut self, _path: &Path, _metadata: &Metadata) { + self.current += 1; + } + + fn should_split(&self) -> bool { + self.current >= self.max + } + + fn reset(&mut self) { + self.current = 0; + } +} + +/// Generate resources for `project_dir` using `filter` +/// breaking them into separate modules using `set_split_strategie` (recommended for large > 128 Mb setups). +/// +/// Result saved in module named `module_name`. It exports +/// only one function named `fn_name`. It is then exported from +/// `generated_filename`. `generated_filename` is also used to determine +/// the parent directory for the module. +/// +/// in `build.rs`: +/// ```rust +/// +/// use std::{env, path::Path}; +/// use pagetop_statics::sets::{generate_resources_sets, SplitByCount}; +/// +/// fn main() { +/// let out_dir = env::var("OUT_DIR").unwrap(); +/// let generated_filename = Path::new(&out_dir).join("generated_sets.rs"); +/// generate_resources_sets( +/// "./tests", +/// None, +/// generated_filename, +/// "sets", +/// "generate", +/// &mut SplitByCount::new(2), +/// "pagetop_statics", +/// ) +/// .unwrap(); +/// } +/// ``` +/// +/// in `main.rs`: +/// ```rust +/// include!(concat!(env!("OUT_DIR"), "/generated_sets.rs")); +/// +/// fn main() { +/// let generated_file = generate(); +/// +/// assert_eq!(generated_file.len(), 4); +/// +/// } +/// ``` +pub fn generate_resources_sets( + project_dir: P, + filter: Option bool>, + generated_filename: G, + module_name: &str, + fn_name: &str, + set_split_strategie: &mut S, + crate_name: &str, +) -> io::Result<()> +where + P: AsRef, + G: AsRef, + S: SetSplitStrategie, +{ + let resources = collect_resources(&project_dir, filter)?; + + let mut generated_file = File::create(&generated_filename)?; + + let module_dir = generated_filename.as_ref().parent().map_or_else( + || PathBuf::from(module_name), + |parent| parent.join(module_name), + ); + fs::create_dir_all(&module_dir)?; + + let mut module_file = File::create(module_dir.join("mod.rs"))?; + + generate_uses(&mut module_file, crate_name)?; + writeln!( + module_file, + " +use ::{crate_name}::StaticResource; +use ::std::collections::HashMap;" + )?; + + let mut modules_count = 1; + + let mut set_file = create_set_module_file(&module_dir, modules_count)?; + let mut should_split = set_split_strategie.should_split(); + + for resource in &resources { + let (path, metadata) = &resource; + if should_split { + set_split_strategie.reset(); + modules_count += 1; + generate_function_end(&mut set_file)?; + set_file = create_set_module_file(&module_dir, modules_count)?; + } + set_split_strategie.register(path, metadata); + should_split = set_split_strategie.should_split(); + + generate_resource_insert(&mut set_file, &project_dir, DEFAULT_VARIABLE_NAME, resource)?; + } + + generate_function_end(&mut set_file)?; + + for module_index in 1..=modules_count { + writeln!(module_file, "mod set_{module_index};")?; + } + + generate_function_header(&mut module_file, fn_name, crate_name)?; + + generate_variable_header(&mut module_file, DEFAULT_VARIABLE_NAME)?; + + for module_index in 1..=modules_count { + writeln!( + module_file, + "set_{module_index}::generate(&mut {DEFAULT_VARIABLE_NAME});", + )?; + } + + generate_variable_return(&mut module_file, DEFAULT_VARIABLE_NAME)?; + + generate_function_end(&mut module_file)?; + + writeln!( + generated_file, + "mod {module_name}; +pub use {module_name}::{fn_name};", + )?; + + Ok(()) +} + +fn create_set_module_file(module_dir: &Path, module_index: usize) -> io::Result { + let mut set_module = File::create(module_dir.join(format!("set_{module_index}.rs")))?; + + writeln!( + set_module, + "#[allow(clippy::wildcard_imports)] +use super::*; +#[allow(clippy::unreadable_literal)] +pub(crate) fn generate({DEFAULT_VARIABLE_NAME}: &mut HashMap<&'static str, StaticResource>) {{", + )?; + + Ok(set_module) +} diff --git a/helpers/pagetop-statics/tests/file1.txt b/helpers/pagetop-statics/tests/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/helpers/pagetop-statics/tests/file2.txt b/helpers/pagetop-statics/tests/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/helpers/pagetop-statics/tests/file3.info b/helpers/pagetop-statics/tests/file3.info new file mode 100644 index 00000000..e69de29b diff --git a/helpers/pagetop-statics/tests/index.html b/helpers/pagetop-statics/tests/index.html new file mode 100644 index 00000000..36f505f2 --- /dev/null +++ b/helpers/pagetop-statics/tests/index.html @@ -0,0 +1,10 @@ + + + + + + Document + + + + diff --git a/src/lib.rs b/src/lib.rs index e0da3617..bbc45302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,21 +98,23 @@ use std::ops::Deref; pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; +pub use pagetop_statics::{resource, StaticResource}; + /// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files). pub struct StaticResources { - bundle: HashMap<&'static str, static_files::Resource>, + bundle: HashMap<&'static str, StaticResource>, } impl StaticResources { /// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar /// [`pagetop_build`](https://docs.rs/pagetop-build)). - pub fn new(bundle: HashMap<&'static str, static_files::Resource>) -> Self { + pub fn new(bundle: HashMap<&'static str, StaticResource>) -> Self { Self { bundle } } } impl Deref for StaticResources { - type Target = HashMap<&'static str, static_files::Resource>; + type Target = HashMap<&'static str, StaticResource>; fn deref(&self) -> &Self::Target { &self.bundle diff --git a/src/service.rs b/src/service.rs index 89ba4966..47f1420d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,9 +8,9 @@ pub use actix_web::dev::ServiceRequest as Request; pub use actix_web::dev::ServiceResponse as Response; pub use actix_web::{cookie, http, rt, web}; pub use actix_web::{App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer}; - pub use actix_web_files::Files as ActixFiles; -pub use actix_web_static_files::ResourceFiles; + +pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; From db1ee4887557951240720899aafee3f232ad11ce Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 Aug 2025 10:17:14 +0200 Subject: [PATCH 076/224] =?UTF-8?q?=F0=9F=94=A8=20A=C3=B1ade=20soporte=20a?= =?UTF-8?q?=20changelog=20de=20`pagetop-statics`=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.cillero.es/manuelcillero/pagetop/pulls/2 Co-authored-by: Manuel Cillero Co-committed-by: Manuel Cillero --- .cargo/cliff.toml | 6 +++--- tools/changelog.sh | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.cargo/cliff.toml b/.cargo/cliff.toml index 5e4cfb85..cbbb57b6 100644 --- a/.cargo/cliff.toml +++ b/.cargo/cliff.toml @@ -21,9 +21,9 @@ body = """ {% else %} ## Pendiente de publicación {% endif %}\ -{% set base = "https://git.cillero.es/manuelcillero/pagetop" %}\ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} + {% for commit in commits %} {%- set msg = commit.message | split(pat="\n") @@ -41,8 +41,8 @@ body = """ | replace(from="📝 ", to="") | replace(from="💡 ", to="") -%} -{% set sha7 = commit.id | truncate(length=7, end="") %} -- {{ msg | trim }} ([{{ sha7 }}]({{ base }}/commit/{{ commit.id }}){% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %}) + +- {{ msg | trim }} {% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %} {% endfor %}{% endfor %} """ diff --git a/tools/changelog.sh b/tools/changelog.sh index 13333d4a..2668ec67 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -35,6 +35,10 @@ cd "$(dirname "$0")/.." || exit 1 # Determina ruta del archivo y ámbito de los archivos afectados para el crate # ------------------------------------------------------------------------------ case "$CRATE" in + pagetop-statics) + CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") + ;; pagetop-build) CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") @@ -46,6 +50,7 @@ case "$CRATE" in pagetop) CHANGELOG_FILE="CHANGELOG.md" PATH_FLAGS=( + --exclude-path "helpers/pagetop-statics/**/*" --exclude-path "helpers/pagetop-build/**/*" --exclude-path "helpers/pagetop-macros/**/*" ) From cb1b6cbdac96baabeb3f3cadf5f3b7385b74ad7f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 Aug 2025 10:27:39 +0200 Subject: [PATCH 077/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-statics=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-statics/CHANGELOG.md | 15 +++++++++++++++ helpers/pagetop-statics/Cargo.toml | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 helpers/pagetop-statics/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 2f15a489..8427dd24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ dependencies = [ [[package]] name = "pagetop-statics" -version = "0.0.1" +version = "0.1.0" dependencies = [ "actix-web", "change-detection", diff --git a/Cargo.toml b/Cargo.toml index 36007421..ff45ad3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,4 +73,4 @@ actix-web = { version = "4.11.0", default-features = false } pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } -pagetop-statics = { version = "0.0", path = "helpers/pagetop-statics" } +pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } diff --git a/helpers/pagetop-statics/CHANGELOG.md b/helpers/pagetop-statics/CHANGELOG.md new file mode 100644 index 00000000..62ad97e8 --- /dev/null +++ b/helpers/pagetop-statics/CHANGELOG.md @@ -0,0 +1,15 @@ +# CHANGELOG + +Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado +en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo +las reglas del [Versionado Semántico](https://semver.org/lang/es/). + +Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, +correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o +internos pueden omitirse si no afectan al uso del proyecto. + +## 0.1.0 (2025-08-09) + +### Añadido + +- Versión inicial diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml index f41cec76..e5c58d5e 100644 --- a/helpers/pagetop-statics/Cargo.toml +++ b/helpers/pagetop-statics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-statics" -version = "0.0.1" +version = "0.1.0" edition = "2021" description = """ From f7c1b56981e407a14ff28338b699a50ce88b045e Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 Aug 2025 10:30:53 +0200 Subject: [PATCH 078/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-build/CHANGELOG.md | 11 +++++++++++ helpers/pagetop-build/Cargo.toml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8427dd24..8f1450ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.1" +version = "0.2.0" dependencies = [ "grass", "pagetop-statics", diff --git a/Cargo.toml b/Cargo.toml index ff45ad3b..b3da8ca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,6 @@ authors = ["Manuel Cillero "] [workspace.dependencies] actix-web = { version = "4.11.0", default-features = false } -pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } +pagetop-build = { version = "0.2", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } diff --git a/helpers/pagetop-build/CHANGELOG.md b/helpers/pagetop-build/CHANGELOG.md index 22429694..4d087556 100644 --- a/helpers/pagetop-build/CHANGELOG.md +++ b/helpers/pagetop-build/CHANGELOG.md @@ -8,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.2.0 (2025-08-09) + +### Añadido + +- Añade librería propia para gestionar recursos estáticos (#1) + +### Otros cambios + +- 🩹 Corrige enlace del botón de licencia en la documentación +- 🚩 Afina Cargo.toml para buscar la mejor categoría + ## 0.1.1 (2025-08-05) - Depura la edición de CHANGELOGs y publicación de nuevas versiones diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 41e826e9..14e6000b 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = """ From 3bb8b9c0515f09009eceb03b305af9741b061dc6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 Aug 2025 10:34:38 +0200 Subject: [PATCH 079/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 ++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b01b9883..2248658d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.2.0 (2025-08-09) + +### Añadido + +- Añade librería para gestionar recursos estáticos (#1) +- Añade soporte a changelog de `pagetop-statics` (#2) + +### Otros cambios + +- 🩹 Corrige enlace del botón de licencia en la documentación +- 🚩 Afina Cargo.toml para buscar la mejor categoría + ## 0.1.0 (2025-08-06) - Versión inicial diff --git a/Cargo.lock b/Cargo.lock index 8f1450ae..22cf3086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,7 +1557,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-files", "actix-session", diff --git a/Cargo.toml b/Cargo.toml index b3da8ca9..cdeae782 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = """ From b77e47d6e4073f3c9c46a4b66919a241d7597bcb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 Aug 2025 00:48:39 +0200 Subject: [PATCH 080/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Mej?= =?UTF-8?q?ora=20funci=C3=B3n=20`from=5Fdir`=20por=20compatibilidad=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.cillero.es/manuelcillero/pagetop/pulls/3 Co-authored-by: Manuel Cillero Co-committed-by: Manuel Cillero --- helpers/pagetop-build/src/lib.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index b360377f..bb326063 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -129,8 +129,7 @@ use std::fs::{create_dir_all, remove_dir_all, File}; use std::io::Write; use std::path::Path; -/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto utilizando -/// [static_files](https://docs.rs/static-files/). +/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto. pub struct StaticFilesBundle { resource_dir: ResourceDir, } @@ -163,8 +162,19 @@ impl StaticFilesBundle { /// .build() /// } /// ``` - pub fn from_dir(dir: impl AsRef, filter: Option bool>) -> Self { - let mut resource_dir = resource_dir(dir.as_ref()); + pub fn from_dir

(dir: P, filter: Option bool>) -> Self + where + P: AsRef, + { + let dir_path = dir.as_ref(); + let dir_str = dir_path.to_str().unwrap_or_else(|| { + panic!( + "Resource directory path is not valid UTF-8: {}", + dir_path.display() + ); + }); + + let mut resource_dir = resource_dir(dir_str); // Aplica el filtro si está definido. if let Some(f) = filter { From 168d5dc648784d334f197c6aaba801ea1e6b7c74 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 Aug 2025 01:10:05 +0200 Subject: [PATCH 081/224] =?UTF-8?q?=F0=9F=93=9D=20Cambia=20el=20formato=20?= =?UTF-8?q?para=20la=20documentaci=C3=B3n=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.cillero.es/manuelcillero/pagetop/pulls/4 Co-authored-by: Manuel Cillero Co-committed-by: Manuel Cillero --- README.md | 14 +- helpers/pagetop-build/README.md | 101 ++++++++++++ helpers/pagetop-build/src/lib.rs | 240 +++++++++++++++-------------- helpers/pagetop-macros/README.md | 17 +- helpers/pagetop-macros/src/lib.rs | 49 +++--- helpers/pagetop-statics/README.md | 21 +-- helpers/pagetop-statics/src/lib.rs | 49 ++++-- src/lib.rs | 169 ++++++++++---------- 8 files changed, 404 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index fe85e665..e7fab94b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) +
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para @@ -32,7 +33,7 @@ según las necesidades de cada proyecto, incluyendo: La aplicación más sencilla de `PageTop` se ve así: -```rust +```rust,no_run use pagetop::prelude::*; #[pagetop::main] @@ -46,7 +47,7 @@ de bienvenida accesible desde un navegador local en la dirección `http://localh Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: -```rust +```rust,no_run use pagetop::prelude::*; struct HelloWorld; @@ -83,9 +84,14 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui ## Auxiliares + * **[pagetop-statics](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics)**, + es la librería que permite incluir archivos estáticos en el ejecutable de las aplicaciones + `PageTop` para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo + de compilación. + * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, - permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el - binario de las aplicaciones `PageTop`. + prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las + aplicaciones `PageTop` durante la compilación de los ejecutables. * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, proporciona una colección de macros que mejoran la experiencia de desarrollo con `PageTop`. diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index d9d52571..5af492b1 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -18,6 +18,107 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares configurables, basadas en HTML, CSS y JavaScript. +# ⚡️ Guía rápida + +Añadir en el archivo `Cargo.toml` del proyecto: + +```toml +[build-dependencies] +pagetop-build = { ... } +``` + +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: + +## Incluir archivos estáticos desde un directorio + +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") + .build() +} +``` + +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; +use std::path::Path; + +fn main() -> std::io::Result<()> { + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } + + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() +} +``` + +## Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +# 📦 Módulos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se crea un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. + +Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con +[`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con +[`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) +configurar un servicio web para servir los recursos desde la ruta indicada: + +```rust,ignore +use pagetop::prelude::*; + +include_files!(guides); + +pub struct MyExtension; + +impl Extension for MyExtension { + // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + include_files_service!(scfg, guides => "/ruta/a/guides"); + } +} +``` + +También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: + +```rust,ignore +include_files!(GUIDES => guides); +``` + + # 🚧 Advertencia `PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index bb326063..09dec428 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -1,122 +1,124 @@ -//!

-//! -//!

PageTop Build

-//! -//!

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) -//! [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) -//! -//!
-//! -//! ## Sobre PageTop -//! -//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la -//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles -//! y configurables, basadas en HTML, CSS y JavaScript. -//! -//! -//! # ⚡️ Guía rápida -//! -//! Añadir en el archivo `Cargo.toml` del proyecto: -//! -//! ```toml -//! [build-dependencies] -//! pagetop-build = { ... } -//! ``` -//! -//! Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los -//! archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: -//! -//! ## Incluir archivos estáticos desde un directorio -//! -//! Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por -//! ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! -//! fn main() -> std::io::Result<()> { -//! StaticFilesBundle::from_dir("./static", None) -//! .with_name("guides") -//! .build() -//! } -//! ``` -//! -//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, -//! por ejemplo: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! use std::path::Path; -//! -//! fn main() -> std::io::Result<()> { -//! fn only_pdf_files(path: &Path) -> bool { -//! // Selecciona únicamente los archivos con extensión `.pdf`. -//! path.extension().map_or(false, |ext| ext == "pdf") -//! } -//! -//! StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) -//! .with_name("guides") -//! .build() -//! } -//! ``` -//! -//! ## Compilar archivos SCSS a CSS -//! -//! Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso -//! con el archivo CSS minificado obtenido. Por ejemplo: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! -//! fn main() -> std::io::Result<()> { -//! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") -//! .with_name("main_styles") -//! .build() -//! } -//! ``` -//! -//! Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un -//! recurso llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. -//! -//! -//! # 📦 Módulos generados -//! -//! Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar -//! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) -//! donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para -//! `with_name("guides")` se crea un archivo llamado `guides.rs`. -//! -//! No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. -//! -//! Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con -//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con -//! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) -//! configurar un servicio web para servir los recursos desde la ruta indicada: -//! -//! ```rust,ignore -//! use pagetop::prelude::*; -//! -//! include_files!(guides); -//! -//! pub struct MyExtension; -//! -//! impl Extension for MyExtension { -//! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. -//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { -//! include_files_service!(scfg, guides => "/ruta/a/guides"); -//! } -//! } -//! ``` -//! -//! También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: -//! -//! ```rust,ignore -//! include_files!(GUIDES => guides); -//! ``` +/*! +
+ +

PageTop Build

+ +

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) +[![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) +[![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) + +
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# ⚡️ Guía rápida + +Añadir en el archivo `Cargo.toml` del proyecto: + +```toml +[build-dependencies] +pagetop-build = { ... } +``` + +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: + +## Incluir archivos estáticos desde un directorio + +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") + .build() +} +``` + +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; +use std::path::Path; + +fn main() -> std::io::Result<()> { + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } + + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() +} +``` + +## Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +# 📦 Módulos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se crea un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. + +Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con +[`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con +[`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) +configurar un servicio web para servir los recursos desde la ruta indicada: + +```rust,ignore +use pagetop::prelude::*; + +include_files!(guides); + +pub struct MyExtension; + +impl Extension for MyExtension { + // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + include_files_service!(scfg, guides => "/ruta/a/guides"); + } +} +``` + +También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: + +```rust,ignore +include_files!(GUIDES => guides); +``` +*/ #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index c5006a87..e58d24c7 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -11,9 +11,16 @@ -## Descripción general +## Sobre PageTop -Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros) +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + +## Créditos + +Esta librería incluye entre sus macros una adaptación de +[maud-macros](https://crates.io/crates/maud_macros) ([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de [Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de [SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de @@ -21,12 +28,6 @@ Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/c necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de cada proyecto `PageTop`. -## Sobre PageTop - -[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web -clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y -configurables, basadas en HTML, CSS y JavaScript. - # 🚧 Advertencia diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 496ce82b..6421ca6e 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -1,21 +1,34 @@ -//!
-//! -//!

PageTop Macros

-//! -//!

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) -//! [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) -//! -//!
-//! -//! ## Sobre PageTop -//! -//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la -//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles -//! y configurables, basadas en HTML, CSS y JavaScript. +/*! +
+ +

PageTop Macros

+ +

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) +[![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) +[![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) + +
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + +## Créditos + +Esta librería incluye entre sus macros una adaptación de +[maud-macros](https://crates.io/crates/maud_macros) +([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de +[Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de +[SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de +[Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la +necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de +cada proyecto `PageTop`. +*/ #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index c053e955..92999c04 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -8,12 +8,21 @@ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + ## Descripción general -Permite a `PageTop` incluir archivos estáticos en el ejecutable de la aplicación para servirlos de -forma eficiente vía web, con detección de cambios que optimiza el tiempo de compilación. +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de +compilación. -Para ello, reúne el código de los *crates* [static-files](https://crates.io/crates/static_files) +## Créditos + +Para ello, adapta el código de los *crates* [static-files](https://crates.io/crates/static_files) (versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y [actix-web-static-files](https://crates.io/crates/actix_web_static_files) (versión [4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por @@ -22,12 +31,6 @@ Para ello, reúne el código de los *crates* [static-files](https://crates.io/cr Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar `static-files` manualmente como dependencia en su `Cargo.toml`. -## Sobre PageTop - -[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web -clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y -configurables, basadas en HTML, CSS y JavaScript. - # 🚧 Advertencia diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index fffd1ae3..dab50d9e 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -1,18 +1,37 @@ -//!
-//! -//!

PageTop Statics

-//! -//!

Librería para automatizar la recopilación de recursos estáticos en PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! -//!
-//! -//! ## Sobre PageTop -//! -//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la -//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles -//! y configurables, basadas en HTML, CSS y JavaScript. +/*! +
+ +

PageTop Statics

+ +

Librería para automatizar la recopilación de recursos estáticos en PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) + +
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + +## Descripción general + +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de +compilación. + +## Créditos + +Para ello, adapta el código de los *crates* [static-files](https://crates.io/crates/static_files) +(versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y +[actix-web-static-files](https://crates.io/crates/actix_web_static_files) (versión +[4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por +[Alexander Korolev](https://crates.io/users/kilork). + +Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar +`static-files` manualmente como dependencia en su `Cargo.toml`. +*/ #![doc(test(no_crate_inject))] #![doc( diff --git a/src/lib.rs b/src/lib.rs index bbc45302..d50a4489 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,86 +1,89 @@ -//!
-//! -//! -//! -//!

PageTop

-//! -//!

Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) -//! [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) -//! -//!
-//!
-//! -//! `PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) -//! para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y -//! JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, -//! extender o adaptar según las necesidades de cada proyecto, incluyendo: -//! -//! * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su -//! flujo de ejecución. -//! * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, -//! configurables y reutilizables. -//! * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las -//! APIs de `PageTop` o de terceros. -//! * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y -//! componentes sin comprometer su funcionalidad. -//! -//! # ⚡️ Guía rápida -//! -//! La aplicación más sencilla de `PageTop` se ve así: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! -//! #[pagetop::main] -//! async fn main() -> std::io::Result<()> { -//! Application::new().run()?.await -//! } -//! ``` -//! -//! Este código arranca el servidor de `PageTop`. Con la -//! [configuración por defecto](crate::global::SETTINGS), muestra una página de bienvenida accesible -//! desde un navegador local en la dirección `http://localhost:8080`. -//! -//! Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! -//! struct HelloWorld; -//! -//! impl Extension for HelloWorld { -//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { -//! scfg.route("/", service::web::get().to(hello_world)); -//! } -//! } -//! -//! async fn hello_world(request: HttpRequest) -> ResultPage { -//! Page::new(Some(request)) -//! .with_component(Html::with(move |_| html! { h1 { "Hello world!" } })) -//! .render() -//! } -//! -//! #[pagetop::main] -//! async fn main() -> std::io::Result<()> { -//! Application::prepare(&HelloWorld).run()?.await -//! } -//! ``` -//! -//! Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta -//! raíz (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. -//! -//! # 🧩 Gestión de Dependencias -//! -//! Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro -//! proyecto en Rust. -//! -//! Sin embargo, es fundamental que cada extensión declare explícitamente sus -//! [dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda -//! estructurar e inicializar la aplicación de forma modular. +/*! +
+ + + +

PageTop

+ +

Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) +[![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) +[![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) + +
+
+ +`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para +la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. +Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar +según las necesidades de cada proyecto, incluyendo: + + * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su flujo + de ejecución. + * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, + configurables y reutilizables. + * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs + de `PageTop` o de terceros. + * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y + componentes sin comprometer su funcionalidad. + + +# ⚡️ Guía rápida + +La aplicación más sencilla de `PageTop` se ve así: + +```rust,no_run +use pagetop::prelude::*; + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::new().run()?.await +} +``` + +Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página +de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. + +Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: + +```rust,no_run +use pagetop::prelude::*; + +struct HelloWorld; + +impl Extension for HelloWorld { + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + scfg.route("/", service::web::get().to(hello_world)); + } +} + +async fn hello_world(request: HttpRequest) -> ResultPage { + Page::new(Some(request)) + .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .render() +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&HelloWorld).run()?.await +} +``` + +Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz +(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. + + +# 🧩 Gestión de Dependencias + +Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro +proyecto en Rust. + +Sin embargo, es fundamental que cada extensión declare explícitamente sus +[dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda +estructurar e inicializar la aplicación de forma modular. +*/ #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( From 801642faabbbe9b29d397790ae40eaaf6d29ca03 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 11:54:48 +0200 Subject: [PATCH 082/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Red?= =?UTF-8?q?efine=20funci=C3=B3n=20para=20directorios=20absolutos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.rs | 27 +++++++---------------- src/util.rs | 53 ++++++++++++++++++++++++++++++--------------- tests/util.rs | 60 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/src/config.rs b/src/config.rs index 08067fe6..27cf630e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,11 +110,13 @@ //! } //! ``` +use crate::util; + use config::builder::DefaultState; use config::{Config, ConfigBuilder, File}; use std::env; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::LazyLock; // Nombre del directorio de configuración por defecto. @@ -125,25 +127,12 @@ const DEFAULT_RUN_MODE: &str = "default"; /// Valores originales cargados desde los archivos de configuración como pares `clave = valor`. pub static CONFIG_VALUES: LazyLock> = LazyLock::new(|| { - // Determina el directorio de configuración: - // - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp). - // - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR). - // - Si nada de esto aplica, entonces usa DEFAULT_CONFIG_DIR relativo al ejecutable. - let config_dir: PathBuf = if let Ok(env_dir) = env::var("CONFIG_DIR") { - env_dir.into() - } else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let manifest_config = Path::new(&manifest_dir).join(DEFAULT_CONFIG_DIR); - if manifest_config.exists() { - manifest_config - } else { - DEFAULT_CONFIG_DIR.into() - } - } else { - DEFAULT_CONFIG_DIR.into() - }; + // CONFIG_DIR (si existe) o DEFAULT_CONFIG_DIR. Si no se puede resolver, se usa tal cual. + let dir = env::var_os("CONFIG_DIR").unwrap_or_else(|| DEFAULT_CONFIG_DIR.into()); + let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir)); - // Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa - // DEFAULT_RUN_MODE si no está definida (p.ej.: PAGETOP_RUN_MODE=production ./myapp). + // Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa + // por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production). let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); Config::builder() diff --git a/src/util.rs b/src/util.rs index b605125f..21537c5d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,35 +2,44 @@ use crate::trace; +use std::env; use std::io; use std::path::{Path, PathBuf}; // FUNCIONES ÚTILES ******************************************************************************** -/// Devuelve la ruta absoluta a un directorio existente. +/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. /// -/// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`. -/// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual. -/// * Devuelve error si la ruta no existe o no es un directorio. +/// - Si la ruta es relativa, se resuelve respecto al directorio del proyecto según la variable de +/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de +/// trabajo. +/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos). +/// - Devuelve error si la ruta no existe o no es un directorio. /// -/// # Ejemplo +/// # Ejemplos /// /// ```rust,no_run /// use pagetop::prelude::*; /// -/// let root = "/home/user"; -/// let rel = "documents"; -/// println!("{:#?}", util::absolute_dir(root, rel)); +/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). +/// println!("{:#?}", util::resolve_absolute_dir("documents")); +/// +/// // Ruta absoluta, se normaliza y valida tal cual. +/// println!("{:#?}", util::resolve_absolute_dir("/var/www")); /// ``` -pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result -where - P: AsRef, - Q: AsRef, -{ - // Une ambas rutas: - // - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`. - // - Si el resultado es aún relativo, lo será respecto al directorio actual. - let candidate = root_path.as_ref().join(relative_path.as_ref()); +pub fn resolve_absolute_dir>(path: P) -> io::Result { + let path = path.as_ref(); + + let candidate = if path.is_absolute() { + path.to_path_buf() + } else { + // Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto. + env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .or_else(|| env::current_dir().ok()) + .unwrap_or_else(|| PathBuf::from(".")) + .join(path) + }; // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. let absolute_dir = candidate.canonicalize()?; @@ -47,6 +56,16 @@ where } } +/// Devuelve la ruta absoluta a un directorio existente. +#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")] +pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result +where + P: AsRef, + Q: AsRef, +{ + resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref())) +} + // MACROS ÚTILES *********************************************************************************** #[doc(hidden)] diff --git a/tests/util.rs b/tests/util.rs index 6e0896ff..70699a74 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use std::{fs, io}; +use std::{env, fs, io}; use tempfile::TempDir; #[cfg(unix)] @@ -13,15 +13,36 @@ mod unix { // /tmp//sub let td = TempDir::new()?; - let root = td.path(); - let sub = root.join("sub"); + let sub = td.path().join("sub"); fs::create_dir(&sub)?; - let abs = util::absolute_dir(root, "sub")?; + let abs = util::resolve_absolute_dir(&sub)?; assert_eq!(abs, std::fs::canonicalize(&sub)?); Ok(()) } + #[pagetop::test] + async fn ok_relative_dir_with_manifest() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let sub = td.path().join("sub"); + fs::create_dir(&sub)?; + + // Fija CARGO_MANIFEST_DIR para que "sub" se resuelva contra td.path() + let prev_manifest_dir = env::var_os("CARGO_MANIFEST_DIR"); + env::set_var("CARGO_MANIFEST_DIR", td.path()); + let res = util::resolve_absolute_dir("sub"); + // Restaura entorno. + match prev_manifest_dir { + Some(v) => env::set_var("CARGO_MANIFEST_DIR", v), + None => env::remove_var("CARGO_MANIFEST_DIR"), + } + + assert_eq!(res?, std::fs::canonicalize(&sub)?); + Ok(()) + } + #[pagetop::test] async fn error_not_a_directory() -> io::Result<()> { let _app = service::test::init_service(Application::new().test()).await; @@ -30,7 +51,7 @@ mod unix { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + let err = util::resolve_absolute_dir(&file).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) } @@ -46,15 +67,36 @@ mod windows { // C:\Users\...\Temp\... let td = TempDir::new()?; - let root = td.path(); - let sub = root.join("sub"); + let sub = td.path().join("sub"); fs::create_dir(&sub)?; - let abs = util::absolute_dir(root, sub.as_path())?; + let abs = util::resolve_absolute_dir(&sub)?; assert_eq!(abs, std::fs::canonicalize(&sub)?); Ok(()) } + #[pagetop::test] + async fn ok_relative_dir_with_manifest() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let sub = td.path().join("sub"); + fs::create_dir(&sub)?; + + // Fija CARGO_MANIFEST_DIR para que "sub" se resuelva contra td.path() + let prev_manifest_dir = env::var_os("CARGO_MANIFEST_DIR"); + env::set_var("CARGO_MANIFEST_DIR", td.path()); + let res = util::resolve_absolute_dir("sub"); + // Restaura entorno. + match prev_manifest_dir { + Some(v) => env::set_var("CARGO_MANIFEST_DIR", v), + None => env::remove_var("CARGO_MANIFEST_DIR"), + } + + assert_eq!(res?, std::fs::canonicalize(&sub)?); + Ok(()) + } + #[pagetop::test] async fn error_not_a_directory() -> io::Result<()> { let _app = service::test::init_service(Application::new().test()).await; @@ -63,7 +105,7 @@ mod windows { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + let err = util::resolve_absolute_dir(&file).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) } From 69fac6d9ea949b664c80bc281d947614ffa8c304 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 12:15:16 +0200 Subject: [PATCH 083/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Mej?= =?UTF-8?q?ora=20la=20integraci=C3=B3n=20de=20archivos=20est=C3=A1ticos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elimina el uso de `include_files!` y sustituye `include_files_service!` por alternativas más completas ofreciadas por `static_files_service!`. --- helpers/pagetop-build/README.md | 26 +++---- helpers/pagetop-build/src/lib.rs | 26 +++---- src/core/extension/all.rs | 8 +-- src/global.rs | 16 +++-- src/html/assets/favicon.rs | 3 +- src/html/assets/javascript.rs | 3 +- src/html/assets/stylesheet.rs | 3 +- src/lib.rs | 3 +- src/prelude.rs | 3 +- src/service.rs | 119 +++++++++++++++++++++++++++++++ 10 files changed, 156 insertions(+), 54 deletions(-) diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 5af492b1..80d6bba2 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -83,41 +83,33 @@ Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. -# 📦 Módulos generados +# 📦 Archivos generados Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) -donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para -`with_name("guides")` se crea un archivo llamado `guides.rs`. +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. -No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. -Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con -[`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con -[`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) -configurar un servicio web para servir los recursos desde la ruta indicada: +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`static_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.static_files_service.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: ```rust,ignore use pagetop::prelude::*; -include_files!(guides); - pub struct MyExtension; impl Extension for MyExtension { // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - include_files_service!(scfg, guides => "/ruta/a/guides"); + static_files_service!(scfg, guides => "/ruta/a/guides"); } } ``` -También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: - -```rust,ignore -include_files!(GUIDES => guides); -``` - # 🚧 Advertencia diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 09dec428..c6e72367 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -84,40 +84,32 @@ Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. -# 📦 Módulos generados +# 📦 Archivos generados Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) -donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para -`with_name("guides")` se crea un archivo llamado `guides.rs`. +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. -No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. -Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con -[`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con -[`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) -configurar un servicio web para servir los recursos desde la ruta indicada: +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`static_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.static_files_service.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: ```rust,ignore use pagetop::prelude::*; -include_files!(guides); - pub struct MyExtension; impl Extension for MyExtension { // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - include_files_service!(scfg, guides => "/ruta/a/guides"); + static_files_service!(scfg, guides => "/ruta/a/guides"); } } ``` - -También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: - -```rust,ignore -include_files!(GUIDES => guides); -``` */ #![doc( diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index 93b5c4bd..a243778c 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -1,7 +1,7 @@ use crate::core::action::add_action; use crate::core::extension::ExtensionRef; use crate::core::theme::all::THEMES; -use crate::{global, include_files, include_files_service, service, trace}; +use crate::{global, service, static_files_service, trace}; use parking_lot::RwLock; @@ -125,8 +125,6 @@ pub fn initialize_extensions() { // CONFIGURA LOS SERVICIOS ************************************************************************* -include_files!(assets); - pub fn configure_services(scfg: &mut service::web::ServiceConfig) { // Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos. #[cfg(debug_assertions)] @@ -140,7 +138,5 @@ pub fn configure_services(scfg: &mut service::web::ServiceConfig) { extension.configure_service(scfg); } - include_files_service!( - scfg, assets => "/", [&global::SETTINGS.dev.pagetop_project_dir, "static"] - ); + static_files_service!(scfg, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/"); } diff --git a/src/global.rs b/src/global.rs index ea659b8b..8a03589c 100644 --- a/src/global.rs +++ b/src/global.rs @@ -13,7 +13,7 @@ include_config!(SETTINGS: Settings => [ "app.startup_banner" => "Slant", // [dev] - "dev.pagetop_project_dir" => "", + "dev.pagetop_static_dir" => "", // [log] "log.enabled" => true, @@ -68,11 +68,15 @@ pub struct App { #[derive(Debug, Deserialize)] /// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. pub struct Dev { - /// Los archivos estáticos requeridos por `PageTop` se integran por defecto en el binario - /// ejecutable. Sin embargo, durante el desarrollo puede resultar útil servirlos desde su propio - /// directorio para evitar recompilar cada vez que se modifican. En ese caso, este ajuste debe - /// indicar la ruta absoluta al directorio raíz del proyecto. - pub pagetop_project_dir: String, + /// Directorio desde el que servir los archivos estáticos de `PageTop`. + /// + /// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una + /// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en + /// ejecución, se servirán desde el sistema de ficheros en su lugar. Esto es especialmente útil + /// en desarrollo, ya que evita recompilar el proyecto por cambios en estos archivos. + /// + /// Si la cadena está vacía, se ignora este ajuste. + pub pagetop_static_dir: String, } #[derive(Debug, Deserialize)] diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 2af01730..1a8b29e7 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -12,8 +12,7 @@ use crate::AutoDefault; /// /// > **Nota** /// > Los archivos de los iconos deben estar disponibles en el servidor web de la aplicación. Pueden -/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con -/// > [`include_files_service!`](crate::include_files_service). +/// > servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 604e85ae..db5754e9 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -30,8 +30,7 @@ enum Source { /// /// > **Nota** /// > Los archivos de los *scripts* deben estar disponibles en el servidor web de la aplicación. -/// > Pueden incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse -/// > con [`include_files_service!`](crate::include_files_service). +/// > Pueden servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 72a79a10..bb60b01c 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -55,8 +55,7 @@ impl TargetMedia { /// /// > **Nota** /// > Las hojas de estilo CSS deben estar disponibles en el servidor web de la aplicación. Pueden -/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con -/// > [`include_files_service!`](crate::include_files_service). +/// > servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/lib.rs b/src/lib.rs index d50a4489..90ea4625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,7 +103,8 @@ pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; pub use pagetop_statics::{resource, StaticResource}; -/// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files). +/// Contenedor para un conjunto de recursos embebidos. +#[derive(AutoDefault)] pub struct StaticResources { bundle: HashMap<&'static str, StaticResource>, } diff --git a/src/prelude.rs b/src/prelude.rs index 6bacaf3a..9072dec9 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,7 +15,8 @@ pub use crate::include_config; // crate::locale pub use crate::include_locales; // crate::service -pub use crate::{include_files, include_files_service}; +#[allow(deprecated)] +pub use crate::{include_files, include_files_service, static_files_service}; // crate::core::action pub use crate::actions_boxed; diff --git a/src/service.rs b/src/service.rs index 47f1420d..288e1eb5 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,6 +15,9 @@ pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; +/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) +/// en su lugar. +/// /// Incluye en código un conjunto de recursos previamente preparado con `build.rs`. /// /// # Formas de uso @@ -39,6 +42,7 @@ pub use actix_web::test; /// /// include_files!(STATIC_ASSETS => assets); /// ``` +#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files { // Forma 1: incluye un conjunto de recursos por nombre. @@ -63,6 +67,9 @@ macro_rules! include_files { }; } +/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) +/// en su lugar. +/// /// Configura un servicio web para publicar los recursos embebidos con [`include_files!`]. /// /// El código expandido de la macro decide durante el arranque de la aplicación si debe servir los @@ -104,6 +111,7 @@ macro_rules! include_files { /// // También desde el directorio actual de ejecución. /// include_files_service!(cfg, assets => "/public", ["", "static"]); /// ``` +#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files_service { ( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{ @@ -137,3 +145,114 @@ macro_rules! include_files_service { } }}; } + +/// Configura un servicio web para publicar archivos estáticos. +/// +/// La macro ofrece tres modos para configurar el servicio: +/// +/// - **Sistema de ficheros o embebido** (`[$path, $bundle]`): trata de servir los archivos desde +/// `$path`; y si es una cadena vacía, no existe o no es un directorio, entonces usará el conjunto +/// de recursos `$bundle` integrado en el binario. +/// - **Sólo embebido** (`[$bundle]`): sirve siempre desde el conjunto de recursos `$bundle` +/// integrado en el binario. +/// - **Sólo sistema de ficheros** (`$path`): sin usar corchetes, sirve únicamente desde el sistema +/// de ficheros si existe; en otro caso no registra el servicio. +/// +/// # Argumentos +/// +/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la +/// configuración. +/// * `$path` – Ruta al directorio local con los archivos estáticos. +/// * `$bundle` – Nombre del conjunto de recursos que esta macro integra en el binario. +/// * `$route` – Ruta URL base desde la que se servirán los archivos. +/// +/// # Ejemplos +/// +/// ```rust,ignore +/// use pagetop::prelude::*; +/// +/// pub struct MyExtension; +/// +/// impl Extension for MyExtension { +/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { +/// // Forma 1) Sistema de ficheros o embebido. +/// static_files_service!(scfg, ["/var/www/static", assets] => "/public"); +/// +/// // Forma 2) Siempre embebido. +/// static_files_service!(scfg, [assets] => "/public"); +/// +/// // Forma 3) Sólo sistema de ficheros (no requiere `assets`). +/// static_files_service!(scfg, "/var/www/static" => "/public"); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! static_files_service { + // Forma 1: primero intenta servir desde el sistema de ficheros; si falla, sirve embebido. + ( $scfg:ident, [$path:expr, $bundle:ident] => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (file system or embedded)", + mode = "fs_or_embedded", + route = $route, + ); + let _ = span.in_scope(|| { + let mut serve_embedded: bool = true; + if !::std::path::Path::new(&$path).as_os_str().is_empty() { + if let Ok(absolute) = $crate::util::resolve_absolute_dir($path) { + $scfg.service($crate::service::ActixFiles::new($route, absolute)); + serve_embedded = false; + } + } + if serve_embedded { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + $scfg.service($crate::service::ResourceFiles::new( + $route, + []::$bundle(), + )); + } + } + }); + }}; + // Forma 2: sirve siempre embebido. + ( $scfg:ident, [$bundle:ident] => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (using embedded only)", + mode = "embedded", + route = $route, + ); + let _ = span.in_scope(|| { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + $scfg.service($crate::service::ResourceFiles::new( + $route, + []::$bundle(), + )); + } + }); + }}; + // Forma 3: intenta servir desde el sistema de ficheros. + ( $scfg:ident, $path:expr => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (file system only)", + mode = "fs", + route = $route, + ); + let _ = span.in_scope(|| match $crate::util::resolve_absolute_dir($path) { + Ok(absolute) => { + $scfg.service($crate::service::ActixFiles::new($route, absolute)); + } + Err(e) => { + $crate::trace::warn!( + "Static dir not found or invalid for route `{}`: {:?} ({e})", + $route, + $path, + ); + } + }); + }}; +} From 662b269423938503d320d1ea85c8830e294fd1f7 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 12:39:51 +0200 Subject: [PATCH 084/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-statics=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-statics/CHANGELOG.md | 6 ++++++ helpers/pagetop-statics/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22cf3086..48adecf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,7 @@ dependencies = [ [[package]] name = "pagetop-statics" -version = "0.1.0" +version = "0.1.1" dependencies = [ "actix-web", "change-detection", diff --git a/helpers/pagetop-statics/CHANGELOG.md b/helpers/pagetop-statics/CHANGELOG.md index 62ad97e8..811a77f4 100644 --- a/helpers/pagetop-statics/CHANGELOG.md +++ b/helpers/pagetop-statics/CHANGELOG.md @@ -8,6 +8,12 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.1.1 (2025-08-16) + +### Documentado + +- Cambia el formato para la documentación (#4) + ## 0.1.0 (2025-08-09) ### Añadido diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml index e5c58d5e..2afe3739 100644 --- a/helpers/pagetop-statics/Cargo.toml +++ b/helpers/pagetop-statics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-statics" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 85497bf45256b530bd7cd84a5b47f5438240801f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 12:47:02 +0200 Subject: [PATCH 085/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-build/CHANGELOG.md | 11 +++++++++++ helpers/pagetop-build/Cargo.toml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48adecf2..8e9795f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.2.0" +version = "0.3.0" dependencies = [ "grass", "pagetop-statics", diff --git a/Cargo.toml b/Cargo.toml index cdeae782..bb31197f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,6 @@ authors = ["Manuel Cillero "] [workspace.dependencies] actix-web = { version = "4.11.0", default-features = false } -pagetop-build = { version = "0.2", path = "helpers/pagetop-build" } +pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } diff --git a/helpers/pagetop-build/CHANGELOG.md b/helpers/pagetop-build/CHANGELOG.md index 4d087556..3e3801e8 100644 --- a/helpers/pagetop-build/CHANGELOG.md +++ b/helpers/pagetop-build/CHANGELOG.md @@ -8,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.3.0 (2025-08-16) + +### Cambiado + +- Mejora función `from_dir` por compatibilidad (#3) +- Mejora la integración de archivos estáticos + +### Documentado + +- Cambia el formato para la documentación (#4) + ## 0.2.0 (2025-08-09) ### Añadido diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 14e6000b..e0f5ef76 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.2.0" +version = "0.3.0" edition = "2021" description = """ From b11b5d97c67b5a6dfdc9392e24e5d5620d15b231 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 12:51:11 +0200 Subject: [PATCH 086/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-macros=200.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-macros/CHANGELOG.md | 11 +++++++++++ helpers/pagetop-macros/Cargo.toml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e9795f1..64f77eeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1595,7 +1595,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.1.0" +version = "0.1.1" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", diff --git a/helpers/pagetop-macros/CHANGELOG.md b/helpers/pagetop-macros/CHANGELOG.md index b01b9883..b61471a1 100644 --- a/helpers/pagetop-macros/CHANGELOG.md +++ b/helpers/pagetop-macros/CHANGELOG.md @@ -8,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.1.1 (2025-08-16) + +### Documentado + +- Cambia el formato para la documentación (#4) +- Corrige enlaces de licencia en la documentación + +### Otros cambios + +- Afina Cargo.toml para buscar la mejor categoría + ## 0.1.0 (2025-08-06) - Versión inicial diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 64f1cf4c..d5ceb1b6 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ From 2cf5ce2a7086d35407cc7dbd9d98bc1ce2eb5559 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Aug 2025 12:57:00 +0200 Subject: [PATCH 087/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop=200.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 18 ++++++++++++++++-- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2248658d..9e187143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.3.0 (2025-08-16) + +### Cambiado + +- Redefine función para directorios absolutos +- Mejora la integración de archivos estáticos + +### Documentado + +- Cambia el formato para la documentación (#4) + ## 0.2.0 (2025-08-09) ### Añadido @@ -15,10 +26,13 @@ internos pueden omitirse si no afectan al uso del proyecto. - Añade librería para gestionar recursos estáticos (#1) - Añade soporte a changelog de `pagetop-statics` (#2) +### Documentado + +- Corrige enlace del botón de licencia en la documentación + ### Otros cambios -- 🩹 Corrige enlace del botón de licencia en la documentación -- 🚩 Afina Cargo.toml para buscar la mejor categoría +- Afina Cargo.toml para buscar la mejor categoría ## 0.1.0 (2025-08-06) diff --git a/Cargo.lock b/Cargo.lock index 64f77eeb..944027d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,7 +1557,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.2.0" +version = "0.3.0" dependencies = [ "actix-files", "actix-session", diff --git a/Cargo.toml b/Cargo.toml index bb31197f..8ccd69ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.2.0" +version = "0.3.0" edition = "2021" description = """ From 313ce391d5a80e94b78aa3ad4cf7f6bdeae53c0a Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 21 Aug 2025 09:12:22 +0200 Subject: [PATCH 088/224] =?UTF-8?q?=F0=9F=93=9D=20Retoques=20menores=20en?= =?UTF-8?q?=20la=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/theme.rs | 7 ++++--- src/core/theme/definition.rs | 11 ++++++++--- src/util.rs | 4 +++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/core/theme.rs b/src/core/theme.rs index 0a0f8199..2cd5bb1f 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -9,9 +9,10 @@ //! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (como animaciones, //! *scripts* de interfaz, etc.). //! -//! Es una extensión más (implementando [`Extension`](crate::core::extension::Extension)). Se -//! instala, activa y declara dependencias igual que el resto de extensiones; y se señala a sí misma -//! como tema (implementando [`theme()`](crate::core::extension::Extension::theme) y [`Theme`]). +//! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension); por +//! lo que se instancian, declaran sus dependencias y se inician igual que el resto de extensiones; +//! pero serán temas si además implementan [`theme()`](crate::core::extension::Extension::theme) y +//! [`Theme`]. mod definition; pub use definition::{Theme, ThemeRef}; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 8de88bdd..e9cfbba9 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -14,7 +14,7 @@ pub type ThemeRef = &'static dyn Theme; /// Interfaz común que debe implementar cualquier tema de `PageTop`. /// /// Un tema implementará [`Theme`] y los métodos que sean necesarios de [`Extension`], aunque el -/// único obligatorio es [`theme()`](Extension::theme). +/// único obligatorio será [`theme()`](Extension::theme). /// /// ```rust /// use pagetop::prelude::*; @@ -22,8 +22,13 @@ pub type ThemeRef = &'static dyn Theme; /// pub struct MyTheme; /// /// impl Extension for MyTheme { -/// fn name(&self) -> L10n { L10n::n("My theme") } -/// fn description(&self) -> L10n { L10n::n("Un tema personal") } +/// fn name(&self) -> L10n { +/// L10n::n("My theme") +/// } +/// +/// fn description(&self) -> L10n { +/// L10n::n("A personal theme") +/// } /// /// fn theme(&self) -> Option { /// Some(&Self) diff --git a/src/util.rs b/src/util.rs index 21537c5d..e70b0998 100644 --- a/src/util.rs +++ b/src/util.rs @@ -56,8 +56,10 @@ pub fn resolve_absolute_dir>(path: P) -> io::Result { } } +/// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar. +/// /// Devuelve la ruta absoluta a un directorio existente. -#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")] +#[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")] pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result where P: AsRef, From 3b5c5dd4e7f7e9ee48da7fe35b1925b1ceac7594 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 21 Aug 2025 09:31:20 +0200 Subject: [PATCH 089/224] =?UTF-8?q?=F0=9F=A6=BA=20Modifica=20tipos=20para?= =?UTF-8?q?=20atributos=20HTML=20a=20min=C3=BAsculas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/opt_classes.rs | 7 ++++--- src/html/opt_id.rs | 5 +++-- src/html/opt_name.rs | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/html/opt_classes.rs b/src/html/opt_classes.rs index abb3ba48..a9857625 100644 --- a/src/html/opt_classes.rs +++ b/src/html/opt_classes.rs @@ -25,6 +25,7 @@ pub enum ClassesOp { /// /// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS. /// - No se permiten clases duplicadas. +/// - Las clases se convierten a minúsculas. /// - Las clases vacías se ignoran. /// /// # Ejemplo @@ -32,8 +33,8 @@ pub enum ClassesOp { /// ```rust /// use pagetop::prelude::*; /// -/// let classes = OptionClasses::new("btn btn-primary") -/// .with_value(ClassesOp::Add, "active") +/// let classes = OptionClasses::new("Btn btn-primary") +/// .with_value(ClassesOp::Add, "Active") /// .with_value(ClassesOp::Remove, "btn-primary"); /// /// assert_eq!(classes.get(), Some(String::from("btn active"))); @@ -51,7 +52,7 @@ impl OptionClasses { #[builder_fn] pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef) -> Self { - let classes: &str = classes.as_ref(); + let classes = classes.as_ref().to_ascii_lowercase(); let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); if classes.is_empty() { diff --git a/src/html/opt_id.rs b/src/html/opt_id.rs index 893ac6d3..139fdcd6 100644 --- a/src/html/opt_id.rs +++ b/src/html/opt_id.rs @@ -7,6 +7,7 @@ use crate::{builder_fn, AutoDefault}; /// # Normalización /// /// - Se eliminan los espacios al principio y al final. +/// - Se convierte a minúsculas. /// - Se sustituyen los espacios intermedios por guiones bajos (`_`). /// - Si el resultado es una cadena vacía, se guarda `None`. /// @@ -15,7 +16,7 @@ use crate::{builder_fn, AutoDefault}; /// ```rust /// use pagetop::prelude::*; /// -/// let id = OptionId::new("main section"); +/// let id = OptionId::new(" main Section "); /// assert_eq!(id.get(), Some(String::from("main_section"))); /// /// let empty = OptionId::default(); @@ -39,7 +40,7 @@ impl OptionId { /// El valor se normaliza automáticamente. #[builder_fn] pub fn with_value(mut self, value: impl AsRef) -> Self { - let value = value.as_ref().trim().replace(' ', "_"); + let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); self.0 = (!value.is_empty()).then_some(value); self } diff --git a/src/html/opt_name.rs b/src/html/opt_name.rs index aa74e3b9..ffb0b989 100644 --- a/src/html/opt_name.rs +++ b/src/html/opt_name.rs @@ -7,6 +7,7 @@ use crate::{builder_fn, AutoDefault}; /// # Normalización /// /// - Se eliminan los espacios al principio y al final. +/// - Se convierte a minúsculas. /// - Se sustituyen los espacios intermedios por guiones bajos (`_`). /// - Si el resultado es una cadena vacía, se guarda `None`. /// @@ -15,7 +16,7 @@ use crate::{builder_fn, AutoDefault}; /// ```rust /// use pagetop::prelude::*; /// -/// let name = OptionName::new(" display name "); +/// let name = OptionName::new(" DISplay name "); /// assert_eq!(name.get(), Some(String::from("display_name"))); /// /// let empty = OptionName::default(); @@ -39,7 +40,7 @@ impl OptionName { /// El valor se normaliza automáticamente. #[builder_fn] pub fn with_value(mut self, value: impl AsRef) -> Self { - let value = value.as_ref().trim().replace(' ', "_"); + let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); self.0 = (!value.is_empty()).then_some(value); self } From 2cb963350341997fea9b10030278fb930dc242a1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 21 Aug 2025 09:36:25 +0200 Subject: [PATCH 090/224] =?UTF-8?q?=F0=9F=8E=A8=20Retoques=20al=20importar?= =?UTF-8?q?=20`fmt`=20para=20usar=20`Display`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/context.rs | 5 ++--- src/response/page/error.rs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/html/context.rs b/src/html/context.rs index 5fbb39b5..72c0ff29 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -9,10 +9,9 @@ use crate::{builder_fn, join}; use std::collections::HashMap; use std::error::Error; +use std::fmt::{self, Display}; use std::str::FromStr; -use std::fmt; - /// Operaciones para modificar el contexto ([`Context`]) del documento. pub enum AssetsOp { // Favicon. @@ -43,7 +42,7 @@ pub enum ErrorParam { ParseError(String), } -impl fmt::Display for ErrorParam { +impl Display for ErrorParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorParam::NotFound => write!(f, "Parameter not found"), diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 3a2511cf..99ba62bf 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -6,7 +6,7 @@ use crate::service::{HttpRequest, HttpResponse}; use super::Page; -use std::fmt; +use std::fmt::{self, Display}; #[derive(Debug)] pub enum ErrorPage { @@ -19,7 +19,7 @@ pub enum ErrorPage { Timeout(HttpRequest), } -impl fmt::Display for ErrorPage { +impl Display for ErrorPage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // Error 304. From 40c4cabe45d7663b74d17a0a90913bd0d146edcb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 21 Aug 2025 09:40:10 +0200 Subject: [PATCH 091/224] =?UTF-8?q?=F0=9F=90=9B=20(welcome):=20Corrige=20g?= =?UTF-8?q?iro=20bot=C3=B3n=20con=20ancho=20estrecho?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/welcome.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/css/welcome.css b/static/css/welcome.css index 8f603485..76b042b9 100644 --- a/static/css/welcome.css +++ b/static/css/welcome.css @@ -295,11 +295,6 @@ a:hover:visited { transform: translateX(-100%); } } -#poweredby-link:hover { - transition: all .5s; - transform: rotate(-3deg) scale(1.1); - box-shadow: 0px 3px 5px rgba(0,0,0,.4); -} #poweredby-link:hover span { animation-play-state: paused; } @@ -323,6 +318,11 @@ a:hover:visited { max-width: 29.375rem; margin-bottom: 0; } + #poweredby-link:hover { + transition: all .5s; + transform: rotate(-3deg) scale(1.1); + box-shadow: 0px 3px 5px rgba(0,0,0,.4); + } } .content-text { From ba71f8a83fab01fb95d51c1464068c8ba76eb9e6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 22 Aug 2025 07:46:36 +0200 Subject: [PATCH 092/224] =?UTF-8?q?=E2=9C=A8=20(app):=20A=C3=B1ade=20manej?= =?UTF-8?q?o=20de=20rutas=20no=20encontradas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index c3576fc5..400b0cd3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,6 +3,9 @@ mod figfont; use crate::core::{extension, extension::ExtensionRef}; +use crate::html::Markup; +use crate::response::page::{ErrorPage, ResultPage}; +use crate::service::HttpRequest; use crate::{global, locale, service, trace}; use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; @@ -170,6 +173,12 @@ impl Application { InitError = (), >, > { - service::App::new().configure(extension::all::configure_services) + service::App::new() + .configure(extension::all::configure_services) + .default_service(service::web::route().to(service_not_found)) } } + +async fn service_not_found(request: HttpRequest) -> ResultPage { + Err(ErrorPage::NotFound(request)) +} From e08c412a36e205bdeddd1bda2eb4d216c5406d3f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 22 Aug 2025 08:29:11 +0200 Subject: [PATCH 093/224] =?UTF-8?q?=F0=9F=8E=A8=20(theme):=20Mejora=20gest?= =?UTF-8?q?i=C3=B3n=20de=20regiones=20en=20p=C3=A1ginas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/extension/definition.rs | 19 +++---- src/core/theme.rs | 5 +- src/core/theme/definition.rs | 81 ++++++++++++++++++++++++++---- src/core/theme/regions.rs | 85 ++++++++++++++++++++++++++------ src/locale/en-US/theme.ftl | 9 +++- src/locale/es-ES/theme.ftl | 9 +++- src/response/page.rs | 15 +++--- 7 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index ac292592..6df70427 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -26,7 +26,7 @@ pub type ExtensionRef = &'static dyn Extension; /// } /// ``` pub trait Extension: AnyInfo + Send + Sync { - /// Nombre legible para el usuario. + /// Nombre localizado de la extensión legible para el usuario. /// /// Predeterminado por el [`short_name()`](AnyInfo::short_name) del tipo asociado a la /// extensión. @@ -34,18 +34,15 @@ pub trait Extension: AnyInfo + Send + Sync { L10n::n(self.short_name()) } - /// Descripción corta para paneles, listados, etc. + /// Descripción corta localizada de la extensión para paneles, listados, etc. fn description(&self) -> L10n { L10n::default() } - /// Los temas son extensiones que implementan [`Extension`] y también - /// [`Theme`](crate::core::theme::Theme). + /// Devuelve una referencia a esta misma extensión cuando se trata de un tema. /// - /// Si la extensión no es un tema, este método devuelve `None` por defecto. - /// - /// En caso contrario, este método debe implementarse para devolver una referencia de sí mismo - /// como tema. Por ejemplo: + /// Para ello, debe implementar [`Extension`] y también [`Theme`](crate::core::theme::Theme). Si + /// la extensión no es un tema, este método devuelve `None` por defecto. /// /// ```rust /// use pagetop::prelude::*; @@ -81,7 +78,7 @@ pub trait Extension: AnyInfo + Send + Sync { actions_boxed![] } - /// Inicializa la extensión durante la lógica de arranque de la aplicación. + /// Inicializa la extensión durante la fase de arranque de la aplicación. /// /// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de /// aceptar cualquier petición HTTP. @@ -104,8 +101,8 @@ pub trait Extension: AnyInfo + Send + Sync { #[allow(unused_variables)] fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {} - /// Permite crear extensiones para deshabilitar y desinstalar los recursos de otras extensiones - /// utilizadas en versiones anteriores de la aplicación. + /// Permite crear extensiones para deshabilitar y desinstalar recursos de otras de versiones + /// anteriores de la aplicación. /// /// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones. fn drop_extensions(&self) -> Vec { diff --git a/src/core/theme.rs b/src/core/theme.rs index 2cd5bb1f..aa526f16 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -19,9 +19,6 @@ pub use definition::{Theme, ThemeRef}; mod regions; pub(crate) use regions::ChildrenInRegions; -pub use regions::InRegion; +pub use regions::{InRegion, Region, REGION_CONTENT}; pub(crate) mod all; - -/// Nombre de la región por defecto: `content`. -pub const CONTENT_REGION_NAME: &str = "content"; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index e9cfbba9..8d1b632f 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,10 +1,12 @@ use crate::core::extension::Extension; -use crate::core::theme::CONTENT_REGION_NAME; +use crate::core::theme::Region; use crate::global; use crate::html::{html, Markup}; use crate::locale::L10n; use crate::response::page::Page; +use std::sync::LazyLock; + /// Representa una referencia a un tema. /// /// Los temas son también extensiones. Por tanto se deben definir igual, es decir, como instancias @@ -38,21 +40,67 @@ pub type ThemeRef = &'static dyn Theme; /// impl Theme for MyTheme {} /// ``` pub trait Theme: Extension + Send + Sync { + /// **Obsoleto desde la versión 0.4.0**: usar [`declared_regions()`](Self::declared_regions) en + /// su lugar. + #[deprecated(since = "0.4.0", note = "Use `declared_regions()` instead")] fn regions(&self) -> Vec<(&'static str, L10n)> { - vec![(CONTENT_REGION_NAME, L10n::l("content"))] + vec![("content", L10n::l("content"))] } + /// Declaración ordenada de las regiones disponibles en la página. + /// + /// Devuelve una lista estática de pares `(Region, L10n)` que se usará para renderizar en el + /// orden indicado todas las regiones que componen una página. Los identificadores deben ser + /// **estables** como `"sidebar-left"` o `"content"`. La etiqueta `L10n` devuelve el nombre de la + /// región en el idioma activo de la página. + /// + /// Si el tema requiere un conjunto distinto de regiones, se puede sobrescribir este método para + /// devolver una lista diferente. Si no, se usará la lista predeterminada: + /// + /// - `"header"`: cabecera. + /// - `"content"`: contenido principal (**obligatoria**). + /// - `"footer"`: pie. + /// + /// Sólo la región `"content"` es obligatoria, usa [`Region::default()`] para declararla. + #[inline] + fn declared_regions(&self) -> &'static [(Region, L10n)] { + static REGIONS: LazyLock<[(Region, L10n); 3]> = LazyLock::new(|| { + [ + (Region::declare("header"), L10n::l("region_header")), + (Region::default(), L10n::l("region_content")), + (Region::declare("footer"), L10n::l("region_footer")), + ] + }); + ®IONS[..] + } + + /// Acciones específicas del tema antes de renderizar el `` de la página. + /// + /// Útil para preparar clases, inyectar recursos o ajustar metadatos. #[allow(unused_variables)] fn before_render_page_body(&self, page: &mut Page) {} + /// Renderiza el contenido del `` de la página. + /// + /// Por defecto, recorre [`declared_regions()`](Self::declared_regions) **en el orden que se han + /// declarado** y, para cada región con contenido, genera un contenedor con `role="region"` y + /// `aria-label` localizado. fn render_page_body(&self, page: &mut Page) -> Markup { html! { body id=[page.body_id().get()] class=[page.body_classes().get()] { - @for (region_name, _) in self.regions() { - @let output = page.render_region(region_name); + @for (region, region_label) in self.declared_regions() { + @let output = page.render_region(region.key()); @if !output.is_empty() { - div id=(region_name) class={ "region-container region-" (region_name) } { - (output) + @let region_name = region.name(); + div + id=(region_name) + class="region" + role="region" + aria-label=[region_label.using(page)] + { + div class={ "region__" (region_name) } { + (output) + } } } } @@ -60,9 +108,16 @@ pub trait Theme: Extension + Send + Sync { } } + /// Acciones específicas del tema después de renderizar el `` de la página. + /// + /// Útil para *tracing*, métricas o ajustes finales del estado de la página. #[allow(unused_variables)] fn after_render_page_body(&self, page: &mut Page) {} + /// Renderiza el contenido del `` de la página. + /// + /// Por defecto, genera las etiquetas básicas (`charset`, `title`, `description`, `viewport`, + /// `X-UA-Compatible`), los metadatos y propiedades de la página y los recursos (CSS/JS). fn render_page_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { @@ -94,11 +149,17 @@ pub trait Theme: Extension + Send + Sync { } } - fn error403(&self, _page: &mut Page) -> Markup { - html! { div { h1 { ("FORBIDDEN ACCESS") } } } + /// Página de error "*403 – Forbidden*" predeterminada. + /// + /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. + fn error403(&self, page: &mut Page) -> Markup { + html! { div { h1 { (L10n::l("error403_notice").to_markup(page)) } } } } - fn error404(&self, _page: &mut Page) -> Markup { - html! { div { h1 { ("RESOURCE NOT FOUND") } } } + /// Página de error "*404 – Not Found*" predeterminada. + /// + /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. + fn error404(&self, page: &mut Page) -> Markup { + html! { div { h1 { (L10n::l("error404_notice").to_markup(page)) } } } } } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 22ab6f25..c8a05554 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -1,5 +1,5 @@ use crate::core::component::{Child, ChildOp, Children}; -use crate::core::theme::{ThemeRef, CONTENT_REGION_NAME}; +use crate::core::theme::ThemeRef; use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; @@ -7,15 +7,71 @@ use parking_lot::RwLock; use std::collections::HashMap; use std::sync::LazyLock; -// Regiones globales con componentes para un tema dado. +// Conjunto de regiones globales asociadas a un tema específico. static THEME_REGIONS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); -// Regiones globales con componentes para cualquier tema. +// Conjunto de regiones globales comunes a todos los temas. static COMMON_REGIONS: LazyLock> = LazyLock::new(|| RwLock::new(ChildrenInRegions::default())); -// Estructura interna para mantener los componentes de una región. +/// Nombre de la región de contenido por defecto (`"content"`). +pub const REGION_CONTENT: &str = "content"; + +/// Identificador de una región de página. +/// +/// Incluye una **clave estática** ([`key()`](Self::key)) que identifica la región en el tema, y un +/// **nombre normalizado** ([`name()`](Self::name)) en minúsculas para su uso en atributos HTML +/// (p.ej., clases `region__{name}`). +/// +/// Se utiliza para declarar las regiones que componen una página en un tema (ver +/// [`declared_regions()`](crate::core::theme::Theme::declared_regions)). +pub struct Region { + key: &'static str, + name: String, +} + +impl Default for Region { + #[inline] + fn default() -> Self { + Self { + key: REGION_CONTENT, + name: String::from(REGION_CONTENT), + } + } +} + +impl Region { + /// Declara una región a partir de su clave estática. + /// + /// Genera además un nombre normalizado de la clave, eliminando espacios iniciales y finales, + /// convirtiendo a minúsculas y sustituyendo los espacios intermedios por guiones (`-`). + /// + /// Esta clave se usará para añadir componentes a la región; por ello se recomiendan nombres + /// sencillos, limitando los caracteres a `[a-z0-9-]` (p.ej., `"sidebar"` o `"main-menu"`), cuyo + /// nombre normalizado coincidirá con la clave. + #[inline] + pub fn declare(key: &'static str) -> Self { + Self { + key, + name: key.trim().to_ascii_lowercase().replace(' ', "-"), + } + } + + /// Devuelve la clave estática asignada a la región. + #[inline] + pub fn key(&self) -> &'static str { + self.key + } + + /// Devuelve el nombre normalizado de la región (para atributos y búsquedas). + #[inline] + pub fn name(&self) -> &str { + &self.name + } +} + +// Contenedor interno de componentes agrupados por región. #[derive(AutoDefault)] pub struct ChildrenInRegions(HashMap<&'static str, Children>); @@ -48,25 +104,24 @@ impl ChildrenInRegions { } } -/// Permite añadir componentes a regiones globales o regiones de temas concretos. +/// Punto de acceso para añadir componentes a regiones globales o específicas de un tema. /// -/// Dada una región, según la variante seleccionada, se le podrán añadir ([`add()`](Self::add)) -/// componentes que se mantendrán durante la ejecución de la aplicación. +/// Según la variante, se pueden añadir componentes ([`add()`](Self::add)) que permanecerán +/// disponibles durante toda la ejecución. /// -/// Estas estructuras de componentes se renderizarán automáticamente al procesar los documentos HTML -/// que las usan, como las páginas de contenido ([`Page`](crate::response::page::Page)), por -/// ejemplo. +/// Estos componentes se renderizarán automáticamente al procesar los documentos HTML que incluyen +/// estas regiones, como las páginas de contenido ([`Page`](crate::response::page::Page)). pub enum InRegion { - /// Representa la región por defecto en la que se pueden añadir componentes. + /// Región de contenido por defecto. Content, - /// Representa la región con el nombre del argumento. + /// Región identificada por el nombre proporcionado. Named(&'static str), - /// Representa la región con el nombre y del tema especificado en los argumentos. + /// Región identificada por un nombre y asociada a un tema concreto. OfTheme(&'static str, ThemeRef), } impl InRegion { - /// Permite añadir un componente en la región de la variante seleccionada. + /// Añade un componente a la región indicada por la variante. /// /// # Ejemplo /// @@ -88,7 +143,7 @@ impl InRegion { InRegion::Content => { COMMON_REGIONS .write() - .alter_child_in_region(CONTENT_REGION_NAME, ChildOp::Add(child)); + .alter_child_in_region(REGION_CONTENT, ChildOp::Add(child)); } InRegion::Named(name) => { COMMON_REGIONS diff --git a/src/locale/en-US/theme.ftl b/src/locale/en-US/theme.ftl index 9c71e6bd..f766766d 100644 --- a/src/locale/en-US/theme.ftl +++ b/src/locale/en-US/theme.ftl @@ -1,2 +1,9 @@ -content = Content +# Regions. +region_header = Header +region_content = Content +region_footer = Footer + +error403_notice = FORBIDDEN ACCESS +error404_notice = RESOURCE NOT FOUND + pagetop_logo = PageTop Logo diff --git a/src/locale/es-ES/theme.ftl b/src/locale/es-ES/theme.ftl index f193c537..b8b91449 100644 --- a/src/locale/es-ES/theme.ftl +++ b/src/locale/es-ES/theme.ftl @@ -1,2 +1,9 @@ -content = Contenido +# Regions. +region_header = Cabecera +region_content = Contenido +region_footer = Pie de página + +error403_notice = ACCESO NO PERMITIDO +error404_notice = RECURSO NO ENCONTRADO + pagetop_logo = Logotipo de PageTop diff --git a/src/response/page.rs b/src/response/page.rs index 44cab725..f30e299e 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -6,7 +6,7 @@ pub use actix_web::Result as ResultPage; use crate::base::action; use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; -use crate::core::theme::{ChildrenInRegions, ThemeRef, CONTENT_REGION_NAME}; +use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, AssetsOp, Context, Markup, DOCTYPE}; use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; @@ -123,7 +123,7 @@ impl Page { /// Añade un componente a la región de contenido por defecto. pub fn with_component(mut self, component: impl Component) -> Self { self.regions - .alter_child_in_region(CONTENT_REGION_NAME, ChildOp::Add(Child::with(component))); + .alter_child_in_region(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } @@ -172,11 +172,6 @@ impl Page { self.context.request() } - /// Devuelve el identificador de idioma asociado. - pub fn langid(&self) -> &LanguageIdentifier { - self.context.langid() - } - /// Devuelve el tema que se usará para renderizar la página. pub fn theme(&self) -> ThemeRef { self.context.theme() @@ -250,3 +245,9 @@ impl Page { }) } } + +impl LangId for Page { + fn langid(&self) -> &'static LanguageIdentifier { + self.context.langid() + } +} From 5ef85154b561e7bd3794ae30cacfbf5b2c2f8afd Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 23 Aug 2025 18:52:45 +0200 Subject: [PATCH 094/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(html):=20Cambia?= =?UTF-8?q?=20tipos=20`Option...`=20por=20`Attr...`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renombra los tipos para atributos HTML `Id`, `Name`, `Value` (`String`), `L10n` (`Translate`) y `Classes`. Y mueve `OptionComponent` al *core* de componentes como `TypedSlot`. --- .../component/after_render_component.rs | 4 +- .../component/before_render_component.rs | 4 +- src/base/action/component/is_renderable.rs | 4 +- src/core/component.rs | 3 + src/core/component/children.rs | 17 ++-- src/core/component/slot.rs | 64 +++++++++++++ src/html.rs | 90 +++++++++++++------ src/html/{opt_classes.rs => attr_classes.rs} | 14 +-- src/html/attr_id.rs | 63 +++++++++++++ src/html/{opt_translated.rs => attr_l10n.rs} | 16 ++-- src/html/attr_name.rs | 63 +++++++++++++ src/html/attr_value.rs | 65 ++++++++++++++ src/html/opt_component.rs | 68 -------------- src/html/opt_id.rs | 59 ------------ src/html/opt_name.rs | 59 ------------ src/html/opt_string.rs | 57 ------------ src/response/page.rs | 28 +++--- 17 files changed, 367 insertions(+), 311 deletions(-) create mode 100644 src/core/component/slot.rs rename src/html/{opt_classes.rs => attr_classes.rs} (90%) create mode 100644 src/html/attr_id.rs rename src/html/{opt_translated.rs => attr_l10n.rs} (79%) create mode 100644 src/html/attr_name.rs create mode 100644 src/html/attr_value.rs delete mode 100644 src/html/opt_component.rs delete mode 100644 src/html/opt_id.rs delete mode 100644 src/html/opt_name.rs delete mode 100644 src/html/opt_string.rs diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs index 917f3225..0cb03347 100644 --- a/src/base/action/component/after_render_component.rs +++ b/src/base/action/component/after_render_component.rs @@ -6,7 +6,7 @@ use crate::base::action::FnActionWithComponent; pub struct AfterRender { f: FnActionWithComponent, referer_type_id: Option, - referer_id: OptionId, + referer_id: AttrId, weight: Weight, } @@ -34,7 +34,7 @@ impl AfterRender { AfterRender { f, referer_type_id: Some(UniqueId::of::()), - referer_id: OptionId::default(), + referer_id: AttrId::default(), weight: 0, } } diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs index 8c2e38d7..46ff9aae 100644 --- a/src/base/action/component/before_render_component.rs +++ b/src/base/action/component/before_render_component.rs @@ -6,7 +6,7 @@ use crate::base::action::FnActionWithComponent; pub struct BeforeRender { f: FnActionWithComponent, referer_type_id: Option, - referer_id: OptionId, + referer_id: AttrId, weight: Weight, } @@ -34,7 +34,7 @@ impl BeforeRender { BeforeRender { f, referer_type_id: Some(UniqueId::of::()), - referer_id: OptionId::default(), + referer_id: AttrId::default(), weight: 0, } } diff --git a/src/base/action/component/is_renderable.rs b/src/base/action/component/is_renderable.rs index baa86f1d..5a0e244e 100644 --- a/src/base/action/component/is_renderable.rs +++ b/src/base/action/component/is_renderable.rs @@ -11,7 +11,7 @@ pub type FnIsRenderable = fn(component: &C, cx: &Context) -> bool; pub struct IsRenderable { f: FnIsRenderable, referer_type_id: Option, - referer_id: OptionId, + referer_id: AttrId, weight: Weight, } @@ -39,7 +39,7 @@ impl IsRenderable { IsRenderable { f, referer_type_id: Some(UniqueId::of::()), - referer_id: OptionId::default(), + referer_id: AttrId::default(), weight: 0, } } diff --git a/src/core/component.rs b/src/core/component.rs index 17b9b735..36914722 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -7,3 +7,6 @@ mod children; pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; + +mod slot; +pub use slot::TypedSlot; diff --git a/src/core/component/children.rs b/src/core/component/children.rs index fb85db7a..cb112e16 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -9,13 +9,13 @@ use std::vec::IntoIter; /// Representa un componente encapsulado de forma segura y compartida. /// -/// Esta estructura permite manipular y renderizar cualquier tipo que implemente [`Component`], -/// garantizando acceso concurrente a través de [`Arc>`]. +/// Esta estructura permite manipular y renderizar un componente que implemente [`Component`], y +/// habilita acceso concurrente mediante [`Arc>`]. #[derive(Clone)] pub struct Child(Arc>); impl Child { - /// Crea un nuevo [`Child`] a partir de un componente. + /// Crea un nuevo `Child` a partir de un componente. pub fn with(component: impl Component) -> Self { Child(Arc::new(RwLock::new(component))) } @@ -46,7 +46,8 @@ impl Child { /// Variante tipada de [`Child`] para evitar conversiones durante el uso. /// -/// Facilita el acceso a componentes del mismo tipo sin necesidad de hacer `downcast`. +/// Esta estructura permite manipular y renderizar un componente concreto que implemente +/// [`Component`], y habilita acceso concurrente mediante [`Arc>`]. pub struct Typed(Arc>); impl Clone for Typed { @@ -56,7 +57,7 @@ impl Clone for Typed { } impl Typed { - /// Crea un nuevo [`Typed`] a partir de un componente. + /// Crea un nuevo `Typed` a partir de un componente. pub fn with(component: C) -> Self { Typed(Arc::new(RwLock::new(component))) } @@ -284,7 +285,7 @@ impl IntoIterator for Children { /// /// # Ejemplo de uso: /// - /// ```rust#ignore + /// ```rust,ignore /// let children = Children::new().with(child1).with(child2); /// for child in children { /// println!("{:?}", child.id()); @@ -303,7 +304,7 @@ impl<'a> IntoIterator for &'a Children { /// /// # Ejemplo de uso: /// - /// ```rust#ignore + /// ```rust,ignore /// let children = Children::new().with(child1).with(child2); /// for child in &children { /// println!("{:?}", child.id()); @@ -322,7 +323,7 @@ impl<'a> IntoIterator for &'a mut Children { /// /// # Ejemplo de uso: /// - /// ```rust#ignore + /// ```rust,ignore /// let mut children = Children::new().with(child1).with(child2); /// for child in &mut children { /// child.render(&mut context); diff --git a/src/core/component/slot.rs b/src/core/component/slot.rs new file mode 100644 index 00000000..19ed72ac --- /dev/null +++ b/src/core/component/slot.rs @@ -0,0 +1,64 @@ +use crate::builder_fn; +use crate::core::component::{Component, Typed}; +use crate::html::{html, Context, Markup}; + +/// Contenedor para un componente [`Typed`] opcional. +/// +/// Un `TypedSlot` actúa como un contenedor dentro de otro componente para incluir o no un +/// subcomponente. Internamente encapsula `Option>`, pero proporciona una API más sencilla +/// para construir estructuras jerárquicas. +/// +/// # Ejemplo +/// +/// ```rust,ignore +/// use pagetop::prelude::*; +/// +/// let comp = MyComponent::new(); +/// let opt = TypedSlot::new(comp); +/// assert!(opt.get().is_some()); +/// ``` +pub struct TypedSlot(Option>); + +impl Default for TypedSlot { + fn default() -> Self { + TypedSlot(None) + } +} + +impl TypedSlot { + /// Crea un nuevo [`TypedSlot`]. + /// + /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. + pub fn new(component: C) -> Self { + TypedSlot(Some(Typed::with(component))) + } + + // TypedSlot BUILDER ********************************************************************* + + /// Establece un componente nuevo, o lo vacía. + /// + /// Si se proporciona `Some(component)`, se guarda en [`Typed`]; y si es `None`, se limpia. + #[builder_fn] + pub fn with_value(mut self, component: Option) -> Self { + self.0 = component.map(Typed::with); + self + } + + // TypedSlot GETTERS ********************************************************************* + + /// Devuelve un clon (incrementa el contador `Arc`) de [`Typed`], si existe. + pub fn get(&self) -> Option> { + self.0.clone() + } + + // TypedSlot RENDER ************************************************************************ + + /// Renderiza el componente, si existe. + pub fn render(&self, cx: &mut Context) -> Markup { + if let Some(component) = &self.0 { + component.render(cx) + } else { + html! {} + } + } +} diff --git a/src/html.rs b/src/html.rs index 82fa906b..784457ed 100644 --- a/src/html.rs +++ b/src/html.rs @@ -3,52 +3,82 @@ mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, Render, DOCTYPE}; +// HTML DOCUMENT ASSETS **************************************************************************** + mod assets; pub use assets::favicon::Favicon; pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; pub(crate) use assets::Assets; +// HTML DOCUMENT CONTEXT *************************************************************************** + mod context; pub use context::{AssetsOp, Context, ErrorParam}; -mod opt_id; -pub use opt_id::OptionId; +// HTML ATTRIBUTES ********************************************************************************* -mod opt_name; -pub use opt_name::OptionName; +mod attr_id; +pub use attr_id::AttrId; +/// **Obsoleto desde la versión 0.4.0**: usar [`AttrId`] en su lugar. +#[deprecated(since = "0.4.0", note = "Use `AttrId` instead")] +pub type OptionId = AttrId; -mod opt_string; -pub use opt_string::OptionString; +mod attr_name; +pub use attr_name::AttrName; +/// **Obsoleto desde la versión 0.4.0**: usar [`AttrName`] en su lugar. +#[deprecated(since = "0.4.0", note = "Use `AttrName` instead")] +pub type OptionName = AttrName; -mod opt_translated; -pub use opt_translated::OptionTranslated; +mod attr_value; +pub use attr_value::AttrValue; +/// **Obsoleto desde la versión 0.4.0**: usar [`AttrValue`] en su lugar. +#[deprecated(since = "0.4.0", note = "Use `AttrValue` instead")] +pub type OptionString = AttrValue; -mod opt_classes; -pub use opt_classes::{ClassesOp, OptionClasses}; +mod attr_l10n; +pub use attr_l10n::AttrL10n; +/// **Obsoleto desde la versión 0.4.0**: usar [`AttrL10n`] en su lugar. +#[deprecated(since = "0.4.0", note = "Use `AttrL10n` instead")] +pub type OptionTranslated = AttrL10n; -mod opt_component; -pub use opt_component::OptionComponent; +mod attr_classes; +pub use attr_classes::{AttrClasses, ClassesOp}; +/// **Obsoleto desde la versión 0.4.0**: usar [`AttrClasses`] en su lugar. +#[deprecated(since = "0.4.0", note = "Use `AttrClasses` instead")] +pub type OptionClasses = AttrClasses; -use crate::AutoDefault; +use crate::{core, AutoDefault}; + +/// **Obsoleto desde la versión 0.4.0**: usar [`TypedSlot`](crate::core::component::TypedSlot) en su +/// lugar. +#[deprecated( + since = "0.4.0", + note = "Use `pagetop::core::component::TypedSlot` instead" +)] +#[allow(type_alias_bounds)] +pub type OptionComponent = core::component::TypedSlot; /// Prepara contenido HTML para su conversión a [`Markup`]. /// -/// Este tipo encapsula distintos orígenes de contenido HTML (texto plano, HTML escapado o marcado -/// ya procesado) para renderizar de forma homogénea en plantillas sin interferir con el uso -/// estándar de [`Markup`]. +/// Este tipo encapsula distintos orígenes de contenido HTML (texto plano, HTML sin escapar o +/// fragmentos ya procesados) para renderizarlos de forma homogénea en plantillas, sin interferir +/// con el uso estándar de [`Markup`]. /// /// # Ejemplo /// /// ```rust /// use pagetop::prelude::*; /// -/// let fragment = PrepareMarkup::Text(String::from("Hola mundo")); +/// // Texto normal, se escapa automáticamente para evitar inyección de HTML. +/// let fragment = PrepareMarkup::Escaped(String::from("Hola mundo")); /// assert_eq!(fragment.render().into_string(), "Hola <b>mundo</b>"); /// -/// let raw_html = PrepareMarkup::Escaped(String::from("negrita")); +/// // HTML literal, se inserta directamente, sin escapado adicional. +/// let raw_html = PrepareMarkup::Raw(String::from("negrita")); /// assert_eq!(raw_html.render().into_string(), "negrita"); /// +/// // Fragmento ya preparado con la macro `html!`. /// let prepared = PrepareMarkup::With(html! { /// h2 { "Título de ejemplo" } /// p { "Este es un párrafo con contenido dinámico." } @@ -60,14 +90,22 @@ use crate::AutoDefault; /// ``` #[derive(AutoDefault)] pub enum PrepareMarkup { - /// No se genera contenido HTML (devuelve `html! {}`). + /// No se genera contenido HTML (equivale a `html! {}`). #[default] None, - /// Texto estático que se escapará automáticamente para no ser interpretado como HTML. - Text(String), - /// Contenido sin escapado adicional, útil para HTML generado externamente. + /// Texto plano que se **escapará automáticamente** para que no sea interpretado como HTML. + /// + /// Úsalo con textos que provengan de usuarios u otras fuentes externas para garantizar la + /// seguridad contra inyección de código. Escaped(String), + /// HTML literal que se inserta **sin escapado adicional**. + /// + /// Úsalo únicamente para contenido generado de forma confiable o controlada, ya que cualquier + /// etiqueta o script incluido será renderizado directamente en el documento. + Raw(String), /// Fragmento HTML ya preparado como [`Markup`], listo para insertarse directamente. + /// + /// Normalmente proviene de expresiones `html! { ... }`. With(Markup), } @@ -76,8 +114,8 @@ impl PrepareMarkup { pub fn is_empty(&self) -> bool { match self { PrepareMarkup::None => true, - PrepareMarkup::Text(text) => text.is_empty(), - PrepareMarkup::Escaped(string) => string.is_empty(), + PrepareMarkup::Escaped(text) => text.is_empty(), + PrepareMarkup::Raw(string) => string.is_empty(), PrepareMarkup::With(markup) => markup.is_empty(), } } @@ -88,8 +126,8 @@ impl Render for PrepareMarkup { fn render(&self) -> Markup { match self { PrepareMarkup::None => html! {}, - PrepareMarkup::Text(text) => html! { (text) }, - PrepareMarkup::Escaped(string) => html! { (PreEscaped(string)) }, + PrepareMarkup::Escaped(text) => html! { (text) }, + PrepareMarkup::Raw(string) => html! { (PreEscaped(string)) }, PrepareMarkup::With(markup) => html! { (markup) }, } } diff --git a/src/html/opt_classes.rs b/src/html/attr_classes.rs similarity index 90% rename from src/html/opt_classes.rs rename to src/html/attr_classes.rs index a9857625..92851aa3 100644 --- a/src/html/opt_classes.rs +++ b/src/html/attr_classes.rs @@ -1,6 +1,6 @@ use crate::{builder_fn, AutoDefault}; -/// Operaciones disponibles sobre la lista de clases en [`OptionClasses`]. +/// Operaciones disponibles sobre la lista de clases en [`AttrClasses`]. pub enum ClassesOp { /// Añade al final (si no existe). Add, @@ -33,7 +33,7 @@ pub enum ClassesOp { /// ```rust /// use pagetop::prelude::*; /// -/// let classes = OptionClasses::new("Btn btn-primary") +/// let classes = AttrClasses::new("Btn btn-primary") /// .with_value(ClassesOp::Add, "Active") /// .with_value(ClassesOp::Remove, "btn-primary"); /// @@ -41,14 +41,14 @@ pub enum ClassesOp { /// assert!(classes.contains("active")); /// ``` #[derive(AutoDefault, Clone, Debug)] -pub struct OptionClasses(Vec); +pub struct AttrClasses(Vec); -impl OptionClasses { +impl AttrClasses { pub fn new(classes: impl AsRef) -> Self { - OptionClasses::default().with_value(ClassesOp::Prepend, classes) + AttrClasses::default().with_value(ClassesOp::Prepend, classes) } - // OptionClasses BUILDER *********************************************************************** + // AttrClasses BUILDER ************************************************************************* #[builder_fn] pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef) -> Self { @@ -114,7 +114,7 @@ impl OptionClasses { } } - // OptionClasses GETTERS *********************************************************************** + // AttrClasses GETTERS ************************************************************************* /// Devuele la cadena de clases, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_id.rs b/src/html/attr_id.rs new file mode 100644 index 00000000..8bb1d33b --- /dev/null +++ b/src/html/attr_id.rs @@ -0,0 +1,63 @@ +use crate::{builder_fn, AutoDefault}; + +/// Identificador normalizado para el atributo `id` o similar de HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: +/// +/// - Se eliminan los espacios al principio y al final. +/// - Se convierte a minúsculas. +/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let id = AttrId::new(" main Section "); +/// assert_eq!(id.as_str(), Some("main_section")); +/// +/// let empty = AttrId::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct AttrId(Option); + +impl AttrId { + /// Crea un nuevo `AttrId` normalizando el valor. + pub fn new(value: impl AsRef) -> Self { + AttrId::default().with_value(value) + } + + // AttrId BUILDER ****************************************************************************** + + /// Establece un identificador nuevo normalizando el valor. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); + self.0 = if value.is_empty() { None } else { Some(value) }; + self + } + + // AttrId GETTERS ****************************************************************************** + + /// Devuelve el identificador normalizado, si existe. + pub fn get(&self) -> Option { + self.0.as_ref().cloned() + } + + /// Devuelve el identificador normalizado (sin clonar), si existe. + pub fn as_str(&self) -> Option<&str> { + self.0.as_deref() + } + + /// Devuelve el identificador normalizado (propiedad), si existe. + pub fn into_inner(self) -> Option { + self.0 + } + + /// `true` si no hay valor. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } +} diff --git a/src/html/opt_translated.rs b/src/html/attr_l10n.rs similarity index 79% rename from src/html/opt_translated.rs rename to src/html/attr_l10n.rs index b15ea18a..cd5b389d 100644 --- a/src/html/opt_translated.rs +++ b/src/html/attr_l10n.rs @@ -2,7 +2,7 @@ use crate::html::Markup; use crate::locale::{L10n, LangId}; use crate::{builder_fn, AutoDefault}; -/// Cadena para traducir al renderizar ([`locale`](crate::locale)). +/// Texto para [traducir](crate::locale) en atributos HTML. /// /// Encapsula un tipo [`L10n`] para manejar traducciones de forma segura. /// @@ -12,7 +12,7 @@ use crate::{builder_fn, AutoDefault}; /// use pagetop::prelude::*; /// /// // Traducción por clave en las locales por defecto de PageTop. -/// let hello = OptionTranslated::new(L10n::l("test-hello-world")); +/// let hello = AttrL10n::new(L10n::l("test-hello-world")); /// /// // Español disponible. /// assert_eq!( @@ -31,15 +31,15 @@ use crate::{builder_fn, AutoDefault}; /// assert_eq!(markup.into_string(), "¡Hola mundo!"); /// ``` #[derive(AutoDefault, Clone, Debug)] -pub struct OptionTranslated(L10n); +pub struct AttrL10n(L10n); -impl OptionTranslated { - /// Crea una nueva instancia [`OptionTranslated`]. +impl AttrL10n { + /// Crea una nueva instancia `AttrL10n`. pub fn new(value: L10n) -> Self { - OptionTranslated(value) + AttrL10n(value) } - // OptionTranslated BUILDER ******************************************************************** + // AttrL10n BUILDER **************************************************************************** /// Establece una traducción nueva. #[builder_fn] @@ -48,7 +48,7 @@ impl OptionTranslated { self } - // OptionTranslated GETTERS ******************************************************************** + // AttrL10n GETTERS **************************************************************************** /// Devuelve la traducción para `language`, si existe. pub fn using(&self, language: &impl LangId) -> Option { diff --git a/src/html/attr_name.rs b/src/html/attr_name.rs new file mode 100644 index 00000000..928f841f --- /dev/null +++ b/src/html/attr_name.rs @@ -0,0 +1,63 @@ +use crate::{builder_fn, AutoDefault}; + +/// Nombre normalizado para el atributo `name` o similar de HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: +/// +/// - Se eliminan los espacios al principio y al final. +/// - Se convierte a minúsculas. +/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let name = AttrName::new(" DISplay name "); +/// assert_eq!(name.as_str(), Some("display_name")); +/// +/// let empty = AttrName::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct AttrName(Option); + +impl AttrName { + /// Crea un nuevo `AttrName` normalizando el valor. + pub fn new(value: impl AsRef) -> Self { + AttrName::default().with_value(value) + } + + // AttrName BUILDER **************************************************************************** + + /// Establece un nombre nuevo normalizando el valor. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); + self.0 = if value.is_empty() { None } else { Some(value) }; + self + } + + // AttrName GETTERS **************************************************************************** + + /// Devuelve el nombre normalizado, si existe. + pub fn get(&self) -> Option { + self.0.as_ref().cloned() + } + + /// Devuelve el nombre normalizado (sin clonar), si existe. + pub fn as_str(&self) -> Option<&str> { + self.0.as_deref() + } + + /// Devuelve el nombre normalizado (propiedad), si existe. + pub fn into_inner(self) -> Option { + self.0 + } + + /// `true` si no hay valor. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } +} diff --git a/src/html/attr_value.rs b/src/html/attr_value.rs new file mode 100644 index 00000000..c70229f9 --- /dev/null +++ b/src/html/attr_value.rs @@ -0,0 +1,65 @@ +use crate::{builder_fn, AutoDefault}; + +/// Cadena normalizada para renderizar en atributos HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: +/// +/// - Se eliminan los espacios al principio y al final. +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let s = AttrValue::new(" a new string "); +/// assert_eq!(s.as_str(), Some("a new string")); +/// +/// let empty = AttrValue::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] +pub struct AttrValue(Option); + +impl AttrValue { + /// Crea un nuevo `AttrValue` normalizando el valor. + pub fn new(value: impl AsRef) -> Self { + AttrValue::default().with_value(value) + } + + // AttrValue BUILDER *************************************************************************** + + /// Establece una cadena nueva normalizando el valor. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + let value = value.as_ref().trim(); + self.0 = if value.is_empty() { + None + } else { + Some(value.to_owned()) + }; + self + } + + // AttrValue GETTERS *************************************************************************** + + /// Devuelve la cadena normalizada, si existe. + pub fn get(&self) -> Option { + self.0.as_ref().cloned() + } + + /// Devuelve la cadena normalizada (sin clonar), si existe. + pub fn as_str(&self) -> Option<&str> { + self.0.as_deref() + } + + /// Devuelve la cadena normalizada (propiedad), si existe. + pub fn into_inner(self) -> Option { + self.0 + } + + /// `true` si no hay valor. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } +} diff --git a/src/html/opt_component.rs b/src/html/opt_component.rs deleted file mode 100644 index 39106d99..00000000 --- a/src/html/opt_component.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::builder_fn; -use crate::core::component::{Component, Typed}; -use crate::html::{html, Context, Markup}; - -/// Contenedor de componente para incluir en otros componentes. -/// -/// Este tipo encapsula `Option>` para incluir un componente de manera segura en otros -/// componentes, útil para representar estructuras complejas. -/// -/// # Ejemplo -/// -/// ```rust,ignore -/// use pagetop::prelude::*; -/// -/// let comp = MyComponent::new(); -/// let opt = OptionComponent::new(comp); -/// assert!(opt.get().is_some()); -/// ``` -pub struct OptionComponent(Option>); - -impl Default for OptionComponent { - fn default() -> Self { - OptionComponent(None) - } -} - -impl OptionComponent { - /// Crea un nuevo [`OptionComponent`]. - /// - /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. - pub fn new(component: C) -> Self { - OptionComponent::default().with_value(Some(component)) - } - - // OptionComponent BUILDER ********************************************************************* - - /// Establece un componente nuevo, o lo vacía. - /// - /// Si se proporciona `Some(component)`, se guarda en [`Typed`]; y si es `None`, se limpia. - #[builder_fn] - pub fn with_value(mut self, component: Option) -> Self { - if let Some(component) = component { - self.0 = Some(Typed::with(component)); - } else { - self.0 = None; - } - self - } - - // OptionComponent GETTERS ********************************************************************* - - /// Devuelve el componente, si existe. - pub fn get(&self) -> Option> { - if let Some(value) = &self.0 { - return Some(value.clone()); - } - None - } - - /// Renderiza el componente, si existe. - pub fn render(&self, cx: &mut Context) -> Markup { - if let Some(component) = &self.0 { - component.render(cx) - } else { - html! {} - } - } -} diff --git a/src/html/opt_id.rs b/src/html/opt_id.rs deleted file mode 100644 index 139fdcd6..00000000 --- a/src/html/opt_id.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::{builder_fn, AutoDefault}; - -/// Identificador normalizado para el atributo `id` o similar de HTML. -/// -/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. -/// -/// # Normalización -/// -/// - Se eliminan los espacios al principio y al final. -/// - Se convierte a minúsculas. -/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). -/// - Si el resultado es una cadena vacía, se guarda `None`. -/// -/// # Ejemplo -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// let id = OptionId::new(" main Section "); -/// assert_eq!(id.get(), Some(String::from("main_section"))); -/// -/// let empty = OptionId::default(); -/// assert_eq!(empty.get(), None); -/// ``` -#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] -pub struct OptionId(Option); - -impl OptionId { - /// Crea un nuevo [`OptionId`]. - /// - /// El valor se normaliza automáticamente. - pub fn new(value: impl AsRef) -> Self { - OptionId::default().with_value(value) - } - - // OptionId BUILDER **************************************************************************** - - /// Establece un identificador nuevo. - /// - /// El valor se normaliza automáticamente. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); - self.0 = (!value.is_empty()).then_some(value); - self - } - - // OptionId GETTERS **************************************************************************** - - /// Devuelve el identificador, si existe. - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/src/html/opt_name.rs b/src/html/opt_name.rs deleted file mode 100644 index ffb0b989..00000000 --- a/src/html/opt_name.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::{builder_fn, AutoDefault}; - -/// Nombre normalizado para el atributo `name` o similar de HTML. -/// -/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. -/// -/// # Normalización -/// -/// - Se eliminan los espacios al principio y al final. -/// - Se convierte a minúsculas. -/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). -/// - Si el resultado es una cadena vacía, se guarda `None`. -/// -/// # Ejemplo -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// let name = OptionName::new(" DISplay name "); -/// assert_eq!(name.get(), Some(String::from("display_name"))); -/// -/// let empty = OptionName::default(); -/// assert_eq!(empty.get(), None); -/// ``` -#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] -pub struct OptionName(Option); - -impl OptionName { - /// Crea un nuevo [`OptionName`]. - /// - /// El valor se normaliza automáticamente. - pub fn new(value: impl AsRef) -> Self { - OptionName::default().with_value(value) - } - - // OptionName BUILDER ************************************************************************** - - /// Establece un nombre nuevo. - /// - /// El valor se normaliza automáticamente. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); - self.0 = (!value.is_empty()).then_some(value); - self - } - - // OptionName GETTERS ************************************************************************** - - /// Devuelve el nombre, si existe. - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/src/html/opt_string.rs b/src/html/opt_string.rs deleted file mode 100644 index 5bfd9c71..00000000 --- a/src/html/opt_string.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::{builder_fn, AutoDefault}; - -/// Cadena normalizada para renderizar en atributos HTML. -/// -/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso. -/// -/// # Normalización -/// -/// - Se eliminan los espacios al principio y al final. -/// - Si el resultado es una cadena vacía, se guarda `None`. -/// -/// # Ejemplo -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// let s = OptionString::new(" a new string "); -/// assert_eq!(s.get(), Some(String::from("a new string"))); -/// -/// let empty = OptionString::default(); -/// assert_eq!(empty.get(), None); -/// ``` -#[derive(AutoDefault, Clone, Debug, Hash, Eq, PartialEq)] -pub struct OptionString(Option); - -impl OptionString { - /// Crea un nuevo [`OptionString`]. - /// - /// El valor se normaliza automáticamente. - pub fn new(value: impl AsRef) -> Self { - OptionString::default().with_value(value) - } - - // OptionString BUILDER ************************************************************************ - - /// Establece una cadena nueva. - /// - /// El valor se normaliza automáticamente. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - let value = value.as_ref().trim().to_owned(); - self.0 = (!value.is_empty()).then_some(value); - self - } - - // OptionString GETTERS ************************************************************************ - - /// Devuelve la cadena, si existe. - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/src/response/page.rs b/src/response/page.rs index f30e299e..ea88e846 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -7,8 +7,10 @@ use crate::base::action; use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; -use crate::html::{html, AssetsOp, Context, Markup, DOCTYPE}; -use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated}; +use crate::html::{html, Markup, DOCTYPE}; +use crate::html::{AssetsOp, Context}; +use crate::html::{AttrClasses, ClassesOp}; +use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; @@ -19,13 +21,13 @@ use crate::service::HttpRequest; /// renderizado. #[rustfmt::skip] pub struct Page { - title : OptionTranslated, - description : OptionTranslated, + title : AttrL10n, + description : AttrL10n, metadata : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>, context : Context, - body_id : OptionId, - body_classes: OptionClasses, + body_id : AttrId, + body_classes: AttrClasses, regions : ChildrenInRegions, } @@ -37,13 +39,13 @@ impl Page { #[rustfmt::skip] pub fn new(request: Option) -> Self { Page { - title : OptionTranslated::default(), - description : OptionTranslated::default(), + title : AttrL10n::default(), + description : AttrL10n::default(), metadata : Vec::default(), properties : Vec::default(), context : Context::new(request), - body_id : OptionId::default(), - body_classes: OptionClasses::default(), + body_id : AttrId::default(), + body_classes: AttrClasses::default(), regions : ChildrenInRegions::default(), } } @@ -113,7 +115,7 @@ impl Page { self } - /// Modifica las clases CSS del elemento `` con una operación sobre [`OptionClasses`]. + /// Modifica las clases CSS del elemento `` con una operación sobre [`AttrClasses`]. #[builder_fn] pub fn with_body_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { self.body_classes.alter_value(op, classes); @@ -183,12 +185,12 @@ impl Page { } /// Devuelve el identificador del elemento ``. - pub fn body_id(&self) -> &OptionId { + pub fn body_id(&self) -> &AttrId { &self.body_id } /// Devuelve las clases CSS del elemento ``. - pub fn body_classes(&self) -> &OptionClasses { + pub fn body_classes(&self) -> &AttrClasses { &self.body_classes } From fbdce1035c13bc0bb386b0e19001c82650c1a232 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 23 Aug 2025 19:34:26 +0200 Subject: [PATCH 095/224] =?UTF-8?q?=E2=9C=85=20(tests):=20Ampl=C3=ADa=20pr?= =?UTF-8?q?uebas=20para=20`PrepareMarkup'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/html.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/tests/html.rs b/tests/html.rs index 315f74ad..1499c709 100644 --- a/tests/html.rs +++ b/tests/html.rs @@ -1,17 +1,110 @@ use pagetop::prelude::*; #[pagetop::test] -async fn prepare_markup_is_empty() { - let _app = service::test::init_service(Application::new().test()).await; +async fn prepare_markup_render_none_is_empty_string() { + assert_eq!(render(&PrepareMarkup::None), ""); +} +#[pagetop::test] +async fn prepare_markup_render_escaped_escapes_html_and_ampersands() { + let pm = PrepareMarkup::Escaped(String::from("& \" ' ")); + assert_eq!(render(&pm), "<b>& " ' </b>"); +} + +#[pagetop::test] +async fn prepare_markup_render_raw_is_inserted_verbatim() { + let pm = PrepareMarkup::Raw(String::from("bold")); + assert_eq!(render(&pm), "bold"); +} + +#[pagetop::test] +async fn prepare_markup_render_with_keeps_structure() { + let pm = PrepareMarkup::With(html! { + h2 { "Sample title" } + p { "This is a paragraph." } + }); + assert_eq!( + render(&pm), + "

Sample title

This is a paragraph.

" + ); +} + +#[pagetop::test] +async fn prepare_markup_does_not_double_escape_when_wrapped_in_html_macro() { + // Escaped: dentro de `html!` no debe volver a escaparse. + let escaped = PrepareMarkup::Escaped("x".into()); + let wrapped_escaped = html! { div { (escaped) } }; + assert_eq!( + wrapped_escaped.into_string(), + "
<i>x</i>
" + ); + + // Raw: tampoco debe escaparse al integrarlo. + let raw = PrepareMarkup::Raw("x".into()); + let wrapped_raw = html! { div { (raw) } }; + assert_eq!(wrapped_raw.into_string(), "
x
"); + + // With: debe incrustar el Markup tal cual. + let with = PrepareMarkup::With(html! { span.title { "ok" } }); + let wrapped_with = html! { div { (with) } }; + assert_eq!( + wrapped_with.into_string(), + "
ok
" + ); +} + +#[pagetop::test] +async fn prepare_markup_unicode_is_preserved() { + // Texto con acentos y emojis debe conservarse (salvo el escape HTML de signos). + let esc = PrepareMarkup::Escaped("Hello, tomorrow coffee ☕ & donuts!".into()); + assert_eq!(render(&esc), "Hello, tomorrow coffee ☕ & donuts!"); + + // Raw debe pasar íntegro. + let raw = PrepareMarkup::Raw("Title — section © 2025".into()); + assert_eq!(render(&raw), "Title — section © 2025"); +} + +#[pagetop::test] +async fn prepare_markup_is_empty_semantics() { assert!(PrepareMarkup::None.is_empty()); - assert!(PrepareMarkup::Text(String::from("")).is_empty()); - assert!(!PrepareMarkup::Text(String::from("x")).is_empty()); - assert!(PrepareMarkup::Escaped(String::new()).is_empty()); - assert!(!PrepareMarkup::Escaped("a".into()).is_empty()); + assert!(PrepareMarkup::Escaped(String::from("")).is_empty()); + assert!(!PrepareMarkup::Escaped(String::from("x")).is_empty()); + + assert!(PrepareMarkup::Raw(String::new()).is_empty()); + assert!(PrepareMarkup::Raw(String::from("")).is_empty()); + assert!(!PrepareMarkup::Raw("a".into()).is_empty()); assert!(PrepareMarkup::With(html! {}).is_empty()); assert!(!PrepareMarkup::With(html! { span { "!" } }).is_empty()); + + // Ojo: espacios NO deberían considerarse vacíos (comportamiento actual). + assert!(!PrepareMarkup::Escaped(" ".into()).is_empty()); + assert!(!PrepareMarkup::Raw(" ".into()).is_empty()); +} + +#[pagetop::test] +async fn prepare_markup_equivalence_between_render_and_inline_in_html_macro() { + let cases = [ + PrepareMarkup::None, + PrepareMarkup::Escaped("x".into()), + PrepareMarkup::Raw("x".into()), + PrepareMarkup::With(html! { b { "x" } }), + ]; + + for pm in cases { + let rendered = render(&pm); + let in_macro = html! { (pm) }.into_string(); + assert_eq!( + rendered, in_macro, + "The output of Render and (pm) inside html! must match" + ); + } +} + +// HELPERS ***************************************************************************************** + +fn render(x: &impl Render) -> String { + x.render().into_string() } From fdf7c40da715162c4fc1217e2b841ca493b16fca Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 24 Aug 2025 10:04:51 +0200 Subject: [PATCH 096/224] =?UTF-8?q?=F0=9F=93=9D=20(component):=20Ampl?= =?UTF-8?q?=C3=ADa=20documentaci=C3=B3n=20de=20preparaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade un párrafo explicando la mejor manera de que `prepare_component()` pueda ser útil a los programadores que sobrescriban su comportamiento. --- src/core/component/definition.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index 2818570a..e9a792d7 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -51,12 +51,17 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { #[allow(unused_variables)] fn setup_before_prepare(&mut self, cx: &mut Context) {} - /// Devuelve una representación estructurada del componente lista para renderizar. + /// Devuelve una representación estructurada del componente preparada para el renderizado. /// /// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente /// durante el proceso de construcción del documento. Puede sobrescribirse para generar /// dinámicamente el contenido HTML con acceso al contexto de renderizado. /// + /// Este método debe ser capaz de preparar el renderizado del componente con los métodos del + /// propio componente y el contexto proporcionado, no debería hacerlo accediendo directamente a + /// los campos de la estructura del componente. Es una forma de garantizar que los programadores + /// podrán sobrescribir este método sin preocuparse por los detalles internos del componente. + /// /// Por defecto, devuelve [`PrepareMarkup::None`]. #[allow(unused_variables)] fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { From c63c4e828914e3a13fd8312e11aedb64f8290999 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 24 Aug 2025 10:05:46 +0200 Subject: [PATCH 097/224] =?UTF-8?q?=F0=9F=92=A1=20Correcci=C3=B3n=20menor?= =?UTF-8?q?=20en=20comentario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/attr_classes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 92851aa3..91ccfaf0 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -116,7 +116,7 @@ impl AttrClasses { // AttrClasses GETTERS ************************************************************************* - /// Devuele la cadena de clases, si existe. + /// Devuelve la cadena de clases, si existe. pub fn get(&self) -> Option { if self.0.is_empty() { None From c8e232d3890069acd2608611aad4216b5998d0e4 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 24 Aug 2025 10:09:22 +0200 Subject: [PATCH 098/224] =?UTF-8?q?=F0=9F=9A=A7=20(html):=20Implementa=20`?= =?UTF-8?q?Default`=20en=20`Context`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/context.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/html/context.rs b/src/html/context.rs index 72c0ff29..96787863 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -117,6 +117,12 @@ pub struct Context { id_counter : usize, // Contador para generar identificadores únicos. } +impl Default for Context { + fn default() -> Self { + Context::new(None) + } +} + impl Context { /// Crea un nuevo contexto asociado a una solicitud HTTP. /// From 551e85b23937d6716bc03860415c7d30ffd68e7a Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 24 Aug 2025 10:16:02 +0200 Subject: [PATCH 099/224] =?UTF-8?q?=F0=9F=9A=A7=20Aplica=20recomendaciones?= =?UTF-8?q?=20en=20componente=20Html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/html.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/base/component/html.rs b/src/base/component/html.rs index 8f273edb..cac39eaf 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -44,11 +44,13 @@ impl Component for Html { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With((self.0)(cx)) + PrepareMarkup::With(self.html(cx)) } } impl Html { + // Html BUILDER ******************************************************************************** + /// Crea una instancia que generará el `Markup`, con acceso opcional al contexto. /// /// El método [`prepare_component()`](crate::core::component::Component::prepare_component) @@ -66,11 +68,24 @@ impl Html { /// Permite a otras extensiones modificar la función de renderizado que se ejecutará cuando /// [`prepare_component()`](crate::core::component::Component::prepare_component) invoque esta /// instancia. La nueva función también recibe una referencia al contexto ([`Context`]). - pub fn alter_html(&mut self, f: F) -> &mut Self + #[builder_fn] + pub fn with_fn(mut self, f: F) -> Self where F: Fn(&mut Context) -> Markup + Send + Sync + 'static, { self.0 = Box::new(f); self } + + // Html GETTERS ******************************************************************************** + + /// Aplica la función interna de renderizado con el [`Context`] proporcionado. + /// + /// Normalmente no se invoca manualmente, ya que el proceso de renderizado de los componentes lo + /// invoca automáticamente durante la construcción de la página. Puede usarse, no obstante, para + /// sobrescribir [`prepare_component()`](crate::core::component::Component::prepare_component) + /// y alterar el comportamiento del componente. + pub fn html(&self, cx: &mut Context) -> Markup { + (self.0)(cx) + } } From bb759e9a3824509886a8312b96638f07d31e2057 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 24 Aug 2025 10:19:17 +0200 Subject: [PATCH 100/224] =?UTF-8?q?=F0=9F=92=84=20A=C3=B1ade=20componente?= =?UTF-8?q?=20`PoweredBy`=20para=20copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adapta la página de bienvenida al tratamiento revisado de regiones y añade en el pie el componente `PoweredBy` para la nota de copyright. --- src/base/component.rs | 3 + src/base/component/poweredby.rs | 69 ++++++++++++++++++++++ src/base/extension/welcome.rs | 49 ++++++++-------- src/base/theme/basic.rs | 5 ++ src/core/theme/definition.rs | 6 +- src/locale/en-US/base.ftl | 2 + src/locale/es-ES/base.ftl | 2 + static/css/basic.css | 11 ++++ static/css/welcome.css | 54 ++++++++++------- tests/component_poweredby.rs | 100 ++++++++++++++++++++++++++++++++ 10 files changed, 251 insertions(+), 50 deletions(-) create mode 100644 src/base/component/poweredby.rs create mode 100644 src/locale/en-US/base.ftl create mode 100644 src/locale/es-ES/base.ftl create mode 100644 static/css/basic.css create mode 100644 tests/component_poweredby.rs diff --git a/src/base/component.rs b/src/base/component.rs index 27f0f739..1bb160b2 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -2,3 +2,6 @@ mod html; pub use html::Html; + +mod poweredby; +pub use poweredby::PoweredBy; diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs new file mode 100644 index 00000000..5a374645 --- /dev/null +++ b/src/base/component/poweredby.rs @@ -0,0 +1,69 @@ +use crate::prelude::*; + +/// Muestra un texto con información de copyright, típica en un pie de página. +/// +/// Por defecto, usando [`default()`](Self::default) sólo se muestra un +/// reconocimiento a PageTop. Sin embargo, se puede usar [`new()`](Self::new) +/// para crear una instancia con un texto de copyright predeterminado. +#[derive(AutoDefault)] +pub struct PoweredBy { + copyright: Option, +} + +impl Component for PoweredBy { + /// Crea una nueva instancia de `PoweredBy`. + /// + /// El copyright se genera automáticamente con el año actual y el nombre de + /// la aplicación configurada en [`global::SETTINGS`]. + fn new() -> Self { + let year = Utc::now().format("%Y").to_string(); + let c = join!(year, " © ", global::SETTINGS.app.name); + PoweredBy { copyright: Some(c) } + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let poweredby_pagetop = L10n::l("poweredby_pagetop") + .with_arg( + "pagetop_link", + "
PageTop", + ) + .to_markup(cx); + + PrepareMarkup::With(html! { + div id=[self.id()] class="poweredby" { + @if let Some(c) = self.copyright() { + span class="poweredby__copyright" { (c) "." } " " + } + span class="poweredby__pagetop" { (poweredby_pagetop) } + } + }) + } +} + +impl PoweredBy { + // PoweredBy BUILDER *************************************************************************** + + /// Establece el texto de copyright que mostrará el componente. + /// + /// Al pasar `Some(valor)` se sobrescribe el texto de copyright por defecto. Al pasar `None` se + /// eliminará, pero en este caso es necesario especificar el tipo explícitamente: + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let p1 = PoweredBy::default().with_copyright(Some("2001 © Foo Inc.")); + /// let p2 = PoweredBy::new().with_copyright(None::); + /// ``` + #[builder_fn] + pub fn with_copyright(mut self, copyright: Option>) -> Self { + self.copyright = copyright.map(Into::into); + self + } + + // PoweredBy GETTERS *************************************************************************** + + /// Devuelve el texto de copyright actual, si existe. + pub fn copyright(&self) -> Option<&str> { + self.copyright.as_deref() + } +} diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 3dda43eb..5f413d3c 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -28,6 +28,7 @@ async fn homepage(request: HttpRequest) -> ResultPage { .with_title(L10n::l("welcome_page")) .with_theme("Basic") .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/welcome.css"))) + .with_body_classes(ClassesOp::Add, "welcome") .with_component(Html::with(move |cx| html! { div id="main-header" { header { @@ -58,7 +59,8 @@ async fn homepage(request: HttpRequest) -> ResultPage { } } } - + })) + .with_component(Html::with(move |cx| html! { main id="main-content" { section class="content-body" { div id="poweredby-button" { @@ -85,32 +87,31 @@ async fn homepage(request: HttpRequest) -> ResultPage { } } } - - footer id="footer" { - section class="footer-inner" { - div class="footer-logo" { - svg - viewBox="0 0 1614 1614" - xmlns="http://www.w3.org/2000/svg" - role="img" - aria-label=[L10n::l("pagetop_logo").using(cx)] - preserveAspectRatio="xMidYMid slice" - focusable="false" - { - path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} - path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} - path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} - } - } - div class="footer-links" { - a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } - a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").to_markup(cx)) } - em { (L10n::l("welcome_have_fun").to_markup(cx)) } + })) + .with_component_in("footer", Html::with(move |cx| html! { + section class="welcome-footer" { + div class="welcome-footer__logo" { + svg + viewBox="0 0 1614 1614" + xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label=[L10n::l("pagetop_logo").using(cx)] + preserveAspectRatio="xMidYMid slice" + focusable="false" + { + path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} } } + div class="welcome-footer__links" { + a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } + a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").to_markup(cx)) } + em { (L10n::l("welcome_have_fun").to_markup(cx)) } + } } - })) + .with_component_in("footer", PoweredBy::new()) .render() } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index b02abfb5..bc380ac3 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -17,6 +17,11 @@ impl Theme for Basic { StyleSheet::from("/css/normalize.css") .with_version("8.0.1") .with_weight(-99), + )) + .alter_assets(AssetsOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(env!("CARGO_PKG_VERSION")) + .with_weight(-99), )); } } diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 8d1b632f..3b26a573 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -94,13 +94,11 @@ pub trait Theme: Extension + Send + Sync { @let region_name = region.name(); div id=(region_name) - class="region" + class={ "region region--" (region_name) } role="region" aria-label=[region_label.using(page)] { - div class={ "region__" (region_name) } { - (output) - } + (output) } } } diff --git a/src/locale/en-US/base.ftl b/src/locale/en-US/base.ftl new file mode 100644 index 00000000..b2c92564 --- /dev/null +++ b/src/locale/en-US/base.ftl @@ -0,0 +1,2 @@ +# PoweredBy component. +poweredby_pagetop = Powered by { $pagetop_link } diff --git a/src/locale/es-ES/base.ftl b/src/locale/es-ES/base.ftl new file mode 100644 index 00000000..74eb62e8 --- /dev/null +++ b/src/locale/es-ES/base.ftl @@ -0,0 +1,2 @@ +# PoweredBy component. +poweredby_pagetop = Funciona con { $pagetop_link } \ No newline at end of file diff --git a/static/css/basic.css b/static/css/basic.css new file mode 100644 index 00000000..312ddf09 --- /dev/null +++ b/static/css/basic.css @@ -0,0 +1,11 @@ +/* Page layout */ + +.region--footer { + padding-bottom: 2rem; +} + +/* PoweredBy component */ + +.poweredby { + text-align: center; +} diff --git a/static/css/welcome.css b/static/css/welcome.css index 76b042b9..7630f3e2 100644 --- a/static/css/welcome.css +++ b/static/css/welcome.css @@ -410,51 +410,61 @@ a:hover:visited { transform: rotate(2deg); } -#footer { - width: 100%; +/* + * Region footer + */ + +.region--footer { background-color: black; color: var(--color-gray); +} + +.welcome-footer { font-size: 1.15rem; font-weight: 300; line-height: 100%; - display: flex; justify-content: center; + display: flex; + flex-direction: column; + max-width: 80rem; + padding: 0 10.625rem 2rem; +/* z-index: 10; +*/ } -#footer a:visited { +.welcome-footer a:visited { color: var(--color-gray); } -.footer-logo { - max-height: 12.625rem; -} -.footer-logo svg { - width: 100%; -} -.footer-logo, -.footer-links, -.footer-inner { +.welcome-footer__logo, +.welcome-footer__links { display: flex; justify-content: center; width: 100%; } -.footer-links { +.welcome-footer__logo { + max-height: 12.625rem; +} +.welcome-footer__logo svg { + width: 100%; +} +.welcome-footer__links { gap: 1.875rem; flex-wrap: wrap; margin-top: 2rem; } -.footer-inner { - max-width: 80rem; - display: flex; - flex-direction: column; - padding: 0 10.625rem 2rem; -} @media (max-width: 48rem) { - .footer-logo { + .welcome-footer__logo { display: none; } } @media (max-width: 64rem) { - .footer-inner { + .welcome-footer { padding: 0 1rem 2rem; } } + +/* PoweredBy component */ + +.poweredby a:visited { + color: var(--color-gray); +} diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs new file mode 100644 index 00000000..b2e4418b --- /dev/null +++ b/tests/component_poweredby.rs @@ -0,0 +1,100 @@ +use pagetop::prelude::*; + +#[pagetop::test] +async fn poweredby_default_shows_only_pagetop_recognition() { + let _app = service::test::init_service(Application::new().test()).await; + + let p = PoweredBy::default(); + let html = render_component(&p); + + // Debe mostrar el bloque de reconocimiento a PageTop. + assert!(html.contains("poweredby__pagetop")); + + // Y NO debe mostrar el bloque de copyright. + assert!(!html.contains("poweredby__copyright")); +} + +#[pagetop::test] +async fn poweredby_new_includes_current_year_and_app_name() { + let _app = service::test::init_service(Application::new().test()).await; + + let p = PoweredBy::new(); + let html = render_component(&p); + + let year = Utc::now().format("%Y").to_string(); + assert!(html.contains(&year), "HTML should include the current year"); + + // El nombre de la app proviene de `global::SETTINGS.app.name`. + let app_name = &global::SETTINGS.app.name; + assert!( + html.contains(app_name), + "HTML should include the application name" + ); + + // Debe existir el span de copyright. + assert!(html.contains("poweredby__copyright")); +} + +#[pagetop::test] +async fn poweredby_with_copyright_overrides_text() { + let _app = service::test::init_service(Application::new().test()).await; + + let custom = "2001 © FooBar Inc."; + let p = PoweredBy::default().with_copyright(Some(custom)); + let html = render_component(&p); + + assert!(html.contains(custom)); + assert!(html.contains("poweredby__copyright")); +} + +#[pagetop::test] +async fn poweredby_with_copyright_none_hides_text() { + let _app = service::test::init_service(Application::new().test()).await; + + let p = PoweredBy::new().with_copyright(None::); + let html = render_component(&p); + + assert!(!html.contains("poweredby__copyright")); + // El reconocimiento a PageTop siempre debe aparecer. + assert!(html.contains("poweredby__pagetop")); +} + +#[pagetop::test] +async fn poweredby_link_points_to_crates_io() { + let _app = service::test::init_service(Application::new().test()).await; + + let p = PoweredBy::default(); + let html = render_component(&p); + + assert!( + html.contains("https://crates.io/crates/pagetop"), + "Link should point to crates.io/pagetop" + ); +} + +#[pagetop::test] +async fn poweredby_getter_reflects_internal_state() { + let _app = service::test::init_service(Application::new().test()).await; + + // Por defecto no hay copyright. + let p0 = PoweredBy::default(); + assert_eq!(p0.copyright(), None); + + // Y `new()` lo inicializa con año + nombre de app. + let p1 = PoweredBy::new(); + let c1 = p1.copyright().expect("Expected copyright to exis"); + assert!(c1.contains(&Utc::now().format("%Y").to_string())); + assert!(c1.contains(&global::SETTINGS.app.name)); +} + +// HELPERS ***************************************************************************************** + +fn render(x: &impl Render) -> String { + x.render().into_string() +} + +fn render_component(c: &C) -> String { + let mut cx = Context::default(); + let pm = c.prepare_component(&mut cx); + render(&pm) +} From a38f983f09d73f49ad123c23f03b49fe7a9064a0 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 25 Aug 2025 07:12:30 +0200 Subject: [PATCH 101/224] =?UTF-8?q?=F0=9F=90=9B=20(welcome):=20Corrige=20c?= =?UTF-8?q?entrado=20del=20pie=20de=20p=C3=A1gina?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/welcome.css | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/static/css/welcome.css b/static/css/welcome.css index 7630f3e2..906c05d5 100644 --- a/static/css/welcome.css +++ b/static/css/welcome.css @@ -420,17 +420,15 @@ a:hover:visited { } .welcome-footer { + display: flex; + justify-content: center; + flex-direction: column; + margin: 0 auto; + padding: 0 10.625rem 2rem; + max-width: 80rem; font-size: 1.15rem; font-weight: 300; line-height: 100%; - justify-content: center; - display: flex; - flex-direction: column; - max-width: 80rem; - padding: 0 10.625rem 2rem; -/* - z-index: 10; -*/ } .welcome-footer a:visited { color: var(--color-gray); From 779c16010a2a7a1507590cf917c91f5b7bdde320 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 25 Aug 2025 23:25:39 +0200 Subject: [PATCH 102/224] =?UTF-8?q?=F0=9F=90=9B=20Corrige=20nombre=20de=20?= =?UTF-8?q?funci=C3=B3n=20en=20prueba=20de=20`Html`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/component_html.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/component_html.rs b/tests/component_html.rs index 978248f1..b9b8e5ec 100644 --- a/tests/component_html.rs +++ b/tests/component_html.rs @@ -35,7 +35,7 @@ async fn component_html_renders_using_context_param() { async fn component_html_allows_replacing_render_function() { let mut component = Html::with(|_| html! { div { "Original" } }); - component.alter_html(|_| html! { div { "Modified" } }); + component.alter_fn(|_| html! { div { "Modified" } }); let markup = component .prepare_component(&mut Context::new(None)) From ce5a6c07bda0f3a5ba2dd7d13c0d82427d0969c7 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 25 Aug 2025 23:30:53 +0200 Subject: [PATCH 103/224] =?UTF-8?q?=F0=9F=92=84=20Aplica=20BEM=20a=20estil?= =?UTF-8?q?os=20de=20bienvenida=20y=20componente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/poweredby.rs | 26 ++++---- src/base/extension/welcome.rs | 30 +++++---- static/css/welcome.css | 111 +++++++++++++++++--------------- 3 files changed, 88 insertions(+), 79 deletions(-) diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 5a374645..afa8db72 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -1,10 +1,13 @@ use crate::prelude::*; -/// Muestra un texto con información de copyright, típica en un pie de página. +// Enlace a la página oficial de PageTop. +const LINK: &str = "PageTop"; + +/// Componente que renderiza la sección 'Powered by' (*Funciona con*) típica del pie de página. /// -/// Por defecto, usando [`default()`](Self::default) sólo se muestra un -/// reconocimiento a PageTop. Sin embargo, se puede usar [`new()`](Self::new) -/// para crear una instancia con un texto de copyright predeterminado. +/// Por defecto, usando [`default()`](Self::default) sólo se muestra un reconocimiento a PageTop. +/// Sin embargo, se puede usar [`new()`](Self::new) para crear una instancia con un texto de +/// copyright predeterminado. #[derive(AutoDefault)] pub struct PoweredBy { copyright: Option, @@ -13,8 +16,8 @@ pub struct PoweredBy { impl Component for PoweredBy { /// Crea una nueva instancia de `PoweredBy`. /// - /// El copyright se genera automáticamente con el año actual y el nombre de - /// la aplicación configurada en [`global::SETTINGS`]. + /// El copyright se genera automáticamente con el año actual y el nombre de la aplicación + /// configurada en [`global::SETTINGS`]. fn new() -> Self { let year = Utc::now().format("%Y").to_string(); let c = join!(year, " © ", global::SETTINGS.app.name); @@ -22,19 +25,14 @@ impl Component for PoweredBy { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let poweredby_pagetop = L10n::l("poweredby_pagetop") - .with_arg( - "pagetop_link", - "PageTop", - ) - .to_markup(cx); - PrepareMarkup::With(html! { div id=[self.id()] class="poweredby" { @if let Some(c) = self.copyright() { span class="poweredby__copyright" { (c) "." } " " } - span class="poweredby__pagetop" { (poweredby_pagetop) } + span class="poweredby__pagetop" { + (L10n::l("poweredby_pagetop").with_arg("pagetop_link", LINK).to_markup(cx)) + } } }) } diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 5f413d3c..f7682104 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -29,19 +29,19 @@ async fn homepage(request: HttpRequest) -> ResultPage { .with_theme("Basic") .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/welcome.css"))) .with_body_classes(ClassesOp::Add, "welcome") - .with_component(Html::with(move |cx| html! { - div id="main-header" { - header { + .with_component_in("header", Html::with(move |cx| html! { + div class="welcome-header" { + header class="welcome-header__body" { h1 - id="header-title" + class="welcome-header__title" aria-label=(L10n::l("welcome_aria").with_arg("app", app).to_markup(cx)) { span { (L10n::l("welcome_title").to_markup(cx)) } (L10n::l("welcome_intro").with_arg("app", app).to_markup(cx)) } } - aside id="header-image" aria-hidden="true" { - div id="monster" { + aside class="welcome-header__image" aria-hidden="true" { + div class="welcome-header__monster" { picture { source type="image/avif" @@ -61,25 +61,27 @@ async fn homepage(request: HttpRequest) -> ResultPage { } })) .with_component(Html::with(move |cx| html! { - main id="main-content" { - section class="content-body" { - div id="poweredby-button" { + main class="welcome-content" { + section class="welcome-content__body" { + div class="welcome-poweredby" { a - id="poweredby-link" + class="welcome-poweredby__link" href="https://pagetop.cillero.es" target="_blank" rel="noreferrer" { span {} span {} span {} - div id="poweredby-text" { (L10n::l("welcome_powered").to_markup(cx)) } + div class="welcome-poweredby__text" { + (L10n::l("welcome_powered").to_markup(cx)) + } } } - div class="content-text" { + div class="welcome-text" { p { (L10n::l("welcome_text1").to_markup(cx)) } p { (L10n::l("welcome_text2").to_markup(cx)) } - div class="subcontent" { - h1 { span { (L10n::l("welcome_about").to_markup(cx)) } } + div class="welcome-text__block" { + h2 { span { (L10n::l("welcome_about").to_markup(cx)) } } p { (L10n::l("welcome_pagetop").to_markup(cx)) } p { (L10n::l("welcome_issues1").to_markup(cx)) } p { (L10n::l("welcome_issues2").with_arg("app", app).to_markup(cx)) } diff --git a/static/css/welcome.css b/static/css/welcome.css index 906c05d5..4ce8046b 100644 --- a/static/css/welcome.css +++ b/static/css/welcome.css @@ -58,12 +58,17 @@ a:hover:visited { align-items: center; } -#main-header { +/* + * Region header + */ + +.welcome-header { display: flex; flex-direction: column-reverse; - padding-bottom: 9rem; - max-width: 80rem; width: 100%; + max-width: 80rem; + margin: 0 auto; + padding-bottom: 9rem; background-image: var(--bg-img-sm); background-image: var(--bg-img-sm-set); background-position: top center; @@ -71,11 +76,11 @@ a:hover:visited { background-size: contain; background-repeat: no-repeat; } -#main-header header { +.welcome-header__body { padding: 0; background: none; } -#header-title { +.welcome-header__title { margin: 0 0 0 1.5rem; text-align: left; display: flex; @@ -89,7 +94,7 @@ a:hover:visited { line-height: 110%; text-shadow: 0 0.125rem 0.1875rem rgba(0, 0, 0, 0.3); } -#header-title > span { +.welcome-header__title > span { background: linear-gradient(180deg, #ddff95 30%, #ffb84b 100%); background-clip: text; -webkit-background-clip: text; @@ -100,40 +105,44 @@ a:hover:visited { line-height: 110%; text-shadow: none; } -#header-image { - width: 100%; - text-align: right; +.welcome-header__image { display: flex; justify-content: flex-start; + text-align: right; + width: 100%; } -#header-image #monster { +.welcome-header__monster { margin-right: 12rem; margin-top: 1rem; flex-shrink: 1; } @media (min-width: 64rem) { - #main-header { + .welcome-header { background-image: var(--bg-img); background-image: var(--bg-img-set); } - #header-title { + .welcome-header__title { padding: 1.2rem 2rem 2.6rem 2rem; } - #header-image { + .welcome-header__image { justify-content: flex-end; } } -#main-content { +/* + * Region content + */ + +.welcome-content { height: auto; margin-top: 1.6rem; } -.content-body { +.welcome-content__body { box-sizing: border-box; max-width: 80rem; } -.content-body:before, -.content-body:after { +.welcome-content__body:before, +.welcome-content__body:after { content: ''; position: absolute; left: 0; @@ -143,38 +152,38 @@ a:hover:visited { filter: blur(2.75rem); opacity: 0.8; inset: 11.75rem; - z-index: 0; + /*z-index: 0;*/ } -.content-body:before { +.welcome-content__body:before { top: -1rem; } -.content-body:after { +.welcome-content__body:after { bottom: -1rem; } @media (max-width: 48rem) { - .content-body { + .welcome-content__body { margin-top: -9.8rem; } - .content-body:before, - .content-body:after { + .welcome-content__body:before, + .welcome-content__body:after { inset: unset; } } @media (min-width: 64rem) { - #main-content { + .welcome-content { margin-top: 0; } - .content-body { + .welcome-content__body { margin-top: -5.7rem; } } -#poweredby-button { +.welcome-poweredby { width: 100%; margin: 0 auto 3rem; z-index: 10; } -#poweredby-link { +.welcome-poweredby__link { background: #7f1d1d; background-image: linear-gradient(to bottom, rgba(255,0,0,0.8), rgba(255,255,255,0)); background-position: top left, center; @@ -187,7 +196,7 @@ a:hover:visited { font-size: 1.5rem; line-height: 1.3; text-decoration: none; - text-shadow: var(--shadow); + /*text-shadow: var(--shadow);*/ transition: transform 0.3s ease-in-out; position: relative; overflow: hidden; @@ -195,7 +204,7 @@ a:hover:visited { min-height: 7.6875rem; outline: none; } -#poweredby-link::before { +.welcome-poweredby__link::before { content: ''; position: absolute; top: -13.125rem; @@ -207,7 +216,7 @@ a:hover:visited { transition: transform 0.3s ease-in-out; z-index: 5; } -#poweredby-text { +.welcome-poweredby__text { display: flex; flex-direction: column; flex: 1; @@ -217,25 +226,25 @@ a:hover:visited { padding: 1rem 1.5rem; text-align: left; color: white; - text-shadow: 0 0.101125rem 0.2021875rem rgba(0, 0, 0, 0.25); + /*text-shadow: 0 0.101125rem 0.2021875rem rgba(0, 0, 0, 0.25);*/ font-size: 1.65rem; font-style: normal; font-weight: 600; line-height: 130.023%; letter-spacing: 0.0075rem; } -#poweredby-text strong { +.welcome-poweredby__text strong { font-size: 2.625rem; font-weight: 600; line-height: 130.023%; letter-spacing: 0.013125rem; } -#poweredby-link span { +.welcome-poweredby__link span { position: absolute; display: block; pointer-events: none; } -#poweredby-link span:nth-child(1) { +.welcome-poweredby__link span:nth-child(1) { height: 8px; width: 100%; top: 0; @@ -255,7 +264,7 @@ a:hover:visited { transform: translateX(100%); } } -#poweredby-link span:nth-child(2) { +.welcome-poweredby__link span:nth-child(2) { width: 8px; height: 100%; top: 0; @@ -275,7 +284,7 @@ a:hover:visited { transform: translateY(100%); } } -#poweredby-link span:nth-child(3) { +.welcome-poweredby__link span:nth-child(3) { height: 8px; width: 100%; bottom: 0; @@ -295,22 +304,22 @@ a:hover:visited { transform: translateX(-100%); } } -#poweredby-link:hover span { +.welcome-poweredby__link:hover span { animation-play-state: paused; } @media (max-width: 48rem) { - #poweredby-link { + .welcome-poweredby__link { height: 6.25rem; min-width: auto; border-radius: 0; } - #poweredby-text { + .welcome-poweredby__text { display: inline; padding-top: .5rem; } } @media (min-width: 48rem) { - #poweredby-button { + .welcome-poweredby { position: absolute; top: 0; left: 50%; @@ -318,14 +327,14 @@ a:hover:visited { max-width: 29.375rem; margin-bottom: 0; } - #poweredby-link:hover { + .welcome-poweredby__link:hover { transition: all .5s; transform: rotate(-3deg) scale(1.1); - box-shadow: 0px 3px 5px rgba(0,0,0,.4); + /*box-shadow: 0px 3px 5px rgba(0,0,0,.4);*/ } } -.content-text { +.welcome-text { z-index: 1; width: 100%; display: flex; @@ -343,7 +352,7 @@ a:hover:visited { padding: 6rem 1.063rem 0.75rem; overflow: hidden; } -.content-text p { +.welcome-text p { width: 100%; line-height: 150%; font-weight: 400; @@ -351,14 +360,14 @@ a:hover:visited { margin: 0 0 1.5rem; } @media (min-width: 48rem) { - .content-text { + .welcome-text { font-size: 1.375rem; line-height: 2rem; padding-top: 7rem; } } @media (min-width: 64rem) { - .content-text { + .welcome-text { border-radius: 0.75rem; box-shadow: var(--shadow); max-width: 60rem; @@ -368,13 +377,13 @@ a:hover:visited { } } -.subcontent { +.welcome-text__block { position: relative; } -.subcontent h1 { +.welcome-text__block h2 { margin: 1em 0 .8em; } -.subcontent h1 span { +.welcome-text__block h2 span { display: inline-block; padding: 10px 30px 14px; margin: 0 0 0 20px; @@ -385,7 +394,7 @@ a:hover:visited { border-color: orangered; transform: rotate(-3deg) translateY(-25%); } -.subcontent h1:before { +.welcome-text__block h2:before { content: ""; height: 5px; position: absolute; @@ -398,7 +407,7 @@ a:hover:visited { transform: rotate(2deg) translateY(-50%); transform-origin: top left; } -.subcontent h1:after { +.welcome-text__block h2:after { content: ""; height: 70rem; position: absolute; From b315e6d08e825176c8e2927d5151ac460046212b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 3 Sep 2025 21:05:35 +0200 Subject: [PATCH 104/224] =?UTF-8?q?=F0=9F=93=9D=20(doc):=20Normaliza=20ref?= =?UTF-8?q?erencias=20al=20nombre=20PageTop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CREDITS.md | 13 ++++++------- README.md | 24 ++++++++++++------------ helpers/pagetop-build/README.md | 2 +- helpers/pagetop-macros/README.md | 4 ++-- helpers/pagetop-macros/src/lib.rs | 6 +++--- helpers/pagetop-statics/README.md | 6 +++--- helpers/pagetop-statics/src/lib.rs | 4 ++-- src/app.rs | 4 ++-- src/base/action.rs | 2 +- src/base/component.rs | 2 +- src/base/extension.rs | 2 +- src/base/extension/welcome.rs | 2 +- src/base/theme.rs | 2 +- src/base/theme/basic.rs | 2 +- src/config.rs | 6 +++--- src/core.rs | 2 +- src/core/component/definition.rs | 2 +- src/core/extension.rs | 2 +- src/core/extension/definition.rs | 4 ++-- src/core/theme.rs | 2 +- src/global.rs | 2 +- src/lib.rs | 18 +++++++++--------- src/locale.rs | 14 +++++++------- src/prelude.rs | 2 +- src/trace.rs | 4 ++-- 25 files changed, 66 insertions(+), 67 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index f5c1b0fa..c5a7bd2e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,8 +1,7 @@ # 🔃 Dependencias -`PageTop` está basado en [Rust](https://www.rust-lang.org/) y crece a hombros de gigantes -aprovechando algunas de las librerías más robustas y populares del [ecosistema Rust](https://lib.rs) -como son: +PageTop está basado en [Rust](https://www.rust-lang.org/) y crece a hombros de gigantes aprovechando +algunas de las librerías más robustas y populares del [ecosistema Rust](https://lib.rs) como son: * [Actix Web](https://actix.rs/) para los servicios web. * [Config](https://docs.rs/config) para cargar y procesar las opciones de configuración. @@ -11,14 +10,14 @@ como son: * [Fluent templates](https://github.com/XAMPPRocky/fluent-templates), que integra [Fluent](https://projectfluent.org/) para internacionalizar las aplicaciones. * Además de otros *crates* adicionales que se pueden explorar en los archivos `Cargo.toml` de - `PageTop` y sus extensiones. + PageTop y sus extensiones. # 🗚 FIGfonts -`PageTop` usa el *crate* [figlet-rs](https://crates.io/crates/figlet-rs) desarrollado por -*yuanbohan* para mostrar un banner de presentación en el terminal con el nombre de la aplicación en -caracteres [FIGlet](http://www.figlet.org). Las fuentes incluidas en `pagetop/src/app` son: +PageTop usa el *crate* [figlet-rs](https://crates.io/crates/figlet-rs) desarrollado por *yuanbohan* +para mostrar un banner de presentación en el terminal con el nombre de la aplicación en caracteres +[FIGlet](http://www.figlet.org). Las fuentes incluidas en `pagetop/src/app` son: * [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) de *Glenn Chappell* * [small.flf](http://www.figlet.org/fontdb_example.cgi?font=small.flf) de *Glenn Chappell* diff --git a/README.md b/README.md index e7fab94b..9d5efc88 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@
-`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para -la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. +PageTop reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para la +creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar según las necesidades de cada proyecto, incluyendo: @@ -24,14 +24,14 @@ según las necesidades de cada proyecto, incluyendo: * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, configurables y reutilizables. * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs - de `PageTop` o de terceros. + de PageTop o de terceros. * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y componentes sin comprometer su funcionalidad. # ⚡️ Guía rápida -La aplicación más sencilla de `PageTop` se ve así: +La aplicación más sencilla de PageTop se ve así: ```rust,no_run use pagetop::prelude::*; @@ -42,10 +42,10 @@ async fn main() -> std::io::Result<()> { } ``` -Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página -de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. +Este código arranca el servidor de PageTop. Con la configuración por defecto, muestra una página de +bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. -Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: +Para personalizar el servicio, se puede crear una extensión de PageTop de la siguiente manera: ```rust,no_run use pagetop::prelude::*; @@ -86,15 +86,15 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui * **[pagetop-statics](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics)**, es la librería que permite incluir archivos estáticos en el ejecutable de las aplicaciones - `PageTop` para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo - de compilación. + PageTop para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo de + compilación. * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las - aplicaciones `PageTop` durante la compilación de los ejecutables. + aplicaciones PageTop durante la compilación de los ejecutables. * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, - proporciona una colección de macros que mejoran la experiencia de desarrollo con `PageTop`. + proporciona una colección de macros que mejoran la experiencia de desarrollo con PageTop. # 🧪 Pruebas @@ -116,7 +116,7 @@ Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de C # 🚧 Advertencia -`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 80d6bba2..57273e83 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -113,7 +113,7 @@ impl Extension for MyExtension { # 🚧 Advertencia -`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index e58d24c7..7c9c2e86 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -26,12 +26,12 @@ Esta librería incluye entre sus macros una adaptación de [SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de [Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de -cada proyecto `PageTop`. +cada proyecto PageTop. # 🚧 Advertencia -`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 6421ca6e..b462ea13 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -27,7 +27,7 @@ Esta librería incluye entre sus macros una adaptación de [SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de [Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de -cada proyecto `PageTop`. +cada proyecto PageTop. */ #![doc( @@ -219,7 +219,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { expanded.into() } -/// Define una función `main` asíncrona como punto de entrada de `PageTop`. +/// Define una función `main` asíncrona como punto de entrada de PageTop. /// /// # Ejemplo /// @@ -240,7 +240,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { output } -/// Define funciones de prueba asíncronas para usar con `PageTop`. +/// Define funciones de prueba asíncronas para usar con PageTop. /// /// # Ejemplo /// diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index 92999c04..4168cd4a 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -16,7 +16,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Descripción general -Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de compilación. @@ -28,13 +28,13 @@ Para ello, adapta el código de los *crates* [static-files](https://crates.io/cr [4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por [Alexander Korolev](https://crates.io/users/kilork). -Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar +Estas implementaciones se integran en PageTop para evitar que cada proyecto tenga que declarar `static-files` manualmente como dependencia en su `Cargo.toml`. # 🚧 Advertencia -`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index dab50d9e..201d90ef 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -17,7 +17,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Descripción general -Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de compilación. @@ -29,7 +29,7 @@ Para ello, adapta el código de los *crates* [static-files](https://crates.io/cr [4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por [Alexander Korolev](https://crates.io/users/kilork). -Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar +Estas implementaciones se integran en PageTop para evitar que cada proyecto tenga que declarar `static-files` manualmente como dependencia en su `Cargo.toml`. */ diff --git a/src/app.rs b/src/app.rs index 400b0cd3..94d901f1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -//! Prepara y ejecuta una aplicación creada con `Pagetop`. +//! Prepara y ejecuta una aplicación creada con PageTop. mod figfont; @@ -17,7 +17,7 @@ use substring::Substring; use std::io::Error; use std::sync::LazyLock; -/// Punto de entrada de una aplicación `PageTop`. +/// Punto de entrada de una aplicación PageTop. /// /// No almacena datos, **encapsula** el inicio completo de configuración y puesta en marcha. Para /// instanciarla se puede usar [`new()`](Application::new) o [`prepare()`](Application::prepare). diff --git a/src/base/action.rs b/src/base/action.rs index be35e922..977ae9e2 100644 --- a/src/base/action.rs +++ b/src/base/action.rs @@ -1,4 +1,4 @@ -//! Acciones predefinidas para alterar el funcionamiento interno de `PageTop`. +//! Acciones predefinidas para alterar el funcionamiento interno de PageTop. use crate::prelude::*; diff --git a/src/base/component.rs b/src/base/component.rs index 1bb160b2..30cb6866 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,4 +1,4 @@ -//! Componentes nativos proporcionados por `PageTop`. +//! Componentes nativos proporcionados por PageTop. mod html; pub use html::Html; diff --git a/src/base/extension.rs b/src/base/extension.rs index 49e408dd..1f94fe23 100644 --- a/src/base/extension.rs +++ b/src/base/extension.rs @@ -1,4 +1,4 @@ -//! Extensiones para funcionalidades avanzadas de `PageTop`. +//! Extensiones para funcionalidades avanzadas de PageTop. mod welcome; pub use welcome::Welcome; diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index f7682104..0252cff3 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -/// Página de bienvenida predeterminada de `PageTop`. +/// Página de bienvenida predeterminada de PageTop. /// /// Esta extensión se instala por defecto y muestra una página en la ruta raíz (`/`) cuando no se ha /// configurado ninguna página de inicio personalizada. Permite confirmar que el servidor está diff --git a/src/base/theme.rs b/src/base/theme.rs index ea9eeb66..40129bfa 100644 --- a/src/base/theme.rs +++ b/src/base/theme.rs @@ -1,4 +1,4 @@ -//! Temas básicos soportados por `PageTop`. +//! Temas básicos soportados por PageTop. mod basic; pub use basic::Basic; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index bc380ac3..961864be 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -1,4 +1,4 @@ -//! Es el tema básico que incluye `PageTop` por defecto. +//! Es el tema básico que incluye PageTop por defecto. use crate::prelude::*; diff --git a/src/config.rs b/src/config.rs index 27cf630e..f2fb9f7f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ //! Estos ajustes se obtienen de archivos [TOML](https://toml.io) como pares `clave = valor` que se //! mapean a estructuras **fuertemente tipadas** y valores predefinidos. //! -//! Siguiendo la metodología [Twelve-Factor App](https://12factor.net/config), `PageTop` separa el +//! Siguiendo la metodología [Twelve-Factor App](https://12factor.net/config), PageTop separa el //! **código** de la **configuración**, lo que permite tener configuraciones diferentes para cada //! despliegue, como *dev*, *staging* o *production*, sin modificar el código fuente. //! @@ -13,14 +13,14 @@ //! Si tu aplicación necesita archivos de configuración, crea un directorio `config` en la raíz del //! proyecto, al mismo nivel que el archivo *Cargo.toml* o que el binario de la aplicación. //! -//! `PageTop` carga en este orden, y siempre de forma opcional, los siguientes archivos TOML: +//! PageTop carga en este orden, y siempre de forma opcional, los siguientes archivos TOML: //! //! 1. **config/common.toml**, para ajustes comunes a todos los entornos. Este enfoque simplifica el //! mantenimiento al centralizar los valores de configuración comunes. //! //! 2. **config/{rm}.toml**, donde `{rm}` es el valor de la variable de entorno `PAGETOP_RUN_MODE`: //! -//! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y `PageTop` intentará +//! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y PageTop intentará //! cargar *config/default.toml* si el archivo existe. //! //! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.ej. diff --git a/src/core.rs b/src/core.rs index 0c8aa21f..79d92074 100644 --- a/src/core.rs +++ b/src/core.rs @@ -117,7 +117,7 @@ impl TypeInfo { /// /// Este *trait* se implementa automáticamente para **todos** los tipos que implementen [`Any`], de /// modo que basta con traer [`AnyInfo`] al ámbito (`use crate::AnyInfo;`) para disponer de estos -/// métodos adicionales, o usar el [`prelude`](crate::prelude) de `PageTop`. +/// métodos adicionales, o usar el [`prelude`](crate::prelude) de PageTop. /// /// # Ejemplo /// diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index e9a792d7..c43dfb0f 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -11,7 +11,7 @@ pub trait ComponentRender { fn render(&mut self, cx: &mut Context) -> Markup; } -/// Interfaz común que debe implementar un componente renderizable en `PageTop`. +/// Interfaz común que debe implementar un componente renderizable en PageTop. /// /// Se recomienda que los componentes deriven [`AutoDefault`](crate::AutoDefault). También deben /// implementar explícitamente el método [`new()`](Self::new) y pueden sobrescribir los otros diff --git a/src/core/extension.rs b/src/core/extension.rs index cabae5cd..6ae6d333 100644 --- a/src/core/extension.rs +++ b/src/core/extension.rs @@ -1,6 +1,6 @@ //! API para añadir nuevas funcionalidades usando extensiones. //! -//! Cada funcionalidad adicional que quiera incorporarse a una aplicación `PageTop` se debe modelar +//! Cada funcionalidad adicional que quiera incorporarse a una aplicación PageTop se debe modelar //! como una **extensión**. Todas comparten la misma interfaz declarada en [`Extension`]. mod definition; diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 6df70427..90bdbad2 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -10,7 +10,7 @@ use crate::{actions_boxed, service}; /// cualquier hilo de la ejecución sin necesidad de sincronización adicional. pub type ExtensionRef = &'static dyn Extension; -/// Interfaz común que debe implementar cualquier extensión de `PageTop`. +/// Interfaz común que debe implementar cualquier extensión de PageTop. /// /// Este *trait* es fácil de implementar, basta con declarar una estructura de tamaño cero para la /// extensión y sobreescribir los métodos que sea necesario. @@ -63,7 +63,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// Otras extensiones que deben habilitarse **antes** de esta. /// - /// `PageTop` las resolverá automáticamente respetando el orden durante el arranque de la + /// PageTop las resolverá automáticamente respetando el orden durante el arranque de la /// aplicación. fn dependencies(&self) -> Vec { vec![] diff --git a/src/core/theme.rs b/src/core/theme.rs index aa526f16..e0c3008e 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -1,6 +1,6 @@ //! API para añadir y gestionar nuevos temas. //! -//! En `PageTop` un tema es la *piel* de la aplicación, decide cómo se muestra cada documento HTML, +//! En PageTop un tema es la *piel* de la aplicación, decide cómo se muestra cada documento HTML, //! especialmente las páginas de contenido ([`Page`](crate::response::page::Page)), sin alterar la //! lógica interna de sus componentes. //! diff --git a/src/global.rs b/src/global.rs index 8a03589c..6be0774d 100644 --- a/src/global.rs +++ b/src/global.rs @@ -68,7 +68,7 @@ pub struct App { #[derive(Debug, Deserialize)] /// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. pub struct Dev { - /// Directorio desde el que servir los archivos estáticos de `PageTop`. + /// Directorio desde el que servir los archivos estáticos de PageTop. /// /// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una /// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en diff --git a/src/lib.rs b/src/lib.rs index 90ea4625..e43da2f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,8 @@
-`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para -la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. +PageTop reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para la +creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar según las necesidades de cada proyecto, incluyendo: @@ -25,14 +25,14 @@ según las necesidades de cada proyecto, incluyendo: * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, configurables y reutilizables. * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs - de `PageTop` o de terceros. + de PageTop o de terceros. * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y componentes sin comprometer su funcionalidad. # ⚡️ Guía rápida -La aplicación más sencilla de `PageTop` se ve así: +La aplicación más sencilla de PageTop se ve así: ```rust,no_run use pagetop::prelude::*; @@ -43,10 +43,10 @@ async fn main() -> std::io::Result<()> { } ``` -Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página -de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. +Este código arranca el servidor de PageTop. Con la configuración por defecto, muestra una página de +bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. -Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: +Para personalizar el servicio, se puede crear una extensión de PageTop de la siguiente manera: ```rust,no_run use pagetop::prelude::*; @@ -77,11 +77,11 @@ Este programa implementa una extensión llamada `HelloWorld` que sirve una pági # 🧩 Gestión de Dependencias -Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro +Los proyectos que utilizan PageTop gestionan las dependencias con `cargo`, como cualquier otro proyecto en Rust. Sin embargo, es fundamental que cada extensión declare explícitamente sus -[dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda +[dependencias](core::extension::Extension::dependencies), si las tiene, para que PageTop pueda estructurar e inicializar la aplicación de forma modular. */ diff --git a/src/locale.rs b/src/locale.rs index f23f51ee..43612bd5 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -1,6 +1,6 @@ //! Localización (L10n). //! -//! `PageTop` utiliza las especificaciones de [Fluent](https://www.projectfluent.org/) para la +//! PageTop utiliza las especificaciones de [Fluent](https://www.projectfluent.org/) para la //! localización de aplicaciones, y aprovecha [fluent-templates](https://docs.rs/fluent-templates/) //! para integrar los recursos de traducción directamente en el binario de la aplicación. //! @@ -87,7 +87,7 @@ //! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); //! ``` //! -//! Y *voilà*, sólo queda operar con los idiomas soportados por `PageTop` usando [`LangMatch`] y +//! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`LangMatch`] y //! traducir textos con [`L10n`]. use crate::html::{Markup, PreEscaped}; @@ -141,10 +141,10 @@ pub trait LangId { fn langid(&self) -> &'static LanguageIdentifier; } -/// Operaciones con los idiomas soportados por `PageTop`. +/// Operaciones con los idiomas soportados por PageTop. /// /// Utiliza [`LangMatch`] para transformar un identificador de idioma en un [`LanguageIdentifier`] -/// soportado por `PageTop`. +/// soportado por PageTop. /// /// # Ejemplos /// @@ -183,11 +183,11 @@ pub trait LangId { pub enum LangMatch { /// Cuando el identificador de idioma es una cadena vacía. Unspecified, - /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por `PageTop` que + /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que /// coincide exactamente con el identificador de idioma (p.ej. "es-ES"), o con el identificador /// del idioma base (p.ej. "es"). Found(&'static LanguageIdentifier), - /// Si el identificador de idioma no está entre los soportados por `PageTop`. + /// Si el identificador de idioma no está entre los soportados por PageTop. Unsupported(String), } @@ -319,7 +319,7 @@ enum L10nOp { /// Cada instancia puede representar: /// /// - Un texto puro (`n()`) que no requiere traducción. -/// - Una clave para traducir un texto de las traducciones predefinidas de `PageTop` (`l()`). +/// - Una clave para traducir un texto de las traducciones predefinidas de PageTop (`l()`). /// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). /// /// # Ejemplo diff --git a/src/prelude.rs b/src/prelude.rs index 9072dec9..484e53c1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,4 +1,4 @@ -//! *Prelude* de `PageTop`. +//! *Prelude* de PageTop. // RE-EXPORTED. diff --git a/src/trace.rs b/src/trace.rs index c57c6a3c..12e428a8 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,13 +1,13 @@ //! Gestión de trazas y registro de eventos de la aplicación. //! -//! `PageTop` recopila información de diagnóstico de la aplicación de forma estructurada y basada en +//! PageTop recopila información de diagnóstico de la aplicación de forma estructurada y basada en //! eventos. //! //! En los sistemas asíncronos, interpretar los mensajes de log tradicionales suele volverse //! complicado. Las tareas individuales se multiplexan en el mismo hilo y los eventos y registros //! asociados se entremezclan, lo que dificulta seguir la secuencia lógica. //! -//! `PageTop` usa [`tracing`](https://docs.rs/tracing) para registrar eventos estructurados y con +//! PageTop usa [`tracing`](https://docs.rs/tracing) para registrar eventos estructurados y con //! información adicional sobre la *temporalidad* y la *causalidad*. A diferencia de un mensaje de //! log, un *span* (intervalo) tiene un momento de inicio y de fin, puede entrar y salir del flujo //! de ejecución y puede existir dentro de un árbol anidado de *spans* similares. Además, estos From fec3c32b8e7b5c1be942423425f97b28d32dd045 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 3 Sep 2025 21:12:19 +0200 Subject: [PATCH 105/224] =?UTF-8?q?=F0=9F=93=9D=20(doc):=20Simplifica=20do?= =?UTF-8?q?cumentaci=C3=B3n=20de=20obsoletos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service.rs | 67 -------------------------------------------------- src/util.rs | 2 -- 2 files changed, 69 deletions(-) diff --git a/src/service.rs b/src/service.rs index 288e1eb5..09dc6183 100644 --- a/src/service.rs +++ b/src/service.rs @@ -17,31 +17,6 @@ pub use actix_web::test; /// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) /// en su lugar. -/// -/// Incluye en código un conjunto de recursos previamente preparado con `build.rs`. -/// -/// # Formas de uso -/// -/// * `include_files!(media)` - Para incluir un conjunto de recursos llamado `media`. Normalmente se -/// usará esta forma. -/// -/// * `include_files!(BLOG => media)` - También se puede asignar el conjunto de recursos a una -/// variable global; p.ej. `BLOG`. -/// -/// # Argumentos -/// -/// * `$bundle` – Nombre del conjunto de recursos generado por `build.rs` (consultar -/// [`pagetop_build`](https://docs.rs/pagetop-build)). -/// * `$STATIC` – Asigna el conjunto de recursos a una variable global de tipo -/// [`StaticResources`](crate::StaticResources). -/// -/// # Ejemplos -/// -/// ```rust,ignore -/// include_files!(assets); // Uso habitual. -/// -/// include_files!(STATIC_ASSETS => assets); -/// ``` #[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files { @@ -69,48 +44,6 @@ macro_rules! include_files { /// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) /// en su lugar. -/// -/// Configura un servicio web para publicar los recursos embebidos con [`include_files!`]. -/// -/// El código expandido de la macro decide durante el arranque de la aplicación si debe servir los -/// archivos de los recursos embebidos o directamente desde el sistema de ficheros, si se ha -/// indicado una ruta válida a un directorio de recursos. -/// -/// # Argumentos -/// -/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la -/// configuración del servicio web. -/// * `$bundle` – Nombre del conjunto de recursos incluido con [`include_files!`]. -/// * `$route` – Ruta URL de origen desde la que se servirán los archivos. -/// * `[ $root, $relative ]` *(opcional)* – Directorio raíz y ruta relativa para construir la ruta -/// absoluta donde buscar los archivos en el sistema de ficheros (ver -/// [`absolute_dir()`](crate::util::absolute_dir)). Si no existe, se usarán los recursos -/// embebidos. -/// -/// # Ejemplos -/// -/// ```rust,ignore -/// use pagetop::prelude::*; -/// -/// include_files!(assets); -/// -/// pub struct MyExtension; -/// -/// impl Extension for MyExtension { -/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { -/// include_files_service!(scfg, assets => "/public"); -/// } -/// } -/// ``` -/// -/// Y para buscar los recursos en el sistema de ficheros (si existe la ruta absoluta): -/// -/// ```rust,ignore -/// include_files_service!(cfg, assets => "/public", ["/var/www", "assets"]); -/// -/// // También desde el directorio actual de ejecución. -/// include_files_service!(cfg, assets => "/public", ["", "static"]); -/// ``` #[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files_service { diff --git a/src/util.rs b/src/util.rs index e70b0998..808014b2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -57,8 +57,6 @@ pub fn resolve_absolute_dir>(path: P) -> io::Result { } /// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar. -/// -/// Devuelve la ruta absoluta a un directorio existente. #[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")] pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result where From fdb8132c37e757abc02de6f63f51db87a37ea5a0 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 4 Sep 2025 00:27:25 +0200 Subject: [PATCH 106/224] =?UTF-8?q?=F0=9F=8E=A8=20(locale):=20Mejora=20el?= =?UTF-8?q?=20uso=20de=20`lookup`=20/=20`using`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/poweredby.rs | 2 +- src/html/attr_l10n.rs | 29 ++++++----- src/html/context.rs | 3 +- src/locale.rs | 86 +++++++++++++++++++-------------- src/response/page.rs | 4 +- tests/locale.rs | 10 ++-- 6 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index afa8db72..bfe38351 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -31,7 +31,7 @@ impl Component for PoweredBy { span class="poweredby__copyright" { (c) "." } " " } span class="poweredby__pagetop" { - (L10n::l("poweredby_pagetop").with_arg("pagetop_link", LINK).to_markup(cx)) + (L10n::l("poweredby_pagetop").with_arg("pagetop_link", LINK).using(cx)) } } }) diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index cd5b389d..3e8a4e4b 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -4,7 +4,7 @@ use crate::{builder_fn, AutoDefault}; /// Texto para [traducir](crate::locale) en atributos HTML. /// -/// Encapsula un tipo [`L10n`] para manejar traducciones de forma segura. +/// Encapsula un [`L10n`] para manejar traducciones de forma segura en atributos. /// /// # Ejemplo /// @@ -16,19 +16,19 @@ use crate::{builder_fn, AutoDefault}; /// /// // Español disponible. /// assert_eq!( -/// hello.using(&LangMatch::resolve("es-ES")), +/// hello.lookup(&LangMatch::resolve("es-ES")), /// Some(String::from("¡Hola mundo!")) /// ); /// /// // Japonés no disponible, traduce al idioma de respaldo ("en-US"). /// assert_eq!( -/// hello.using(&LangMatch::resolve("ja-JP")), +/// hello.lookup(&LangMatch::resolve("ja-JP")), /// Some(String::from("Hello world!")) /// ); /// -/// // Para incrustar en HTML escapado: -/// let markup = hello.to_markup(&LangMatch::resolve("es-ES")); -/// assert_eq!(markup.into_string(), "¡Hola mundo!"); +/// // Uso típico en un atributo: +/// let title = hello.value(&LangMatch::resolve("es-ES")); +/// // Ejemplo: html! { a title=(title) { "Link" } } /// ``` #[derive(AutoDefault, Clone, Debug)] pub struct AttrL10n(L10n); @@ -51,15 +51,18 @@ impl AttrL10n { // AttrL10n GETTERS **************************************************************************** /// Devuelve la traducción para `language`, si existe. - pub fn using(&self, language: &impl LangId) -> Option { - self.0.using(language) + pub fn lookup(&self, language: &impl LangId) -> Option { + self.0.lookup(language) } - /// Devuelve la traducción *escapada* como [`Markup`] para `language`, si existe. - /// - /// Útil para incrustar el texto directamente en plantillas HTML sin riesgo de inyección de - /// contenido. + /// Devuelve la traducción para `language` o una cadena vacía si no existe. + pub fn value(&self, language: &impl LangId) -> String { + self.0.lookup(language).unwrap_or_default() + } + + /// **Obsoleto desde la versión 0.4.0**: no recomendado para atributos HTML. + #[deprecated(since = "0.4.0", note = "For attributes use `lookup()` or `value()`")] pub fn to_markup(&self, language: &impl LangId) -> Markup { - self.0.to_markup(language) + self.0.using(language) } } diff --git a/src/html/context.rs b/src/html/context.rs index 96787863..8b7afba3 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -306,8 +306,7 @@ impl Context { /// 4. Y si ninguna de las opciones anteriores aplica, se usa el idioma de respaldo (`"en-US"`). /// /// Resulta útil para usar un contexto ([`Context`]) como fuente de traducción en -/// [`L10n::using()`](crate::locale::L10n::using) o -/// [`L10n::to_markup()`](crate::locale::L10n::to_markup). +/// [`L10n::lookup()`](crate::locale::L10n::lookup) o [`L10n::using()`](crate::locale::L10n::using). impl LangId for Context { fn langid(&self) -> &'static LanguageIdentifier { self.langid diff --git a/src/locale.rs b/src/locale.rs index 43612bd5..cf44dd82 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -13,7 +13,7 @@ //! //! # Recursos Fluent //! -//! Por defecto las traducciones están en el directorio `src/locale`, con subdirectorios para cada +//! Por defecto, las traducciones están en el directorio `src/locale`, con subdirectorios para cada //! [Identificador de Idioma Unicode](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier) //! válido. Podríamos tener una estructura como esta: //! @@ -34,7 +34,7 @@ //! └── main.ftl //! ``` //! -//! Ejemplo de un archivo en `src/locale/en-US/main.ftl` +//! Ejemplo de un archivo en `src/locale/en-US/main.ftl`: //! //! ```text //! hello-world = Hello world! @@ -53,7 +53,7 @@ //! Y su archivo equivalente para español en `src/locale/es-ES/main.ftl`: //! //! ```text -//! hello-world = Hola mundo! +//! hello-world = ¡Hola, mundo! //! hello-user = ¡Hola, {$userName}! //! shared-photos = //! {$userName} {$photoCount -> @@ -81,7 +81,7 @@ //! include_locales!(LOCALES_SAMPLE); //! ``` //! -//! Si están ubicados en otro directorio se puede usar la forma: +//! Si están ubicados en otro directorio, se puede usar la forma: //! //! ```rust,ignore //! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); @@ -129,7 +129,7 @@ pub(crate) static FALLBACK_LANGID: LazyLock = // Identificador de idioma **por defecto** para la aplicación. // // Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador -// de idioma no es válido o no está disponible entonces resuelve como [`FALLBACK_LANGID`]. +// de idioma no es válido o no está disponible, se usa [`FALLBACK_LANGID`]. pub(crate) static DEFAULT_LANGID: LazyLock> = LazyLock::new(|| LangMatch::resolve(&global::SETTINGS.app.language).as_option()); @@ -155,7 +155,7 @@ pub trait LangId { /// let lang = LangMatch::resolve("es-ES"); /// assert_eq!(lang.langid().to_string(), "es-ES"); /// -/// // Coincidencia parcial (con el idioma base). +/// // Coincidencia parcial (retrocede al idioma base si no hay variante regional). /// let lang = LangMatch::resolve("es-EC"); /// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. /// @@ -221,7 +221,7 @@ impl LangMatch { } } - // En otro caso indica que el idioma no está soportado. + // En caso contrario, indica que el idioma no está soportado. Self::Unsupported(String::from(language)) } @@ -241,7 +241,7 @@ impl LangMatch { /// let lang = LangMatch::resolve("es-ES").as_option(); /// assert_eq!(lang.unwrap().to_string(), "es-ES"); /// - /// let lang = LangMatch::resolve("jp-JP").as_option(); + /// let lang = LangMatch::resolve("ja-JP").as_option(); /// assert!(lang.is_none()); /// ``` #[inline] @@ -259,8 +259,8 @@ impl LangMatch { /// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de /// respaldo ("en-US"). /// -/// Resulta útil para usar un valor de [`LangMatch`] como fuente de traducción en [`L10n::using()`] -/// o [`L10n::to_markup()`]. +/// Resulta útil para usar un valor de [`LangMatch`] como fuente de traducción en [`L10n::lookup()`] +/// o [`L10n::using()`]. impl LangId for LangMatch { fn langid(&self) -> &'static LanguageIdentifier { match self { @@ -271,10 +271,10 @@ impl LangId for LangMatch { } #[macro_export] -/// Define un conjunto de elementos de localización y textos de traducción local. +/// Incluye un conjunto de recursos **Fluent** y textos de traducción propios. macro_rules! include_locales { - // Se eliminan las marcas de aislamiento Unicode en los argumentos para mejorar la legibilidad y - // la compatibilidad en ciertos contextos de renderizado. + // Se desactiva la inserción de marcas de aislamiento Unicode (FSI/PDI) en los argumentos para + // mejorar la legibilidad y la compatibilidad en ciertos contextos de renderizado. ( $LOCALES:ident $(, $core_locales:literal)? ) => { $crate::locale::fluent_templates::static_loader! { static $LOCALES = { @@ -310,8 +310,8 @@ include_locales!(LOCALES_PAGETOP); enum L10nOp { #[default] None, - Text(String), - Translate(String), + Text(Cow<'static, str>), + Translate(Cow<'static, str>), } /// Crea instancias para traducir textos localizados. @@ -324,7 +324,7 @@ enum L10nOp { /// /// # Ejemplo /// -/// Los argumentos dinámicos se añaden usando `with_arg()` o `with_args()`. +/// Los argumentos dinámicos se añaden con `with_arg()` o `with_args()`. /// /// ```rust /// use pagetop::prelude::*; @@ -338,11 +338,11 @@ enum L10nOp { /// .get(); /// ``` /// -/// También para traducciones a idiomas concretos. +/// También sirve para traducciones contra un conjunto de recursos concreto. /// /// ```rust,ignore /// // Traducción con clave, conjunto de traducciones y fuente de idioma. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).using(&LangMatch::resolve("it")); +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&LangMatch::resolve("it")); /// ``` #[derive(AutoDefault, Clone)] pub struct L10n { @@ -354,7 +354,7 @@ pub struct L10n { impl L10n { /// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción. - pub fn n(text: impl Into) -> Self { + pub fn n(text: impl Into>) -> Self { L10n { op: L10nOp::Text(text.into()), ..Default::default() @@ -363,7 +363,7 @@ impl L10n { /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de /// traducciones predefinidas. - pub fn l(key: impl Into) -> Self { + pub fn l(key: impl Into>) -> Self { L10n { op: L10nOp::Translate(key.into()), ..Default::default() @@ -372,7 +372,7 @@ impl L10n { /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de /// traducciones específico. - pub fn t(key: impl Into, locales: &'static Locales) -> Self { + pub fn t(key: impl Into>, locales: &'static Locales) -> Self { L10n { op: L10nOp::Translate(key.into()), locales, @@ -399,7 +399,8 @@ impl L10n { self } - /// Resuelve la traducción usando el idioma por defecto o de respaldo de la aplicación. + /// Resuelve la traducción usando el idioma por defecto o, si no procede, el de respaldo de la + /// aplicación. /// /// Devuelve `None` si no aplica o no encuentra una traducción válida. /// @@ -411,7 +412,7 @@ impl L10n { /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); /// ``` pub fn get(&self) -> Option { - self.using(&LangMatch::default()) + self.lookup(&LangMatch::default()) } /// Resuelve la traducción usando la fuente de idioma proporcionada. @@ -432,20 +433,27 @@ impl L10n { /// } /// /// let r = ResourceLang; - /// let text = L10n::l("greeting").with_arg("name", "Usuario").using(&r); + /// let text = L10n::l("greeting").with_arg("name", "Usuario").lookup(&r); /// ``` - pub fn using(&self, language: &impl LangId) -> Option { + pub fn lookup(&self, language: &impl LangId) -> Option { match &self.op { L10nOp::None => None, - L10nOp::Text(text) => Some(text.to_owned()), - L10nOp::Translate(key) => self.locales.try_lookup_with_args( - language.langid(), - key, - &self.args.iter().fold(HashMap::new(), |mut arg, (k, v)| { - arg.insert(Cow::Owned(k.clone()), v.to_owned().into()); - arg - }), - ), + L10nOp::Text(text) => Some(text.clone().into_owned()), + L10nOp::Translate(key) => { + if self.args.is_empty() { + self.locales.try_lookup(language.langid(), key.as_ref()) + } else { + self.locales.try_lookup_with_args( + language.langid(), + key.as_ref(), + &self + .args + .iter() + .map(|(k, v)| (Cow::Owned(k.clone()), v.clone().into())) + .collect::>(), + ) + } + } } } @@ -458,10 +466,16 @@ impl L10n { /// ```rust /// use pagetop::prelude::*; /// - /// let html = L10n::l("welcome.message").to_markup(&LangMatch::resolve("es")); + /// let html = L10n::l("welcome.message").using(&LangMatch::resolve("es")); /// ``` + pub fn using(&self, language: &impl LangId) -> Markup { + PreEscaped(self.lookup(language).unwrap_or_default()) + } + + /// **Obsoleto desde la versión 0.4.0**: usar [`using()`](Self::using) en su lugar. + #[deprecated(since = "0.4.0", note = "Use `using()` instead")] pub fn to_markup(&self, language: &impl LangId) -> Markup { - PreEscaped(self.using(language).unwrap_or_default()) + self.using(language) } } diff --git a/src/response/page.rs b/src/response/page.rs index ea88e846..7ef52703 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -151,12 +151,12 @@ impl Page { /// Devuelve el título traducido para el idioma de la página, si existe. pub fn title(&mut self) -> Option { - self.title.using(&self.context) + self.title.lookup(&self.context) } /// Devuelve la descripción traducida para el idioma de la página, si existe. pub fn description(&mut self) -> Option { - self.description.using(&self.context) + self.description.lookup(&self.context) } /// Devuelve la lista de metadatos ``. diff --git a/tests/locale.rs b/tests/locale.rs index ef875d7d..ee7ac27c 100644 --- a/tests/locale.rs +++ b/tests/locale.rs @@ -13,7 +13,7 @@ async fn translation_without_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(&LangMatch::resolve("es-ES")); + let translation = l10n.lookup(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola mundo!".to_string())); } @@ -22,7 +22,7 @@ async fn translation_with_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-user").with_arg("userName", "Manuel"); - let translation = l10n.using(&LangMatch::resolve("es-ES")); + let translation = l10n.lookup(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); } @@ -35,7 +35,7 @@ async fn translation_with_plural_and_select() { ("photoCount", "3"), ("userGender", "male"), ]); - let translation = l10n.using(&LangMatch::resolve("es-ES")).unwrap(); + let translation = l10n.lookup(&LangMatch::resolve("es-ES")).unwrap(); assert!(translation.contains("añadido 3 nuevas fotos de él")); } @@ -44,7 +44,7 @@ async fn check_fallback_language() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test-hello-world"); - let translation = l10n.using(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". + let translation = l10n.lookup(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". assert_eq!(translation, Some("Hello world!".to_string())); } @@ -53,6 +53,6 @@ async fn check_unknown_key() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("non-existent-key"); - let translation = l10n.using(&LangMatch::resolve("en-US")); + let translation = l10n.lookup(&LangMatch::resolve("en-US")); assert_eq!(translation, None); } From 543f2de333adf8b7635b2556dd98796ddfb0e99b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 4 Sep 2025 01:11:03 +0200 Subject: [PATCH 107/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`with=5Fcompo?= =?UTF-8?q?nent`=20por=20`add=5Fcomponent`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/hello-name.rs | 2 +- examples/hello-world.rs | 2 +- src/core/theme/regions.rs | 12 +++++------ src/response/page.rs | 42 ++++++++++++++++++++++++++++++++------ src/response/page/error.rs | 4 ++-- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/examples/hello-name.rs b/examples/hello-name.rs index 7a6db545..3a491a68 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,7 @@ async fn hello_name( ) -> ResultPage { let name = path.into_inner(); Page::new(Some(request)) - .with_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) + .add_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) .render() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index ba268dcd..5550514b 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(Some(request)) - .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) .render() } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index c8a05554..4fcd7dfa 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -77,11 +77,11 @@ pub struct ChildrenInRegions(HashMap<&'static str, Children>); impl ChildrenInRegions { pub fn with(region_name: &'static str, child: Child) -> Self { - ChildrenInRegions::default().with_child_in_region(region_name, ChildOp::Add(child)) + ChildrenInRegions::default().with_child_in(region_name, ChildOp::Add(child)) } #[builder_fn] - pub fn with_child_in_region(mut self, region_name: &'static str, op: ChildOp) -> Self { + pub fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { if let Some(region) = self.0.get_mut(region_name) { region.alter_child(op); } else { @@ -143,17 +143,17 @@ impl InRegion { InRegion::Content => { COMMON_REGIONS .write() - .alter_child_in_region(REGION_CONTENT, ChildOp::Add(child)); + .alter_child_in(REGION_CONTENT, ChildOp::Add(child)); } - InRegion::Named(name) => { + InRegion::Named(region_name) => { COMMON_REGIONS .write() - .alter_child_in_region(name, ChildOp::Add(child)); + .alter_child_in(region_name, ChildOp::Add(child)); } InRegion::OfTheme(region_name, theme_ref) => { let mut regions = THEME_REGIONS.write(); if let Some(r) = regions.get_mut(&theme_ref.type_id()) { - r.alter_child_in_region(region_name, ChildOp::Add(child)); + r.alter_child_in(region_name, ChildOp::Add(child)); } else { regions.insert( theme_ref.type_id(), diff --git a/src/response/page.rs b/src/response/page.rs index 7ef52703..5ac3720c 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -122,28 +122,58 @@ impl Page { self } + /// **Obsoleto desde la versión 0.4.0**: usar [`add_component()`](Self::add_component) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `add_component()` instead")] + pub fn with_component(self, component: impl Component) -> Self { + self.add_component(component) + } + + /// **Obsoleto desde la versión 0.4.0**: usar [`add_component_in()`](Self::add_component_in) en + /// su lugar. + #[deprecated(since = "0.4.0", note = "Use `add_component_in()` instead")] + pub fn with_component_in(self, region_name: &'static str, component: impl Component) -> Self { + self.add_component_in(region_name, component) + } + /// Añade un componente a la región de contenido por defecto. - pub fn with_component(mut self, component: impl Component) -> Self { + pub fn add_component(mut self, component: impl Component) -> Self { self.regions - .alter_child_in_region(REGION_CONTENT, ChildOp::Add(Child::with(component))); + .alter_child_in(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } /// Añade un componente en una región (`region_name`) de la página. - pub fn with_component_in( + pub fn add_component_in( mut self, region_name: &'static str, component: impl Component, ) -> Self { self.regions - .alter_child_in_region(region_name, ChildOp::Add(Child::with(component))); + .alter_child_in(region_name, ChildOp::Add(Child::with(component))); + self + } + + /// **Obsoleto desde la versión 0.4.0**: usar [`with_child_in()`](Self::with_child_in) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `with_child_in()` instead")] + pub fn with_child_in_region(mut self, region_name: &'static str, op: ChildOp) -> Self { + self.alter_child_in(region_name, op); + self + } + + /// **Obsoleto desde la versión 0.4.0**: usar [`alter_child_in()`](Self::alter_child_in) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `alter_child_in()` instead")] + pub fn alter_child_in_region(&mut self, region_name: &'static str, op: ChildOp) -> &mut Self { + self.alter_child_in(region_name, op); self } /// Opera con [`ChildOp`] en una región (`region_name`) de la página. #[builder_fn] - pub fn with_child_in_region(mut self, region_name: &'static str, op: ChildOp) -> Self { - self.regions.alter_child_in_region(region_name, op); + pub fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { + self.regions.alter_child_in(region_name, op); self } diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 99ba62bf..ab56338f 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -33,7 +33,7 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) .with_layout("error") - .with_component(Html::with(move |_| error403.clone())) + .add_component(Html::with(move |_| error403.clone())) .render() { write!(f, "{}", page.into_string()) @@ -48,7 +48,7 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) .with_layout("error") - .with_component(Html::with(move |_| error404.clone())) + .add_component(Html::with(move |_| error404.clone())) .render() { write!(f, "{}", page.into_string()) From 970442a45aaeea242073845d4cfbfd5109d00653 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 4 Sep 2025 01:12:59 +0200 Subject: [PATCH 108/224] =?UTF-8?q?=F0=9F=9A=A7=20Retoques=20en=20el=20c?= =?UTF-8?q?=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 2 +- tests/component_poweredby.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index b462ea13..0a60d532 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -191,7 +191,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { // Genera el método alter_...() con el código del método with_...(). let fn_alter_doc = - format!("Igual que [`Self::{fn_with_name_str}()`], pero sin usar el patrón *builder*."); + format!("Equivalente a [`Self::{fn_with_name_str}()`], pero sin usar el patrón *builder*."); let fn_alter = quote! { #[doc = #fn_alter_doc] diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs index b2e4418b..9f8e8222 100644 --- a/tests/component_poweredby.rs +++ b/tests/component_poweredby.rs @@ -67,8 +67,8 @@ async fn poweredby_link_points_to_crates_io() { let html = render_component(&p); assert!( - html.contains("https://crates.io/crates/pagetop"), - "Link should point to crates.io/pagetop" + html.contains("https://pagetop.cillero.es"), + "Link should point to pagetop.cillero.es" ); } From c43c8458cce0453d8c7fd34579ae51f92a27a7ed Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 4 Sep 2025 01:53:51 +0200 Subject: [PATCH 109/224] =?UTF-8?q?=F0=9F=9A=A7=20(welcome):=20Crea=20p?= =?UTF-8?q?=C3=A1gina=20de=20bienvenida=20desde=20intro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementa un nuevo *layout* en el tema `Basic` para crear una plantilla de páginas de introducción. - Añade nuevo fichero CSS `intro.css` para los estilos globales de la página de introducción. - Incorpora nuevos recursos gráficos para la cabecera de la página de introducción en varios formatos (AVIF, WebP, JPEG). - Revisa los ficheros de localización. --- src/base/component.rs | 3 + src/base/component/block.rs | 103 +++++++++ src/base/extension/welcome.rs | 105 ++------- src/base/theme/basic.rs | 91 +++++++- src/core/component/definition.rs | 6 +- src/core/theme.rs | 6 +- src/core/theme/definition.rs | 210 +++++++++++------- src/core/theme/regions.rs | 2 +- src/locale/en-US/welcome.ftl | 1 - src/locale/es-ES/welcome.ftl | 1 - static/css/{welcome.css => intro.css} | 150 ++++++------- ...me-header-sm.avif => intro-header-sm.avif} | Bin ...come-header-sm.jpg => intro-header-sm.jpg} | Bin ...me-header-sm.webp => intro-header-sm.webp} | Bin ...{welcome-header.avif => intro-header.avif} | Bin .../{welcome-header.jpg => intro-header.jpg} | Bin ...{welcome-header.webp => intro-header.webp} | Bin 17 files changed, 420 insertions(+), 258 deletions(-) create mode 100644 src/base/component/block.rs rename static/css/{welcome.css => intro.css} (78%) rename static/img/{welcome-header-sm.avif => intro-header-sm.avif} (100%) rename static/img/{welcome-header-sm.jpg => intro-header-sm.jpg} (100%) rename static/img/{welcome-header-sm.webp => intro-header-sm.webp} (100%) rename static/img/{welcome-header.avif => intro-header.avif} (100%) rename static/img/{welcome-header.jpg => intro-header.jpg} (100%) rename static/img/{welcome-header.webp => intro-header.webp} (100%) diff --git a/src/base/component.rs b/src/base/component.rs index 30cb6866..4df64ff1 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -3,5 +3,8 @@ mod html; pub use html::Html; +mod block; +pub use block::Block; + mod poweredby; pub use poweredby::PoweredBy; diff --git a/src/base/component/block.rs b/src/base/component/block.rs new file mode 100644 index 00000000..c96f2baf --- /dev/null +++ b/src/base/component/block.rs @@ -0,0 +1,103 @@ +use crate::prelude::*; + +/// Componente genérico que representa un bloque de contenido. +/// +/// Los bloques se utilizan como contenedores de otros componentes o contenidos, con un título +/// opcional y un cuerpo que sólo se renderiza si existen componentes hijos (*children*). +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Block { + id : AttrId, + classes : AttrClasses, + title : L10n, + children: Children, +} + +impl Component for Block { + fn new() -> Self { + Block::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "block"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let block_body = self.children().render(cx); + + if block_body.is_empty() { + return PrepareMarkup::None; + } + + let id = cx.required_id::(self.id()); + + PrepareMarkup::With(html! { + div id=(id) class=[self.classes().get()] { + @if let Some(title) = self.title().lookup(cx) { + h2 class="block__title" { span { (title) } } + } + div class="block__body" { (block_body) } + } + }) + } +} + +impl Block { + // Block BUILDER ******************************************************************************* + + /// Establece el identificador único (`id`) del bloque. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al bloque. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Establece el título del bloque. + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; + self + } + + /// Añade un nuevo componente hijo al bloque. + pub fn add_component(mut self, component: impl Component) -> Self { + self.children + .alter_child(ChildOp::Add(Child::with(component))); + self + } + + /// Modifica la lista de hijos (`children`) aplicando una operación. + #[builder_fn] + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // Block GETTERS ******************************************************************************* + + /// Devuelve las clases CSS asociadas al bloque. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el título del bloque como [`L10n`]. + pub fn title(&self) -> &L10n { + &self.title + } + + /// Devuelve la lista de hijos (`children`) del bloque. + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 0252cff3..3c0de3db 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -25,95 +25,26 @@ async fn homepage(request: HttpRequest) -> ResultPage { let app = &global::SETTINGS.app.name; Page::new(Some(request)) - .with_title(L10n::l("welcome_page")) - .with_theme("Basic") - .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/welcome.css"))) - .with_body_classes(ClassesOp::Add, "welcome") - .with_component_in("header", Html::with(move |cx| html! { - div class="welcome-header" { - header class="welcome-header__body" { - h1 - class="welcome-header__title" - aria-label=(L10n::l("welcome_aria").with_arg("app", app).to_markup(cx)) - { - span { (L10n::l("welcome_title").to_markup(cx)) } - (L10n::l("welcome_intro").with_arg("app", app).to_markup(cx)) - } - } - aside class="welcome-header__image" aria-hidden="true" { - div class="welcome-header__monster" { - picture { - source - type="image/avif" - src="/img/monster-pagetop_250.avif" - srcset="/img/monster-pagetop_500.avif 1.5x"; - source - type="image/webp" - src="/img/monster-pagetop_250.webp" - srcset="/img/monster-pagetop_500.webp 1.5x"; - img - src="/img/monster-pagetop_250.png" - srcset="/img/monster-pagetop_500.png 1.5x" - alt="Monster PageTop"; - } - } - } + .with_theme("basic") + .with_layout("intro") + .with_title(L10n::l("welcome_title")) + .with_description(L10n::l("welcome_intro").with_arg("app", app)) + .add_component(Html::with(|cx| { + html! { + p { (L10n::l("welcome_text1").using(cx)) } + p { (L10n::l("welcome_text2").using(cx)) } } })) - .with_component(Html::with(move |cx| html! { - main class="welcome-content" { - section class="welcome-content__body" { - div class="welcome-poweredby" { - a - class="welcome-poweredby__link" - href="https://pagetop.cillero.es" - target="_blank" - rel="noreferrer" - { - span {} span {} span {} - div class="welcome-poweredby__text" { - (L10n::l("welcome_powered").to_markup(cx)) - } - } + .add_component( + Block::new() + .with_title(L10n::l("welcome_about")) + .add_component(Html::with(move |cx| { + html! { + p { (L10n::l("welcome_pagetop").using(cx)) } + p { (L10n::l("welcome_issues1").using(cx)) } + p { (L10n::l("welcome_issues2").with_arg("app", app).using(cx)) } } - div class="welcome-text" { - p { (L10n::l("welcome_text1").to_markup(cx)) } - p { (L10n::l("welcome_text2").to_markup(cx)) } - - div class="welcome-text__block" { - h2 { span { (L10n::l("welcome_about").to_markup(cx)) } } - p { (L10n::l("welcome_pagetop").to_markup(cx)) } - p { (L10n::l("welcome_issues1").to_markup(cx)) } - p { (L10n::l("welcome_issues2").with_arg("app", app).to_markup(cx)) } - } - } - } - } - })) - .with_component_in("footer", Html::with(move |cx| html! { - section class="welcome-footer" { - div class="welcome-footer__logo" { - svg - viewBox="0 0 1614 1614" - xmlns="http://www.w3.org/2000/svg" - role="img" - aria-label=[L10n::l("pagetop_logo").using(cx)] - preserveAspectRatio="xMidYMid slice" - focusable="false" - { - path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} - path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} - path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} - } - } - div class="welcome-footer__links" { - a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } - a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").to_markup(cx)) } - em { (L10n::l("welcome_have_fun").to_markup(cx)) } - } - } - })) - .with_component_in("footer", PoweredBy::new()) + })), + ) .render() } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 961864be..dc16f2ac 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -12,16 +12,105 @@ impl Extension for Basic { } impl Theme for Basic { + fn render_page_body(&self, page: &mut Page) -> Markup { + match page.layout() { + "intro" => render_intro(page), + _ => ::render_body(self, page, self.page_regions()), + } + } + fn after_render_page_body(&self, page: &mut Page) { + let styles = match page.layout() { + "intro" => "/css/intro.css", + _ => "/css/basic.css", + }; page.alter_assets(AssetsOp::AddStyleSheet( StyleSheet::from("/css/normalize.css") .with_version("8.0.1") .with_weight(-99), )) .alter_assets(AssetsOp::AddStyleSheet( - StyleSheet::from("/css/basic.css") + StyleSheet::from(styles) .with_version(env!("CARGO_PKG_VERSION")) .with_weight(-99), )); } } + +fn render_intro(page: &mut Page) -> Markup { + let title = page.title().unwrap_or_default(); + let intro = page.description().unwrap_or_default(); + + html! { + body id=[page.body_id().get()] class=[page.body_classes().get()] { + header class="intro-header" { + section class="intro-header__body" { + h1 class="intro-header__title" { + span { (title) } + (intro) + } + } + aside class="intro-header__image" aria-hidden="true" { + div class="intro-header__monster" { + picture { + source + type="image/avif" + src="/img/monster-pagetop_250.avif" + srcset="/img/monster-pagetop_500.avif 1.5x"; + source + type="image/webp" + src="/img/monster-pagetop_250.webp" + srcset="/img/monster-pagetop_500.webp 1.5x"; + img + src="/img/monster-pagetop_250.png" + srcset="/img/monster-pagetop_500.png 1.5x" + alt="Monster PageTop"; + } + } + } + } + main class="intro-content" { + section class="intro-content__body" { + div class="intro-button" { + a + class="intro-button__link" + href="https://pagetop.cillero.es" + target="_blank" + rel="noreferrer" + { + span {} span {} span {} + div class="intro-button__text" { + (L10n::l("welcome_powered").using(page)) + } + } + } + div class="intro-text" { (page.render_region("content")) } + } + } + footer class="intro-footer" { + section class="intro-footer__body" { + div class="intro-footer__logo" { + svg + viewBox="0 0 1614 1614" + xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label=[L10n::l("pagetop_logo").lookup(page)] + preserveAspectRatio="xMidYMid slice" + focusable="false" + { + path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} + } + } + div class="intro-footer__links" { + a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } + a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").using(page)) } + em { (L10n::l("welcome_have_fun").using(page)) } + } + } + } + } + } +} diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index c43dfb0f..d547c4b3 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -29,14 +29,14 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { TypeInfo::ShortName.of::() } - /// Devuelve una descripción opcional del componente. + /// Devuelve una descripción del componente, si existe. /// /// Por defecto, no se proporciona ninguna descripción (`None`). fn description(&self) -> Option { None } - /// Devuelve un identificador opcional para el componente. + /// Devuelve el identificador del componente, si existe. /// /// Este identificador puede usarse para referenciar el componente en el HTML. Por defecto, no /// tiene ningún identificador (`None`). @@ -51,7 +51,7 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { #[allow(unused_variables)] fn setup_before_prepare(&mut self, cx: &mut Context) {} - /// Devuelve una representación estructurada del componente preparada para el renderizado. + /// Devuelve una representación renderizada del componente. /// /// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente /// durante el proceso de construcción del documento. Puede sobrescribirse para generar diff --git a/src/core/theme.rs b/src/core/theme.rs index e0c3008e..5889dcf7 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -15,10 +15,10 @@ //! [`Theme`]. mod definition; -pub use definition::{Theme, ThemeRef}; +pub use definition::{Theme, ThemePage, ThemeRef}; mod regions; -pub(crate) use regions::ChildrenInRegions; -pub use regions::{InRegion, Region, REGION_CONTENT}; +pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT}; +pub use regions::{InRegion, Region}; pub(crate) mod all; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 3b26a573..a4faf0b8 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -7,88 +7,34 @@ use crate::response::page::Page; use std::sync::LazyLock; -/// Representa una referencia a un tema. +/// Referencia estática a un tema. /// -/// Los temas son también extensiones. Por tanto se deben definir igual, es decir, como instancias -/// estáticas globales que implementan [`Theme`], pero también [`Extension`]. +/// Los temas son también extensiones. Por tanto, deben declararse como **instancias estáticas** que +/// implementen [`Theme`] y, a su vez, [`Extension`]. pub type ThemeRef = &'static dyn Theme; -/// Interfaz común que debe implementar cualquier tema de `PageTop`. +/// Métodos predefinidos de renderizado para las páginas de un tema. /// -/// Un tema implementará [`Theme`] y los métodos que sean necesarios de [`Extension`], aunque el -/// único obligatorio será [`theme()`](Extension::theme). +/// Contiene las implementaciones base de las **secciones** `` y ``. Se implementa +/// automáticamente para cualquier tipo que implemente [`Theme`], por lo que normalmente no requiere +/// implementación explícita. /// -/// ```rust -/// use pagetop::prelude::*; +/// Si un tema **sobrescribe** [`render_page_head()`](Theme::render_page_head) o +/// [`render_page_body()`](Theme::render_page_body), se puede volver al comportamiento por defecto +/// cuando se necesite usando FQS (*Fully Qualified Syntax*): /// -/// pub struct MyTheme; -/// -/// impl Extension for MyTheme { -/// fn name(&self) -> L10n { -/// L10n::n("My theme") -/// } -/// -/// fn description(&self) -> L10n { -/// L10n::n("A personal theme") -/// } -/// -/// fn theme(&self) -> Option { -/// Some(&Self) -/// } -/// } -/// -/// impl Theme for MyTheme {} -/// ``` -pub trait Theme: Extension + Send + Sync { - /// **Obsoleto desde la versión 0.4.0**: usar [`declared_regions()`](Self::declared_regions) en - /// su lugar. - #[deprecated(since = "0.4.0", note = "Use `declared_regions()` instead")] - fn regions(&self) -> Vec<(&'static str, L10n)> { - vec![("content", L10n::l("content"))] - } - - /// Declaración ordenada de las regiones disponibles en la página. - /// - /// Devuelve una lista estática de pares `(Region, L10n)` que se usará para renderizar en el - /// orden indicado todas las regiones que componen una página. Los identificadores deben ser - /// **estables** como `"sidebar-left"` o `"content"`. La etiqueta `L10n` devuelve el nombre de la - /// región en el idioma activo de la página. - /// - /// Si el tema requiere un conjunto distinto de regiones, se puede sobrescribir este método para - /// devolver una lista diferente. Si no, se usará la lista predeterminada: - /// - /// - `"header"`: cabecera. - /// - `"content"`: contenido principal (**obligatoria**). - /// - `"footer"`: pie. - /// - /// Sólo la región `"content"` es obligatoria, usa [`Region::default()`] para declararla. - #[inline] - fn declared_regions(&self) -> &'static [(Region, L10n)] { - static REGIONS: LazyLock<[(Region, L10n); 3]> = LazyLock::new(|| { - [ - (Region::declare("header"), L10n::l("region_header")), - (Region::default(), L10n::l("region_content")), - (Region::declare("footer"), L10n::l("region_footer")), - ] - }); - ®IONS[..] - } - - /// Acciones específicas del tema antes de renderizar el `` de la página. - /// - /// Útil para preparar clases, inyectar recursos o ajustar metadatos. - #[allow(unused_variables)] - fn before_render_page_body(&self, page: &mut Page) {} - +/// - `::render_body(self, page, self.page_regions())` +/// - `::render_head(self, page)` +pub trait ThemePage { /// Renderiza el contenido del `` de la página. /// - /// Por defecto, recorre [`declared_regions()`](Self::declared_regions) **en el orden que se han - /// declarado** y, para cada región con contenido, genera un contenedor con `role="region"` y - /// `aria-label` localizado. - fn render_page_body(&self, page: &mut Page) -> Markup { + /// Recorre `regions` en el **orden declarado** y, para cada región con contenido, genera un + /// contenedor con `role="region"` y un `aria-label` localizado. Se asume que cada identificador + /// de región es **único** dentro de la página. + fn render_body(&self, page: &mut Page, regions: &[(Region, L10n)]) -> Markup { html! { body id=[page.body_id().get()] class=[page.body_classes().get()] { - @for (region, region_label) in self.declared_regions() { + @for (region, region_label) in regions { @let output = page.render_region(region.key()); @if !output.is_empty() { @let region_name = region.name(); @@ -96,7 +42,7 @@ pub trait Theme: Extension + Send + Sync { id=(region_name) class={ "region region--" (region_name) } role="region" - aria-label=[region_label.using(page)] + aria-label=[region_label.lookup(page)] { (output) } @@ -106,17 +52,12 @@ pub trait Theme: Extension + Send + Sync { } } - /// Acciones específicas del tema después de renderizar el `` de la página. - /// - /// Útil para *tracing*, métricas o ajustes finales del estado de la página. - #[allow(unused_variables)] - fn after_render_page_body(&self, page: &mut Page) {} - /// Renderiza el contenido del `` de la página. /// - /// Por defecto, genera las etiquetas básicas (`charset`, `title`, `description`, `viewport`, - /// `X-UA-Compatible`), los metadatos y propiedades de la página y los recursos (CSS/JS). - fn render_page_head(&self, page: &mut Page) -> Markup { + /// Por defecto incluye las etiquetas básicas (`charset`, `title`, `description`, `viewport`, + /// `X-UA-Compatible`), los metadatos (`name/content`) y propiedades (`property/content`), + /// además de los recursos CSS/JS de la página. + fn render_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { head { @@ -146,18 +87,115 @@ pub trait Theme: Extension + Send + Sync { } } } +} - /// Página de error "*403 – Forbidden*" predeterminada. +/// Interfaz común que debe implementar cualquier tema de PageTop. +/// +/// Un tema implementa [`Theme`] y los métodos necesarios de [`Extension`]. El único método +/// **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme). +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// pub struct MyTheme; +/// +/// impl Extension for MyTheme { +/// fn name(&self) -> L10n { +/// L10n::n("My theme") +/// } +/// +/// fn description(&self) -> L10n { +/// L10n::n("A personal theme") +/// } +/// +/// fn theme(&self) -> Option { +/// Some(&Self) +/// } +/// } +/// +/// impl Theme for MyTheme {} +/// ``` +pub trait Theme: Extension + ThemePage + Send + Sync { + /// **Obsoleto desde la versión 0.4.0**: usar [`page_regions()`](Self::page_regions) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `page_regions()` instead")] + fn regions(&self) -> Vec<(&'static str, L10n)> { + vec![("content", L10n::l("content"))] + } + + /// Declaración ordenada de las regiones disponibles en la página. + /// + /// Devuelve una **lista estática** de pares `(Region, L10n)` que se usará para renderizar en el + /// orden indicado todas las regiones que componen una página. + /// + /// Requisitos y recomendaciones: + /// + /// - Los identificadores deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). + /// - La región `"content"` es **obligatoria**. Se puede usar [`Region::default()`] para + /// declararla. + /// - La etiqueta `L10n` se evalúa con el idioma activo de la página. + /// + /// Si tu tema define un conjunto distinto, se puede **sobrescribir** este método. Por defecto + /// devuelve: + /// + /// - `"header"`: cabecera. + /// - `"content"`: contenido principal (**obligatoria**). + /// - `"footer"`: pie. + fn page_regions(&self) -> &'static [(Region, L10n)] { + static REGIONS: LazyLock<[(Region, L10n); 3]> = LazyLock::new(|| { + [ + (Region::declare("header"), L10n::l("region_header")), + (Region::default(), L10n::l("region_content")), + (Region::declare("footer"), L10n::l("region_footer")), + ] + }); + ®IONS[..] + } + + /// Acciones específicas del tema antes de renderizar el `` de la página. + /// + /// Útil para preparar clases, inyectar recursos o ajustar metadatos. + #[allow(unused_variables)] + fn before_render_page_body(&self, page: &mut Page) {} + + /// Renderiza el contenido del `` de la página. + /// + /// Si se sobrescribe este método, se puede volver al comportamiento base con: + /// `::render_body(self, page, self.page_regions())`. + #[inline] + fn render_page_body(&self, page: &mut Page) -> Markup { + ::render_body(self, page, self.page_regions()) + } + + /// Acciones específicas del tema después de renderizar el `` de la página. + /// + /// Útil para *tracing*, métricas o ajustes finales del estado de la página. + #[allow(unused_variables)] + fn after_render_page_body(&self, page: &mut Page) {} + + /// Renderiza el contenido del `` de la página. + /// + /// Si se sobrescribe este método, se puede volver al comportamiento base con: + /// `::render_head(self, page)`. + #[inline] + fn render_page_head(&self, page: &mut Page) -> Markup { + ::render_head(self, page) + } + + /// Contenido predeterminado para la página de error "*403 – Forbidden*". /// /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. fn error403(&self, page: &mut Page) -> Markup { - html! { div { h1 { (L10n::l("error403_notice").to_markup(page)) } } } + html! { div { h1 { (L10n::l("error403_notice").using(page)) } } } } - /// Página de error "*404 – Not Found*" predeterminada. + /// Contenido predeterminado para la página de error "*404 – Not Found*". /// /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. fn error404(&self, page: &mut Page) -> Markup { - html! { div { h1 { (L10n::l("error404_notice").to_markup(page)) } } } + html! { div { h1 { (L10n::l("error404_notice").using(page)) } } } } } + +/// Se implementa automáticamente `ThemePage` para cualquier tema. +impl ThemePage for T {} diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 4fcd7dfa..1a2e0fbb 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -25,7 +25,7 @@ pub const REGION_CONTENT: &str = "content"; /// (p.ej., clases `region__{name}`). /// /// Se utiliza para declarar las regiones que componen una página en un tema (ver -/// [`declared_regions()`](crate::core::theme::Theme::declared_regions)). +/// [`page_regions()`](crate::core::theme::Theme::page_regions)). pub struct Region { key: &'static str, name: String, diff --git a/src/locale/en-US/welcome.ftl b/src/locale/en-US/welcome.ftl index 7d98f447..7b7d74d8 100644 --- a/src/locale/en-US/welcome.ftl +++ b/src/locale/en-US/welcome.ftl @@ -3,7 +3,6 @@ welcome_extension_description = Displays a landing page when none is configured. welcome_page = Welcome Page welcome_title = Hello world! -welcome_aria = Say hello to your { $app } installation welcome_intro = Discover⚡{ $app } welcome_powered = A web solution powered by PageTop! diff --git a/src/locale/es-ES/welcome.ftl b/src/locale/es-ES/welcome.ftl index 8a384253..78238322 100644 --- a/src/locale/es-ES/welcome.ftl +++ b/src/locale/es-ES/welcome.ftl @@ -3,7 +3,6 @@ welcome_extension_description = Muestra una página de inicio predeterminada cua welcome_page = Página de Bienvenida welcome_title = ¡Hola mundo! -welcome_aria = Saluda a tu instalación { $app } welcome_intro = Descubre⚡{ $app } welcome_powered = Una solución web creada con PageTop! diff --git a/static/css/welcome.css b/static/css/intro.css similarity index 78% rename from static/css/welcome.css rename to static/css/intro.css index 4ce8046b..5a5461e4 100644 --- a/static/css/welcome.css +++ b/static/css/intro.css @@ -1,8 +1,8 @@ :root { - --bg-img: url('/img/welcome-header.jpg'); - --bg-img-set: image-set(url('/img/welcome-header.avif') type('image/avif'), url('/img/welcome-header.webp') type('image/webp'), var(--bg-img) type('image/jpeg')); - --bg-img-sm: url('/img/welcome-header-sm.jpg'); - --bg-img-sm-set: image-set(url('/img/welcome-header-sm.avif') type('image/avif'), url('/img/welcome-header-sm.webp') type('image/webp'), var(--bg-img-sm) type('image/jpeg')); + --bg-img: url('/img/intro-header.jpg'); + --bg-img-set: image-set(url('/img/intro-header.avif') type('image/avif'), url('/img/intro-header.webp') type('image/webp'), var(--bg-img) type('image/jpeg')); + --bg-img-sm: url('/img/intro-header-sm.jpg'); + --bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--bg-img-sm) type('image/jpeg')); --bg-color: #8c5919; --color: #1a202c; --color-red: #fecaca; @@ -28,9 +28,14 @@ body { font-weight: 300; color: var(--color); line-height: 1.6; + + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } -header, section { position: relative; text-align: center; @@ -50,19 +55,11 @@ a:hover:visited { text-decoration-color: var(--color-link); } -#content { - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - /* - * Region header + * Header */ -.welcome-header { +.intro-header { display: flex; flex-direction: column-reverse; width: 100%; @@ -76,11 +73,11 @@ a:hover:visited { background-size: contain; background-repeat: no-repeat; } -.welcome-header__body { +.intro-header__body { padding: 0; background: none; } -.welcome-header__title { +.intro-header__title { margin: 0 0 0 1.5rem; text-align: left; display: flex; @@ -94,7 +91,7 @@ a:hover:visited { line-height: 110%; text-shadow: 0 0.125rem 0.1875rem rgba(0, 0, 0, 0.3); } -.welcome-header__title > span { +.intro-header__title > span { background: linear-gradient(180deg, #ddff95 30%, #ffb84b 100%); background-clip: text; -webkit-background-clip: text; @@ -105,44 +102,44 @@ a:hover:visited { line-height: 110%; text-shadow: none; } -.welcome-header__image { +.intro-header__image { display: flex; justify-content: flex-start; text-align: right; width: 100%; } -.welcome-header__monster { +.intro-header__monster { margin-right: 12rem; margin-top: 1rem; flex-shrink: 1; } @media (min-width: 64rem) { - .welcome-header { + .intro-header { background-image: var(--bg-img); background-image: var(--bg-img-set); } - .welcome-header__title { + .intro-header__title { padding: 1.2rem 2rem 2.6rem 2rem; } - .welcome-header__image { + .intro-header__image { justify-content: flex-end; } } /* - * Region content + * Content */ -.welcome-content { +.intro-content { height: auto; margin-top: 1.6rem; } -.welcome-content__body { +.intro-content__body { box-sizing: border-box; max-width: 80rem; } -.welcome-content__body:before, -.welcome-content__body:after { +.intro-content__body:before, +.intro-content__body:after { content: ''; position: absolute; left: 0; @@ -152,38 +149,37 @@ a:hover:visited { filter: blur(2.75rem); opacity: 0.8; inset: 11.75rem; - /*z-index: 0;*/ } -.welcome-content__body:before { +.intro-content__body:before { top: -1rem; } -.welcome-content__body:after { +.intro-content__body:after { bottom: -1rem; } @media (max-width: 48rem) { - .welcome-content__body { + .intro-content__body { margin-top: -9.8rem; } - .welcome-content__body:before, - .welcome-content__body:after { + .intro-content__body:before, + .intro-content__body:after { inset: unset; } } @media (min-width: 64rem) { - .welcome-content { + .intro-content { margin-top: 0; } - .welcome-content__body { + .intro-content__body { margin-top: -5.7rem; } } -.welcome-poweredby { +.intro-button { width: 100%; margin: 0 auto 3rem; z-index: 10; } -.welcome-poweredby__link { +.intro-button__link { background: #7f1d1d; background-image: linear-gradient(to bottom, rgba(255,0,0,0.8), rgba(255,255,255,0)); background-position: top left, center; @@ -196,7 +192,6 @@ a:hover:visited { font-size: 1.5rem; line-height: 1.3; text-decoration: none; - /*text-shadow: var(--shadow);*/ transition: transform 0.3s ease-in-out; position: relative; overflow: hidden; @@ -204,7 +199,7 @@ a:hover:visited { min-height: 7.6875rem; outline: none; } -.welcome-poweredby__link::before { +.intro-button__link::before { content: ''; position: absolute; top: -13.125rem; @@ -216,7 +211,7 @@ a:hover:visited { transition: transform 0.3s ease-in-out; z-index: 5; } -.welcome-poweredby__text { +.intro-button__text { display: flex; flex-direction: column; flex: 1; @@ -226,25 +221,24 @@ a:hover:visited { padding: 1rem 1.5rem; text-align: left; color: white; - /*text-shadow: 0 0.101125rem 0.2021875rem rgba(0, 0, 0, 0.25);*/ font-size: 1.65rem; font-style: normal; font-weight: 600; line-height: 130.023%; letter-spacing: 0.0075rem; } -.welcome-poweredby__text strong { +.intro-button__text strong { font-size: 2.625rem; font-weight: 600; line-height: 130.023%; letter-spacing: 0.013125rem; } -.welcome-poweredby__link span { +.intro-button__link span { position: absolute; display: block; pointer-events: none; } -.welcome-poweredby__link span:nth-child(1) { +.intro-button__link span:nth-child(1) { height: 8px; width: 100%; top: 0; @@ -264,7 +258,7 @@ a:hover:visited { transform: translateX(100%); } } -.welcome-poweredby__link span:nth-child(2) { +.intro-button__link span:nth-child(2) { width: 8px; height: 100%; top: 0; @@ -284,7 +278,7 @@ a:hover:visited { transform: translateY(100%); } } -.welcome-poweredby__link span:nth-child(3) { +.intro-button__link span:nth-child(3) { height: 8px; width: 100%; bottom: 0; @@ -304,22 +298,22 @@ a:hover:visited { transform: translateX(-100%); } } -.welcome-poweredby__link:hover span { +.intro-button__link:hover span { animation-play-state: paused; } @media (max-width: 48rem) { - .welcome-poweredby__link { + .intro-button__link { height: 6.25rem; min-width: auto; border-radius: 0; } - .welcome-poweredby__text { + .intro-button__text { display: inline; padding-top: .5rem; } } @media (min-width: 48rem) { - .welcome-poweredby { + .intro-button { position: absolute; top: 0; left: 50%; @@ -327,14 +321,13 @@ a:hover:visited { max-width: 29.375rem; margin-bottom: 0; } - .welcome-poweredby__link:hover { + .intro-button__link:hover { transition: all .5s; transform: rotate(-3deg) scale(1.1); - /*box-shadow: 0px 3px 5px rgba(0,0,0,.4);*/ } } -.welcome-text { +.intro-text { z-index: 1; width: 100%; display: flex; @@ -346,13 +339,16 @@ a:hover:visited { font-weight: 400; line-height: 1.5; margin-top: -6rem; - background: #fff; margin-bottom: 0; + background: #fff; position: relative; - padding: 6rem 1.063rem 0.75rem; + padding: 2.5rem 1.063rem 0.75rem; overflow: hidden; } -.welcome-text p { +.intro-button + .intro-text { + padding-top: 6rem; +} +.intro-text p { width: 100%; line-height: 150%; font-weight: 400; @@ -360,14 +356,16 @@ a:hover:visited { margin: 0 0 1.5rem; } @media (min-width: 48rem) { - .welcome-text { + .intro-text { font-size: 1.375rem; line-height: 2rem; + } + .intro-button + .intro-text { padding-top: 7rem; } } @media (min-width: 64rem) { - .welcome-text { + .intro-text { border-radius: 0.75rem; box-shadow: var(--shadow); max-width: 60rem; @@ -377,13 +375,13 @@ a:hover:visited { } } -.welcome-text__block { +.intro-text .block { position: relative; } -.welcome-text__block h2 { +.intro-text .block__title { margin: 1em 0 .8em; } -.welcome-text__block h2 span { +.intro-text .block__title span { display: inline-block; padding: 10px 30px 14px; margin: 0 0 0 20px; @@ -394,7 +392,7 @@ a:hover:visited { border-color: orangered; transform: rotate(-3deg) translateY(-25%); } -.welcome-text__block h2:before { +.intro-text .block__title:before { content: ""; height: 5px; position: absolute; @@ -407,7 +405,7 @@ a:hover:visited { transform: rotate(2deg) translateY(-50%); transform-origin: top left; } -.welcome-text__block h2:after { +.intro-text .block__title:after { content: ""; height: 70rem; position: absolute; @@ -420,15 +418,17 @@ a:hover:visited { } /* - * Region footer + * Footer */ -.region--footer { +.intro-footer { + width: 100%; background-color: black; color: var(--color-gray); + padding-bottom: 2rem; } -.welcome-footer { +.intro-footer__body { display: flex; justify-content: center; flex-direction: column; @@ -439,33 +439,33 @@ a:hover:visited { font-weight: 300; line-height: 100%; } -.welcome-footer a:visited { +.intro-footer__body a:visited { color: var(--color-gray); } -.welcome-footer__logo, -.welcome-footer__links { +.intro-footer__logo, +.intro-footer__links { display: flex; justify-content: center; width: 100%; } -.welcome-footer__logo { +.intro-footer__logo { max-height: 12.625rem; } -.welcome-footer__logo svg { +.intro-footer__logo svg { width: 100%; } -.welcome-footer__links { +.intro-footer__links { gap: 1.875rem; flex-wrap: wrap; margin-top: 2rem; } @media (max-width: 48rem) { - .welcome-footer__logo { + .intro-footer__logo { display: none; } } @media (max-width: 64rem) { - .welcome-footer { + .intro-footer__body { padding: 0 1rem 2rem; } } diff --git a/static/img/welcome-header-sm.avif b/static/img/intro-header-sm.avif similarity index 100% rename from static/img/welcome-header-sm.avif rename to static/img/intro-header-sm.avif diff --git a/static/img/welcome-header-sm.jpg b/static/img/intro-header-sm.jpg similarity index 100% rename from static/img/welcome-header-sm.jpg rename to static/img/intro-header-sm.jpg diff --git a/static/img/welcome-header-sm.webp b/static/img/intro-header-sm.webp similarity index 100% rename from static/img/welcome-header-sm.webp rename to static/img/intro-header-sm.webp diff --git a/static/img/welcome-header.avif b/static/img/intro-header.avif similarity index 100% rename from static/img/welcome-header.avif rename to static/img/intro-header.avif diff --git a/static/img/welcome-header.jpg b/static/img/intro-header.jpg similarity index 100% rename from static/img/welcome-header.jpg rename to static/img/intro-header.jpg diff --git a/static/img/welcome-header.webp b/static/img/intro-header.webp similarity index 100% rename from static/img/welcome-header.webp rename to static/img/intro-header.webp From da1390c2fe7eb1ba2df5db199049482bf7b770df Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 6 Sep 2025 09:41:01 +0200 Subject: [PATCH 110/224] =?UTF-8?q?=F0=9F=9A=A7=20(context):=20Generaliza?= =?UTF-8?q?=20los=20par=C3=A1metros=20de=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/html.rs | 2 +- src/html/context.rs | 181 ++++++++++++++++++++++++++++++------- src/response/page.rs | 21 ++++- tests/component_html.rs | 2 +- 4 files changed, 168 insertions(+), 38 deletions(-) diff --git a/src/base/component/html.rs b/src/base/component/html.rs index cac39eaf..7bde94a0 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -25,7 +25,7 @@ use crate::prelude::*; /// use pagetop::prelude::*; /// /// let component = Html::with(|cx| { -/// let user = cx.get_param::("username").unwrap_or(String::from("visitor")); +/// let user = cx.param::("username").cloned().unwrap_or(String::from("visitor")); /// html! { /// h1 { "Hello, " (user) } /// } diff --git a/src/html/context.rs b/src/html/context.rs index 8b7afba3..4ebd510e 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -7,10 +7,8 @@ use crate::locale::{LangId, LangMatch, LanguageIdentifier, DEFAULT_LANGID, FALLB use crate::service::HttpRequest; use crate::{builder_fn, join}; +use std::any::Any; use std::collections::HashMap; -use std::error::Error; -use std::fmt::{self, Display}; -use std::str::FromStr; /// Operaciones para modificar el contexto ([`Context`]) del documento. pub enum AssetsOp { @@ -33,32 +31,28 @@ pub enum AssetsOp { RemoveJavaScript(&'static str), } -/// Errores de lectura o conversión de parámetros almacenados en el contexto. +/// Errores de acceso a parámetros dinámicos del contexto. +/// +/// - [`ErrorParam::NotFound`]: la clave no existe. +/// - [`ErrorParam::TypeMismatch`]: la clave existe, pero el valor guardado no coincide con el tipo +/// solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo realmente +/// guardado (`saved`) para facilitar el diagnóstico. #[derive(Debug)] pub enum ErrorParam { - /// El parámetro solicitado no existe. NotFound, - /// El valor del parámetro no pudo convertirse al tipo requerido. - ParseError(String), + TypeMismatch { + key: &'static str, + expected: &'static str, + saved: &'static str, + }, } -impl Display for ErrorParam { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ErrorParam::NotFound => write!(f, "Parameter not found"), - ErrorParam::ParseError(e) => write!(f, "Parse error: {e}"), - } - } -} - -impl Error for ErrorParam {} - /// Representa el contexto de un documento HTML. /// /// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de /// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]), -/// hojas de estilo ([`StyleSheet`]) y *scripts* ([`JavaScript`]), así como parámetros de contexto -/// definidos en tiempo de ejecución. +/// hojas de estilo ([`StyleSheet`]) y *scripts* ([`JavaScript`]), así como *parámetros dinámicos +/// heterogéneos* de contexto definidos en tiempo de ejecución. /// /// # Ejemplos /// @@ -95,7 +89,7 @@ impl Error for ErrorParam {} /// assert_eq!(active_theme.short_name(), "aliner"); /// /// // Recupera el parámetro a su tipo original. -/// let id: i32 = cx.get_param("usuario_id").unwrap(); +/// let id: i32 = *cx.get_param::("usuario_id").unwrap(); /// assert_eq!(id, 42); /// /// // Genera un identificador para un componente de tipo `Menu`. @@ -113,7 +107,7 @@ pub struct Context { favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. - params : HashMap<&'static str, String>, // Parámetros definidos en tiempo de ejecución. + params : HashMap<&'static str, (Box, &'static str)>, // Parámetros definidos en tiempo de ejecución. id_counter : usize, // Contador para generar identificadores únicos. } @@ -152,7 +146,7 @@ impl Context { favicon : None, stylesheets: Assets::::new(), javascripts: Assets::::new(), - params : HashMap::<&str, String>::new(), + params : HashMap::default(), id_counter : 0, } } @@ -246,29 +240,146 @@ impl Context { // Context PARAMS ****************************************************************************** - /// Añade o modifica un parámetro del contexto almacenando el valor como [`String`]. + /// Añade o modifica un parámetro dinámico del contexto. + /// + /// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error + /// posteriores. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None) + /// .with_param("usuario_id", 42_i32) + /// .with_param("titulo", String::from("Hola")) + /// .with_param("flags", vec!["a", "b"]); + /// ``` #[builder_fn] - pub fn with_param(mut self, key: &'static str, value: T) -> Self { - self.params.insert(key, value.to_string()); + pub fn with_param(mut self, key: &'static str, value: T) -> Self { + let type_name = TypeInfo::FullName.of::(); + self.params.insert(key, (Box::new(value), type_name)); self } - /// Recupera un parámetro del contexto convertido al tipo especificado. + /// Recupera un parámetro como [`Option`], simplificando el acceso. /// - /// Devuelve un error si el parámetro no existe ([`ErrorParam::NotFound`]) o la conversión falla - /// ([`ErrorParam::ParseError`]). - pub fn get_param(&self, key: &'static str) -> Result { - self.params - .get(key) - .ok_or(ErrorParam::NotFound) - .and_then(|v| T::from_str(v).map_err(|_| ErrorParam::ParseError(v.clone()))) + /// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información + /// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor + /// guardado no coincide con el tipo solicitado. + /// + /// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo + /// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None).with_param("username", String::from("Alice")); + /// + /// // Devuelve Some(&String) si existe y coincide el tipo. + /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); + /// + /// // Devuelve None si no existe o si el tipo no coincide. + /// assert!(cx.param::("username").is_none()); + /// assert!(cx.param::("missing").is_none()); + /// + /// // Acceso con valor por defecto. + /// let user = cx.param::("missing") + /// .cloned() + /// .unwrap_or_else(|| "visitor".to_string()); + /// assert_eq!(user, "visitor"); + /// ``` + pub fn param(&self, key: &'static str) -> Option<&T> { + self.get_param::(key).ok() } - /// Elimina un parámetro del contexto. Devuelve `true` si existía y se eliminó. + /// Recupera una *referencia tipada* al parámetro solicitado. + /// + /// Devuelve: + /// + /// - `Ok(&T)` si la clave existe y el tipo coincide. + /// - `Err(ErrorParam::NotFound)` si la clave no existe. + /// - `Err(ErrorParam::TypeMismatch)` si la clave existe pero el tipo no coincide. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None) + /// .with_param("usuario_id", 42_i32) + /// .with_param("titulo", String::from("Hola")); + /// + /// let id: &i32 = cx.get_param("usuario_id").unwrap(); + /// let titulo: &String = cx.get_param("titulo").unwrap(); + /// + /// // Error de tipo: + /// assert!(cx.get_param::("usuario_id").is_err()); + /// ``` + pub fn get_param(&self, key: &'static str) -> Result<&T, ErrorParam> { + let (any, type_name) = self.params.get(key).ok_or(ErrorParam::NotFound)?; + any.downcast_ref::() + .ok_or_else(|| ErrorParam::TypeMismatch { + key, + expected: TypeInfo::FullName.of::(), + saved: *type_name, + }) + } + + /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. + /// + /// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let mut cx = Context::new(None).with_param("temp", 1u8); + /// assert!(cx.remove_param("temp")); + /// assert!(!cx.remove_param("temp")); // ya no existe + /// ``` pub fn remove_param(&mut self, key: &'static str) -> bool { self.params.remove(key).is_some() } + /// Recupera el parámetro solicitado y lo elimina del contexto. + /// + /// Devuelve: + /// + /// - `Ok(T)` si la clave existía y el tipo coincide. + /// - `Err(ErrorParam::NotFound)` si la clave no existe. + /// - `Err(ErrorParam::TypeMismatch)` si el tipo no coincide. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let mut cx = Context::new(None) + /// .with_param("contador", 7_i32) + /// .with_param("titulo", String::from("Hola")); + /// + /// let n: i32 = cx.take_param("contador").unwrap(); + /// assert!(cx.get_param::("contador").is_err()); // ya no está + /// + /// // Error de tipo: + /// assert!(cx.take_param::("titulo").is_err()); + /// ``` + pub fn take_param(&mut self, key: &'static str) -> Result { + let (boxed, saved) = self.params.remove(key).ok_or(ErrorParam::NotFound)?; + boxed + .downcast::() + .map(|b| *b) + .map_err(|_| ErrorParam::TypeMismatch { + key, + expected: TypeInfo::FullName.of::(), + saved, + }) + } + // Context EXTRAS ****************************************************************************** /// Genera un identificador único si no se proporciona uno explícito. diff --git a/src/response/page.rs b/src/response/page.rs index 5ac3720c..0942f8c8 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -108,6 +108,12 @@ impl Page { self } + #[builder_fn] + pub fn with_param(mut self, key: &'static str, value: T) -> Self { + self.context.alter_param(key, value); + self + } + /// Establece el atributo `id` del elemento ``. #[builder_fn] pub fn with_body_id(mut self, id: impl AsRef) -> Self { @@ -214,6 +220,10 @@ impl Page { self.context.layout() } + pub fn param(&self, key: &'static str) -> Option<&T> { + self.context.param(key) + } + /// Devuelve el identificador del elemento ``. pub fn body_id(&self) -> &AttrId { &self.body_id @@ -223,7 +233,16 @@ impl Page { pub fn body_classes(&self) -> &AttrClasses { &self.body_classes } - + /* + /// Devuelve una referencia mutable al [`Context`] de la página. + /// + /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, + /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente + /// o un tema necesita recibir el contexto como parámetro. + pub fn context(&mut self) -> &mut Context { + &mut self.context + } + */ // Page RENDER ********************************************************************************* /// Renderiza los componentes de una región (`regiona_name`) de la página. diff --git a/tests/component_html.rs b/tests/component_html.rs index b9b8e5ec..bd7f3c08 100644 --- a/tests/component_html.rs +++ b/tests/component_html.rs @@ -20,7 +20,7 @@ async fn component_html_renders_using_context_param() { let mut cx = Context::new(None).with_param("username", String::from("Alice")); let component = Html::with(|cx| { - let name = cx.get_param::("username").unwrap_or_default(); + let name = cx.param::("username").cloned().unwrap_or_default(); html! { span { (name) } } From ff76504e781a8fecdee200532a1315a0452b5d6e Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 7 Sep 2025 21:06:41 +0200 Subject: [PATCH 111/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(macros):=20Majora?= =?UTF-8?q?=20la=20validaci=C3=B3n=20de=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 260 +++++++++++++++++++++--------- 1 file changed, 181 insertions(+), 79 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 0a60d532..28c6b1b4 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -39,7 +39,7 @@ mod smart_default; use proc_macro::TokenStream; use quote::{quote, quote_spanned}; -use syn::{parse_macro_input, spanned::Spanned, DeriveInput, ItemFn}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; /// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)). #[proc_macro] @@ -107,114 +107,216 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// `alter_...()`, que permitirá más adelante modificar instancias existentes. #[proc_macro_attribute] pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { - let fn_with = parse_macro_input!(item as ItemFn); - let fn_with_name = fn_with.sig.ident.clone(); - let fn_with_name_str = fn_with.sig.ident.to_string(); + use syn::{parse2, FnArg, Ident, ImplItemFn, Pat, ReturnType, TraitItemFn, Type}; + + let ts: proc_macro2::TokenStream = item.clone().into(); + + enum Kind { + Impl(ImplItemFn), + Trait(TraitItemFn), + } + + // Detecta si estamos en `impl` o `trait`. + let kind = if let Ok(it) = parse2::(ts.clone()) { + Kind::Impl(it) + } else if let Ok(tt) = parse2::(ts.clone()) { + Kind::Trait(tt) + } else { + return quote! { + compile_error!("#[builder_fn] only supports methods in `impl` blocks or `trait` items"); + } + .into(); + }; + + // Extrae piezas comunes (sig, attrs, vis, bloque?, es_trait?). + let (sig, attrs, vis, body_opt, is_trait) = match &kind { + Kind::Impl(m) => (&m.sig, &m.attrs, Some(&m.vis), Some(&m.block), false), + Kind::Trait(t) => (&t.sig, &t.attrs, None, t.default.as_ref(), true), + }; + + let with_name = sig.ident.clone(); + let with_name_str = sig.ident.to_string(); // Valida el nombre del método. - if !fn_with_name_str.starts_with("with_") { - let expanded = quote_spanned! { - fn_with.sig.ident.span() => - compile_error!("expected a \"pub fn with_...(mut self, ...) -> Self\" method"); - }; - return expanded.into(); - } - // Valida que el método es público. - if !matches!(fn_with.vis, syn::Visibility::Public(_)) { + if !with_name_str.starts_with("with_") { return quote_spanned! { - fn_with.sig.ident.span() => compile_error!("expected method to be `pub`"); + sig.ident.span() => compile_error!("expected a named `with_...()` method"); } .into(); } - // Valida que el primer argumento es exactamente `mut self`. - if let Some(syn::FnArg::Receiver(receiver)) = fn_with.sig.inputs.first() { - if receiver.mutability.is_none() || receiver.reference.is_some() { - return quote_spanned! { - receiver.span() => compile_error!("expected `mut self` as the first argument"); + + // Sólo se exige `pub` en `impl` (en `trait` no aplica). + let vis_pub = match (is_trait, vis) { + (false, Some(v)) => quote! { #v }, + _ => quote! {}, + }; + + // Validaciones comunes. + if sig.asyncness.is_some() { + return quote_spanned! { + sig.asyncness.span() => compile_error!("`with_...()` cannot be `async`"); + } + .into(); + } + if sig.constness.is_some() { + return quote_spanned! { + sig.constness.span() => compile_error!("`with_...()` cannot be `const`"); + } + .into(); + } + if sig.abi.is_some() { + return quote_spanned! { + sig.abi.span() => compile_error!("`with_...()` cannot be `extern`"); + } + .into(); + } + if sig.unsafety.is_some() { + return quote_spanned! { + sig.unsafety.span() => compile_error!("`with_...()` cannot be `unsafe`"); + } + .into(); + } + + // En `impl` se exige exactamente `mut self`; y en `trait` se exige `self` (sin &). + let receiver_ok = match sig.inputs.first() { + Some(FnArg::Receiver(r)) => { + // Rechaza `self: SomeType`. + if r.colon_token.is_some() { + false + } else if is_trait { + // Exactamente `self` (sin &, sin mut). + r.reference.is_none() && r.mutability.is_none() + } else { + // Exactamente `mut self`. + r.reference.is_none() && r.mutability.is_some() } - .into(); } - } else { + _ => false, + }; + if !receiver_ok { + let msg = if is_trait { + "expected `self` (not `mut self`, `&self` or `&mut self`) in trait method" + } else { + "expected first argument to be exactly `mut self`" + }; + let err = sig + .inputs + .first() + .map(|a| a.span()) + .unwrap_or(sig.ident.span()); return quote_spanned! { - fn_with.sig.ident.span() => compile_error!("expected `mut self` as the first argument"); + err => compile_error!(#msg); } .into(); } + // Valida que el método devuelve exactamente `Self`. - if let syn::ReturnType::Type(_, ty) = &fn_with.sig.output { - if let syn::Type::Path(type_path) = ty.as_ref() { - if type_path.qself.is_some() || !type_path.path.is_ident("Self") { - return quote_spanned! { ty.span() => - compile_error!("expected return type to be exactly `Self`"); + match &sig.output { + ReturnType::Type(_, ty) => match ty.as_ref() { + Type::Path(p) if p.qself.is_none() && p.path.is_ident("Self") => {} + _ => { + return quote_spanned! { + ty.span() => compile_error!("expected return type to be exactly `Self`"); } .into(); } - } else { - return quote_spanned! { ty.span() => - compile_error!("expected return type to be exactly `Self`"); + }, + _ => { + return quote_spanned! { + sig.output.span() => compile_error!("expected return type to be exactly `Self`"); } .into(); } - } else { - return quote_spanned! { - fn_with.sig.output.span() => compile_error!("expected method to return `Self`"); - } - .into(); } // Genera el nombre del método alter_...(). - let fn_alter_name_str = fn_with_name_str.replace("with_", "alter_"); - let fn_alter_name = syn::Ident::new(&fn_alter_name_str, fn_with.sig.ident.span()); + let stem = with_name_str.strip_prefix("with_").expect("validated"); + let alter_ident = Ident::new(&format!("alter_{stem}"), with_name.span()); // Extrae genéricos y cláusulas where. - let fn_generics = &fn_with.sig.generics; - let where_clause = &fn_with.sig.generics.where_clause; + let generics = &sig.generics; + let where_clause = &sig.generics.where_clause; - // Extrae argumentos y parámetros de llamada. - let args: Vec<_> = fn_with.sig.inputs.iter().skip(1).collect(); - let params: Vec<_> = fn_with - .sig - .inputs + // Extrae identificadores de los argumentos para la llamada (sin `mut` ni patrones complejos). + let args: Vec<_> = sig.inputs.iter().skip(1).collect(); + let call_idents: Vec = { + let mut v = Vec::new(); + for arg in sig.inputs.iter().skip(1) { + match arg { + FnArg::Typed(pat) => { + if let Pat::Ident(pat_ident) = pat.pat.as_ref() { + v.push(pat_ident.ident.clone()); + } else { + return quote_spanned! { + pat.pat.span() => compile_error!( + "each parameter must be a simple identifier, e.g. `value: T`" + ); + } + .into(); + } + } + _ => { + return quote_spanned! { + arg.span() => compile_error!("unexpected receiver in parameter list"); + } + .into(); + } + } + } + v + }; + + // Extrae atributos descartando la documentación para incluir en `alter_...()`. + let non_doc_attrs: Vec<_> = attrs .iter() - .skip(1) - .map(|arg| match arg { - syn::FnArg::Typed(pat) => &pat.pat, - _ => panic!("unexpected argument type"), - }) + .cloned() + .filter(|a| !a.path().is_ident("doc")) .collect(); - // Extrae bloque del método. - let fn_with_block = &fn_with.block; - - // Extrae documentación y otros atributos del método. - let fn_with_attrs = &fn_with.attrs; - - // Genera el método alter_...() con el código del método with_...(). - let fn_alter_doc = - format!("Equivalente a [`Self::{fn_with_name_str}()`], pero sin usar el patrón *builder*."); - - let fn_alter = quote! { - #[doc = #fn_alter_doc] - pub fn #fn_alter_name #fn_generics(&mut self, #(#args),*) -> &mut Self #where_clause { - #fn_with_block - } - }; - - // Redefine el método with_...() para que llame a alter_...(). - let fn_with = quote! { - #(#fn_with_attrs)* - #[inline] - pub fn #fn_with_name #fn_generics(mut self, #(#args),*) -> Self #where_clause { - self.#fn_alter_name(#(#params),*); - self - } - }; + // Documentación del método alter_...(). + let alter_doc = + format!("Equivalente a [`Self::{with_name_str}()`], pero fuera del patrón *builder*."); // Genera el código final. - let expanded = quote! { - #fn_with - #[inline] - #fn_alter + let expanded = match body_opt { + None => { + quote! { + #(#attrs)* + fn #with_name #generics (self, #(#args),*) -> Self #where_clause; + + #(#non_doc_attrs)* + #[doc = #alter_doc] + fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause; + } + } + Some(body) => { + let with_fn = if is_trait { + quote! { + #vis_pub fn #with_name #generics (self, #(#args),*) -> Self #where_clause { + let mut s = self; + s.#alter_ident(#(#call_idents),*); + s + } + } + } else { + quote! { + #vis_pub fn #with_name #generics (mut self, #(#args),*) -> Self #where_clause { + self.#alter_ident(#(#call_idents),*); + self + } + } + }; + quote! { + #(#attrs)* + #with_fn + + #(#non_doc_attrs)* + #[doc = #alter_doc] + #vis_pub fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause { + #body + } + } + } }; expanded.into() } From b10d0aadf4c998c28a4b07b040de2c91a1f45394 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 7 Sep 2025 21:06:50 +0200 Subject: [PATCH 112/224] =?UTF-8?q?=F0=9F=9A=A7=20(context):=20Define=20un?= =?UTF-8?q?=20`trait`=20com=C3=BAn=20de=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.rs | 13 +- src/html.rs | 4 +- src/html/assets.rs | 16 +- src/html/assets/javascript.rs | 4 +- src/html/assets/stylesheet.rs | 4 +- src/html/context.rs | 416 ++++++++++++++++++++-------------- src/response/page.rs | 158 +++++++------ src/response/page/error.rs | 1 + 8 files changed, 356 insertions(+), 260 deletions(-) diff --git a/src/global.rs b/src/global.rs index 6be0774d..c81eec99 100644 --- a/src/global.rs +++ b/src/global.rs @@ -48,14 +48,13 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma por defecto para la aplicación. + /// Idioma por defecto de la aplicación. /// - /// Si no se especifica un valor válido, normalmente se usará el idioma devuelto por la - /// implementación de [`LangId`](crate::locale::LangId) para [`Context`](crate::html::Context), - /// en el siguiente orden: primero, el idioma establecido explícitamente con - /// [`Context::with_langid()`](crate::html::Context::with_langid); si no se ha definido, se - /// usará el indicado en la cabecera `Accept-Language` del navegador; y, si ninguno aplica, se - /// empleará el idioma de respaldo ("en-US"). + /// Si este valor no es válido, el idioma efectivo para el renderizado se resolverá mediante la + /// implementación de [`LangId`](crate::locale::LangId) en este orden: primero, el establecido + /// explícitamente con [`Contextual::with_langid()`](crate::html::Contextual::with_langid); si + /// no se ha definido, se usará el indicado en la cabecera `Accept-Language` del navegador; y, + /// si ninguno aplica, se empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html.rs b/src/html.rs index 784457ed..9f3d70cf 100644 --- a/src/html.rs +++ b/src/html.rs @@ -9,12 +9,12 @@ mod assets; pub use assets::favicon::Favicon; pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; -pub(crate) use assets::Assets; +pub use assets::{Asset, Assets}; // HTML DOCUMENT CONTEXT *************************************************************************** mod context; -pub use context::{AssetsOp, Context, ErrorParam}; +pub use context::{AssetsOp, Context, Contextual, ErrorParam}; // HTML ATTRIBUTES ********************************************************************************* diff --git a/src/html/assets.rs b/src/html/assets.rs index 894b7e84..e53e8e35 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -5,20 +5,20 @@ pub mod stylesheet; use crate::html::{html, Markup, Render}; use crate::{AutoDefault, Weight}; -pub trait AssetsTrait: Render { - // Devuelve el nombre del recurso, utilizado como clave única. +pub trait Asset: Render { + /// Devuelve el nombre del recurso, utilizado como clave única. fn name(&self) -> &str; - // Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. + /// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. fn weight(&self) -> Weight; } #[derive(AutoDefault)] -pub(crate) struct Assets(Vec); +pub struct Assets(Vec); -impl Assets { +impl Assets { pub fn new() -> Self { - Assets::(Vec::::new()) + Self(Vec::new()) } pub fn add(&mut self, asset: T) -> bool { @@ -49,14 +49,14 @@ impl Assets { } } -impl Render for Assets { +impl Render for Assets { fn render(&self) -> Markup { let mut assets = self.0.iter().collect::>(); assets.sort_by_key(|a| a.weight()); html! { @for a in assets { - (a.render()) + (a) } } } diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index db5754e9..89b5261c 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,4 +1,4 @@ -use crate::html::assets::AssetsTrait; +use crate::html::assets::Asset; use crate::html::{html, Markup, Render}; use crate::{join, join_pair, AutoDefault, Weight}; @@ -137,7 +137,7 @@ impl JavaScript { } } -impl AssetsTrait for JavaScript { +impl Asset for JavaScript { // Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador. fn name(&self) -> &str { match &self.source { diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index bb60b01c..a5537264 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,4 +1,4 @@ -use crate::html::assets::AssetsTrait; +use crate::html::assets::Asset; use crate::html::{html, Markup, PreEscaped, Render}; use crate::{join_pair, AutoDefault, Weight}; @@ -142,7 +142,7 @@ impl StyleSheet { } } -impl AssetsTrait for StyleSheet { +impl Asset for StyleSheet { // Para hojas de estilos externas es la ruta; para las embebidas, un identificador. fn name(&self) -> &str { match &self.source { diff --git a/src/html/context.rs b/src/html/context.rs index 4ebd510e..7af884cf 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -10,7 +10,7 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// Operaciones para modificar el contexto ([`Context`]) del documento. +/// Operaciones para modificar el contexto ([`Context`]) de un documento. pub enum AssetsOp { // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. @@ -47,7 +47,64 @@ pub enum ErrorParam { }, } -/// Representa el contexto de un documento HTML. +pub trait Contextual: LangId { + // Contextual BUILDER ************************************************************************** + + /// Asigna la fuente de idioma del documento. + #[builder_fn] + fn with_langid(self, language: &impl LangId) -> Self; + + /// Asigna la solicitud HTTP al contexto. + #[builder_fn] + fn with_request(self, request: Option) -> Self; + + /// Asigna el tema para renderizar el documento. + #[builder_fn] + fn with_theme(self, theme_name: &'static str) -> Self; + + /// Asigna la composición para renderizar el documento. + #[builder_fn] + fn with_layout(self, layout_name: &'static str) -> Self; + + /// Añade o modifica un parámetro dinámico del contexto. + #[builder_fn] + fn with_param(self, key: &'static str, value: T) -> Self; + + /// Define los recursos del contexto usando [`AssetsOp`]. + #[builder_fn] + fn with_assets(self, op: AssetsOp) -> Self; + + // Contextual GETTERS ************************************************************************** + + /// Devuelve una referencia a la solicitud HTTP asociada, si existe. + fn request(&self) -> Option<&HttpRequest>; + + /// Devuelve el tema que se usará para renderizar el documento. + fn theme(&self) -> ThemeRef; + + /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. + fn layout(&self) -> &str; + + /// Recupera un parámetro como [`Option`], simplificando el acceso. + fn param(&self, key: &'static str) -> Option<&T>; + + /// Devuelve el Favicon de los recursos del contexto. + fn favicon(&self) -> Option<&Favicon>; + + /// Devuelve las hojas de estilo de los recursos del contexto. + fn stylesheets(&self) -> &Assets; + + /// Devuelve los scripts JavaScript de los recursos del contexto. + fn javascripts(&self) -> &Assets; + + // Contextual HELPERS ************************************************************************** + + /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona + /// un `id` explícito. + fn required_id(&mut self, id: Option) -> String; +} + +/// Implementa el contexto de un documento HTML. /// /// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de /// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]), @@ -107,7 +164,7 @@ pub struct Context { favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. - params : HashMap<&'static str, (Box, &'static str)>, // Parámetros definidos en tiempo de ejecución. + params : HashMap<&'static str, (Box, &'static str)>, // Parámetros en ejecución. id_counter : usize, // Contador para generar identificadores únicos. } @@ -151,80 +208,6 @@ impl Context { } } - // Context BUILDER ***************************************************************************** - - /// Modifica la fuente de idioma del documento. - #[builder_fn] - pub fn with_langid(mut self, language: &impl LangId) -> Self { - self.langid = language.langid(); - self - } - - /// Modifica el tema que se usará para renderizar el documento. - /// - /// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica - /// ninguno entonces usará el tema por defecto. - #[builder_fn] - pub fn with_theme(mut self, theme_name: &'static str) -> Self { - self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); - self - } - - /// Modifica la composición para renderizar el documento. - #[builder_fn] - pub fn with_layout(mut self, layout_name: &'static str) -> Self { - self.layout = layout_name; - self - } - - /// Define los recursos del contexto usando [`AssetsOp`]. - #[builder_fn] - pub fn with_assets(mut self, op: AssetsOp) -> Self { - match op { - // Favicon. - AssetsOp::SetFavicon(favicon) => { - self.favicon = favicon; - } - AssetsOp::SetFaviconIfNone(icon) => { - if self.favicon.is_none() { - self.favicon = Some(icon); - } - } - // Stylesheets. - AssetsOp::AddStyleSheet(css) => { - self.stylesheets.add(css); - } - AssetsOp::RemoveStyleSheet(path) => { - self.stylesheets.remove(path); - } - // JavaScripts. - AssetsOp::AddJavaScript(js) => { - self.javascripts.add(js); - } - AssetsOp::RemoveJavaScript(path) => { - self.javascripts.remove(path); - } - } - self - } - - // Context GETTERS ***************************************************************************** - - /// Devuelve una referencia a la solicitud HTTP asociada, si existe. - pub fn request(&self) -> Option<&HttpRequest> { - self.request.as_ref() - } - - /// Devuelve el tema que se usará para renderizar el documento. - pub fn theme(&self) -> ThemeRef { - self.theme - } - - /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. - pub fn layout(&self) -> &str { - self.layout - } - // Context RENDER ****************************************************************************** /// Renderiza los recursos del contexto. @@ -240,61 +223,6 @@ impl Context { // Context PARAMS ****************************************************************************** - /// Añade o modifica un parámetro dinámico del contexto. - /// - /// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error - /// posteriores. - /// - /// # Ejemplos - /// - /// ```rust - /// use pagetop::prelude::*; - /// - /// let cx = Context::new(None) - /// .with_param("usuario_id", 42_i32) - /// .with_param("titulo", String::from("Hola")) - /// .with_param("flags", vec!["a", "b"]); - /// ``` - #[builder_fn] - pub fn with_param(mut self, key: &'static str, value: T) -> Self { - let type_name = TypeInfo::FullName.of::(); - self.params.insert(key, (Box::new(value), type_name)); - self - } - - /// Recupera un parámetro como [`Option`], simplificando el acceso. - /// - /// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información - /// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor - /// guardado no coincide con el tipo solicitado. - /// - /// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo - /// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo. - /// - /// # Ejemplo - /// - /// ```rust - /// use pagetop::prelude::*; - /// - /// let cx = Context::new(None).with_param("username", String::from("Alice")); - /// - /// // Devuelve Some(&String) si existe y coincide el tipo. - /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); - /// - /// // Devuelve None si no existe o si el tipo no coincide. - /// assert!(cx.param::("username").is_none()); - /// assert!(cx.param::("missing").is_none()); - /// - /// // Acceso con valor por defecto. - /// let user = cx.param::("missing") - /// .cloned() - /// .unwrap_or_else(|| "visitor".to_string()); - /// assert_eq!(user, "visitor"); - /// ``` - pub fn param(&self, key: &'static str) -> Option<&T> { - self.get_param::(key).ok() - } - /// Recupera una *referencia tipada* al parámetro solicitado. /// /// Devuelve: @@ -328,23 +256,6 @@ impl Context { }) } - /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. - /// - /// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada. - /// - /// # Ejemplos - /// - /// ```rust - /// use pagetop::prelude::*; - /// - /// let mut cx = Context::new(None).with_param("temp", 1u8); - /// assert!(cx.remove_param("temp")); - /// assert!(!cx.remove_param("temp")); // ya no existe - /// ``` - pub fn remove_param(&mut self, key: &'static str) -> bool { - self.params.remove(key).is_some() - } - /// Recupera el parámetro solicitado y lo elimina del contexto. /// /// Devuelve: @@ -380,30 +291,21 @@ impl Context { }) } - // Context EXTRAS ****************************************************************************** - - /// Genera un identificador único si no se proporciona uno explícito. + /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. /// - /// Si no se proporciona un `id`, se genera un identificador único en la forma `-` - /// donde `` es el nombre corto del tipo en minúsculas (sin espacios) y `` es un - /// contador interno incremental. - pub fn required_id(&mut self, id: Option) -> String { - if let Some(id) = id { - id - } else { - let prefix = TypeInfo::ShortName - .of::() - .trim() - .replace(' ', "_") - .to_lowercase(); - let prefix = if prefix.is_empty() { - "prefix".to_owned() - } else { - prefix - }; - self.id_counter += 1; - join!(prefix, "-", self.id_counter.to_string()) - } + /// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let mut cx = Context::new(None).with_param("temp", 1u8); + /// assert!(cx.remove_param("temp")); + /// assert!(!cx.remove_param("temp")); // ya no existe + /// ``` + pub fn remove_param(&mut self, key: &'static str) -> bool { + self.params.remove(key).is_some() } } @@ -423,3 +325,173 @@ impl LangId for Context { self.langid } } + +impl Contextual for Context { + // Contextual BUILDER ************************************************************************** + + #[builder_fn] + fn with_request(mut self, request: Option) -> Self { + self.request = request; + self + } + + #[builder_fn] + fn with_langid(mut self, language: &impl LangId) -> Self { + self.langid = language.langid(); + self + } + + /// Asigna el tema para renderizar el documento. + /// + /// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica + /// ninguno entonces usará el tema por defecto. + #[builder_fn] + fn with_theme(mut self, theme_name: &'static str) -> Self { + self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); + self + } + + #[builder_fn] + fn with_layout(mut self, layout_name: &'static str) -> Self { + self.layout = layout_name; + self + } + + /// Añade o modifica un parámetro dinámico del contexto. + /// + /// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error + /// posteriores. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None) + /// .with_param("usuario_id", 42_i32) + /// .with_param("titulo", String::from("Hola")) + /// .with_param("flags", vec!["a", "b"]); + /// ``` + #[builder_fn] + fn with_param(mut self, key: &'static str, value: T) -> Self { + let type_name = TypeInfo::FullName.of::(); + self.params.insert(key, (Box::new(value), type_name)); + self + } + + #[builder_fn] + fn with_assets(mut self, op: AssetsOp) -> Self { + match op { + // Favicon. + AssetsOp::SetFavicon(favicon) => { + self.favicon = favicon; + } + AssetsOp::SetFaviconIfNone(icon) => { + if self.favicon.is_none() { + self.favicon = Some(icon); + } + } + // Stylesheets. + AssetsOp::AddStyleSheet(css) => { + self.stylesheets.add(css); + } + AssetsOp::RemoveStyleSheet(path) => { + self.stylesheets.remove(path); + } + // JavaScripts. + AssetsOp::AddJavaScript(js) => { + self.javascripts.add(js); + } + AssetsOp::RemoveJavaScript(path) => { + self.javascripts.remove(path); + } + } + self + } + + // Contextual GETTERS ************************************************************************** + + fn request(&self) -> Option<&HttpRequest> { + self.request.as_ref() + } + + fn theme(&self) -> ThemeRef { + self.theme + } + + fn layout(&self) -> &str { + self.layout + } + + /// Recupera un parámetro como [`Option`], simplificando el acceso. + /// + /// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información + /// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor + /// guardado no coincide con el tipo solicitado. + /// + /// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo + /// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None).with_param("username", String::from("Alice")); + /// + /// // Devuelve Some(&String) si existe y coincide el tipo. + /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); + /// + /// // Devuelve None si no existe o si el tipo no coincide. + /// assert!(cx.param::("username").is_none()); + /// assert!(cx.param::("missing").is_none()); + /// + /// // Acceso con valor por defecto. + /// let user = cx.param::("missing") + /// .cloned() + /// .unwrap_or_else(|| "visitor".to_string()); + /// assert_eq!(user, "visitor"); + /// ``` + fn param(&self, key: &'static str) -> Option<&T> { + self.get_param::(key).ok() + } + + fn favicon(&self) -> Option<&Favicon> { + self.favicon.as_ref() + } + + fn stylesheets(&self) -> &Assets { + &self.stylesheets + } + + fn javascripts(&self) -> &Assets { + &self.javascripts + } + + // Contextual HELPERS ************************************************************************** + + /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona + /// un `id` explícito. + /// + /// Si no se proporciona un `id`, se genera un identificador único en la forma `-` + /// donde `` es el nombre corto del tipo en minúsculas (sin espacios) y `` es un + /// contador interno incremental. + fn required_id(&mut self, id: Option) -> String { + if let Some(id) = id { + id + } else { + let prefix = TypeInfo::ShortName + .of::() + .trim() + .replace(' ', "_") + .to_lowercase(); + let prefix = if prefix.is_empty() { + "prefix".to_owned() + } else { + prefix + }; + self.id_counter += 1; + join!(prefix, "-", self.id_counter.to_string()) + } + } +} diff --git a/src/response/page.rs b/src/response/page.rs index 0942f8c8..77bc9c47 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -8,7 +8,8 @@ use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; -use crate::html::{AssetsOp, Context}; +use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; +use crate::html::{AssetsOp, Context, Contextual}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; @@ -25,9 +26,9 @@ pub struct Page { description : AttrL10n, metadata : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>, - context : Context, body_id : AttrId, body_classes: AttrClasses, + context : Context, regions : ChildrenInRegions, } @@ -43,9 +44,9 @@ impl Page { description : AttrL10n::default(), metadata : Vec::default(), properties : Vec::default(), - context : Context::new(request), body_id : AttrId::default(), body_classes: AttrClasses::default(), + context : Context::new(request), regions : ChildrenInRegions::default(), } } @@ -80,40 +81,6 @@ impl Page { self } - /// Modifica la fuente de idioma de la página ([`Context::with_langid()`]). - #[builder_fn] - pub fn with_langid(mut self, language: &impl LangId) -> Self { - self.context.alter_langid(language); - self - } - - /// Modifica el tema que se usará para renderizar la página ([`Context::with_theme()`]). - #[builder_fn] - pub fn with_theme(mut self, theme_name: &'static str) -> Self { - self.context.alter_theme(theme_name); - self - } - - /// Modifica la composición para renderizar la página ([`Context::with_layout()`]). - #[builder_fn] - pub fn with_layout(mut self, layout_name: &'static str) -> Self { - self.context.alter_layout(layout_name); - self - } - - /// Define los recursos de la página usando [`AssetsOp`]. - #[builder_fn] - pub fn with_assets(mut self, op: AssetsOp) -> Self { - self.context.alter_assets(op); - self - } - - #[builder_fn] - pub fn with_param(mut self, key: &'static str, value: T) -> Self { - self.context.alter_param(key, value); - self - } - /// Establece el atributo `id` del elemento ``. #[builder_fn] pub fn with_body_id(mut self, id: impl AsRef) -> Self { @@ -205,25 +172,6 @@ impl Page { &self.properties } - /// Devuelve la solicitud HTTP asociada. - pub fn request(&self) -> Option<&HttpRequest> { - self.context.request() - } - - /// Devuelve el tema que se usará para renderizar la página. - pub fn theme(&self) -> ThemeRef { - self.context.theme() - } - - /// Devuelve la composición para renderizar la página. Por defecto es `"default"`. - pub fn layout(&self) -> &str { - self.context.layout() - } - - pub fn param(&self, key: &'static str) -> Option<&T> { - self.context.param(key) - } - /// Devuelve el identificador del elemento ``. pub fn body_id(&self) -> &AttrId { &self.body_id @@ -233,19 +181,19 @@ impl Page { pub fn body_classes(&self) -> &AttrClasses { &self.body_classes } - /* - /// Devuelve una referencia mutable al [`Context`] de la página. - /// - /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, - /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente - /// o un tema necesita recibir el contexto como parámetro. - pub fn context(&mut self) -> &mut Context { - &mut self.context - } - */ + + /// Devuelve una referencia mutable al [`Context`] de la página. + /// + /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, + /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente + /// o un tema necesita recibir el contexto como parámetro. + pub fn context(&mut self) -> &mut Context { + &mut self.context + } + // Page RENDER ********************************************************************************* - /// Renderiza los componentes de una región (`regiona_name`) de la página. + /// Renderiza los componentes de una región (`region_name`) de la página. pub fn render_region(&mut self, region_name: &'static str) -> Markup { self.regions .merge_all_components(self.context.theme(), region_name) @@ -302,3 +250,79 @@ impl LangId for Page { self.context.langid() } } + +impl Contextual for Page { + // Contextual BUILDER ************************************************************************** + + #[builder_fn] + fn with_request(mut self, request: Option) -> Self { + self.context.alter_request(request); + self + } + + #[builder_fn] + fn with_langid(mut self, language: &impl LangId) -> Self { + self.context.alter_langid(language); + self + } + + #[builder_fn] + fn with_theme(mut self, theme_name: &'static str) -> Self { + self.context.alter_theme(theme_name); + self + } + + #[builder_fn] + fn with_layout(mut self, layout_name: &'static str) -> Self { + self.context.alter_layout(layout_name); + self + } + + #[builder_fn] + fn with_param(mut self, key: &'static str, value: T) -> Self { + self.context.alter_param(key, value); + self + } + + #[builder_fn] + fn with_assets(mut self, op: AssetsOp) -> Self { + self.context.alter_assets(op); + self + } + + // Contextual GETTERS ************************************************************************** + + fn request(&self) -> Option<&HttpRequest> { + self.context.request() + } + + fn theme(&self) -> ThemeRef { + self.context.theme() + } + + fn layout(&self) -> &str { + self.context.layout() + } + + fn param(&self, key: &'static str) -> Option<&T> { + self.context.param(key) + } + + fn favicon(&self) -> Option<&Favicon> { + self.context.favicon() + } + + fn stylesheets(&self) -> &Assets { + self.context.stylesheets() + } + + fn javascripts(&self) -> &Assets { + self.context.javascripts() + } + + // Contextual HELPERS ************************************************************************** + + fn required_id(&mut self, id: Option) -> String { + self.context.required_id::(id) + } +} diff --git a/src/response/page/error.rs b/src/response/page/error.rs index ab56338f..be48e3ed 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,4 +1,5 @@ use crate::base::component::Html; +use crate::html::Contextual; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode}; From 6f11207d8ee471d040d7bc5461fe281828d07218 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 8 Sep 2025 00:10:23 +0200 Subject: [PATCH 113/224] =?UTF-8?q?=F0=9F=93=9D=20Mejora=20la=20documentac?= =?UTF-8?q?i=C3=B3n=20de=20recursos=20y=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.rs | 12 ++++---- src/html/assets.rs | 32 ++++++++++++++++++- src/html/assets/javascript.rs | 4 ++- src/html/assets/stylesheet.rs | 4 ++- src/html/context.rs | 58 +++++++++++++++++++++++++++-------- 5 files changed, 88 insertions(+), 22 deletions(-) diff --git a/src/global.rs b/src/global.rs index c81eec99..ccc6d9de 100644 --- a/src/global.rs +++ b/src/global.rs @@ -48,13 +48,13 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma por defecto de la aplicación. + /// Idioma por defecto para la aplicación. /// - /// Si este valor no es válido, el idioma efectivo para el renderizado se resolverá mediante la - /// implementación de [`LangId`](crate::locale::LangId) en este orden: primero, el establecido - /// explícitamente con [`Contextual::with_langid()`](crate::html::Contextual::with_langid); si - /// no se ha definido, se usará el indicado en la cabecera `Accept-Language` del navegador; y, - /// si ninguno aplica, se empleará el idioma de respaldo ("en-US"). + /// Si no está definido o no es válido, el idioma efectivo para el renderizado se resolverá + /// según la implementación de [`LangId`](crate::locale::LangId) en este orden: primero intenta + /// con el establecido en [`Contextual::with_langid()`](crate::html::Contextual::with_langid); + /// pero si no se ha definido explícitamente, usará el indicado en la cabecera `Accept-Language` + /// del navegador; y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html/assets.rs b/src/html/assets.rs index e53e8e35..ee5431f0 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -5,22 +5,49 @@ pub mod stylesheet; use crate::html::{html, Markup, Render}; use crate::{AutoDefault, Weight}; +/// Representación genérica de un *script* [`JavaScript`](crate::html::JavaScript) o una hoja de +/// estilos [`StyleSheet`](crate::html::StyleSheet). +/// +/// Estos recursos se incluyen en los conjuntos de recursos ([`Assets`]) que suelen renderizarse en +/// un documento HTML. +/// +/// Cada recurso se identifica por un **nombre único** ([`Asset::name()`]), usado como clave; y un +/// **peso** ([`Asset::weight()`]), que determina su orden relativo de renderizado. pub trait Asset: Render { /// Devuelve el nombre del recurso, utilizado como clave única. fn name(&self) -> &str; - /// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. + /// Devuelve el peso del recurso, usado para ordenar el renderizado de menor a mayor peso. fn weight(&self) -> Weight; } +/// Gestión común para conjuntos de recursos como [`JavaScript`](crate::html::JavaScript) y +/// [`StyleSheet`](crate::html::StyleSheet). +/// +/// Se emplea normalmente para agrupar, administrar y renderizar los recursos de un documento HTML. +/// Cada recurso se identifica por un nombre único ([`Asset::name()`]) y tiene asociado un peso +/// ([`Asset::weight()`]) que determina su orden de renderizado. +/// +/// Durante el renderizado, los recursos se procesan en orden ascendente de peso. En caso de +/// igualdad, se respeta el orden de inserción. #[derive(AutoDefault)] pub struct Assets(Vec); impl Assets { + /// Crea un nuevo conjunto vacío de recursos. + /// + /// Normalmente no se instancia directamente, sino como parte de la gestión de recursos que + /// hacen páginas o temas. pub fn new() -> Self { Self(Vec::new()) } + /// Inserta un recurso. + /// + /// Si no existe otro con el mismo nombre, lo añade. Si ya existe y su peso era mayor, lo + /// reemplaza. Y si su peso era menor o igual, entonces no realiza ningún cambio. + /// + /// Devuelve `true` si el recurso fue insertado o reemplazado. pub fn add(&mut self, asset: T) -> bool { match self.0.iter().position(|x| x.name() == asset.name()) { Some(index) => { @@ -39,6 +66,9 @@ impl Assets { } } + /// Elimina un recurso por nombre. + /// + /// Devuelve `true` si el recurso existía y fue eliminado. pub fn remove(&mut self, name: impl AsRef) -> bool { if let Some(index) = self.0.iter().position(|x| x.name() == name.as_ref()) { self.0.remove(index); diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 89b5261c..be6f9060 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -138,7 +138,9 @@ impl JavaScript { } impl Asset for JavaScript { - // Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador. + /// Devuelve el nombre del recurso, utilizado como clave única. + /// + /// Para *scripts* externos es la ruta del recurso; para *scripts* embebidos, un identificador. fn name(&self) -> &str { match &self.source { Source::From(path) => path, diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index a5537264..38a97d7f 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -143,7 +143,9 @@ impl StyleSheet { } impl Asset for StyleSheet { - // Para hojas de estilos externas es la ruta; para las embebidas, un identificador. + /// Devuelve el nombre del recurso, utilizado como clave única. + /// + /// Para hojas de estilos externas es la ruta del recurso; para las embebidas, un identificador. fn name(&self) -> &str { match &self.source { Source::From(path) => path, diff --git a/src/html/context.rs b/src/html/context.rs index 7af884cf..79148b06 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -47,22 +47,52 @@ pub enum ErrorParam { }, } +/// Interfaz para gestionar el **contexto de renderizado** de un documento HTML. +/// +/// `Contextual` extiende [`LangId`] y define los métodos para: +/// +/// - Establecer el **idioma** del documento. +/// - Almacenar la **solicitud HTTP** de origen. +/// - Seleccionar **tema** y **composición** (*layout*) de renderizado. +/// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo +/// [`StyleSheet`] o los *scripts* [`JavaScript`] mediante [`AssetsOp`]. +/// - Leer y mantener **parámetros dinámicos tipados** de contexto. +/// - Generar **identificadores únicos** por tipo de componente. +/// +/// Lo implementan, típicamente, estructuras que representan el contexto de renderizado, como +/// [`Context`](crate::html::Context) o [`Page`](crate::response::page::Page). +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// fn prepare_context(cx: C) -> C { +/// cx.with_langid(&LangMatch::resolve("es-ES")) +/// .with_theme("aliner") +/// .with_layout("default") +/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) +/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) +/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js"))) +/// .with_param("usuario_id", 42_i32) +/// } +/// ``` pub trait Contextual: LangId { // Contextual BUILDER ************************************************************************** - /// Asigna la fuente de idioma del documento. + /// Establece el idioma del documento. #[builder_fn] fn with_langid(self, language: &impl LangId) -> Self; - /// Asigna la solicitud HTTP al contexto. + /// Almacena la solicitud HTTP de origen en el contexto. #[builder_fn] fn with_request(self, request: Option) -> Self; - /// Asigna el tema para renderizar el documento. + /// Especifica el tema para renderizar el documento. #[builder_fn] fn with_theme(self, theme_name: &'static str) -> Self; - /// Asigna la composición para renderizar el documento. + /// Especifica la composición para renderizar el documento. #[builder_fn] fn with_layout(self, layout_name: &'static str) -> Self; @@ -85,7 +115,7 @@ pub trait Contextual: LangId { /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. fn layout(&self) -> &str; - /// Recupera un parámetro como [`Option`], simplificando el acceso. + /// Recupera un parámetro como [`Option`]. fn param(&self, key: &'static str) -> Option<&T>; /// Devuelve el Favicon de los recursos del contexto. @@ -94,22 +124,24 @@ pub trait Contextual: LangId { /// Devuelve las hojas de estilo de los recursos del contexto. fn stylesheets(&self) -> &Assets; - /// Devuelve los scripts JavaScript de los recursos del contexto. + /// Devuelve los *scripts* JavaScript de los recursos del contexto. fn javascripts(&self) -> &Assets; // Contextual HELPERS ************************************************************************** - /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona - /// un `id` explícito. + /// Genera un identificador único por tipo (`-`) cuando no se aporta uno explícito. + /// + /// Es útil para componentes u otros elementos HTML que necesitan un identificador predecible si + /// no se proporciona ninguno. fn required_id(&mut self, id: Option) -> String; } -/// Implementa el contexto de un documento HTML. +/// Implementa un **contexto de renderizado** para un documento HTML. /// -/// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de -/// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]), -/// hojas de estilo ([`StyleSheet`]) y *scripts* ([`JavaScript`]), así como *parámetros dinámicos -/// heterogéneos* de contexto definidos en tiempo de ejecución. +/// Extiende [`Contextual`] con métodos para **instanciar** y configurar un nuevo contexto, +/// **renderizar los recursos** del documento (incluyendo el [`Favicon`], las hojas de estilo +/// [`StyleSheet`] y los *scripts* [`JavaScript`]), o extender el uso de **parámetros dinámicos +/// tipados** con nuevos métodos. /// /// # Ejemplos /// From 50ba58ed74c4802ec170b0a276aebeab403eedd2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 8 Sep 2025 18:57:09 +0200 Subject: [PATCH 114/224] =?UTF-8?q?=E2=9C=A8=20(context):=20A=C3=B1ade=20m?= =?UTF-8?q?=C3=A9todos=20auxiliares=20de=20par=C3=A1metros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/context.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/html/context.rs b/src/html/context.rs index 79148b06..26e2478c 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -118,6 +118,21 @@ pub trait Contextual: LangId { /// Recupera un parámetro como [`Option`]. fn param(&self, key: &'static str) -> Option<&T>; + /// Devuelve el parámetro clonado o el **valor por defecto del tipo** (`T::default()`). + fn param_or_default(&self, key: &'static str) -> T { + self.param::(key).cloned().unwrap_or_default() + } + + /// Devuelve el parámetro clonado o un **valor por defecto** si no existe. + fn param_or(&self, key: &'static str, default: T) -> T { + self.param::(key).cloned().unwrap_or(default) + } + + /// Devuelve el parámetro clonado o el **valor evaluado** por la función `f` si no existe. + fn param_or_else T>(&self, key: &'static str, f: F) -> T { + self.param::(key).cloned().unwrap_or_else(f) + } + /// Devuelve el Favicon de los recursos del contexto. fn favicon(&self) -> Option<&Favicon>; From 415e524f69a82f78f6d0c26fc343b4231b0d09e5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 8 Sep 2025 23:27:56 +0200 Subject: [PATCH 115/224] =?UTF-8?q?=F0=9F=8E=A8=20Generaliza=20p=C3=A1gina?= =?UTF-8?q?=20de=20bienvenida=20con=20par=C3=A1metros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/extension/welcome.rs | 4 +++- src/base/theme/basic.rs | 25 +++++++++++++++---------- src/response/page.rs | 11 ++++++----- src/response/page/error.rs | 4 ++-- static/css/intro.css | 23 +++++++++++++++++++++-- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 3c0de3db..eaa9f339 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -24,11 +24,13 @@ impl Extension for Welcome { async fn homepage(request: HttpRequest) -> ResultPage { let app = &global::SETTINGS.app.name; - Page::new(Some(request)) + Page::new(request) .with_theme("basic") .with_layout("intro") .with_title(L10n::l("welcome_title")) .with_description(L10n::l("welcome_intro").with_arg("app", app)) + .with_param("intro_button_text", L10n::l("welcome_powered")) + .with_param("intro_button_link", "https://pagetop.cillero.es".to_owned()) .add_component(Html::with(|cx| { html! { p { (L10n::l("welcome_text1").using(cx)) } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index dc16f2ac..fbf4caf5 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -41,6 +41,9 @@ fn render_intro(page: &mut Page) -> Markup { let title = page.title().unwrap_or_default(); let intro = page.description().unwrap_or_default(); + let intro_button_text: L10n = page.param_or_default("intro_button_text"); + let intro_button_link: Option<&String> = page.param("intro_button_link"); + html! { body id=[page.body_id().get()] class=[page.body_classes().get()] { header class="intro-header" { @@ -71,16 +74,18 @@ fn render_intro(page: &mut Page) -> Markup { } main class="intro-content" { section class="intro-content__body" { - div class="intro-button" { - a - class="intro-button__link" - href="https://pagetop.cillero.es" - target="_blank" - rel="noreferrer" - { - span {} span {} span {} - div class="intro-button__text" { - (L10n::l("welcome_powered").using(page)) + @if intro_button_link.is_some() { + div class="intro-button" { + a + class="intro-button__link" + href=[intro_button_link] + target="_blank" + rel="noreferrer" + { + span {} span {} span {} + div class="intro-button__text" { + (intro_button_text.using(page)) + } } } } diff --git a/src/response/page.rs b/src/response/page.rs index 77bc9c47..86a0bdcc 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -4,7 +4,6 @@ pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; @@ -14,6 +13,7 @@ use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; +use crate::{builder_fn, AutoDefault}; /// Representa una página HTML completa lista para renderizar. /// @@ -21,6 +21,7 @@ use crate::service::HttpRequest; /// regiones donde disponer los componentes, atributos de `` y otros aspectos del contexto de /// renderizado. #[rustfmt::skip] +#[derive(AutoDefault)] pub struct Page { title : AttrL10n, description : AttrL10n, @@ -35,10 +36,10 @@ pub struct Page { impl Page { /// Crea una nueva instancia de página. /// - /// Si se proporciona la solicitud HTTP, se guardará en el contexto de renderizado de la página - /// para poder ser recuperada por los componentes si es necesario. + /// La solicitud HTTP se guardará en el contexto de renderizado de la página para poder ser + /// recuperada por los componentes si es necesario. #[rustfmt::skip] - pub fn new(request: Option) -> Self { + pub fn new(request: HttpRequest) -> Self { Page { title : AttrL10n::default(), description : AttrL10n::default(), @@ -46,7 +47,7 @@ impl Page { properties : Vec::default(), body_id : AttrId::default(), body_classes: AttrClasses::default(), - context : Context::new(request), + context : Context::new(Some(request)), regions : ChildrenInRegions::default(), } } diff --git a/src/response/page/error.rs b/src/response/page/error.rs index be48e3ed..2355d234 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -29,7 +29,7 @@ impl Display for ErrorPage { ErrorPage::BadRequest(_) => write!(f, "Bad Client Data"), // Error 403. ErrorPage::AccessDenied(request) => { - let mut error_page = Page::new(Some(request.clone())); + let mut error_page = Page::new(request.clone()); let error403 = error_page.theme().error403(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) @@ -44,7 +44,7 @@ impl Display for ErrorPage { } // Error 404. ErrorPage::NotFound(request) => { - let mut error_page = Page::new(Some(request.clone())); + let mut error_page = Page::new(request.clone()); let error404 = error_page.theme().error404(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) diff --git a/static/css/intro.css b/static/css/intro.css index 5a5461e4..19fa9f15 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -5,9 +5,13 @@ --bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--bg-img-sm) type('image/jpeg')); --bg-color: #8c5919; --color: #1a202c; - --color-red: #fecaca; --color-gray: #e4e4e7; --color-link: #1e4eae; + --color-block-1: #fecaca; + --color-block-2: #e6a9e2; + --color-block-3: #b689ff; + --color-block-4: #ffedca; + --color-block-5: #ffffff; --focus-outline: 2px solid var(--color-link); --focus-outline-offset: 2px; --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); @@ -413,9 +417,24 @@ a:hover:visited { left: -15%; width: 130%; z-index: -10; - background: var(--color-red); + background: var(--color-block-1); transform: rotate(2deg); } +.intro-text .block:nth-of-type(5n+1) .block__title:after { + background: var(--color-block-1); +} +.intro-text .block:nth-of-type(5n+2) .block__title:after { + background: var(--color-block-2); +} +.intro-text .block:nth-of-type(5n+3) .block__title:after { + background: var(--color-block-3); +} +.intro-text .block:nth-of-type(5n+4) .block__title:after { + background: var(--color-block-4); +} +.intro-text .block:nth-of-type(5n+5) .block__title:after { + background: var(--color-block-5); +} /* * Footer From b16c9378d05626f4222b4c3c28c72bd7b4fc66e6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 9 Sep 2025 01:01:18 +0200 Subject: [PATCH 116/224] =?UTF-8?q?=F0=9F=A9=B9=20Corrige=20doc=20y=20c?= =?UTF-8?q?=C3=B3digo=20por=20cambios=20en=20Page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- examples/hello-name.rs | 2 +- examples/hello-world.rs | 2 +- src/lib.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9d5efc88..c6c12e0e 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ impl Extension for HelloWorld { } async fn hello_world(request: HttpRequest) -> ResultPage { - Page::new(Some(request)) - .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + Page::new(request) + .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) .render() } diff --git a/examples/hello-name.rs b/examples/hello-name.rs index 3a491a68..e1285d09 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -13,7 +13,7 @@ async fn hello_name( path: service::web::Path, ) -> ResultPage { let name = path.into_inner(); - Page::new(Some(request)) + Page::new(request) .add_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) .render() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 5550514b..d56f2105 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -9,7 +9,7 @@ impl Extension for HelloWorld { } async fn hello_world(request: HttpRequest) -> ResultPage { - Page::new(Some(request)) + Page::new(request) .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) .render() } diff --git a/src/lib.rs b/src/lib.rs index e43da2f2..93b8564b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,8 +60,8 @@ impl Extension for HelloWorld { } async fn hello_world(request: HttpRequest) -> ResultPage { - Page::new(Some(request)) - .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + Page::new(request) + .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) .render() } From 7179cf08314d36583a4fc9c140fb25aaa56c43b3 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 11 Sep 2025 19:03:34 +0200 Subject: [PATCH 117/224] =?UTF-8?q?=F0=9F=8E=A8=20Unifica=20conversiones?= =?UTF-8?q?=20a=20String=20con=20`to=5Fstring()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Como `String::from()` y `.to_string()` son equivalentes, se sustituyen todas las ocurrencias de `String::from()` por `to_string()` para mayor coherencia y legibilidad. --- src/app.rs | 2 +- src/base/component/html.rs | 2 +- src/core/theme/regions.rs | 2 +- src/html.rs | 4 ++-- src/html/assets/favicon.rs | 2 +- src/html/attr_classes.rs | 2 +- src/html/attr_l10n.rs | 4 ++-- src/html/attr_value.rs | 2 +- src/html/context.rs | 10 +++++----- src/locale.rs | 4 ++-- src/util.rs | 22 +++++++++++----------- tests/component_html.rs | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/app.rs b/src/app.rs index 94d901f1..c8ffba11 100644 --- a/src/app.rs +++ b/src/app.rs @@ -84,7 +84,7 @@ impl Application { if let Some((Width(term_width), _)) = terminal_size() { if term_width >= 80 { let maxlen: usize = ((term_width / 10) - 2).into(); - let mut app = app_name.substring(0, maxlen).to_owned(); + let mut app = app_name.substring(0, maxlen).to_string(); if app_name.len() > maxlen { app = format!("{app}..."); } diff --git a/src/base/component/html.rs b/src/base/component/html.rs index 7bde94a0..8fa56902 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -25,7 +25,7 @@ use crate::prelude::*; /// use pagetop::prelude::*; /// /// let component = Html::with(|cx| { -/// let user = cx.param::("username").cloned().unwrap_or(String::from("visitor")); +/// let user = cx.param::("username").cloned().unwrap_or("visitor".to_string()); /// html! { /// h1 { "Hello, " (user) } /// } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 1a2e0fbb..8082aac9 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -36,7 +36,7 @@ impl Default for Region { fn default() -> Self { Self { key: REGION_CONTENT, - name: String::from(REGION_CONTENT), + name: REGION_CONTENT.to_string(), } } } diff --git a/src/html.rs b/src/html.rs index 9f3d70cf..37ab3f41 100644 --- a/src/html.rs +++ b/src/html.rs @@ -71,11 +71,11 @@ pub type OptionComponent = core::component::Typed /// use pagetop::prelude::*; /// /// // Texto normal, se escapa automáticamente para evitar inyección de HTML. -/// let fragment = PrepareMarkup::Escaped(String::from("Hola mundo")); +/// let fragment = PrepareMarkup::Escaped("Hola mundo".to_string()); /// assert_eq!(fragment.render().into_string(), "Hola <b>mundo</b>"); /// /// // HTML literal, se inserta directamente, sin escapado adicional. -/// let raw_html = PrepareMarkup::Raw(String::from("negrita")); +/// let raw_html = PrepareMarkup::Raw("negrita".to_string()); /// assert_eq!(raw_html.render().into_string(), "negrita"); /// /// // Fragmento ya preparado con la macro `html!`. diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 1a8b29e7..e951df58 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -129,7 +129,7 @@ impl Favicon { icon_color: Option, ) -> Self { let icon_type = match icon_source.rfind('.') { - Some(i) => match icon_source[i..].to_owned().to_lowercase().as_str() { + Some(i) => match icon_source[i..].to_string().to_lowercase().as_str() { ".avif" => Some("image/avif"), ".gif" => Some("image/gif"), ".ico" => Some("image/x-icon"), diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 91ccfaf0..098c26cd 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -37,7 +37,7 @@ pub enum ClassesOp { /// .with_value(ClassesOp::Add, "Active") /// .with_value(ClassesOp::Remove, "btn-primary"); /// -/// assert_eq!(classes.get(), Some(String::from("btn active"))); +/// assert_eq!(classes.get(), Some("btn active".to_string())); /// assert!(classes.contains("active")); /// ``` #[derive(AutoDefault, Clone, Debug)] diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 3e8a4e4b..8250c742 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -17,13 +17,13 @@ use crate::{builder_fn, AutoDefault}; /// // Español disponible. /// assert_eq!( /// hello.lookup(&LangMatch::resolve("es-ES")), -/// Some(String::from("¡Hola mundo!")) +/// Some("¡Hola mundo!".to_string()) /// ); /// /// // Japonés no disponible, traduce al idioma de respaldo ("en-US"). /// assert_eq!( /// hello.lookup(&LangMatch::resolve("ja-JP")), -/// Some(String::from("Hello world!")) +/// Some("Hello world!".to_string()) /// ); /// /// // Uso típico en un atributo: diff --git a/src/html/attr_value.rs b/src/html/attr_value.rs index c70229f9..4e03120b 100644 --- a/src/html/attr_value.rs +++ b/src/html/attr_value.rs @@ -36,7 +36,7 @@ impl AttrValue { self.0 = if value.is_empty() { None } else { - Some(value.to_owned()) + Some(value.to_string()) }; self } diff --git a/src/html/context.rs b/src/html/context.rs index 26e2478c..8ef3a053 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -285,7 +285,7 @@ impl Context { /// /// let cx = Context::new(None) /// .with_param("usuario_id", 42_i32) - /// .with_param("titulo", String::from("Hola")); + /// .with_param("titulo", "Hola".to_string()); /// /// let id: &i32 = cx.get_param("usuario_id").unwrap(); /// let titulo: &String = cx.get_param("titulo").unwrap(); @@ -318,7 +318,7 @@ impl Context { /// /// let mut cx = Context::new(None) /// .with_param("contador", 7_i32) - /// .with_param("titulo", String::from("Hola")); + /// .with_param("titulo", "Hola".to_string()); /// /// let n: i32 = cx.take_param("contador").unwrap(); /// assert!(cx.get_param::("contador").is_err()); // ya no está @@ -416,7 +416,7 @@ impl Contextual for Context { /// /// let cx = Context::new(None) /// .with_param("usuario_id", 42_i32) - /// .with_param("titulo", String::from("Hola")) + /// .with_param("titulo", "Hola".to_string()) /// .with_param("flags", vec!["a", "b"]); /// ``` #[builder_fn] @@ -484,7 +484,7 @@ impl Contextual for Context { /// ```rust /// use pagetop::prelude::*; /// - /// let cx = Context::new(None).with_param("username", String::from("Alice")); + /// let cx = Context::new(None).with_param("username", "Alice".to_string()); /// /// // Devuelve Some(&String) si existe y coincide el tipo. /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); @@ -533,7 +533,7 @@ impl Contextual for Context { .replace(' ', "_") .to_lowercase(); let prefix = if prefix.is_empty() { - "prefix".to_owned() + "prefix".to_string() } else { prefix }; diff --git a/src/locale.rs b/src/locale.rs index cf44dd82..2bf0da90 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -165,7 +165,7 @@ pub trait LangId { /// /// // Idioma no soportado. /// let lang = LangMatch::resolve("ja-JP"); -/// assert_eq!(lang, LangMatch::Unsupported(String::from("ja-JP"))); +/// assert_eq!(lang, LangMatch::Unsupported("ja-JP".to_string())); /// ``` /// /// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque @@ -222,7 +222,7 @@ impl LangMatch { } // En caso contrario, indica que el idioma no está soportado. - Self::Unsupported(String::from(language)) + Self::Unsupported(language.to_string()) } /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. diff --git a/src/util.rs b/src/util.rs index 808014b2..56b098d8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -110,15 +110,15 @@ macro_rules! hm { /// /// // Concatena todos los fragmentos directamente. /// let result = join!("Hello", " ", "World"); -/// assert_eq!(result, String::from("Hello World")); +/// assert_eq!(result, "Hello World".to_string()); /// /// // También funciona con valores vacíos. /// let result_with_empty = join!("Hello", "", "World"); -/// assert_eq!(result_with_empty, String::from("HelloWorld")); +/// assert_eq!(result_with_empty, "HelloWorld".to_string()); /// /// // Un único fragmento devuelve el mismo valor. /// let single_result = join!("Hello"); -/// assert_eq!(single_result, String::from("Hello")); +/// assert_eq!(single_result, "Hello".to_string()); /// ``` #[macro_export] macro_rules! join { @@ -141,11 +141,11 @@ macro_rules! join { /// /// // Concatena los fragmentos no vacíos con un espacio como separador. /// let result_with_separator = join_opt!(["Hello", "", "World"]; " "); -/// assert_eq!(result_with_separator, Some(String::from("Hello World"))); +/// assert_eq!(result_with_separator, Some("Hello World".to_string())); /// /// // Concatena los fragmentos no vacíos sin un separador. /// let result_without_separator = join_opt!(["Hello", "", "World"]); -/// assert_eq!(result_without_separator, Some(String::from("HelloWorld"))); +/// assert_eq!(result_without_separator, Some("HelloWorld".to_string())); /// /// // Devuelve `None` si todos los fragmentos están vacíos. /// let result_empty = join_opt!(["", "", ""]); @@ -185,19 +185,19 @@ macro_rules! join_opt { /// /// // Concatena los dos fragmentos cuando ambos no están vacíos. /// let result = join_pair!(first, separator, second); -/// assert_eq!(result, String::from("Hello-World")); +/// assert_eq!(result, "Hello-World".to_string()); /// /// // Si el primer fragmento está vacío, devuelve el segundo. /// let result_empty_first = join_pair!("", separator, second); -/// assert_eq!(result_empty_first, String::from("World")); +/// assert_eq!(result_empty_first, "World".to_string()); /// /// // Si el segundo fragmento está vacío, devuelve el primero. /// let result_empty_second = join_pair!(first, separator, ""); -/// assert_eq!(result_empty_second, String::from("Hello")); +/// assert_eq!(result_empty_second, "Hello".to_string()); /// /// // Si ambos fragmentos están vacíos, devuelve una cadena vacía. /// let result_both_empty = join_pair!("", separator, ""); -/// assert_eq!(result_both_empty, String::from("")); +/// assert_eq!(result_both_empty, "".to_string()); /// ``` #[macro_export] macro_rules! join_pair { @@ -224,11 +224,11 @@ macro_rules! join_pair { /// /// // Concatena los fragmentos. /// let result = join_strict!(["Hello", "World"]); -/// assert_eq!(result, Some(String::from("HelloWorld"))); +/// assert_eq!(result, Some("HelloWorld".to_string())); /// /// // Concatena los fragmentos con un separador. /// let result_with_separator = join_strict!(["Hello", "World"]; " "); -/// assert_eq!(result_with_separator, Some(String::from("Hello World"))); +/// assert_eq!(result_with_separator, Some("Hello World".to_string())); /// /// // Devuelve `None` si alguno de los fragmentos está vacío. /// let result_with_empty = join_strict!(["Hello", "", "World"]); diff --git a/tests/component_html.rs b/tests/component_html.rs index bd7f3c08..851315a9 100644 --- a/tests/component_html.rs +++ b/tests/component_html.rs @@ -17,7 +17,7 @@ async fn component_html_renders_static_markup() { #[pagetop::test] async fn component_html_renders_using_context_param() { - let mut cx = Context::new(None).with_param("username", String::from("Alice")); + let mut cx = Context::new(None).with_param("username", "Alice".to_string()); let component = Html::with(|cx| { let name = cx.param::("username").cloned().unwrap_or_default(); From a96a3fdf9f933ddaddbc90146fbb1fcfe10c49e8 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 11 Sep 2025 19:58:50 +0200 Subject: [PATCH 118/224] =?UTF-8?q?=F0=9F=94=A5=20Elimina=20`Render`=20par?= =?UTF-8?q?a=20usar=20siempre=20el=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component/definition.rs | 2 +- src/core/theme.rs | 2 +- src/html.rs | 6 +- src/html/assets.rs | 16 ++-- src/html/assets/favicon.rs | 10 ++- src/html/assets/javascript.rs | 144 +++++++++++++++++++++---------- src/html/assets/stylesheet.rs | 34 ++++---- src/html/context.rs | 39 ++++++--- src/html/maud.rs | 21 +---- src/response/page.rs | 2 +- tests/component_poweredby.rs | 31 ++++--- tests/html.rs | 44 +++++----- 12 files changed, 205 insertions(+), 146 deletions(-) diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index d547c4b3..333cf694 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -1,6 +1,6 @@ use crate::base::action; use crate::core::{AnyInfo, TypeInfo}; -use crate::html::{html, Context, Markup, PrepareMarkup, Render}; +use crate::html::{html, Context, Markup, PrepareMarkup}; /// Define la función de renderizado para todos los componentes. /// diff --git a/src/core/theme.rs b/src/core/theme.rs index 5889dcf7..61d820b4 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -7,7 +7,7 @@ //! Un tema **declara las regiones** (*cabecera*, *barra lateral*, *pie*, etc.) que estarán //! disponibles para colocar contenido. Los temas son responsables últimos de los estilos, //! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (como animaciones, -//! *scripts* de interfaz, etc.). +//! scripts de interfaz, etc.). //! //! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension); por //! lo que se instancian, declaran sus dependencias y se inician igual que el resto de extensiones; diff --git a/src/html.rs b/src/html.rs index 37ab3f41..4858bbfa 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,7 +1,7 @@ //! HTML en código. mod maud; -pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, Render, DOCTYPE}; +pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; // HTML DOCUMENT ASSETS **************************************************************************** @@ -119,11 +119,9 @@ impl PrepareMarkup { PrepareMarkup::With(markup) => markup.is_empty(), } } -} -impl Render for PrepareMarkup { /// Integra el renderizado fácilmente en la macro [`html!`]. - fn render(&self) -> Markup { + pub fn render(&self) -> Markup { match self { PrepareMarkup::None => html! {}, PrepareMarkup::Escaped(text) => html! { (text) }, diff --git a/src/html/assets.rs b/src/html/assets.rs index ee5431f0..41cd4710 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -2,10 +2,10 @@ pub mod favicon; pub mod javascript; pub mod stylesheet; -use crate::html::{html, Markup, Render}; +use crate::html::{html, Context, Markup}; use crate::{AutoDefault, Weight}; -/// Representación genérica de un *script* [`JavaScript`](crate::html::JavaScript) o una hoja de +/// Representación genérica de un script [`JavaScript`](crate::html::JavaScript) o una hoja de /// estilos [`StyleSheet`](crate::html::StyleSheet). /// /// Estos recursos se incluyen en los conjuntos de recursos ([`Assets`]) que suelen renderizarse en @@ -13,12 +13,15 @@ use crate::{AutoDefault, Weight}; /// /// Cada recurso se identifica por un **nombre único** ([`Asset::name()`]), usado como clave; y un /// **peso** ([`Asset::weight()`]), que determina su orden relativo de renderizado. -pub trait Asset: Render { +pub trait Asset { /// Devuelve el nombre del recurso, utilizado como clave única. fn name(&self) -> &str; /// Devuelve el peso del recurso, usado para ordenar el renderizado de menor a mayor peso. fn weight(&self) -> Weight; + + /// Renderiza el recurso en el contexto proporcionado. + fn render(&self, cx: &mut Context) -> Markup; } /// Gestión común para conjuntos de recursos como [`JavaScript`](crate::html::JavaScript) y @@ -77,16 +80,13 @@ impl Assets { false } } -} -impl Render for Assets { - fn render(&self) -> Markup { + pub fn render(&self, cx: &mut Context) -> Markup { let mut assets = self.0.iter().collect::>(); assets.sort_by_key(|a| a.weight()); - html! { @for a in assets { - (a) + (a.render(cx)) } } } diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index e951df58..d731b8f5 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -1,4 +1,4 @@ -use crate::html::{html, Markup, Render}; +use crate::html::{html, Context, Markup}; use crate::AutoDefault; /// Un **Favicon** es un recurso gráfico que usa el navegador como icono asociado al sitio. @@ -151,10 +151,12 @@ impl Favicon { }); self } -} -impl Render for Favicon { - fn render(&self) -> Markup { + /// Renderiza el **Favicon** completo con todas las etiquetas declaradas. + /// + /// El parámetro `Context` se acepta por coherencia con el resto de *assets*, aunque en este + /// caso es ignorado. + pub fn render(&self, _cx: &mut Context) -> Markup { html! { @for item in &self.0 { (item) diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index be6f9060..a8ed3e8c 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,35 +1,45 @@ use crate::html::assets::Asset; -use crate::html::{html, Markup, Render}; +use crate::html::{html, Context, Markup, PreEscaped}; use crate::{join, join_pair, AutoDefault, Weight}; // Define el origen del recurso JavaScript y cómo debe cargarse en el navegador. // // Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del -// script. +// script en relación con el análisis del documento HTML y la ejecución del resto de scripts. // -// - [`From`] – Carga el script de forma estándar con la etiqueta ``. El parámetro `name` se usa como identificador interno del - /// *script*. - pub fn inline(name: impl Into, script: impl Into) -> Self { + /// script. + /// + /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + pub fn inline(name: impl Into, f: F) -> Self + where + F: Fn(&mut Context) -> String + Send + Sync + 'static, + { JavaScript { - source: Source::Inline(name.into(), script.into()), + source: Source::Inline(name.into(), Box::new(f)), ..Default::default() } } - /// Crea un **script embebido** que se ejecuta automáticamente al terminar de cargarse el - /// documento HTML. + /// Crea un **script embebido** que se ejecuta cuando **el DOM está listo**. /// - /// El código se envuelve automáticamente en un `addEventListener('DOMContentLoaded', ...)`. El - /// parámetro `name` se usa como identificador interno del *script*. - pub fn on_load(name: impl Into, script: impl Into) -> Self { + /// El código se envuelve en un `addEventListener('DOMContentLoaded',function(){...})` que lo + /// ejecuta tras analizar el documento HTML, **no** espera imágenes ni otros recursos externos. + /// Útil para inicializaciones que no dependen de `await`. El parámetro `name` se usa como + /// identificador interno del script. + /// + /// Los scripts con `defer` se ejecutan antes de `DOMContentLoaded`. + /// + /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + pub fn on_load(name: impl Into, f: F) -> Self + where + F: Fn(&mut Context) -> String + Send + Sync + 'static, + { JavaScript { - source: Source::OnLoad(name.into(), script.into()), + source: Source::OnLoad(name.into(), Box::new(f)), + ..Default::default() + } + } + + /// Crea un **script embebido** con un **manejador asíncrono**. + /// + /// El código se envuelve en un `addEventListener('DOMContentLoaded',async()=>{...})`, que + /// emplea una función `async` para que el cuerpo devuelto por la función *closure* pueda usar + /// `await`. Ideal para hidratar la interfaz, cargar módulos dinámicos o realizar lecturas + /// iniciales. + /// + /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + pub fn on_load_async(name: impl Into, f: F) -> Self + where + F: Fn(&mut Context) -> String + Send + Sync + 'static, + { + JavaScript { + source: Source::OnLoadAsync(name.into(), Box::new(f)), ..Default::default() } } // JavaScript BUILDER ************************************************************************** - /// Asocia una versión al recurso (usada para control de la caché del navegador). + /// Asocia una **versión** al recurso (usada para control de la caché del navegador). /// - /// Si `version` está vacío, no se añade ningún parámetro a la URL. + /// Si `version` está vacío, **no** se añade ningún parámetro a la URL. pub fn with_version(mut self, version: impl Into) -> Self { self.version = version.into(); self } - /// Modifica el peso del recurso. + /// Modifica el **peso** del recurso. /// /// Los recursos se renderizan de menor a mayor peso. Por defecto es `0`, que respeta el orden /// de creación. @@ -140,7 +194,7 @@ impl JavaScript { impl Asset for JavaScript { /// Devuelve el nombre del recurso, utilizado como clave única. /// - /// Para *scripts* externos es la ruta del recurso; para *scripts* embebidos, un identificador. + /// Para scripts externos es la ruta del recurso; para scripts embebidos, un identificador. fn name(&self) -> &str { match &self.source { Source::From(path) => path, @@ -148,16 +202,15 @@ impl Asset for JavaScript { Source::Async(path) => path, Source::Inline(name, _) => name, Source::OnLoad(name, _) => name, + Source::OnLoadAsync(name, _) => name, } } fn weight(&self) -> Weight { self.weight } -} -impl Render for JavaScript { - fn render(&self) -> Markup { + fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { script src=(join_pair!(path, "?v=", self.version.as_str())) {}; @@ -168,12 +221,15 @@ impl Render for JavaScript { Source::Async(path) => html! { script src=(join_pair!(path, "?v=", self.version.as_str())) async {}; }, - Source::Inline(_, code) => html! { - script { (code) }; + Source::Inline(_, f) => html! { + script { (PreEscaped((f)(cx))) }; }, - Source::OnLoad(_, code) => html! { (join!( - "document.addEventListener('DOMContentLoaded',function(){", code, "});" - )) }, + Source::OnLoad(_, f) => html! { script { (PreEscaped(join!( + "document.addEventListener(\"DOMContentLoaded\",function(){", (f)(cx), "});" + ))) } }, + Source::OnLoadAsync(_, f) => html! { script { (PreEscaped(join!( + "document.addEventListener(\"DOMContentLoaded\",async()=>{", (f)(cx), "});" + ))) } }, } } } diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 38a97d7f..3ecc77fc 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,5 +1,5 @@ use crate::html::assets::Asset; -use crate::html::{html, Markup, PreEscaped, Render}; +use crate::html::{html, Context, Markup, PreEscaped}; use crate::{join_pair, AutoDefault, Weight}; // Define el origen del recurso CSS y cómo se incluye en el documento. @@ -14,7 +14,8 @@ use crate::{join_pair, AutoDefault, Weight}; enum Source { #[default] From(String), - Inline(String, String), + // `name`, `closure(Context) -> String`. + Inline(String, Box String + Send + Sync>), } /// Define el medio objetivo para la hoja de estilos. @@ -34,7 +35,7 @@ pub enum TargetMedia { Speech, } -/// Devuelve el texto asociado al punto de interrupción usado por Bootstrap. +/// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`. #[rustfmt::skip] impl TargetMedia { fn as_str_opt(&self) -> Option<&str> { @@ -69,12 +70,12 @@ impl TargetMedia { /// .with_weight(-10); /// /// // Crea una hoja de estilos embebida en el documento HTML. -/// let embedded = StyleSheet::inline("custom_theme", r#" +/// let embedded = StyleSheet::inline("custom_theme", |_| r#" /// body { /// background-color: #f5f5f5; /// font-family: 'Segoe UI', sans-serif; /// } -/// "#); +/// "#.to_string()); /// ``` #[rustfmt::skip] #[derive(AutoDefault)] @@ -100,9 +101,14 @@ impl StyleSheet { /// /// Equivale a ``. El parámetro `name` se usa como identificador interno del /// recurso. - pub fn inline(name: impl Into, styles: impl Into) -> Self { + /// + /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + pub fn inline(name: impl Into, f: F) -> Self + where + F: Fn(&mut Context) -> String + Send + Sync + 'static, + { StyleSheet { - source: Source::Inline(name.into(), styles.into()), + source: Source::Inline(name.into(), Box::new(f)), ..Default::default() } } @@ -133,9 +139,9 @@ impl StyleSheet { /// Según el argumento `media`: /// /// - `TargetMedia::Default` - Se aplica en todos los casos (medio por defecto). - /// - `TargetMedia::Print` - Se aplican cuando el documento se imprime. - /// - `TargetMedia::Screen` - Se aplican en pantallas. - /// - `TargetMedia::Speech` - Se aplican en dispositivos que convierten el texto a voz. + /// - `TargetMedia::Print` - Se aplica cuando el documento se imprime. + /// - `TargetMedia::Screen` - Se aplica en pantallas. + /// - `TargetMedia::Speech` - Se aplica en dispositivos que convierten el texto a voz. pub fn for_media(mut self, media: TargetMedia) -> Self { self.media = media; self @@ -156,10 +162,8 @@ impl Asset for StyleSheet { fn weight(&self) -> Weight { self.weight } -} -impl Render for StyleSheet { - fn render(&self) -> Markup { + fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { link @@ -167,8 +171,8 @@ impl Render for StyleSheet { href=(join_pair!(path, "?v=", self.version.as_str())) media=[self.media.as_str_opt()]; }, - Source::Inline(_, code) => html! { - style { (PreEscaped(code)) }; + Source::Inline(_, f) => html! { + style { (PreEscaped((f)(cx))) }; }, } } diff --git a/src/html/context.rs b/src/html/context.rs index 8ef3a053..2f3e0f07 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -25,9 +25,9 @@ pub enum AssetsOp { RemoveStyleSheet(&'static str), // JavaScripts. - /// Añade un *script* JavaScript al documento. + /// Añade un script JavaScript al documento. AddJavaScript(JavaScript), - /// Elimina un *script* por su ruta o identificador. + /// Elimina un script por su ruta o identificador. RemoveJavaScript(&'static str), } @@ -55,7 +55,7 @@ pub enum ErrorParam { /// - Almacenar la **solicitud HTTP** de origen. /// - Seleccionar **tema** y **composición** (*layout*) de renderizado. /// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo -/// [`StyleSheet`] o los *scripts* [`JavaScript`] mediante [`AssetsOp`]. +/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`AssetsOp`]. /// - Leer y mantener **parámetros dinámicos tipados** de contexto. /// - Generar **identificadores únicos** por tipo de componente. /// @@ -139,7 +139,7 @@ pub trait Contextual: LangId { /// Devuelve las hojas de estilo de los recursos del contexto. fn stylesheets(&self) -> &Assets; - /// Devuelve los *scripts* JavaScript de los recursos del contexto. + /// Devuelve los scripts JavaScript de los recursos del contexto. fn javascripts(&self) -> &Assets; // Contextual HELPERS ************************************************************************** @@ -155,7 +155,7 @@ pub trait Contextual: LangId { /// /// Extiende [`Contextual`] con métodos para **instanciar** y configurar un nuevo contexto, /// **renderizar los recursos** del documento (incluyendo el [`Favicon`], las hojas de estilo -/// [`StyleSheet`] y los *scripts* [`JavaScript`]), o extender el uso de **parámetros dinámicos +/// [`StyleSheet`] y los scripts [`JavaScript`]), o extender el uso de **parámetros dinámicos /// tipados** con nuevos métodos. /// /// # Ejemplos @@ -258,14 +258,29 @@ impl Context { // Context RENDER ****************************************************************************** /// Renderiza los recursos del contexto. - pub fn render_assets(&self) -> Markup { - html! { - @if let Some(favicon) = &self.favicon { - (favicon) + pub fn render_assets(&mut self) -> Markup { + use std::mem::take as mem_take; + + // Extrae temporalmente los recursos. + let favicon = mem_take(&mut self.favicon); // Deja valor por defecto (None) en self. + let stylesheets = mem_take(&mut self.stylesheets); // Assets::default() en self. + let javascripts = mem_take(&mut self.javascripts); // Assets::default() en self. + + // Renderiza con `&mut self` como contexto. + let markup = html! { + @if let Some(fi) = &favicon { + (fi.render(self)) } - (self.stylesheets) - (self.javascripts) - } + (stylesheets.render(self)) + (javascripts.render(self)) + }; + + // Restaura los campos tal y como estaban. + self.favicon = favicon; + self.stylesheets = stylesheets; + self.javascripts = javascripts; + + markup } // Context PARAMS ****************************************************************************** diff --git a/src/html/maud.rs b/src/html/maud.rs index 9bf179ec..65360360 100644 --- a/src/html/maud.rs +++ b/src/html/maud.rs @@ -69,23 +69,6 @@ impl fmt::Write for Escaper<'_> { /// `.render()` or `.render_to()`. Since the default definitions of /// these methods call each other, not doing this will result in /// infinite recursion. -/// -/// # Example -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// /// Provides a shorthand for linking to a CSS stylesheet. -/// pub struct Stylesheet(&'static str); -/// -/// impl Render for Stylesheet { -/// fn render(&self) -> Markup { -/// html! { -/// link rel="stylesheet" type="text/css" href=(self.0); -/// } -/// } -/// } -/// ``` pub trait Render { /// Renders `self` as a block of `Markup`. fn render(&self) -> Markup { @@ -238,6 +221,10 @@ impl Markup { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } } impl> PreEscaped { diff --git a/src/response/page.rs b/src/response/page.rs index 86a0bdcc..2dc27f9f 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -202,7 +202,7 @@ impl Page { } /// Renderiza los recursos de la página. - pub fn render_assets(&self) -> Markup { + pub fn render_assets(&mut self) -> Markup { self.context.render_assets() } diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs index 9f8e8222..e4551d17 100644 --- a/tests/component_poweredby.rs +++ b/tests/component_poweredby.rs @@ -8,10 +8,10 @@ async fn poweredby_default_shows_only_pagetop_recognition() { let html = render_component(&p); // Debe mostrar el bloque de reconocimiento a PageTop. - assert!(html.contains("poweredby__pagetop")); + assert!(html.as_str().contains("poweredby__pagetop")); // Y NO debe mostrar el bloque de copyright. - assert!(!html.contains("poweredby__copyright")); + assert!(!html.as_str().contains("poweredby__copyright")); } #[pagetop::test] @@ -22,17 +22,20 @@ async fn poweredby_new_includes_current_year_and_app_name() { let html = render_component(&p); let year = Utc::now().format("%Y").to_string(); - assert!(html.contains(&year), "HTML should include the current year"); + assert!( + html.as_str().contains(&year), + "HTML should include the current year" + ); // El nombre de la app proviene de `global::SETTINGS.app.name`. let app_name = &global::SETTINGS.app.name; assert!( - html.contains(app_name), + html.as_str().contains(app_name), "HTML should include the application name" ); // Debe existir el span de copyright. - assert!(html.contains("poweredby__copyright")); + assert!(html.as_str().contains("poweredby__copyright")); } #[pagetop::test] @@ -43,8 +46,8 @@ async fn poweredby_with_copyright_overrides_text() { let p = PoweredBy::default().with_copyright(Some(custom)); let html = render_component(&p); - assert!(html.contains(custom)); - assert!(html.contains("poweredby__copyright")); + assert!(html.as_str().contains(custom)); + assert!(html.as_str().contains("poweredby__copyright")); } #[pagetop::test] @@ -54,9 +57,9 @@ async fn poweredby_with_copyright_none_hides_text() { let p = PoweredBy::new().with_copyright(None::); let html = render_component(&p); - assert!(!html.contains("poweredby__copyright")); + assert!(!html.as_str().contains("poweredby__copyright")); // El reconocimiento a PageTop siempre debe aparecer. - assert!(html.contains("poweredby__pagetop")); + assert!(html.as_str().contains("poweredby__pagetop")); } #[pagetop::test] @@ -67,7 +70,7 @@ async fn poweredby_link_points_to_crates_io() { let html = render_component(&p); assert!( - html.contains("https://pagetop.cillero.es"), + html.as_str().contains("https://pagetop.cillero.es"), "Link should point to pagetop.cillero.es" ); } @@ -89,12 +92,8 @@ async fn poweredby_getter_reflects_internal_state() { // HELPERS ***************************************************************************************** -fn render(x: &impl Render) -> String { - x.render().into_string() -} - -fn render_component(c: &C) -> String { +fn render_component(c: &C) -> Markup { let mut cx = Context::default(); let pm = c.prepare_component(&mut cx); - render(&pm) + pm.render() } diff --git a/tests/html.rs b/tests/html.rs index 1499c709..ae4517bc 100644 --- a/tests/html.rs +++ b/tests/html.rs @@ -2,19 +2,19 @@ use pagetop::prelude::*; #[pagetop::test] async fn prepare_markup_render_none_is_empty_string() { - assert_eq!(render(&PrepareMarkup::None), ""); + assert_eq!(PrepareMarkup::None.render().as_str(), ""); } #[pagetop::test] async fn prepare_markup_render_escaped_escapes_html_and_ampersands() { - let pm = PrepareMarkup::Escaped(String::from("& \" ' ")); - assert_eq!(render(&pm), "<b>& " ' </b>"); + let pm = PrepareMarkup::Escaped("& \" ' ".to_string()); + assert_eq!(pm.render().as_str(), "<b>& " ' </b>"); } #[pagetop::test] async fn prepare_markup_render_raw_is_inserted_verbatim() { - let pm = PrepareMarkup::Raw(String::from("bold")); - assert_eq!(render(&pm), "bold"); + let pm = PrepareMarkup::Raw("bold".to_string()); + assert_eq!(pm.render().as_str(), "bold"); } #[pagetop::test] @@ -24,7 +24,7 @@ async fn prepare_markup_render_with_keeps_structure() { p { "This is a paragraph." } }); assert_eq!( - render(&pm), + pm.render().as_str(), "

Sample title

This is a paragraph.

" ); } @@ -33,7 +33,7 @@ async fn prepare_markup_render_with_keeps_structure() { async fn prepare_markup_does_not_double_escape_when_wrapped_in_html_macro() { // Escaped: dentro de `html!` no debe volver a escaparse. let escaped = PrepareMarkup::Escaped("x".into()); - let wrapped_escaped = html! { div { (escaped) } }; + let wrapped_escaped = html! { div { (escaped.render()) } }; assert_eq!( wrapped_escaped.into_string(), "
<i>x</i>
" @@ -41,12 +41,12 @@ async fn prepare_markup_does_not_double_escape_when_wrapped_in_html_macro() { // Raw: tampoco debe escaparse al integrarlo. let raw = PrepareMarkup::Raw("x".into()); - let wrapped_raw = html! { div { (raw) } }; + let wrapped_raw = html! { div { (raw.render()) } }; assert_eq!(wrapped_raw.into_string(), "
x
"); // With: debe incrustar el Markup tal cual. let with = PrepareMarkup::With(html! { span.title { "ok" } }); - let wrapped_with = html! { div { (with) } }; + let wrapped_with = html! { div { (with.render()) } }; assert_eq!( wrapped_with.into_string(), "
ok
" @@ -57,11 +57,14 @@ async fn prepare_markup_does_not_double_escape_when_wrapped_in_html_macro() { async fn prepare_markup_unicode_is_preserved() { // Texto con acentos y emojis debe conservarse (salvo el escape HTML de signos). let esc = PrepareMarkup::Escaped("Hello, tomorrow coffee ☕ & donuts!".into()); - assert_eq!(render(&esc), "Hello, tomorrow coffee ☕ & donuts!"); + assert_eq!( + esc.render().as_str(), + "Hello, tomorrow coffee ☕ & donuts!" + ); // Raw debe pasar íntegro. let raw = PrepareMarkup::Raw("Title — section © 2025".into()); - assert_eq!(render(&raw), "Title — section © 2025"); + assert_eq!(raw.render().as_str(), "Title — section © 2025"); } #[pagetop::test] @@ -69,11 +72,11 @@ async fn prepare_markup_is_empty_semantics() { assert!(PrepareMarkup::None.is_empty()); assert!(PrepareMarkup::Escaped(String::new()).is_empty()); - assert!(PrepareMarkup::Escaped(String::from("")).is_empty()); - assert!(!PrepareMarkup::Escaped(String::from("x")).is_empty()); + assert!(PrepareMarkup::Escaped("".to_string()).is_empty()); + assert!(!PrepareMarkup::Escaped("x".to_string()).is_empty()); assert!(PrepareMarkup::Raw(String::new()).is_empty()); - assert!(PrepareMarkup::Raw(String::from("")).is_empty()); + assert!(PrepareMarkup::Raw("".to_string()).is_empty()); assert!(!PrepareMarkup::Raw("a".into()).is_empty()); assert!(PrepareMarkup::With(html! {}).is_empty()); @@ -94,17 +97,12 @@ async fn prepare_markup_equivalence_between_render_and_inline_in_html_macro() { ]; for pm in cases { - let rendered = render(&pm); - let in_macro = html! { (pm) }.into_string(); + let rendered = pm.render(); + let in_macro = html! { (rendered) }.into_string(); assert_eq!( - rendered, in_macro, + rendered.as_str(), + in_macro, "The output of Render and (pm) inside html! must match" ); } } - -// HELPERS ***************************************************************************************** - -fn render(x: &impl Render) -> String { - x.render().into_string() -} From cc03a0ea324ae43e31bc486234db5881c904241b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 12 Sep 2025 01:13:17 +0200 Subject: [PATCH 119/224] =?UTF-8?q?=E2=9C=A8=20(util):=20A=C3=B1ade=20`ind?= =?UTF-8?q?oc`=20para=20indentar=20c=C3=B3digo=20bien?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/lib.rs | 2 +- src/util.rs | 128 +++++++++++++++++++++++++++------------------------- 4 files changed, 75 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 944027d8..3053e20c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1307,6 +1307,12 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + [[package]] name = "inout" version = "0.1.4" @@ -1568,6 +1574,7 @@ dependencies = [ "config", "figlet-rs", "fluent-templates", + "indoc", "itoa", "pagetop-build", "pagetop-macros", diff --git a/Cargo.toml b/Cargo.toml index 8ccd69ec..ab7551f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ colored = "3.0.0" concat-string = "1.0.1" config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" +indoc = "2.0.6" itoa = "1.0.15" parking_lot = "0.12.4" paste = { package = "pastey", version = "0.1.0" } diff --git a/src/lib.rs b/src/lib.rs index 93b8564b..1c1ba2c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,7 @@ pub type Weight = i8; // API ********************************************************************************************* -// Funciones y macros útiles. +// Macros y funciones útiles. pub mod util; // Carga las opciones de configuración. pub mod config; diff --git a/src/util.rs b/src/util.rs index 56b098d8..cb101763 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -//! Funciones y macros útiles. +//! Macros y funciones útiles. use crate::trace; @@ -6,67 +6,7 @@ use std::env; use std::io; use std::path::{Path, PathBuf}; -// FUNCIONES ÚTILES ******************************************************************************** - -/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. -/// -/// - Si la ruta es relativa, se resuelve respecto al directorio del proyecto según la variable de -/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de -/// trabajo. -/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos). -/// - Devuelve error si la ruta no existe o no es un directorio. -/// -/// # Ejemplos -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). -/// println!("{:#?}", util::resolve_absolute_dir("documents")); -/// -/// // Ruta absoluta, se normaliza y valida tal cual. -/// println!("{:#?}", util::resolve_absolute_dir("/var/www")); -/// ``` -pub fn resolve_absolute_dir>(path: P) -> io::Result { - let path = path.as_ref(); - - let candidate = if path.is_absolute() { - path.to_path_buf() - } else { - // Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto. - env::var_os("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .or_else(|| env::current_dir().ok()) - .unwrap_or_else(|| PathBuf::from(".")) - .join(path) - }; - - // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. - let absolute_dir = candidate.canonicalize()?; - - // Asegura que realmente es un directorio existente. - if absolute_dir.is_dir() { - Ok(absolute_dir) - } else { - Err({ - let msg = format!("Path \"{}\" is not a directory", absolute_dir.display()); - trace::warn!(msg); - io::Error::new(io::ErrorKind::InvalidInput, msg) - }) - } -} - -/// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar. -#[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")] -pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result -where - P: AsRef, - Q: AsRef, -{ - resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref())) -} - -// MACROS ÚTILES *********************************************************************************** +// MACROS INTEGRADAS ******************************************************************************* #[doc(hidden)] pub use paste::paste; @@ -74,6 +14,10 @@ pub use paste::paste; #[doc(hidden)] pub use concat_string::concat_string; +pub use indoc::{concatdoc, formatdoc, indoc}; + +// MACROS ÚTILES *********************************************************************************** + #[macro_export] /// Macro para construir una colección de pares clave-valor. /// @@ -253,3 +197,63 @@ macro_rules! join_strict { } }}; } + +// FUNCIONES ÚTILES ******************************************************************************** + +/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. +/// +/// - Si la ruta es relativa, se resuelve respecto al directorio del proyecto según la variable de +/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de +/// trabajo. +/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos). +/// - Devuelve error si la ruta no existe o no es un directorio. +/// +/// # Ejemplos +/// +/// ```rust,no_run +/// use pagetop::prelude::*; +/// +/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). +/// println!("{:#?}", util::resolve_absolute_dir("documents")); +/// +/// // Ruta absoluta, se normaliza y valida tal cual. +/// println!("{:#?}", util::resolve_absolute_dir("/var/www")); +/// ``` +pub fn resolve_absolute_dir>(path: P) -> io::Result { + let path = path.as_ref(); + + let candidate = if path.is_absolute() { + path.to_path_buf() + } else { + // Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto. + env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .or_else(|| env::current_dir().ok()) + .unwrap_or_else(|| PathBuf::from(".")) + .join(path) + }; + + // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. + let absolute_dir = candidate.canonicalize()?; + + // Asegura que realmente es un directorio existente. + if absolute_dir.is_dir() { + Ok(absolute_dir) + } else { + Err({ + let msg = format!("Path \"{}\" is not a directory", absolute_dir.display()); + trace::warn!(msg); + io::Error::new(io::ErrorKind::InvalidInput, msg) + }) + } +} + +/// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar. +#[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")] +pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result +where + P: AsRef, + Q: AsRef, +{ + resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref())) +} From c6e323b21dae5f48bcb9a47b426571592dc1b7cb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 18 Sep 2025 14:06:33 +0200 Subject: [PATCH 120/224] =?UTF-8?q?=F0=9F=9A=A8=20Ajustes=20menores=20suge?= =?UTF-8?q?ridos=20por=20clippy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 2 +- src/html/context.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 28c6b1b4..5af5f9c9 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -269,8 +269,8 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { // Extrae atributos descartando la documentación para incluir en `alter_...()`. let non_doc_attrs: Vec<_> = attrs .iter() + .filter(|&a| !a.path().is_ident("doc")) .cloned() - .filter(|a| !a.path().is_ident("doc")) .collect(); // Documentación del método alter_...(). diff --git a/src/html/context.rs b/src/html/context.rs index 2f3e0f07..7b782681 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -314,7 +314,7 @@ impl Context { .ok_or_else(|| ErrorParam::TypeMismatch { key, expected: TypeInfo::FullName.of::(), - saved: *type_name, + saved: type_name, }) } From 25a71bf526f7a52e1f903c31ab5615c457946385 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 18 Sep 2025 14:17:01 +0200 Subject: [PATCH 121/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20la=20p=C3=A1gin?= =?UTF-8?q?a=20de=20bienvenida=20y=20el=20tema=20b=C3=A1sico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convierte la estructura de la página de bienvenida en una composición del tema básico para ser usada en otros contextos. Por ejemplo, para la página de mantenimiento, o la página de inicio de la guía de uso de PageTop. --- src/base/extension/welcome.rs | 52 +++++++++++++++++++++++++++-------- src/base/theme/basic.rs | 4 +-- src/locale/en-US/base.ftl | 4 +++ src/locale/en-US/welcome.ftl | 29 +++++++++---------- src/locale/es-ES/base.ftl | 6 +++- src/locale/es-ES/welcome.ftl | 25 +++++++++-------- 6 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index eaa9f339..e2a77128 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -30,22 +30,50 @@ async fn homepage(request: HttpRequest) -> ResultPage { .with_title(L10n::l("welcome_title")) .with_description(L10n::l("welcome_intro").with_arg("app", app)) .with_param("intro_button_text", L10n::l("welcome_powered")) - .with_param("intro_button_link", "https://pagetop.cillero.es".to_owned()) - .add_component(Html::with(|cx| { - html! { - p { (L10n::l("welcome_text1").using(cx)) } - p { (L10n::l("welcome_text2").using(cx)) } + .with_param("intro_button_link", "https://pagetop.cillero.es".to_string()) + .with_assets(AssetsOp::AddJavaScript(JavaScript::on_load_async("welcome-js", |cx| + util::indoc!(r#" + try { + const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); + const data = await resp.json(); + const date = new Date(data.versions[0].created_at); + const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" }); + document.getElementById("welcome-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`; + document.getElementById("welcome-badges").style.display = "block"; + } catch (e) { + console.error("Failed to fetch release date from crates.io:", e); } + "#) + .replace("LANGID", cx.langid().to_string().as_str()) + .replace("LABEL", L10n::l("welcome_release_label").using(cx).as_str()) + .to_string(), + ))) + .add_component(Html::with(|cx| html! { + p { (L10n::l("welcome_text1").using(cx)) } + div id="welcome-badges" style="display: none; margin-bottom: 1.1rem;" { + img + src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge" + alt=[L10n::l("welcome_pagetop_label").lookup(cx)] {} (" ") + img + id="welcome-release" + alt=[L10n::l("welcome_release_label").lookup(cx)] {} (" ") + img + src=(format!( + "https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge", + L10n::l("welcome_license_label").lookup(cx).unwrap_or_default() + )) + alt=[L10n::l("welcome_license_label").lookup(cx)] {} + } + p { (L10n::l("welcome_text2").using(cx)) } })) .add_component( Block::new() - .with_title(L10n::l("welcome_about")) - .add_component(Html::with(move |cx| { - html! { - p { (L10n::l("welcome_pagetop").using(cx)) } - p { (L10n::l("welcome_issues1").using(cx)) } - p { (L10n::l("welcome_issues2").with_arg("app", app).using(cx)) } - } + .with_title(L10n::l("welcome_notice_title")) + .add_component(Html::with(move |cx| html! { + p { (L10n::l("welcome_notice_1").using(cx)) } + p { (L10n::l("welcome_notice_2").using(cx)) } + p { (L10n::l("welcome_notice_3").using(cx)) } + p { (L10n::l("welcome_notice_4").with_arg("app", app).using(cx)) } })), ) .render() diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index fbf4caf5..e6525489 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -111,8 +111,8 @@ fn render_intro(page: &mut Page) -> Markup { div class="intro-footer__links" { a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("welcome_code").using(page)) } - em { (L10n::l("welcome_have_fun").using(page)) } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(page)) } + em { (L10n::l("intro_have_fun").using(page)) } } } } diff --git a/src/locale/en-US/base.ftl b/src/locale/en-US/base.ftl index b2c92564..7a9701de 100644 --- a/src/locale/en-US/base.ftl +++ b/src/locale/en-US/base.ftl @@ -1,2 +1,6 @@ +# Basic theme, intro layout. +intro_code = Code +intro_have_fun = Coding is creating + # PoweredBy component. poweredby_pagetop = Powered by { $pagetop_link } diff --git a/src/locale/en-US/welcome.ftl b/src/locale/en-US/welcome.ftl index 7b7d74d8..0a227f1d 100644 --- a/src/locale/en-US/welcome.ftl +++ b/src/locale/en-US/welcome.ftl @@ -1,20 +1,21 @@ -welcome_extension_name = Default homepage -welcome_extension_description = Displays a landing page when none is configured. +welcome_extension_name = Default Homepage +welcome_extension_description = Displays a default homepage when none is configured. -welcome_page = Welcome Page -welcome_title = Hello world! +welcome_page = Welcome page +welcome_title = Hello, world! welcome_intro = Discover⚡{ $app } -welcome_powered = A web solution powered by PageTop! +welcome_powered = A web solution powered by PageTop -welcome_text1 = If you can read this page, it means that the PageTop server is running correctly but has not yet been fully configured. This usually means the site is either experiencing temporary issues or is undergoing routine maintenance. -welcome_text2 = If the issue persists, please contact your system administrator for assistance. +welcome_pagetop_label = PageTop version on Crates.io +welcome_release_label = Release date +welcome_license_label = License -welcome_about = About -welcome_pagetop = PageTop is a Rust-based web development framework for building modular, extensible, and configurable web solutions. +welcome_text1 = PageTop is a Rust-based web development framework designed to build modular, extensible, and configurable web solutions. +welcome_text2 = PageTop brings back the essence of the classic web, renders on the server (SSR) and uses HTML-first components, CSS and JavaScript, with the performance and security of Rust. -welcome_issues1 = To report issues related to the PageTop framework, please use SoloGit. Before opening a new issue, check existing reports to avoid duplicates. -welcome_issues2 = For issues related specifically to { $app }, please refer to its official repository or support channel, rather than directly to PageTop. - -welcome_code = Code -welcome_have_fun = Coding is creating +welcome_notice_title = Notice +welcome_notice_1 = If you can see this page, the PageTop server is running correctly, but the application is not fully configured. This may be due to routine maintenance or a temporary issue. +welcome_notice_2 = If the issue persists, please contact the system administrator. +welcome_notice_3 = To report issues with the PageTop framework, use SoloGit. Before opening a new issue, review the existing ones to avoid duplicates. +welcome_notice_4 = For issues specific to the application ({ $app }), please use its official repository or support channel. diff --git a/src/locale/es-ES/base.ftl b/src/locale/es-ES/base.ftl index 74eb62e8..99f6c7e8 100644 --- a/src/locale/es-ES/base.ftl +++ b/src/locale/es-ES/base.ftl @@ -1,2 +1,6 @@ +# Basic theme, intro layout. +intro_code = Código +intro_have_fun = Programar es crear + # PoweredBy component. -poweredby_pagetop = Funciona con { $pagetop_link } \ No newline at end of file +poweredby_pagetop = Funciona con { $pagetop_link } diff --git a/src/locale/es-ES/welcome.ftl b/src/locale/es-ES/welcome.ftl index 78238322..b98d9199 100644 --- a/src/locale/es-ES/welcome.ftl +++ b/src/locale/es-ES/welcome.ftl @@ -1,20 +1,21 @@ welcome_extension_name = Página de inicio predeterminada welcome_extension_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada. -welcome_page = Página de Bienvenida -welcome_title = ¡Hola mundo! +welcome_page = Página de bienvenida +welcome_title = ¡Hola, mundo! welcome_intro = Descubre⚡{ $app } -welcome_powered = Una solución web creada con PageTop! +welcome_powered = Una solución web creada con PageTop -welcome_text1 = Si puedes leer esta página, significa que el servidor de PageTop funciona correctamente, pero aún no ha sido completamente configurado. Esto suele indicar que el sitio está experimentando problemas temporales o está pasando por un mantenimiento de rutina. -welcome_text2 = Si el problema persiste, por favor contacta con el administrador del sistema para recibir asistencia técnica. +welcome_pagetop_label = Versión de PageTop en Crates.io +welcome_release_label = Lanzamiento +welcome_license_label = Licencia -welcome_about = Acerca de -welcome_pagetop = PageTop es un entorno de desarrollo web basado en Rust, diseñado para crear soluciones web modulares, extensibles y configurables. +welcome_text1 = PageTop es un entorno de desarrollo web basado en Rust, pensado para construir soluciones web modulares, extensibles y configurables. +welcome_text2 = PageTop reivindica la esencia de la web clásica, renderiza en el servidor (SSR) utilizando componentes HTML-first, CSS y JavaScript, con el rendimiento y la seguridad de Rust. -welcome_issues1 = Para comunicar cualquier problema con PageTop, utiliza SoloGit. Antes de informar de una incidencia, revisa los informes ya existentes para evitar duplicados. -welcome_issues2 = Si se trata de fallos específicos de { $app }, por favor acude a su repositorio oficial o canal de soporte, y no al de PageTop directamente. - -welcome_code = Código -welcome_have_fun = Programar es crear +welcome_notice_title = Aviso +welcome_notice_1 = Si puedes ver esta página, el servidor de PageTop está funcionando correctamente, pero la aplicación no está completamente configurada. Esto puede deberse a tareas de mantenimiento o a una incidencia temporal. +welcome_notice_2 = Si el problema persiste, por favor, contacta con el administrador del sistema. +welcome_notice_3 = Para comunicar incidencias del propio entorno PageTop, utiliza SoloGit. Antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. +welcome_notice_4 = Para fallos específicos de la aplicación ({ $app }), utiliza su repositorio oficial o su canal de soporte. From 2b2ce2c50164f53e90c76e292889dfd87188e9a4 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 18 Sep 2025 23:38:25 +0200 Subject: [PATCH 122/224] =?UTF-8?q?=F0=9F=8E=A8=20Unifica=20par=C3=A1metro?= =?UTF-8?q?s=20y=20estilos=20del=20tema=20b=C3=A1sico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ofrece dos composiciones de página dirigidas a una introducción de contenidos neutra o con referencias a PageTop. --- src/base/extension/welcome.rs | 65 +++++++++------------------ src/base/theme/basic.rs | 85 +++++++++++++++++++++++++++++++---- src/core/theme/definition.rs | 10 ++--- src/locale/en-US/base.ftl | 7 +++ src/locale/en-US/welcome.ftl | 17 +++---- src/locale/es-ES/base.ftl | 7 +++ src/locale/es-ES/welcome.ftl | 17 +++---- static/css/intro.css | 6 +-- 8 files changed, 130 insertions(+), 84 deletions(-) diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index e2a77128..5c6fec5a 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -25,55 +25,30 @@ async fn homepage(request: HttpRequest) -> ResultPage { let app = &global::SETTINGS.app.name; Page::new(request) - .with_theme("basic") - .with_layout("intro") + .with_theme("Basic") + .with_layout("PageTopIntro") .with_title(L10n::l("welcome_title")) .with_description(L10n::l("welcome_intro").with_arg("app", app)) - .with_param("intro_button_text", L10n::l("welcome_powered")) - .with_param("intro_button_link", "https://pagetop.cillero.es".to_string()) - .with_assets(AssetsOp::AddJavaScript(JavaScript::on_load_async("welcome-js", |cx| - util::indoc!(r#" - try { - const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); - const data = await resp.json(); - const date = new Date(data.versions[0].created_at); - const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" }); - document.getElementById("welcome-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`; - document.getElementById("welcome-badges").style.display = "block"; - } catch (e) { - console.error("Failed to fetch release date from crates.io:", e); - } - "#) - .replace("LANGID", cx.langid().to_string().as_str()) - .replace("LABEL", L10n::l("welcome_release_label").using(cx).as_str()) - .to_string(), - ))) - .add_component(Html::with(|cx| html! { - p { (L10n::l("welcome_text1").using(cx)) } - div id="welcome-badges" style="display: none; margin-bottom: 1.1rem;" { - img - src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge" - alt=[L10n::l("welcome_pagetop_label").lookup(cx)] {} (" ") - img - id="welcome-release" - alt=[L10n::l("welcome_release_label").lookup(cx)] {} (" ") - img - src=(format!( - "https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge", - L10n::l("welcome_license_label").lookup(cx).unwrap_or_default() - )) - alt=[L10n::l("welcome_license_label").lookup(cx)] {} - } - p { (L10n::l("welcome_text2").using(cx)) } - })) + .with_param("intro_button_txt", L10n::l("welcome_powered")) + .with_param("intro_button_lnk", "https://pagetop.cillero.es".to_string()) .add_component( Block::new() - .with_title(L10n::l("welcome_notice_title")) - .add_component(Html::with(move |cx| html! { - p { (L10n::l("welcome_notice_1").using(cx)) } - p { (L10n::l("welcome_notice_2").using(cx)) } - p { (L10n::l("welcome_notice_3").using(cx)) } - p { (L10n::l("welcome_notice_4").with_arg("app", app).using(cx)) } + .with_title(L10n::l("welcome_status_title")) + .add_component(Html::with(move |cx| { + html! { + p { (L10n::l("welcome_status_1").using(cx)) } + p { (L10n::l("welcome_status_2").using(cx)) } + } + })), + ) + .add_component( + Block::new() + .with_title(L10n::l("welcome_support_title")) + .add_component(Html::with(move |cx| { + html! { + p { (L10n::l("welcome_support_1").using(cx)) } + p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } + } })), ) .render() diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index e6525489..2f492748 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -1,8 +1,33 @@ -//! Es el tema básico que incluye PageTop por defecto. - +/// Es el tema básico que incluye PageTop por defecto. use crate::prelude::*; /// Tema básico por defecto. +/// +/// Ofrece las siguientes composiciones (*layouts*): +/// +/// - **Composición predeterminada** +/// - Renderizado genérico con +/// [`ThemePage::render_body()`](crate::core::theme::ThemePage::render_body) usando las regiones +/// predefinidas en [`page_regions()`](crate::core::theme::Theme::page_regions). +/// +/// - **`Intro`** +/// - Página de entrada con cabecera visual, título y descripción y un botón opcional de llamada a +/// la acción. Ideal para una página de inicio o bienvenida en el contexto de PageTop. +/// - **Regiones:** `content` (se renderiza dentro de `.intro-content__body`). +/// - **Parámetros:** +/// - `intro_button_txt` (`L10n`) – Texto del botón. +/// - `intro_button_lnk` (`Option`) – URL del botón; si no se indica, el botón no se +/// muestra. +/// +/// - **`PageTopIntro`** +/// - Variante de `Intro` con textos predefinidos sobre PageTop al inicio del contenido. Añade una +/// banda de *badges* con la versión de [PageTop en crates.io](https://crates.io/crates/pagetop) +/// más la fecha de la última versión publicada y la licencia de uso. +/// - **Regiones:** `content` (igual que `Intro`). +/// - **Parámetros:** los mismos que `Intro`. +/// +/// **Nota:** si no se especifica `layout` o el valor no coincide con ninguno de los anteriores, se +/// aplica la composición predeterminada. pub struct Basic; impl Extension for Basic { @@ -14,14 +39,16 @@ impl Extension for Basic { impl Theme for Basic { fn render_page_body(&self, page: &mut Page) -> Markup { match page.layout() { - "intro" => render_intro(page), + "Intro" => render_intro(page), + "PageTopIntro" => render_pagetop_intro(page), _ => ::render_body(self, page, self.page_regions()), } } fn after_render_page_body(&self, page: &mut Page) { let styles = match page.layout() { - "intro" => "/css/intro.css", + "Intro" => "/css/intro.css", + "PageTopIntro" => "/css/intro.css", _ => "/css/basic.css", }; page.alter_assets(AssetsOp::AddStyleSheet( @@ -41,8 +68,8 @@ fn render_intro(page: &mut Page) -> Markup { let title = page.title().unwrap_or_default(); let intro = page.description().unwrap_or_default(); - let intro_button_text: L10n = page.param_or_default("intro_button_text"); - let intro_button_link: Option<&String> = page.param("intro_button_link"); + let intro_button_txt: L10n = page.param_or_default("intro_button_txt"); + let intro_button_lnk: Option<&String> = page.param("intro_button_lnk"); html! { body id=[page.body_id().get()] class=[page.body_classes().get()] { @@ -74,17 +101,17 @@ fn render_intro(page: &mut Page) -> Markup { } main class="intro-content" { section class="intro-content__body" { - @if intro_button_link.is_some() { + @if intro_button_lnk.is_some() { div class="intro-button" { a class="intro-button__link" - href=[intro_button_link] + href=[intro_button_lnk] target="_blank" rel="noreferrer" { span {} span {} span {} div class="intro-button__text" { - (intro_button_text.using(page)) + (intro_button_txt.using(page)) } } } @@ -119,3 +146,43 @@ fn render_intro(page: &mut Page) -> Markup { } } } + +fn render_pagetop_intro(page: &mut Page) -> Markup { + page.alter_assets(AssetsOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| + util::indoc!(r#" + try { + const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); + const data = await resp.json(); + const date = new Date(data.versions[0].created_at); + const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" }); + document.getElementById("intro-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`; + document.getElementById("intro-badges").style.display = "block"; + } catch (e) { + console.error("Failed to fetch release date from crates.io:", e); + } + "#) + .replace("LANGID", cx.langid().to_string().as_str()) + .replace("LABEL", L10n::l("intro_release_label").using(cx).as_str()) + .to_string(), + ))) + .alter_child_in("content", ChildOp::Prepend(Child::with(Html::with(|cx| html! { + p { (L10n::l("intro_text1").using(cx)) } + div id="intro-badges" style="display: none; margin-bottom: 1.1rem;" { + img + src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge" + alt=[L10n::l("intro_pagetop_label").lookup(cx)] {} (" ") + img + id="intro-release" + alt=[L10n::l("intro_release_label").lookup(cx)] {} (" ") + img + src=(format!( + "https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge", + L10n::l("intro_license_label").lookup(cx).unwrap_or_default() + )) + alt=[L10n::l("intro_license_label").lookup(cx)] {} + } + p { (L10n::l("intro_text2").using(cx)) } + })))); + + render_intro(page) +} diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index a4faf0b8..38a0bfc4 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -125,18 +125,18 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// Declaración ordenada de las regiones disponibles en la página. /// - /// Devuelve una **lista estática** de pares `(Region, L10n)` que se usará para renderizar en el - /// orden indicado todas las regiones que componen una página. + /// Devuelve una **lista estática** de pares `(Region, L10n)` que se usará para renderizar todas + /// las regiones que componen una página en el orden indicado . /// - /// Requisitos y recomendaciones: + /// Si un tema necesita un conjunto distinto de regiones, se puede **sobrescribir** este método + /// con los siguientes requisitos y recomendaciones: /// /// - Los identificadores deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). /// - La región `"content"` es **obligatoria**. Se puede usar [`Region::default()`] para /// declararla. /// - La etiqueta `L10n` se evalúa con el idioma activo de la página. /// - /// Si tu tema define un conjunto distinto, se puede **sobrescribir** este método. Por defecto - /// devuelve: + /// Por defecto devuelve: /// /// - `"header"`: cabecera. /// - `"content"`: contenido principal (**obligatoria**). diff --git a/src/locale/en-US/base.ftl b/src/locale/en-US/base.ftl index 7a9701de..16b1a3ef 100644 --- a/src/locale/en-US/base.ftl +++ b/src/locale/en-US/base.ftl @@ -1,4 +1,11 @@ # Basic theme, intro layout. +intro_pagetop_label = PageTop version on Crates.io +intro_release_label = Release date +intro_license_label = License + +intro_text1 = PageTop is an opinionated Rust web development framework designed to build modular, extensible, and configurable web solutions. +intro_text2 = PageTop brings back the essence of the classic web, renders on the server (SSR) and uses HTML-first components, CSS and JavaScript, with the performance and security of Rust. + intro_code = Code intro_have_fun = Coding is creating diff --git a/src/locale/en-US/welcome.ftl b/src/locale/en-US/welcome.ftl index 0a227f1d..20faf964 100644 --- a/src/locale/en-US/welcome.ftl +++ b/src/locale/en-US/welcome.ftl @@ -7,15 +7,10 @@ welcome_title = Hello, world! welcome_intro = Discover⚡{ $app } welcome_powered = A web solution powered by PageTop -welcome_pagetop_label = PageTop version on Crates.io -welcome_release_label = Release date -welcome_license_label = License +welcome_status_title = Status +welcome_status_1 = If you can see this page, it means the PageTop server is running correctly, but the application is not fully configured. This may be due to routine maintenance or a temporary issue. +welcome_status_2 = If the issue persists, please contact the system administrator. -welcome_text1 = PageTop is a Rust-based web development framework designed to build modular, extensible, and configurable web solutions. -welcome_text2 = PageTop brings back the essence of the classic web, renders on the server (SSR) and uses HTML-first components, CSS and JavaScript, with the performance and security of Rust. - -welcome_notice_title = Notice -welcome_notice_1 = If you can see this page, the PageTop server is running correctly, but the application is not fully configured. This may be due to routine maintenance or a temporary issue. -welcome_notice_2 = If the issue persists, please contact the system administrator. -welcome_notice_3 = To report issues with the PageTop framework, use SoloGit. Before opening a new issue, review the existing ones to avoid duplicates. -welcome_notice_4 = For issues specific to the application ({ $app }), please use its official repository or support channel. +welcome_support_title = Support +welcome_support_1 = To report issues with the PageTop framework, use SoloGit. Remember, before opening a new issue, review the existing ones to avoid duplicates. +welcome_support_2 = For issues specific to the application ({ $app }), please use its official repository or support channel. diff --git a/src/locale/es-ES/base.ftl b/src/locale/es-ES/base.ftl index 99f6c7e8..fee21a93 100644 --- a/src/locale/es-ES/base.ftl +++ b/src/locale/es-ES/base.ftl @@ -1,4 +1,11 @@ # Basic theme, intro layout. +intro_pagetop_label = Versión de PageTop en Crates.io +intro_release_label = Lanzamiento +intro_license_label = Licencia + +intro_text1 = PageTop es un entorno de desarrollo web basado en Rust, pensado para construir soluciones web modulares, extensibles y configurables. +intro_text2 = PageTop reivindica la esencia de la web clásica, renderiza en el servidor (SSR) utilizando componentes HTML-first, CSS y JavaScript, con el rendimiento y la seguridad de Rust. + intro_code = Código intro_have_fun = Programar es crear diff --git a/src/locale/es-ES/welcome.ftl b/src/locale/es-ES/welcome.ftl index b98d9199..13330dbf 100644 --- a/src/locale/es-ES/welcome.ftl +++ b/src/locale/es-ES/welcome.ftl @@ -7,15 +7,10 @@ welcome_title = ¡Hola, mundo! welcome_intro = Descubre⚡{ $app } welcome_powered = Una solución web creada con PageTop -welcome_pagetop_label = Versión de PageTop en Crates.io -welcome_release_label = Lanzamiento -welcome_license_label = Licencia +welcome_status_title = Estado +welcome_status_1 = Si puedes ver esta página, es porque el servidor de PageTop está funcionando correctamente, pero la aplicación no está completamente configurada. Esto puede deberse a tareas de mantenimiento o a una incidencia temporal. +welcome_status_2 = Si el problema persiste, por favor, contacta con el administrador del sistema. -welcome_text1 = PageTop es un entorno de desarrollo web basado en Rust, pensado para construir soluciones web modulares, extensibles y configurables. -welcome_text2 = PageTop reivindica la esencia de la web clásica, renderiza en el servidor (SSR) utilizando componentes HTML-first, CSS y JavaScript, con el rendimiento y la seguridad de Rust. - -welcome_notice_title = Aviso -welcome_notice_1 = Si puedes ver esta página, el servidor de PageTop está funcionando correctamente, pero la aplicación no está completamente configurada. Esto puede deberse a tareas de mantenimiento o a una incidencia temporal. -welcome_notice_2 = Si el problema persiste, por favor, contacta con el administrador del sistema. -welcome_notice_3 = Para comunicar incidencias del propio entorno PageTop, utiliza SoloGit. Antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. -welcome_notice_4 = Para fallos específicos de la aplicación ({ $app }), utiliza su repositorio oficial o su canal de soporte. +welcome_support_title = Soporte +welcome_support_1 = Para comunicar incidencias del propio entorno PageTop, utiliza SoloGit. Recuerda, antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. +welcome_support_2 = Para fallos específicos de la aplicación ({ $app }), utiliza su repositorio oficial o su canal de soporte. diff --git a/static/css/intro.css b/static/css/intro.css index 19fa9f15..39c9d6ad 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -7,9 +7,9 @@ --color: #1a202c; --color-gray: #e4e4e7; --color-link: #1e4eae; - --color-block-1: #fecaca; - --color-block-2: #e6a9e2; - --color-block-3: #b689ff; + --color-block-1: #b689ff; + --color-block-2: #fecaca; + --color-block-3: #e6a9e2; --color-block-4: #ffedca; --color-block-5: #ffffff; --focus-outline: 2px solid var(--color-link); From 6d5e1dfdb483470c9fff935c71afed3bd2c16418 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:08:47 +0200 Subject: [PATCH 123/224] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20(pagetop):=20Actua?= =?UTF-8?q?liza=20dependencias=20para=200.4.0=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.cillero.es/manuelcillero/pagetop/pulls/7 Co-authored-by: Manuel Cillero Co-committed-by: Manuel Cillero --- Cargo.lock | 179 +++++++++++++++++++---------- Cargo.toml | 40 +++---- helpers/pagetop-build/Cargo.toml | 2 +- helpers/pagetop-macros/Cargo.toml | 8 +- helpers/pagetop-statics/Cargo.toml | 4 +- 5 files changed, 146 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3053e20c..be745714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,16 +145,16 @@ dependencies = [ [[package]] name = "actix-session" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe6976a74f34f1b6d07a6c05aadc0ed0359304a7781c367fa5b4029418db08f" +checksum = "400c27fd4cdbe0082b7bbd29ac44a3070cbda1b2114138dc106ba39fe2f90dff" dependencies = [ "actix-service", "actix-utils", "actix-web", "anyhow", - "derive_more 1.0.0", - "rand 0.8.5", + "derive_more 2.0.1", + "rand 0.9.1", "serde", "serde_json", "tracing", @@ -334,9 +334,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -364,22 +364,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -406,7 +406,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -512,7 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a" dependencies = [ "path-matchers", - "path-slash", + "path-slash 0.1.5", ] [[package]] @@ -541,18 +541,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -729,34 +729,13 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "derive_more-impl", ] [[package]] @@ -815,7 +794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1619,7 +1598,7 @@ dependencies = [ "derive_more 0.99.20", "futures-util", "mime_guess", - "path-slash", + "path-slash 0.2.1", ] [[package]] @@ -1642,7 +1621,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1666,6 +1645,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696" +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1802,9 +1787,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -2207,9 +2192,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2229,15 +2214,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2798,7 +2783,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2807,7 +2792,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -2816,14 +2810,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -2832,48 +2843,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.11" diff --git a/Cargo.toml b/Cargo.toml index ab7551f6..54f16c34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,29 +15,29 @@ license.workspace = true authors.workspace = true [dependencies] -chrono = "0.4.41" -colored = "3.0.0" -concat-string = "1.0.1" -config = { version = "0.15.13", default-features = false, features = ["toml"] } -figlet-rs = "0.1.5" -indoc = "2.0.6" -itoa = "1.0.15" -parking_lot = "0.12.4" -paste = { package = "pastey", version = "0.1.0" } -substring = "1.4.5" -terminal_size = "0.4.2" +chrono = "0.4" +colored = "3.0" +concat-string = "1.0" +config = { version = "0.15", default-features = false, features = ["toml"] } +figlet-rs = "0.1" +indoc = "2.0" +itoa = "1.0" +parking_lot = "0.12" +paste = { package = "pastey", version = "0.1" } +substring = "1.4" +terminal_size = "0.4" -tracing = "0.1.41" -tracing-appender = "0.2.3" -tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } -tracing-actix-web = "0.7.19" +tracing = "0.1" +tracing-appender = "0.2" +tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } +tracing-actix-web = "0.7" -fluent-templates = "0.13.0" -unic-langid = { version = "0.9.6", features = ["macros"] } +fluent-templates = "0.13" +unic-langid = { version = "0.9", features = ["macros"] } actix-web = { workspace = true, default-features = true } -actix-session = { version = "0.10.1", features = ["cookie-session"] } -actix-web-files = { package = "actix-files", version = "0.6.6" } +actix-session = { version = "0.11", features = ["cookie-session"] } +actix-web-files = { package = "actix-files", version = "0.6" } serde = { version = "1.0", features = ["derive"] } @@ -49,7 +49,7 @@ default = [] testing = [] [dev-dependencies] -tempfile = "3.20.0" +tempfile = "3.22" [build-dependencies] pagetop-build.workspace = true diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index e0f5ef76..25fb6187 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -16,5 +16,5 @@ license.workspace = true authors.workspace = true [dependencies] -grass = "0.13.4" +grass = "0.13" pagetop-statics.workspace = true diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index d5ceb1b6..5c508dc8 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -18,7 +18,7 @@ authors.workspace = true proc-macro = true [dependencies] -proc-macro2 = "1.0.95" -proc-macro2-diagnostics = { version = "0.10.1", default-features = false } -quote = "1.0.40" -syn = { version = "2.0.104", features = ["full", "extra-traits"] } +proc-macro2 = "1.0" +proc-macro2-diagnostics = { version = "0.10", default-features = false } +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml index 2afe3739..1f6deede 100644 --- a/helpers/pagetop-statics/Cargo.toml +++ b/helpers/pagetop-statics/Cargo.toml @@ -21,7 +21,7 @@ sort = [] [dependencies] change-detection = { version = "1.2", optional = true } mime_guess = "2.0" -path-slash = "0.1" +path-slash = "0.2" actix-web.workspace = true derive_more = "0.99.17" @@ -30,4 +30,4 @@ futures-util = { version = "0.3", default-features = false, features = ["std"] } [build-dependencies] change-detection = { version = "1.2", optional = true } mime_guess = "2.0" -path-slash = "0.1" +path-slash = "0.2" From b808c00eb1a7fd35ee34cb5bd75139fa0ab46c83 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:14:52 +0200 Subject: [PATCH 124/224] =?UTF-8?q?=F0=9F=A9=B9=20Corrige=20dependencia=20?= =?UTF-8?q?no=20actualizada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 54f16c34..750aaaa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ license = "MIT OR Apache-2.0" authors = ["Manuel Cillero "] [workspace.dependencies] -actix-web = { version = "4.11.0", default-features = false } +actix-web = { version = "4.11", default-features = false } pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } From adec5c2636f2ac0443bb061a8fecc2199516edf5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:19:14 +0200 Subject: [PATCH 125/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-statics=200.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-statics/CHANGELOG.md | 12 +++++++++++- helpers/pagetop-statics/Cargo.toml | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be745714..c5db7142 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "pagetop-statics" -version = "0.1.1" +version = "0.1.2" dependencies = [ "actix-web", "change-detection", diff --git a/helpers/pagetop-statics/CHANGELOG.md b/helpers/pagetop-statics/CHANGELOG.md index 811a77f4..0d6f5b1b 100644 --- a/helpers/pagetop-statics/CHANGELOG.md +++ b/helpers/pagetop-statics/CHANGELOG.md @@ -8,11 +8,21 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.1.2 (2025-09-20) + +### Dependencias + +- Actualiza dependencias para 0.4.0 + +### Documentado + +- Normaliza referencias al nombre PageTop + ## 0.1.1 (2025-08-16) ### Documentado -- Cambia el formato para la documentación (#4) +- Cambia el formato para la documentación ## 0.1.0 (2025-08-09) diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml index 1f6deede..0172a190 100644 --- a/helpers/pagetop-statics/Cargo.toml +++ b/helpers/pagetop-statics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-statics" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = """ From ccafedaa0e4b3ba36bdededdd3493199318bef1f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:22:23 +0200 Subject: [PATCH 126/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-build=200.3.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- helpers/pagetop-build/CHANGELOG.md | 18 ++++++++++++++---- helpers/pagetop-build/Cargo.toml | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5db7142..bbe745b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1573,7 +1573,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.3.0" +version = "0.3.1" dependencies = [ "grass", "pagetop-statics", diff --git a/helpers/pagetop-build/CHANGELOG.md b/helpers/pagetop-build/CHANGELOG.md index 3e3801e8..5bd9caf3 100644 --- a/helpers/pagetop-build/CHANGELOG.md +++ b/helpers/pagetop-build/CHANGELOG.md @@ -8,22 +8,32 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.3.1 (2025-09-20) + +### Dependencias + +- Actualiza dependencias para 0.4.0 + +### Documentado + +- Normaliza referencias al nombre PageTop + ## 0.3.0 (2025-08-16) ### Cambiado -- Mejora función `from_dir` por compatibilidad (#3) -- Mejora la integración de archivos estáticos +- Mejora función `from_dir` por compatibilidad +- Mejora la integración de archivos estáticos ### Documentado -- Cambia el formato para la documentación (#4) +- Cambia el formato para la documentación ## 0.2.0 (2025-08-09) ### Añadido -- Añade librería propia para gestionar recursos estáticos (#1) +- Añade librería propia para gestionar recursos estáticos ### Otros cambios diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 25fb6187..cea1de3e 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = """ From e4a9a72fa791acbba2fcfac8b860152dba82213d Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:25:59 +0200 Subject: [PATCH 127/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop-macros=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- helpers/pagetop-macros/CHANGELOG.md | 19 +++++++++++++++++++ helpers/pagetop-macros/Cargo.toml | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbe745b5..d125b63f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1581,7 +1581,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.1.1" +version = "0.2.0" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", diff --git a/Cargo.toml b/Cargo.toml index 750aaaa8..0fd702e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,5 +73,5 @@ authors = ["Manuel Cillero "] actix-web = { version = "4.11", default-features = false } pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } -pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } +pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } diff --git a/helpers/pagetop-macros/CHANGELOG.md b/helpers/pagetop-macros/CHANGELOG.md index b61471a1..66a5f8d4 100644 --- a/helpers/pagetop-macros/CHANGELOG.md +++ b/helpers/pagetop-macros/CHANGELOG.md @@ -8,6 +8,25 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.2.0 (2025-09-20) + +### Cambiado + +- Retoques en el código +- Majora la validación de `builder_fn` + +### Dependencias + +- Actualiza dependencias para 0.4.0 + +### Documentado + +- Normaliza referencias al nombre PageTop + +### Otros cambios + +- 🚨 Ajustes menores sugeridos por clippy + ## 0.1.1 (2025-08-16) ### Documentado diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 5c508dc8..601c551d 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = """ From 6fac597a163706a6a5d4645b339d854eaf7e8c16 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 13:43:36 +0200 Subject: [PATCH 128/224] =?UTF-8?q?=F0=9F=94=A8=20(tools):=20Fuerza=20puls?= =?UTF-8?q?ar=20intro=20para=20confirmar=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/changelog.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/changelog.sh b/tools/changelog.sh index 2668ec67..211e896a 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -88,12 +88,12 @@ git-cliff --unreleased "${COMMON_ARGS[@]}" echo "CHANGELOG generated at '$CHANGELOG_FILE'" # Pregunta por la revisión del archivo de cambios generado -read -p "Do you want to review the changelog before continuing? (y/n) " -n 1 -r || exit 1 +read -p "Do you want to review the changelog before continuing? (y/n) " -r || exit 1 echo if [[ "$REPLY" =~ ^[Yy]$ ]]; then ${EDITOR:-nano} "$CHANGELOG_FILE" fi -read -p "Do you want to proceed with the release of $CRATE? (y/n) " -n 1 -r || exit 1 +read -p "Do you want to proceed with the release of $CRATE? (y/n) " -r || exit 1 echo if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then echo "Aborting release process." >&2 From 9536003badba7e3b59b79390e22cc25c2da0ba5d Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 14:00:09 +0200 Subject: [PATCH 129/224] =?UTF-8?q?=F0=9F=94=96=20Prepara=20publicaci?= =?UTF-8?q?=C3=B3n=20de=20pagetop=200.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e187143..f2926a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,53 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.4.0 (2025-09-20) + +### Añadido + +- [app] Añade manejo de rutas no encontradas +- [context] Añade métodos auxiliares de parámetros +- [util] Añade `indoc` para indentar código bien +- Añade componente `PoweredBy` para copyright + +### Cambiado + +- [html] Cambia tipos `Option...` por `Attr...` +- [html] Implementa `Default` en `Context` +- [welcome] Crea página de bienvenida desde intro +- [context] Generaliza los parámetros de contexto +- [context] Define un `trait` común de contexto +- Modifica tipos para atributos HTML a minúsculas +- Renombra `with_component` por `add_component` + +### Corregido + +- [welcome] Corrige giro botón con ancho estrecho +- [welcome] Corrige centrado del pie de página +- Corrige nombre de función en prueba de `Html` +- Corrige doc y código por cambios en Page + +### Dependencias + +- Actualiza dependencias para 0.4.0 + +### Documentado + +- [component] Amplía documentación de preparación +- Normaliza referencias al nombre PageTop +- Simplifica documentación de obsoletos +- Mejora la documentación de recursos y contexto + +### Otros cambios + +- 🎨 [theme] Mejora gestión de regiones en páginas +- ✅ [tests] Amplía pruebas para `PrepareMarkup' +- 🎨 [locale] Mejora el uso de `lookup` / `using` +- 🔨 [tools] Fuerza pulsar intro para confirmar input +- 💄 Aplica BEM a estilos de bienvenida y componente +- 🎨 Unifica conversiones a String con `to_string()` +- 🔥 Elimina `Render` para usar siempre el contexto + ## 0.3.0 (2025-08-16) ### Cambiado @@ -17,14 +64,14 @@ internos pueden omitirse si no afectan al uso del proyecto. ### Documentado -- Cambia el formato para la documentación (#4) +- Cambia el formato para la documentación ## 0.2.0 (2025-08-09) ### Añadido -- Añade librería para gestionar recursos estáticos (#1) -- Añade soporte a changelog de `pagetop-statics` (#2) +- Añade librería para gestionar recursos estáticos +- Añade soporte a changelog de `pagetop-statics` ### Documentado diff --git a/Cargo.lock b/Cargo.lock index d125b63f..01140831 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1542,7 +1542,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.3.0" +version = "0.4.0" dependencies = [ "actix-files", "actix-session", diff --git a/Cargo.toml b/Cargo.toml index 0fd702e6..913f3d7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.3.0" +version = "0.4.0" edition = "2021" description = """ From 95777379a9d0b45d8f3f0e1e66b60d2f5b84adcb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 17:55:44 +0200 Subject: [PATCH 130/224] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Actualiza=20dep?= =?UTF-8?q?endencias=20mirando?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 553 +++++++++++++++++++++++++++-------------------------- 1 file changed, 286 insertions(+), 267 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01140831..e0d497c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-files" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be" +checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576" dependencies = [ "actix-http", "actix-service", @@ -31,7 +31,7 @@ dependencies = [ "actix-web", "bitflags", "bytes", - "derive_more 0.99.20", + "derive_more 2.0.1", "futures-core", "http-range", "log", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" +checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0" dependencies = [ "actix-codec", "actix-rt", @@ -72,7 +72,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.1", + "rand 0.9.2", "sha1", "smallvec", "tokio", @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ "futures-core", "tokio", @@ -128,7 +128,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.10", "tokio", "tracing", ] @@ -154,7 +154,7 @@ dependencies = [ "actix-web", "anyhow", "derive_more 2.0.1", - "rand 0.9.1", + "rand 0.9.2", "serde", "serde_json", "tracing", @@ -207,7 +207,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.5.10", "time", "tracing", "url", @@ -317,12 +317,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -384,9 +378,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" @@ -423,9 +417,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block-buffer" @@ -438,9 +432,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -481,19 +475,20 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytestring" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" dependencies = [ "bytes", ] [[package]] name = "cc" -version = "1.2.27" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -501,9 +496,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "change-detection" @@ -517,16 +512,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -595,12 +589,12 @@ checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609" [[package]] name = "config" -version = "0.15.13" +version = "0.15.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1eb4fb07bc7f012422df02766c7bd5971effb894f573865642f06fa3265440" +checksum = "cef036f0ecf99baef11555578630e2cca559909b4c50822dbba828c252d21c49" dependencies = [ "pathdiff", - "serde", + "serde_core", "toml", "winnow", ] @@ -709,9 +703,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -789,12 +783,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -809,6 +803,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4742a071cd9694fc86f9fa1a08fa3e53d40cc899d7ee532295da2d085639fbc5" +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "flate2" version = "1.1.2" @@ -821,16 +821,16 @@ dependencies = [ [[package]] name = "fluent-bundle" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" dependencies = [ "fluent-langneg", "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash 1.1.0", - "self_cell 0.10.3", + "rustc-hash", + "self_cell", "smallvec", "unic-langid", ] @@ -846,18 +846,19 @@ dependencies = [ [[package]] name = "fluent-syntax" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ - "thiserror", + "memchr", + "thiserror 2.0.16", ] [[package]] name = "fluent-template-macros" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed02449601d0dacdc05cb5e13db5dab8f2b98d773aff5c53b62fad43a1b19a1" +checksum = "e6222b8a208b9f0e7b984da3616651b0cc74e4461571b118cb1c713e0f7617ee" dependencies = [ "flume", "ignore", @@ -869,9 +870,9 @@ dependencies = [ [[package]] name = "fluent-templates" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69855a5fe87629495efca79aec72adfa97954f1006f928e3a2ec750cb3e85386" +checksum = "f8a893d77c0e48dc3f78421e9cba5d82e02bcb85d4520c506cec2fbebd0f513b" dependencies = [ "fluent-bundle", "fluent-langneg", @@ -881,7 +882,7 @@ dependencies = [ "ignore", "intl-memoizer", "log", - "thiserror", + "thiserror 1.0.69", "unic-langid", ] @@ -908,9 +909,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -978,7 +979,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -999,9 +1000,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" @@ -1012,8 +1013,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1072,9 +1073,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hkdf" @@ -1125,9 +1126,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1235,9 +1236,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1264,7 +1265,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata", "same-file", "walkdir", "winapi-util", @@ -1278,12 +1279,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.0", ] [[package]] @@ -1320,6 +1321,17 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1334,9 +1346,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -1344,9 +1356,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -1375,15 +1387,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1420,17 +1432,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -1484,12 +1496,11 @@ checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "overload", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1534,12 +1545,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "pagetop" version = "0.4.0" @@ -1626,9 +1631,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a8cb46bdc156b1c90460339ae6bfd45ba0394e5effbaa640badb4987fdc261" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] name = "path-matchers" @@ -1659,9 +1664,9 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -1757,9 +1762,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -1834,9 +1839,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -1882,74 +1887,53 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-lite" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -1968,22 +1952,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2006,15 +1990,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "self_cell" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" -dependencies = [ - "self_cell 1.2.0", -] - [[package]] name = "self_cell" version = "1.2.0" @@ -2023,24 +1998,34 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -2049,23 +2034,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -2119,9 +2105,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -2134,9 +2120,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2154,6 +2140,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -2222,17 +2218,17 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2241,7 +2237,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", ] [[package]] @@ -2255,6 +2260,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -2266,9 +2282,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -2281,15 +2297,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -2307,26 +2323,28 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", - "windows-sys 0.52.0", + "slab", + "socket2 0.6.0", + "windows-sys 0.59.0", ] [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -2337,11 +2355,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.0" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -2350,18 +2368,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] @@ -2398,7 +2416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.69", "time", "tracing-subscriber", ] @@ -2447,14 +2465,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "serde", "serde_json", "sharded-slab", @@ -2472,7 +2490,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash", ] [[package]] @@ -2532,9 +2550,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-xid" @@ -2554,13 +2572,14 @@ dependencies = [ [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2577,9 +2596,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -2622,30 +2641,40 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", @@ -2657,9 +2686,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2667,9 +2696,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", @@ -2680,53 +2709,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.2.0", "windows-result", "windows-strings", ] @@ -2760,21 +2767,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows-result" -version = "0.3.4" +name = "windows-link" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -2804,6 +2817,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2826,7 +2848,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2935,21 +2957,18 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -2983,18 +3002,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -3035,9 +3054,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -3075,9 +3094,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", From f8202aef2edc19d860a3b98cad5aabaaf4a8c3a4 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 20 Sep 2025 18:07:37 +0200 Subject: [PATCH 131/224] =?UTF-8?q?=F0=9F=94=A8=20(tools):=20Homogeiniza?= =?UTF-8?q?=20inputs=20de=20distintas=20fuentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/changelog.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/changelog.sh b/tools/changelog.sh index 211e896a..722cdb7f 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -88,12 +88,13 @@ git-cliff --unreleased "${COMMON_ARGS[@]}" echo "CHANGELOG generated at '$CHANGELOG_FILE'" # Pregunta por la revisión del archivo de cambios generado -read -p "Do you want to review the changelog before continuing? (y/n) " -r || exit 1 -echo +echo "Do you want to review the changelog before continuing? [y/N]" +read -r REPLY if [[ "$REPLY" =~ ^[Yy]$ ]]; then ${EDITOR:-nano} "$CHANGELOG_FILE" fi -read -p "Do you want to proceed with the release of $CRATE? (y/n) " -r || exit 1 +echo "Do you want to proceed with the release of $CRATE? [y/N]" +read -r REPLY echo if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then echo "Aborting release process." >&2 From ce4557684e278fbd44103043c7269e09b4231c57 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 25 Sep 2025 21:36:37 +0200 Subject: [PATCH 132/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`TypedSlot`?= =?UTF-8?q?=20por=20`TypedOpt`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component.rs | 4 +-- src/core/component/optional.rs | 59 +++++++++++++++++++++++++++++++ src/core/component/slot.rs | 64 ---------------------------------- src/html.rs | 6 ++-- 4 files changed, 64 insertions(+), 69 deletions(-) create mode 100644 src/core/component/optional.rs delete mode 100644 src/core/component/slot.rs diff --git a/src/core/component.rs b/src/core/component.rs index 36914722..e5858cbf 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -8,5 +8,5 @@ pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; -mod slot; -pub use slot::TypedSlot; +mod optional; +pub use optional::TypedOpt; diff --git a/src/core/component/optional.rs b/src/core/component/optional.rs new file mode 100644 index 00000000..c13b8335 --- /dev/null +++ b/src/core/component/optional.rs @@ -0,0 +1,59 @@ +use crate::core::component::{Component, Typed}; +use crate::html::{html, Context, Markup}; +use crate::{builder_fn, AutoDefault}; + +/// Contenedor **opcional** para un componente [`Typed`]. +/// +/// Un `TypedOpt` actúa como un contenedor para incluir o no un subcomponente tipado. Internamente +/// encapsula `Option>`, pero ofrece una API más sencilla para construir estructuras +/// jerárquicas o contenidas de componentes. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// let icon = Icon::default(); +/// let icon = TypedOpt::new(icon); +/// assert!(icon.get().is_some()); +/// ``` +#[derive(AutoDefault)] +pub struct TypedOpt(Option>); + +impl TypedOpt { + /// Crea un nuevo [`TypedOpt`]. + /// + /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. + pub fn new(component: C) -> Self { + TypedOpt(Some(Typed::with(component))) + } + + // TypedOpt BUILDER **************************************************************************** + + /// Establece un componente nuevo, o lo vacía. + /// + /// Si se proporciona `Some(component)`, se guarda como [`Typed`]; y si es `None`, se limpia. + #[builder_fn] + pub fn with_component(mut self, component: Option) -> Self { + self.0 = component.map(Typed::with); + self + } + + // TypedOpt GETTERS **************************************************************************** + + /// Devuelve un clon (incrementa el contador `Arc`) de [`Typed`], si existe. + pub fn get(&self) -> Option> { + self.0.clone() + } + + // TypedOpt RENDER ***************************************************************************** + + /// Renderiza el componente, si existe. + pub fn render(&self, cx: &mut Context) -> Markup { + if let Some(component) = &self.0 { + component.render(cx) + } else { + html! {} + } + } +} diff --git a/src/core/component/slot.rs b/src/core/component/slot.rs deleted file mode 100644 index 19ed72ac..00000000 --- a/src/core/component/slot.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::builder_fn; -use crate::core::component::{Component, Typed}; -use crate::html::{html, Context, Markup}; - -/// Contenedor para un componente [`Typed`] opcional. -/// -/// Un `TypedSlot` actúa como un contenedor dentro de otro componente para incluir o no un -/// subcomponente. Internamente encapsula `Option>`, pero proporciona una API más sencilla -/// para construir estructuras jerárquicas. -/// -/// # Ejemplo -/// -/// ```rust,ignore -/// use pagetop::prelude::*; -/// -/// let comp = MyComponent::new(); -/// let opt = TypedSlot::new(comp); -/// assert!(opt.get().is_some()); -/// ``` -pub struct TypedSlot(Option>); - -impl Default for TypedSlot { - fn default() -> Self { - TypedSlot(None) - } -} - -impl TypedSlot { - /// Crea un nuevo [`TypedSlot`]. - /// - /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. - pub fn new(component: C) -> Self { - TypedSlot(Some(Typed::with(component))) - } - - // TypedSlot BUILDER ********************************************************************* - - /// Establece un componente nuevo, o lo vacía. - /// - /// Si se proporciona `Some(component)`, se guarda en [`Typed`]; y si es `None`, se limpia. - #[builder_fn] - pub fn with_value(mut self, component: Option) -> Self { - self.0 = component.map(Typed::with); - self - } - - // TypedSlot GETTERS ********************************************************************* - - /// Devuelve un clon (incrementa el contador `Arc`) de [`Typed`], si existe. - pub fn get(&self) -> Option> { - self.0.clone() - } - - // TypedSlot RENDER ************************************************************************ - - /// Renderiza el componente, si existe. - pub fn render(&self, cx: &mut Context) -> Markup { - if let Some(component) = &self.0 { - component.render(cx) - } else { - html! {} - } - } -} diff --git a/src/html.rs b/src/html.rs index 4858bbfa..89d19d4e 100644 --- a/src/html.rs +++ b/src/html.rs @@ -50,14 +50,14 @@ pub type OptionClasses = AttrClasses; use crate::{core, AutoDefault}; -/// **Obsoleto desde la versión 0.4.0**: usar [`TypedSlot`](crate::core::component::TypedSlot) en su +/// **Obsoleto desde la versión 0.4.0**: usar [`TypedOpt`](crate::core::component::TypedOpt) en su /// lugar. #[deprecated( since = "0.4.0", - note = "Use `pagetop::core::component::TypedSlot` instead" + note = "Use `pagetop::core::component::TypedOpt` instead" )] #[allow(type_alias_bounds)] -pub type OptionComponent = core::component::TypedSlot; +pub type OptionComponent = core::component::TypedOpt; /// Prepara contenido HTML para su conversión a [`Markup`]. /// From 2ac3c2a9c4a0210a13c8745d2277f859c2e57579 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 27 Sep 2025 21:18:54 +0200 Subject: [PATCH 133/224] =?UTF-8?q?=F0=9F=94=A5=20Elimina=20definitivament?= =?UTF-8?q?e=20`TypedOpt`=20por=20`Typed`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component.rs | 3 -- src/core/component/children.rs | 77 +++++++++++++++++++++++----------- src/core/component/optional.rs | 59 -------------------------- src/html.rs | 7 ++-- 4 files changed, 56 insertions(+), 90 deletions(-) delete mode 100644 src/core/component/optional.rs diff --git a/src/core/component.rs b/src/core/component.rs index e5858cbf..17b9b735 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -7,6 +7,3 @@ mod children; pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; - -mod optional; -pub use optional::TypedOpt; diff --git a/src/core/component/children.rs b/src/core/component/children.rs index cb112e16..2b16f068 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -1,6 +1,6 @@ use crate::core::component::Component; use crate::html::{html, Context, Markup}; -use crate::{builder_fn, UniqueId}; +use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; @@ -11,76 +11,105 @@ use std::vec::IntoIter; /// /// Esta estructura permite manipular y renderizar un componente que implemente [`Component`], y /// habilita acceso concurrente mediante [`Arc>`]. -#[derive(Clone)] -pub struct Child(Arc>); +#[derive(AutoDefault, Clone)] +pub struct Child(Option>>); impl Child { /// Crea un nuevo `Child` a partir de un componente. pub fn with(component: impl Component) -> Self { - Child(Arc::new(RwLock::new(component))) + Child(Some(Arc::new(RwLock::new(component)))) + } + + // Child BUILDER ******************************************************************************* + + /// Establece un componente nuevo, o lo vacía. + /// + /// Si se proporciona `Some(component)`, se encapsula como [`Child`]; y si es `None`, se limpia. + #[builder_fn] + pub fn with_component(mut self, component: Option) -> Self { + if let Some(c) = component { + self.0 = Some(Arc::new(RwLock::new(c))); + } else { + self.0 = None; + } + self } // Child GETTERS ******************************************************************************* - /// Devuelve el identificador del componente, si está definido. + /// Devuelve el identificador del componente, si existe y está definido. + #[inline] pub fn id(&self) -> Option { - self.0.read().id() + self.0.as_ref().and_then(|c| c.read().id()) } // Child RENDER ******************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { - self.0.write().render(cx) + self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) } // Child HELPERS ******************************************************************************* - // Devuelve el [`UniqueId`] del tipo del componente. - fn type_id(&self) -> UniqueId { - self.0.read().type_id() + // Devuelve el [`UniqueId`] del tipo del componente, si existe. + #[inline] + fn type_id(&self) -> Option { + self.0.as_ref().map(|c| c.read().type_id()) } } // ************************************************************************************************* -/// Variante tipada de [`Child`] para evitar conversiones durante el uso. +/// Variante tipada de [`Child`] para evitar conversiones de tipo durante el uso. /// /// Esta estructura permite manipular y renderizar un componente concreto que implemente /// [`Component`], y habilita acceso concurrente mediante [`Arc>`]. -pub struct Typed(Arc>); - -impl Clone for Typed { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} +#[derive(AutoDefault, Clone)] +pub struct Typed(Option>>); impl Typed { /// Crea un nuevo `Typed` a partir de un componente. pub fn with(component: C) -> Self { - Typed(Arc::new(RwLock::new(component))) + Typed(Some(Arc::new(RwLock::new(component)))) + } + + // Typed BUILDER ******************************************************************************* + + /// Establece un componente nuevo, o lo vacía. + /// + /// Si se proporciona `Some(component)`, se encapsula como [`Typed`]; y si es `None`, se limpia. + #[builder_fn] + pub fn with_component(mut self, component: Option) -> Self { + self.0 = component.map(|c| Arc::new(RwLock::new(c))); + self } // Typed GETTERS ******************************************************************************* - /// Devuelve el identificador del componente, si está definido. + /// Devuelve el identificador del componente, si existe y está definido. + #[inline] pub fn id(&self) -> Option { - self.0.read().id() + self.0.as_ref().and_then(|c| c.read().id()) } // Typed RENDER ******************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { - self.0.write().render(cx) + self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) } // Typed HELPERS ******************************************************************************* // Convierte el componente tipado en un [`Child`]. + #[inline] fn into_child(self) -> Child { - Child(self.0.clone()) + if let Some(c) = &self.0 { + Child(Some(c.clone())) + } else { + Child(None) + } } } @@ -201,7 +230,7 @@ impl Children { /// Devuelve un iterador sobre los componentes hijo con el identificador de tipo ([`UniqueId`]) /// indicado. pub fn iter_by_type_id(&self, type_id: UniqueId) -> impl Iterator { - self.0.iter().filter(move |&c| c.type_id() == type_id) + self.0.iter().filter(move |&c| c.type_id() == Some(type_id)) } // Children RENDER ***************************************************************************** diff --git a/src/core/component/optional.rs b/src/core/component/optional.rs deleted file mode 100644 index c13b8335..00000000 --- a/src/core/component/optional.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::core::component::{Component, Typed}; -use crate::html::{html, Context, Markup}; -use crate::{builder_fn, AutoDefault}; - -/// Contenedor **opcional** para un componente [`Typed`]. -/// -/// Un `TypedOpt` actúa como un contenedor para incluir o no un subcomponente tipado. Internamente -/// encapsula `Option>`, pero ofrece una API más sencilla para construir estructuras -/// jerárquicas o contenidas de componentes. -/// -/// # Ejemplo -/// -/// ```rust -/// use pagetop::prelude::*; -/// -/// let icon = Icon::default(); -/// let icon = TypedOpt::new(icon); -/// assert!(icon.get().is_some()); -/// ``` -#[derive(AutoDefault)] -pub struct TypedOpt(Option>); - -impl TypedOpt { - /// Crea un nuevo [`TypedOpt`]. - /// - /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. - pub fn new(component: C) -> Self { - TypedOpt(Some(Typed::with(component))) - } - - // TypedOpt BUILDER **************************************************************************** - - /// Establece un componente nuevo, o lo vacía. - /// - /// Si se proporciona `Some(component)`, se guarda como [`Typed`]; y si es `None`, se limpia. - #[builder_fn] - pub fn with_component(mut self, component: Option) -> Self { - self.0 = component.map(Typed::with); - self - } - - // TypedOpt GETTERS **************************************************************************** - - /// Devuelve un clon (incrementa el contador `Arc`) de [`Typed`], si existe. - pub fn get(&self) -> Option> { - self.0.clone() - } - - // TypedOpt RENDER ***************************************************************************** - - /// Renderiza el componente, si existe. - pub fn render(&self, cx: &mut Context) -> Markup { - if let Some(component) = &self.0 { - component.render(cx) - } else { - html! {} - } - } -} diff --git a/src/html.rs b/src/html.rs index 89d19d4e..4a965cab 100644 --- a/src/html.rs +++ b/src/html.rs @@ -50,14 +50,13 @@ pub type OptionClasses = AttrClasses; use crate::{core, AutoDefault}; -/// **Obsoleto desde la versión 0.4.0**: usar [`TypedOpt`](crate::core::component::TypedOpt) en su -/// lugar. +/// **Obsoleto desde la versión 0.4.0**: usar [`Typed`](crate::core::component::Typed) en su lugar. #[deprecated( since = "0.4.0", - note = "Use `pagetop::core::component::TypedOpt` instead" + note = "Use `pagetop::core::component::Typed` instead" )] #[allow(type_alias_bounds)] -pub type OptionComponent = core::component::TypedOpt; +pub type OptionComponent = core::component::Typed; /// Prepara contenido HTML para su conversión a [`Markup`]. /// From 31435f30d3b53d9219dc4ce27384b9816d7fa4fc Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 28 Sep 2025 08:51:21 +0200 Subject: [PATCH 134/224] =?UTF-8?q?=F0=9F=92=A1=20Mejora=20legibilidad=20d?= =?UTF-8?q?e=20comentarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/block.rs | 6 +++--- src/base/component/html.rs | 4 ++-- src/base/component/poweredby.rs | 4 ++-- src/core/action/all.rs | 6 +++--- src/core/component/children.rs | 24 ++++++++++++------------ src/core/extension/all.rs | 10 +++++----- src/core/theme/all.rs | 6 +++--- src/html.rs | 6 +++--- src/html/assets/favicon.rs | 4 +++- src/html/assets/javascript.rs | 4 +++- src/html/assets/stylesheet.rs | 6 ++++-- src/html/attr_classes.rs | 4 ++-- src/html/attr_id.rs | 4 ++-- src/html/attr_l10n.rs | 4 ++-- src/html/attr_name.rs | 4 ++-- src/html/attr_value.rs | 4 ++-- src/html/context.rs | 16 ++++++++-------- src/lib.rs | 6 +++--- src/response/page.rs | 12 ++++++------ src/util.rs | 6 +++--- tests/component_poweredby.rs | 2 +- 21 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/base/component/block.rs b/src/base/component/block.rs index c96f2baf..9a04c4ea 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -47,7 +47,7 @@ impl Component for Block { } impl Block { - // Block BUILDER ******************************************************************************* + // **< Block BUILDER >************************************************************************** /// Establece el identificador único (`id`) del bloque. #[builder_fn] @@ -77,14 +77,14 @@ impl Block { self } - /// Modifica la lista de hijos (`children`) aplicando una operación. + /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. #[builder_fn] pub fn with_child(mut self, op: ChildOp) -> Self { self.children.alter_child(op); self } - // Block GETTERS ******************************************************************************* + // **< Block GETTERS >************************************************************************** /// Devuelve las clases CSS asociadas al bloque. pub fn classes(&self) -> &AttrClasses { diff --git a/src/base/component/html.rs b/src/base/component/html.rs index 8fa56902..b8c4aaaa 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -49,7 +49,7 @@ impl Component for Html { } impl Html { - // Html BUILDER ******************************************************************************** + // **< Html BUILDER >*************************************************************************** /// Crea una instancia que generará el `Markup`, con acceso opcional al contexto. /// @@ -77,7 +77,7 @@ impl Html { self } - // Html GETTERS ******************************************************************************** + // **< Html GETTERS >*************************************************************************** /// Aplica la función interna de renderizado con el [`Context`] proporcionado. /// diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index bfe38351..4b54af36 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -39,7 +39,7 @@ impl Component for PoweredBy { } impl PoweredBy { - // PoweredBy BUILDER *************************************************************************** + // **< PoweredBy BUILDER >********************************************************************** /// Establece el texto de copyright que mostrará el componente. /// @@ -58,7 +58,7 @@ impl PoweredBy { self } - // PoweredBy GETTERS *************************************************************************** + // **< PoweredBy GETTERS >********************************************************************** /// Devuelve el texto de copyright actual, si existe. pub fn copyright(&self) -> Option<&str> { diff --git a/src/core/action/all.rs b/src/core/action/all.rs index 7fff970c..fbbf8427 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -5,12 +5,12 @@ use parking_lot::RwLock; use std::collections::HashMap; use std::sync::LazyLock; -// ACCIONES **************************************************************************************** +// **< ACCIONES >*********************************************************************************** static ACTIONS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); -// AÑADIR ACCIONES ********************************************************************************* +// **< AÑADIR ACCIONES >**************************************************************************** // Registra una nueva acción en el sistema. // @@ -36,7 +36,7 @@ pub fn add_action(action: ActionBox) { } } -// DESPLEGAR ACCIONES ****************************************************************************** +// **< DESPLEGAR ACCIONES >************************************************************************* /// Despacha y ejecuta las funciones asociadas a una [`ActionKey`]. /// diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 2b16f068..920dacf8 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -20,7 +20,7 @@ impl Child { Child(Some(Arc::new(RwLock::new(component)))) } - // Child BUILDER ******************************************************************************* + // **< Child BUILDER >************************************************************************** /// Establece un componente nuevo, o lo vacía. /// @@ -35,7 +35,7 @@ impl Child { self } - // Child GETTERS ******************************************************************************* + // **< Child GETTERS >************************************************************************** /// Devuelve el identificador del componente, si existe y está definido. #[inline] @@ -43,14 +43,14 @@ impl Child { self.0.as_ref().and_then(|c| c.read().id()) } - // Child RENDER ******************************************************************************** + // **< Child RENDER >*************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) } - // Child HELPERS ******************************************************************************* + // **< Child HELPERS >************************************************************************** // Devuelve el [`UniqueId`] del tipo del componente, si existe. #[inline] @@ -74,7 +74,7 @@ impl Typed { Typed(Some(Arc::new(RwLock::new(component)))) } - // Typed BUILDER ******************************************************************************* + // **< Typed BUILDER >************************************************************************** /// Establece un componente nuevo, o lo vacía. /// @@ -85,7 +85,7 @@ impl Typed { self } - // Typed GETTERS ******************************************************************************* + // **< Typed GETTERS >************************************************************************** /// Devuelve el identificador del componente, si existe y está definido. #[inline] @@ -93,14 +93,14 @@ impl Typed { self.0.as_ref().and_then(|c| c.read().id()) } - // Typed RENDER ******************************************************************************** + // **< Typed RENDER >*************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) } - // Typed HELPERS ******************************************************************************* + // **< Typed HELPERS >************************************************************************** // Convierte el componente tipado en un [`Child`]. #[inline] @@ -165,7 +165,7 @@ impl Children { opt } - // Children BUILDER **************************************************************************** + // **< Children BUILDER >*********************************************************************** /// Ejecuta una operación con [`ChildOp`] en la lista. #[builder_fn] @@ -204,7 +204,7 @@ impl Children { self } - // Children GETTERS **************************************************************************** + // **< Children GETTERS >*********************************************************************** /// Devuelve el número de componentes hijo de la lista. pub fn len(&self) -> usize { @@ -233,7 +233,7 @@ impl Children { self.0.iter().filter(move |&c| c.type_id() == Some(type_id)) } - // Children RENDER ***************************************************************************** + // **< Children RENDER >************************************************************************ /// Renderiza todos los componentes hijo, en orden. pub fn render(&self, cx: &mut Context) -> Markup { @@ -244,7 +244,7 @@ impl Children { } } - // Children HELPERS **************************************************************************** + // **< Children HELPERS >*********************************************************************** // Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra. #[inline] diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index a243778c..fa67671d 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -7,7 +7,7 @@ use parking_lot::RwLock; use std::sync::LazyLock; -// EXTENSIONES ************************************************************************************* +// **< EXTENSIONES >******************************************************************************** static ENABLED_EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); @@ -15,7 +15,7 @@ static ENABLED_EXTENSIONS: LazyLock>> = static DROPPED_EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); -// REGISTRO DE LAS EXTENSIONES ********************************************************************* +// **< REGISTRO DE LAS EXTENSIONES >**************************************************************** pub fn register_extensions(root_extension: Option) { // Prepara la lista de extensiones habilitadas. @@ -104,7 +104,7 @@ fn add_to_dropped(list: &mut Vec, extension: ExtensionRef) { } } -// REGISTRO DE LAS ACCIONES ************************************************************************ +// **< REGISTRO DE LAS ACCIONES >******************************************************************* pub fn register_actions() { for extension in ENABLED_EXTENSIONS.read().iter() { @@ -114,7 +114,7 @@ pub fn register_actions() { } } -// INICIALIZA LAS EXTENSIONES ********************************************************************** +// **< INICIALIZA LAS EXTENSIONES >***************************************************************** pub fn initialize_extensions() { trace::info!("Calling application bootstrap"); @@ -123,7 +123,7 @@ pub fn initialize_extensions() { } } -// CONFIGURA LOS SERVICIOS ************************************************************************* +// **< CONFIGURA LOS SERVICIOS >******************************************************************** pub fn configure_services(scfg: &mut service::web::ServiceConfig) { // Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos. diff --git a/src/core/theme/all.rs b/src/core/theme/all.rs index ebb28481..5e6b65b0 100644 --- a/src/core/theme/all.rs +++ b/src/core/theme/all.rs @@ -5,11 +5,11 @@ use parking_lot::RwLock; use std::sync::LazyLock; -// TEMAS ******************************************************************************************* +// **< TEMAS >************************************************************************************** pub static THEMES: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); -// TEMA PREDETERMINADO ***************************************************************************** +// **< TEMA PREDETERMINADO >************************************************************************ pub static DEFAULT_THEME: LazyLock = LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) { @@ -17,7 +17,7 @@ pub static DEFAULT_THEME: LazyLock = None => &crate::base::theme::Basic, }); -// TEMA POR NOMBRE ********************************************************************************* +// **< TEMA POR NOMBRE >**************************************************************************** // Devuelve el tema identificado por su [`short_name()`](AnyInfo::short_name). pub fn theme_by_short_name(short_name: &'static str) -> Option { diff --git a/src/html.rs b/src/html.rs index 4a965cab..b6815ece 100644 --- a/src/html.rs +++ b/src/html.rs @@ -3,7 +3,7 @@ mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; -// HTML DOCUMENT ASSETS **************************************************************************** +// **< HTML DOCUMENT ASSETS >*********************************************************************** mod assets; pub use assets::favicon::Favicon; @@ -11,12 +11,12 @@ pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; pub use assets::{Asset, Assets}; -// HTML DOCUMENT CONTEXT *************************************************************************** +// **< HTML DOCUMENT CONTEXT >********************************************************************** mod context; pub use context::{AssetsOp, Context, Contextual, ErrorParam}; -// HTML ATTRIBUTES ********************************************************************************* +// **< HTML ATTRIBUTES >**************************************************************************** mod attr_id; pub use attr_id::AttrId; diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index d731b8f5..56c39056 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -52,7 +52,7 @@ impl Favicon { Favicon::default() } - // Favicon BUILDER ***************************************************************************** + // **< Favicon BUILDER >************************************************************************ /// Le añade un icono genérico apuntando a `image`. El tipo MIME se infiere automáticamente a /// partir de la extensión. @@ -152,6 +152,8 @@ impl Favicon { self } + // **< Favicon RENDER >************************************************************************* + /// Renderiza el **Favicon** completo con todas las etiquetas declaradas. /// /// El parámetro `Context` se acepta por coherencia con el resto de *assets*, aunque en este diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index a8ed3e8c..4649cdb4 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -171,7 +171,7 @@ impl JavaScript { } } - // JavaScript BUILDER ************************************************************************** + // **< JavaScript BUILDER >********************************************************************* /// Asocia una **versión** al recurso (usada para control de la caché del navegador). /// @@ -210,6 +210,8 @@ impl Asset for JavaScript { self.weight } + // **< JavaScript RENDER >********************************************************************** + fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 3ecc77fc..b54f4cf3 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -113,7 +113,7 @@ impl StyleSheet { } } - // StyleSheet BUILDER ************************************************************************** + // **< StyleSheet BUILDER >********************************************************************* /// Asocia una versión al recurso (usada para control de la caché del navegador). /// @@ -132,7 +132,7 @@ impl StyleSheet { self } - // StyleSheet EXTRAS *************************************************************************** + // **< StyleSheet HELPERS >********************************************************************* /// Especifica el medio donde se aplican los estilos. /// @@ -163,6 +163,8 @@ impl Asset for StyleSheet { self.weight } + // **< StyleSheet RENDER >********************************************************************** + fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 098c26cd..80fdad79 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -48,7 +48,7 @@ impl AttrClasses { AttrClasses::default().with_value(ClassesOp::Prepend, classes) } - // AttrClasses BUILDER ************************************************************************* + // **< AttrClasses BUILDER >******************************************************************** #[builder_fn] pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef) -> Self { @@ -114,7 +114,7 @@ impl AttrClasses { } } - // AttrClasses GETTERS ************************************************************************* + // **< AttrClasses GETTERS >******************************************************************** /// Devuelve la cadena de clases, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_id.rs b/src/html/attr_id.rs index 8bb1d33b..3d5f3eb4 100644 --- a/src/html/attr_id.rs +++ b/src/html/attr_id.rs @@ -29,7 +29,7 @@ impl AttrId { AttrId::default().with_value(value) } - // AttrId BUILDER ****************************************************************************** + // **< AttrId BUILDER >************************************************************************* /// Establece un identificador nuevo normalizando el valor. #[builder_fn] @@ -39,7 +39,7 @@ impl AttrId { self } - // AttrId GETTERS ****************************************************************************** + // **< AttrId GETTERS >************************************************************************* /// Devuelve el identificador normalizado, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 8250c742..37fc80fa 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -39,7 +39,7 @@ impl AttrL10n { AttrL10n(value) } - // AttrL10n BUILDER **************************************************************************** + // **< AttrL10n BUILDER >*********************************************************************** /// Establece una traducción nueva. #[builder_fn] @@ -48,7 +48,7 @@ impl AttrL10n { self } - // AttrL10n GETTERS **************************************************************************** + // **< AttrL10n GETTERS >*********************************************************************** /// Devuelve la traducción para `language`, si existe. pub fn lookup(&self, language: &impl LangId) -> Option { diff --git a/src/html/attr_name.rs b/src/html/attr_name.rs index 928f841f..9bc9659e 100644 --- a/src/html/attr_name.rs +++ b/src/html/attr_name.rs @@ -29,7 +29,7 @@ impl AttrName { AttrName::default().with_value(value) } - // AttrName BUILDER **************************************************************************** + // **< AttrName BUILDER >*********************************************************************** /// Establece un nombre nuevo normalizando el valor. #[builder_fn] @@ -39,7 +39,7 @@ impl AttrName { self } - // AttrName GETTERS **************************************************************************** + // **< AttrName GETTERS >*********************************************************************** /// Devuelve el nombre normalizado, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_value.rs b/src/html/attr_value.rs index 4e03120b..eff80660 100644 --- a/src/html/attr_value.rs +++ b/src/html/attr_value.rs @@ -27,7 +27,7 @@ impl AttrValue { AttrValue::default().with_value(value) } - // AttrValue BUILDER *************************************************************************** + // **< AttrValue BUILDER >********************************************************************** /// Establece una cadena nueva normalizando el valor. #[builder_fn] @@ -41,7 +41,7 @@ impl AttrValue { self } - // AttrValue GETTERS *************************************************************************** + // **< AttrValue GETTERS >********************************************************************** /// Devuelve la cadena normalizada, si existe. pub fn get(&self) -> Option { diff --git a/src/html/context.rs b/src/html/context.rs index 7b782681..355ccead 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -78,7 +78,7 @@ pub enum ErrorParam { /// } /// ``` pub trait Contextual: LangId { - // Contextual BUILDER ************************************************************************** + // **< Contextual BUILDER >********************************************************************* /// Establece el idioma del documento. #[builder_fn] @@ -104,7 +104,7 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: AssetsOp) -> Self; - // Contextual GETTERS ************************************************************************** + // **< Contextual GETTERS >********************************************************************* /// Devuelve una referencia a la solicitud HTTP asociada, si existe. fn request(&self) -> Option<&HttpRequest>; @@ -142,7 +142,7 @@ pub trait Contextual: LangId { /// Devuelve los scripts JavaScript de los recursos del contexto. fn javascripts(&self) -> &Assets; - // Contextual HELPERS ************************************************************************** + // **< Contextual HELPERS >********************************************************************* /// Genera un identificador único por tipo (`-`) cuando no se aporta uno explícito. /// @@ -255,7 +255,7 @@ impl Context { } } - // Context RENDER ****************************************************************************** + // **< Context RENDER >************************************************************************* /// Renderiza los recursos del contexto. pub fn render_assets(&mut self) -> Markup { @@ -283,7 +283,7 @@ impl Context { markup } - // Context PARAMS ****************************************************************************** + // **< Context PARAMS >************************************************************************* /// Recupera una *referencia tipada* al parámetro solicitado. /// @@ -389,7 +389,7 @@ impl LangId for Context { } impl Contextual for Context { - // Contextual BUILDER ************************************************************************** + // **< Contextual BUILDER >********************************************************************* #[builder_fn] fn with_request(mut self, request: Option) -> Self { @@ -471,7 +471,7 @@ impl Contextual for Context { self } - // Contextual GETTERS ************************************************************************** + // **< Contextual GETTERS >********************************************************************* fn request(&self) -> Option<&HttpRequest> { self.request.as_ref() @@ -530,7 +530,7 @@ impl Contextual for Context { &self.javascripts } - // Contextual HELPERS ************************************************************************** + // **< Contextual HELPERS >********************************************************************* /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona /// un `id` explícito. diff --git a/src/lib.rs b/src/lib.rs index 1c1ba2c1..6f5c5cfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ extern crate self as pagetop; use std::collections::HashMap; use std::ops::Deref; -// RE-EXPORTED ************************************************************************************* +// **< RE-EXPORTED >******************************************************************************** pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; @@ -136,7 +136,7 @@ pub type UniqueId = std::any::TypeId; /// antes en la ordenación. pub type Weight = i8; -// API ********************************************************************************************* +// **< API >**************************************************************************************** // Macros y funciones útiles. pub mod util; @@ -163,6 +163,6 @@ pub mod base; // Prepara y ejecuta la aplicación. pub mod app; -// PRELUDE ***************************************************************************************** +// **< PRELUDE >************************************************************************************ pub mod prelude; diff --git a/src/response/page.rs b/src/response/page.rs index 2dc27f9f..e9d30671 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -52,7 +52,7 @@ impl Page { } } - // Page BUILDER ******************************************************************************** + // **< Page BUILDER >*************************************************************************** /// Establece el título de la página como un valor traducible. #[builder_fn] @@ -151,7 +151,7 @@ impl Page { self } - // Page GETTERS ******************************************************************************** + // **< Page GETTERS >*************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. pub fn title(&mut self) -> Option { @@ -192,7 +192,7 @@ impl Page { &mut self.context } - // Page RENDER ********************************************************************************* + // **< Page RENDER >**************************************************************************** /// Renderiza los componentes de una región (`region_name`) de la página. pub fn render_region(&mut self, region_name: &'static str) -> Markup { @@ -253,7 +253,7 @@ impl LangId for Page { } impl Contextual for Page { - // Contextual BUILDER ************************************************************************** + // **< Contextual BUILDER >********************************************************************* #[builder_fn] fn with_request(mut self, request: Option) -> Self { @@ -291,7 +291,7 @@ impl Contextual for Page { self } - // Contextual GETTERS ************************************************************************** + // **< Contextual GETTERS >********************************************************************* fn request(&self) -> Option<&HttpRequest> { self.context.request() @@ -321,7 +321,7 @@ impl Contextual for Page { self.context.javascripts() } - // Contextual HELPERS ************************************************************************** + // **< Contextual HELPERS >********************************************************************* fn required_id(&mut self, id: Option) -> String { self.context.required_id::(id) diff --git a/src/util.rs b/src/util.rs index cb101763..a4daf677 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,7 +6,7 @@ use std::env; use std::io; use std::path::{Path, PathBuf}; -// MACROS INTEGRADAS ******************************************************************************* +// **< MACROS INTEGRADAS >************************************************************************** #[doc(hidden)] pub use paste::paste; @@ -16,7 +16,7 @@ pub use concat_string::concat_string; pub use indoc::{concatdoc, formatdoc, indoc}; -// MACROS ÚTILES *********************************************************************************** +// **< MACROS ÚTILES >****************************************************************************** #[macro_export] /// Macro para construir una colección de pares clave-valor. @@ -198,7 +198,7 @@ macro_rules! join_strict { }}; } -// FUNCIONES ÚTILES ******************************************************************************** +// **< FUNCIONES ÚTILES >*************************************************************************** /// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. /// diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs index e4551d17..27683d95 100644 --- a/tests/component_poweredby.rs +++ b/tests/component_poweredby.rs @@ -90,7 +90,7 @@ async fn poweredby_getter_reflects_internal_state() { assert!(c1.contains(&global::SETTINGS.app.name)); } -// HELPERS ***************************************************************************************** +// **< HELPERS >************************************************************************************ fn render_component(c: &C) -> Markup { let mut cx = Context::default(); From df1f386241c3b2d8cb09de5bbaa115297bf91cbc Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 28 Sep 2025 13:46:02 +0200 Subject: [PATCH 135/224] =?UTF-8?q?=F0=9F=9A=A7=20(base):=20A=C3=B1ade=20n?= =?UTF-8?q?uevo=20componente=20`Icon`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component.rs | 3 + src/base/component/icon.rs | 134 +++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/base/component/icon.rs diff --git a/src/base/component.rs b/src/base/component.rs index 4df64ff1..5bbe746d 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -8,3 +8,6 @@ pub use block::Block; mod poweredby; pub use poweredby::PoweredBy; + +mod icon; +pub use icon::{Icon, IconKind}; diff --git a/src/base/component/icon.rs b/src/base/component/icon.rs new file mode 100644 index 00000000..73e5ac4e --- /dev/null +++ b/src/base/component/icon.rs @@ -0,0 +1,134 @@ +use crate::prelude::*; + +const DEFAULT_VIEWBOX: &str = "0 0 16 16"; + +#[derive(AutoDefault)] +pub enum IconKind { + #[default] + None, + Font(FontSize), + Svg { + shapes: Markup, + viewbox: AttrValue, + }, +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Icon { + classes : AttrClasses, + icon_kind : IconKind, + aria_label: AttrL10n, +} + +impl Component for Icon { + fn new() -> Self { + Icon::default() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + if !matches!(self.icon_kind(), IconKind::None) { + self.alter_classes(ClassesOp::Prepend, "icon"); + } + if let IconKind::Font(font_size) = self.icon_kind() { + self.alter_classes(ClassesOp::Add, font_size.as_str()); + } + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self.icon_kind() { + IconKind::None => PrepareMarkup::None, + IconKind::Font(_) => { + let aria_label = self.aria_label().lookup(cx); + let has_label = aria_label.is_some(); + PrepareMarkup::With(html! { + i + class=[self.classes().get()] + role=[has_label.then_some("img")] + aria-label=[aria_label] + aria-hidden=[(!has_label).then_some("true")] + {} + }) + } + IconKind::Svg { shapes, viewbox } => { + let aria_label = self.aria_label().lookup(cx); + let has_label = aria_label.is_some(); + let viewbox = viewbox.get().unwrap_or_else(|| DEFAULT_VIEWBOX.to_string()); + PrepareMarkup::With(html! { + svg + xmlns="http://www.w3.org/2000/svg" + viewBox=(viewbox) + fill="currentColor" + focusable="false" + class=[self.classes().get()] + role=[has_label.then_some("img")] + aria-label=[aria_label] + aria-hidden=[(!has_label).then_some("true")] + { + (shapes) + } + }) + } + } + } +} + +impl Icon { + pub fn font() -> Self { + Icon::default().with_icon_kind(IconKind::Font(FontSize::default())) + } + + pub fn font_sized(font_size: FontSize) -> Self { + Icon::default().with_icon_kind(IconKind::Font(font_size)) + } + + pub fn svg(shapes: Markup) -> Self { + Icon::default().with_icon_kind(IconKind::Svg { + shapes, + viewbox: AttrValue::default(), + }) + } + + pub fn svg_with_viewbox(shapes: Markup, viewbox: impl AsRef) -> Self { + Icon::default().with_icon_kind(IconKind::Svg { + shapes, + viewbox: AttrValue::new(viewbox), + }) + } + + // **< Icon BUILDER >*************************************************************************** + + /// Modifica la lista de clases CSS aplicadas al icono. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[builder_fn] + pub fn with_icon_kind(mut self, icon_kind: IconKind) -> Self { + self.icon_kind = icon_kind; + self + } + + #[builder_fn] + pub fn with_aria_label(mut self, label: L10n) -> Self { + self.aria_label.alter_value(label); + self + } + + // **< Icon GETTERS >*************************************************************************** + + /// Devuelve las clases CSS asociadas al icono. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + pub fn icon_kind(&self) -> &IconKind { + &self.icon_kind + } + + pub fn aria_label(&self) -> &AttrL10n { + &self.aria_label + } +} From 67a9b057ee172676934891035305001d921bb81b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 28 Sep 2025 13:47:33 +0200 Subject: [PATCH 136/224] =?UTF-8?q?=F0=9F=9A=A7=20(base):=20A=C3=B1ade=20n?= =?UTF-8?q?uevo=20componente=20`menu`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component.rs | 50 +++++ src/base/component/menu.rs | 17 ++ src/base/component/menu/element.rs | 56 +++++ src/base/component/menu/group.rs | 58 +++++ src/base/component/menu/item.rs | 183 ++++++++++++++++ src/base/component/menu/megamenu.rs | 57 +++++ src/base/component/menu/menu_menu.rs | 106 +++++++++ src/base/component/menu/submenu.rs | 73 +++++++ src/base/theme/basic.rs | 23 +- src/html.rs | 1 + static/css/basic.css | 6 - static/css/components.css | 12 ++ static/css/menu.css | 309 +++++++++++++++++++++++++++ static/css/root.css | 211 ++++++++++++++++++ static/js/menu.js | 94 ++++++++ 15 files changed, 1249 insertions(+), 7 deletions(-) create mode 100644 src/base/component/menu.rs create mode 100644 src/base/component/menu/element.rs create mode 100644 src/base/component/menu/group.rs create mode 100644 src/base/component/menu/item.rs create mode 100644 src/base/component/menu/megamenu.rs create mode 100644 src/base/component/menu/menu_menu.rs create mode 100644 src/base/component/menu/submenu.rs create mode 100644 static/css/components.css create mode 100644 static/css/menu.css create mode 100644 static/css/root.css create mode 100644 static/js/menu.js diff --git a/src/base/component.rs b/src/base/component.rs index 5bbe746d..4edfc9a9 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,5 +1,53 @@ //! Componentes nativos proporcionados por PageTop. +use crate::AutoDefault; + +use std::fmt; + +// **< FontSize >*********************************************************************************** + +#[derive(AutoDefault)] +pub enum FontSize { + ExtraLarge, + XxLarge, + XLarge, + Large, + Medium, + #[default] + Normal, + Small, + XSmall, + XxSmall, + ExtraSmall, +} + +#[rustfmt::skip] +impl FontSize { + #[inline] + pub const fn as_str(&self) -> &'static str { + match self { + FontSize::ExtraLarge => "fs__x3l", + FontSize::XxLarge => "fs__x2l", + FontSize::XLarge => "fs__xl", + FontSize::Large => "fs__l", + FontSize::Medium => "fs__m", + FontSize::Normal => "", + FontSize::Small => "fs__s", + FontSize::XSmall => "fs__xs", + FontSize::XxSmall => "fs__x2s", + FontSize::ExtraSmall => "fs__x3s", + } + } +} + +impl fmt::Display for FontSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +// ************************************************************************************************* + mod html; pub use html::Html; @@ -11,3 +59,5 @@ pub use poweredby::PoweredBy; mod icon; pub use icon::{Icon, IconKind}; + +pub mod menu; diff --git a/src/base/component/menu.rs b/src/base/component/menu.rs new file mode 100644 index 00000000..14f75898 --- /dev/null +++ b/src/base/component/menu.rs @@ -0,0 +1,17 @@ +mod menu_menu; +pub use menu_menu::Menu; + +mod item; +pub use item::{Item, ItemKind}; + +mod submenu; +pub use submenu::Submenu; + +mod megamenu; +pub use megamenu::Megamenu; + +mod group; +pub use group::Group; + +mod element; +pub use element::{Element, ElementType}; diff --git a/src/base/component/menu/element.rs b/src/base/component/menu/element.rs new file mode 100644 index 00000000..6d142048 --- /dev/null +++ b/src/base/component/menu/element.rs @@ -0,0 +1,56 @@ +use crate::prelude::*; + +type Content = Typed; +type SubmenuItems = Typed; + +#[derive(AutoDefault)] +pub enum ElementType { + #[default] + Void, + Html(Content), + Submenu(SubmenuItems), +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Element { + element_type: ElementType, +} + +impl Component for Element { + fn new() -> Self { + Element::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self.element_type() { + ElementType::Void => PrepareMarkup::None, + ElementType::Html(content) => PrepareMarkup::With(html! { + (content.render(cx)) + }), + ElementType::Submenu(submenu) => PrepareMarkup::With(html! { + (submenu.render(cx)) + }), + } + } +} + +impl Element { + pub fn html(content: Html) -> Self { + Element { + element_type: ElementType::Html(Content::with(content)), + } + } + + pub fn submenu(submenu: menu::Submenu) -> Self { + Element { + element_type: ElementType::Submenu(SubmenuItems::with(submenu)), + } + } + + // **< Element GETTERS >************************************************************************ + + pub fn element_type(&self) -> &ElementType { + &self.element_type + } +} diff --git a/src/base/component/menu/group.rs b/src/base/component/menu/group.rs new file mode 100644 index 00000000..41279b4e --- /dev/null +++ b/src/base/component/menu/group.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Group { + id : AttrId, + elements: Children, +} + +impl Component for Group { + fn new() -> Self { + Group::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + PrepareMarkup::With(html! { + div id=[self.id()] class="menu-group" { + (self.elements().render(cx)) + } + }) + } +} + +impl Group { + // **< Group BUILDER >************************************************************************** + + /// Establece el identificador único (`id`) del grupo. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_value(id); + self + } + + /// Añade un nuevo elemento al menú. + pub fn add_element(mut self, element: menu::Element) -> Self { + self.elements + .alter_typed(TypedOp::Add(Typed::with(element))); + self + } + + /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_elements(mut self, op: TypedOp) -> Self { + self.elements.alter_typed(op); + self + } + + // **< Group GETTERS >************************************************************************** + + /// Devuelve la lista de elementos (`children`) del grupo. + pub fn elements(&self) -> &Children { + &self.elements + } +} diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs new file mode 100644 index 00000000..07d629ae --- /dev/null +++ b/src/base/component/menu/item.rs @@ -0,0 +1,183 @@ +use crate::prelude::*; + +//use super::{Megamenu, Submenu}; + +type Label = L10n; +type Content = Typed; +type SubmenuItems = Typed; +//type MegamenuGroups = Typed; + +#[derive(AutoDefault)] +pub enum ItemKind { + #[default] + Void, + Label(Label), + Link(Label, FnPathByContext), + LinkBlank(Label, FnPathByContext), + Html(Content), + Submenu(Label, SubmenuItems), + // Megamenu(Label, MegamenuGroups), +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + item_kind : ItemKind, + description: AttrL10n, + left_icon : Typed, + right_icon : Typed, +} + +impl Component for Item { + fn new() -> Self { + Item::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let description = self.description().lookup(cx); + let left_icon = self.left_icon().render(cx); + let right_icon = self.right_icon().render(cx); + + match self.item_kind() { + ItemKind::Void => PrepareMarkup::None, + ItemKind::Label(label) => PrepareMarkup::With(html! { + li class="menu__label" { + span title=[description] { + (left_icon) + (label.using(cx)) + (right_icon) + } + } + }), + ItemKind::Link(label, path) => PrepareMarkup::With(html! { + li class="menu__link" { + a href=(path(cx)) title=[description] { + (left_icon) + (label.using(cx)) + (right_icon) + } + } + }), + ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! { + li class="menu__link" { + a href=(path(cx)) title=[description] target="_blank" { + (left_icon) + (label.using(cx)) + (right_icon) + } + } + }), + ItemKind::Html(content) => PrepareMarkup::With(html! { + li class="menu__html" { + (content.render(cx)) + } + }), + ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! { + li class="menu__children" { + a href="#" title=[description] { + (left_icon) + (label.using(cx)) i class="menu__icon bi-chevron-down" {} + } + div class="menu__subs" { + (submenu.render(cx)) + } + } + }), + /* + ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! { + li class="menu__children" { + a href="#" title=[description] { + (left_icon) + (label.escaped(cx.langid())) i class="menu__icon bi-chevron-down" {} + } + div class="menu__subs menu__mega" { + (megamenu.render(cx)) + } + } + }), + */ + } + } +} + +impl Item { + pub fn label(label: L10n) -> Self { + Item { + item_kind: ItemKind::Label(label), + ..Default::default() + } + } + + pub fn link(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link(label, path), + ..Default::default() + } + } + + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::LinkBlank(label, path), + ..Default::default() + } + } + + pub fn html(content: Html) -> Self { + Item { + item_kind: ItemKind::Html(Content::with(content)), + ..Default::default() + } + } + + pub fn submenu(label: L10n, submenu: menu::Submenu) -> Self { + Item { + item_kind: ItemKind::Submenu(label, SubmenuItems::with(submenu)), + ..Default::default() + } + } + /* + pub fn megamenu(label: L10n, megamenu: Megamenu) -> Self { + Item { + item_kind: ItemKind::Megamenu(label, MegamenuGroups::with(megamenu)), + ..Default::default() + } + } + */ + // **< Item BUILDER >*************************************************************************** + + #[builder_fn] + pub fn with_description(mut self, text: L10n) -> Self { + self.description.alter_value(text); + self + } + + #[builder_fn] + pub fn with_left_icon>(mut self, icon: Option) -> Self { + self.left_icon.alter_component(icon.map(Into::into)); + self + } + + #[builder_fn] + pub fn with_right_icon>(mut self, icon: Option) -> Self { + self.right_icon.alter_component(icon.map(Into::into)); + self + } + + // **< Item GETTERS >*************************************************************************** + + pub fn item_kind(&self) -> &ItemKind { + &self.item_kind + } + + pub fn description(&self) -> &AttrL10n { + &self.description + } + + pub fn left_icon(&self) -> &Typed { + &self.left_icon + } + + pub fn right_icon(&self) -> &Typed { + &self.right_icon + } +} diff --git a/src/base/component/menu/megamenu.rs b/src/base/component/menu/megamenu.rs new file mode 100644 index 00000000..f22b184e --- /dev/null +++ b/src/base/component/menu/megamenu.rs @@ -0,0 +1,57 @@ +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Megamenu { + id : AttrId, + groups: Children, +} + +impl Component for Megamenu { + fn new() -> Self { + Megamenu::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + PrepareMarkup::With(html! { + div id=[self.id()] class="menu__groups" { + (self.groups().render(cx)) + } + }) + } +} + +impl Megamenu { + // **< Megamenu BUILDER >*********************************************************************** + + /// Establece el identificador único (`id`) del megamenú. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_value(id); + self + } + + /// Añade un nuevo grupo al menú. + pub fn add_group(mut self, group: menu::Group) -> Self { + self.groups.alter_typed(TypedOp::Add(Typed::with(group))); + self + } + + /// Modifica la lista de grupos (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_groups(mut self, op: TypedOp) -> Self { + self.groups.alter_typed(op); + self + } + + // **< Megamenu GETTERS >*********************************************************************** + + /// Devuelve la lista de grupos (`children`) del megamenú. + pub fn groups(&self) -> &Children { + &self.groups + } +} diff --git a/src/base/component/menu/menu_menu.rs b/src/base/component/menu/menu_menu.rs new file mode 100644 index 00000000..a7f91be1 --- /dev/null +++ b/src/base/component/menu/menu_menu.rs @@ -0,0 +1,106 @@ +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Menu { + id : AttrId, + classes: AttrClasses, + items : Children, +} + +impl Component for Menu { + fn new() -> Self { + Menu::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "menu"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + // cx.set_param::(PARAM_BASE_INCLUDE_MENU_ASSETS, &true); + // cx.set_param::(PARAM_BASE_INCLUDE_ICONS, &true); + + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + div class="menu__wrapper" { + div class="menu__panel" { + div class="menu__overlay" {} + nav class="menu__nav" { + div class="menu__header" { + button type="button" class="menu__back" { + (Icon::svg(html! { + path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0" {} + }).render(cx)) + } + div class="menu__title" {} + button type="button" class="menu__close" { + (Icon::svg(html! { + path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" {} + }).render(cx)) + } + } + ul class="menu__list" { + (self.items().render(cx)) + } + } + } + button + type="button" + class="menu__trigger" + title=[L10n::l("menu_toggle").lookup(cx)] + { + span {} span {} span {} + } + } + } + }) + } +} + +impl Menu { + // **< Menu BUILDER >*************************************************************************** + + /// Establece el identificador único (`id`) del menú. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al menú. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Añade un nuevo ítem al menú. + pub fn add_item(mut self, item: menu::Item) -> Self { + self.items.alter_typed(TypedOp::Add(Typed::with(item))); + self + } + + /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); + self + } + + // **< Menu GETTERS >*************************************************************************** + + /// Devuelve las clases CSS asociadas al menú. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve la lista de ítems (`children`) del menú. + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/src/base/component/menu/submenu.rs b/src/base/component/menu/submenu.rs new file mode 100644 index 00000000..58770529 --- /dev/null +++ b/src/base/component/menu/submenu.rs @@ -0,0 +1,73 @@ +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Submenu { + id : AttrId, + title: AttrL10n, + items: Children, +} + +impl Component for Submenu { + fn new() -> Self { + Submenu::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + PrepareMarkup::With(html! { + div id=[self.id()] class="menu__items" { + @if let Some(title) = self.title().lookup(cx) { + h4 class="menu__title" { (title) } + } + ul { + (self.items().render(cx)) + } + } + }) + } +} + +impl Submenu { + // **< Submenu BUILDER >************************************************************************ + + /// Establece el identificador único (`id`) del submenú. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title.alter_value(title); + self + } + + /// Añade un nuevo ítem al submenú. + pub fn add_item(mut self, item: menu::Item) -> Self { + self.items.alter_typed(TypedOp::Add(Typed::with(item))); + self + } + + /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); + self + } + + // **< Submenu GETTERS >************************************************************************ + + pub fn title(&self) -> &AttrL10n { + &self.title + } + + /// Devuelve la lista de ítems (`children`) del submenú. + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 2f492748..20f51994 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -51,14 +51,35 @@ impl Theme for Basic { "PageTopIntro" => "/css/intro.css", _ => "/css/basic.css", }; + let pkg_version = env!("CARGO_PKG_VERSION"); page.alter_assets(AssetsOp::AddStyleSheet( StyleSheet::from("/css/normalize.css") .with_version("8.0.1") .with_weight(-99), )) + .alter_assets(AssetsOp::AddStyleSheet( + StyleSheet::from("/css/root.css") + .with_version(pkg_version) + .with_weight(-99), + )) + .alter_assets(AssetsOp::AddStyleSheet( + StyleSheet::from("/css/components.css") + .with_version(pkg_version) + .with_weight(-99), + )) + .alter_assets(AssetsOp::AddStyleSheet( + StyleSheet::from("/css/menu.css") + .with_version(pkg_version) + .with_weight(-99), + )) .alter_assets(AssetsOp::AddStyleSheet( StyleSheet::from(styles) - .with_version(env!("CARGO_PKG_VERSION")) + .with_version(pkg_version) + .with_weight(-99), + )) + .alter_assets(AssetsOp::AddJavaScript( + JavaScript::defer("/js/menu.js") + .with_version(pkg_version) .with_weight(-99), )); } diff --git a/src/html.rs b/src/html.rs index b6815ece..c4195a9d 100644 --- a/src/html.rs +++ b/src/html.rs @@ -15,6 +15,7 @@ pub use assets::{Asset, Assets}; mod context; pub use context::{AssetsOp, Context, Contextual, ErrorParam}; +pub type FnPathByContext = fn(cx: &Context) -> &str; // **< HTML ATTRIBUTES >**************************************************************************** diff --git a/static/css/basic.css b/static/css/basic.css index 312ddf09..04801dd0 100644 --- a/static/css/basic.css +++ b/static/css/basic.css @@ -3,9 +3,3 @@ .region--footer { padding-bottom: 2rem; } - -/* PoweredBy component */ - -.poweredby { - text-align: center; -} diff --git a/static/css/components.css b/static/css/components.css new file mode 100644 index 00000000..ec5d3f00 --- /dev/null +++ b/static/css/components.css @@ -0,0 +1,12 @@ +/* Icon component */ + +.icon { + width: 1rem; + height: 1rem; +} + +/* PoweredBy component */ + +.poweredby { + text-align: center; +} diff --git a/static/css/menu.css b/static/css/menu.css new file mode 100644 index 00000000..a8b2854c --- /dev/null +++ b/static/css/menu.css @@ -0,0 +1,309 @@ +.menu { + width: 100%; + height: auto; + margin: 0; + padding: 0; + z-index: 9999; + border: none; + outline: none; + background: var(--val-menu--color-bg); +} + +.menu__wrapper { + padding-right: var(--val-gap); +} +.menu__wrapper a, +.menu__wrapper button { + cursor: pointer; + border: none; + background: none; + text-decoration: none; +} + +.menu__nav ul { + margin: 0; + padding: 0; +} +.menu__nav li { + display: inline-block; + margin: 0 0 0 1.5rem; + padding: var(--val-menu--line-padding) 0; + line-height: var(--val-menu--line-height); + list-style: none; + list-style-type: none; +} + +.menu__nav li.menu__label, +.menu__nav li > a { + position: relative; + font-weight: 500; + color: var(--val-color--text); + text-rendering: optimizeLegibility; +} +.menu__nav li > a { + border: none; + transition: color 0.3s ease-in-out; +} +.menu__nav li:hover > a, +.menu__nav li > a:focus { + color: var(--val-menu--color-highlight); +} +.menu__nav li > a > i.menu__icon { + margin-left: 0.25rem; +} + +.menu__nav li .menu__subs { + position: absolute; + max-width: 100%; + height: auto; + padding: 1rem 2rem; + border: none; + outline: none; + background: var(--val-menu--color-bg); + border-radius: var(--val-menu--border-radius); + border-top: 3px solid var(--val-menu--color-highlight); + z-index: 500; + opacity: 0; + visibility: hidden; + box-shadow: 0 4px 6px -1px var(--val-menu--color-border), 0 2px 4px -1px var(--val-menu--color-shadow); + transition: all 0.5s ease-in-out; +} + +.menu__nav li.menu__children:hover > .menu__subs, +.menu__nav li.menu__children > a:focus + .menu__subs, +.menu__nav li.menu__children .menu__subs:focus-within { + margin-top: 0.4rem; + opacity: 1; + visibility: visible; +} + +.menu__nav li .menu__items { + min-width: var(--val-menu--item-width-min); + max-width: var(--val-menu--item-width-max); +} +.menu__nav li .menu__items .menu__title { + font-family: inherit; + font-size: 1rem; + font-weight: 500; + margin: 0; + padding: var(--val-menu--line-padding) 0; + line-height: var(--val-menu--line-height); + border: none; + outline: none; + color: var(--val-menu--color-highlight); + text-transform: uppercase; + text-rendering: optimizeLegibility; +} +.menu__nav li .menu__items li { + display: block; + margin-left: 0; +} + +.menu__nav li .menu__mega { + left: 50%; + transform: translateX(-50%); +} + +.menu__nav li .menu__groups { + display: flex; + flex-wrap: nowrap; +} + +.menu__header, +.menu__trigger { + display: none; +} + +/* Applies <= 992px */ +@media only screen and (max-width: 62rem) { + .menu { + border-radius: var(--val-border-radius); + } + .menu__wrapper { + padding-right: var(--val-gap-0-5); + } + .menu__wrapper button { + margin: var(--val-gap-0-5) 0 var(--val-gap-0-5) var(--val-gap-0-5); + } + .menu__trigger { + cursor: pointer; + width: var(--val-menu--trigger-width); + height: var(--val-menu--item-height); + border: none; + outline: none; + background: none; + display: flex; + flex-direction: column; + justify-content: center; + } + .menu__trigger span { + width: 100%; + height: 2px; + margin: 12.675% 0; + border-radius: var(--val-border-radius); + background: var(--val-color--text); + } + + .menu__nav { + position: fixed; + top: 0; + left: 0; + width: var(--val-menu--side-width); + height: 100%; + z-index: 9099; + overflow: hidden; + background: var(--val-menu--color-bg); + transform: translate(-100%); + transition: all 0.5s ease-in-out; + } + .menu__panel .menu__nav.active { + transform: translate(0%); + } + + .menu__nav li { + display: block; + margin: 0; + padding: 0; + } + .menu__nav li.menu__label, + .menu__nav li > a { + display: block; + padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); + border-bottom: 1px solid var(--val-menu--color-border); + } + .menu__nav li ul li.menu__label, + .menu__nav li ul li > a { + border-bottom: 0; + } + .menu__nav li > a > i.menu__icon { + position: absolute; + top: var(--val-menu--line-padding); + right: var(--val-menu--line-padding); + font-size: 1.25rem; + transform: rotate(-90deg); + } + + .menu__nav li .menu__subs { + position: absolute; + display: none; + top: 0; + left: 0; + max-width: none; + min-width: auto; + width: 100%; + height: 100%; + margin: 0 !important; + padding: 0; + border-top: 0; + opacity: 1; + overflow-y: auto; + visibility: visible; + transform: translateX(0%); + box-shadow: none; + } + .menu__nav li .menu__subs.active { + display: block; + } + .menu__nav li .menu__subs > :first-child { + margin-top: 4rem; + } + + .menu__nav li .menu__items .menu__title { + padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); + } + + .menu__nav li .menu__groups { + display: block; + } + + .menu__nav .menu__header { + position: sticky; + display: flex; + align-items: center; + justify-content: space-between; + top: 0; + height: var(--val-menu--item-height); + border-bottom: 1px solid var(--val-menu--color-border); + background: var(--val-menu--color-bg); + z-index: 501; + } + .menu__nav .menu__header .menu__title { + padding: var(--val-menu--line-padding); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .menu__nav .menu__header .menu__close, + .menu__nav .menu__header .menu__back { + width: var(--val-menu--item-height); + min-width: var(--val-menu--item-height); + height: var(--val-menu--item-height); + line-height: var(--val-menu--item-height); + color: var(--val-color--text); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + } + .menu__nav .menu__header .menu__close { + font-size: 2.25rem; + border-left: 1px solid var(--val-menu--color-border); + } + .menu__nav .menu__header .menu__back { + font-size: 1.25rem; + border-right: 1px solid var(--val-menu--color-border); + display: none; + } + .menu__nav .menu__header.active .menu__back { + display: flex; + } + + .menu__nav .menu__list { + height: 100%; + overflow-y: auto; + overflow-x: hidden; + padding: 0; + margin: 0; + } + + .menu__overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9098; + opacity: 0; + visibility: hidden; + background: rgba(0, 0, 0, 0.55); + transition: all 0.5s ease-in-out; + } + .menu__overlay.active { + opacity: 1; + visibility: visible; + } +} + +/* ANIMATIONS */ + +@keyframes slideLeft { + 0% { + opacity: 0; + transform: translateX(100%); + } + 100% { + opacity: 1; + transform: translateX(0%); + } +} + +@keyframes slideRight { + 0% { + opacity: 1; + transform: translateX(0%); + } + 100% { + opacity: 0; + transform: translateX(100%); + } +} diff --git a/static/css/root.css b/static/css/root.css new file mode 100644 index 00000000..bc55a443 --- /dev/null +++ b/static/css/root.css @@ -0,0 +1,211 @@ +:root { + --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + --val-font-serif: "Lora","georgia",serif; + --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; + --val-font-family: var(--val-font-sans); + + /* Font size */ + --val-fs--x3l: 2.5rem; + --val-fs--x2l: 2rem; + --val-fs--xl: 1.75rem; + --val-fs--l: 1.5rem; + --val-fs--m: 1.25rem; + --val-fs--base: 1rem; + --val-fs--s: 0.875rem; + --val-fs--xs: 0.75rem; + --val-fs--x2s: 0.5625rem; + --val-fs--x3s: 0.375rem; + + /* Font weight */ + --val-fw--light: 300; + --val-fw--base: 400; + --val-fw--bold: 500; + + /* Line height */ + --val-lh--base: 1.5; + --val-lh--header: 1.2; + + --val-max-width: 90rem; +/* + --val-color-rgb: 33,37,41; + --val-main--bg-rgb: 255,255,255; + --val-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + + --line-height-base: 1.6875rem; + --line-height-s: 1.125rem; + --max-bg-color: 98.125rem; +*/ + --val-gap: 1.125rem; +/* + --content-left: 5.625rem; + --site-header-height-wide: var(--val-gap10); + --container-padding: var(--val-gap); +*/ +} +/* +@media (min-width: 75rem) { + :root { + --container-padding:var(--val-gap2); + } +} + +:root { + --scrollbar-width: 0px; + --grid-col-count: 6; + --grid-gap: var(--val-gap); + --grid-gap-count: calc(var(--grid-col-count) - 1); + --grid-full-width: calc(100vw - var(--val-gap2) - var(--scrollbar-width)); + --grid-col-width: calc((var(--grid-full-width) - (var(--grid-gap-count) * var(--grid-gap))) / var(--grid-col-count)); +} + +@media (min-width: 43.75rem) { + :root { + --grid-col-count:14; + --grid-gap: var(--val-gap2); + } +} + +@media (min-width: 62.5rem) { + :root { + --scrollbar-width:0.9375rem; + } +} + +@media (min-width: 75rem) { + :root { + --grid-full-width:calc(100vw - var(--scrollbar-width) - var(--content-left) - var(--val-gap4)); + } +} + +@media (min-width: 90rem) { + :root { + --grid-full-width:calc(var(--max-width) - var(--val-gap4)); + } +} +*/ +:root { + --val-gap-0-15: calc(0.15 * var(--val-gap)); + --val-gap-0-25: calc(0.25 * var(--val-gap)); + --val-gap-0-35: calc(0.35 * var(--val-gap)); + --val-gap-0-5: calc(0.5 * var(--val-gap)); + --val-gap-0-75: calc(0.75 * var(--val-gap)); + --val-gap-1-5: calc(1.5 * var(--val-gap)); + --val-gap-2: calc(2 * var(--val-gap)); + + --primary-hue: 216; + --primary-sat: 60%; + --val-color--primary: hsl(var(--primary-hue), var(--primary-sat), 50%); + --val-color--primary-light: hsl(var(--primary-hue), var(--primary-sat), 60%); + --val-color--primary-dark: hsl(var(--primary-hue), var(--primary-sat), 40%); + --val-color--primary-link: hsl(var(--primary-hue), var(--primary-sat), 55%); + --val-color--primary-link-hover: hsl(var(--primary-hue), var(--primary-sat), 30%); + --val-color--primary-link-active: hsl(var(--primary-hue), var(--primary-sat), 70%); + + --info-hue: 190; + --info-sat: 90%; + --val-color--info: hsl(var(--info-hue), var(--info-sat), 54%); + --val-color--info-light: hsl(var(--info-hue), var(--info-sat), 70%); + --val-color--info-dark: hsl(var(--info-hue), var(--info-sat), 45%); + --val-color--info-link: hsl(var(--info-hue), var(--info-sat), 30%); + --val-color--info-link-hover: hsl(var(--info-hue), var(--info-sat), 20%); + --val-color--info-link-active: hsl(var(--info-hue), var(--info-sat), 40%); + + --success-hue: 150; + --success-sat: 50%; + --val-color--success: hsl(var(--success-hue), var(--success-sat), 50%); + --val-color--success-light: hsl(var(--success-hue), var(--success-sat), 68%); + --val-color--success-dark: hsl(var(--success-hue), var(--success-sat), 38%); + --val-color--success-link: hsl(var(--success-hue), var(--success-sat), 26%); + --val-color--success-link-hover: hsl(var(--success-hue), var(--success-sat), 18%); + --val-color--success-link-active: hsl(var(--success-hue), var(--success-sat), 36%); + + --warning-hue: 44; + --warning-sat: 100%; + --val-color--warning: hsl(var(--warning-hue), var(--warning-sat), 50%); + --val-color--warning-light: hsl(var(--warning-hue), var(--warning-sat), 60%); + --val-color--warning-dark: hsl(var(--warning-hue), var(--warning-sat), 40%); + --val-color--warning-link: hsl(var(--warning-hue), var(--warning-sat), 30%); + --val-color--warning-link-hover: hsl(var(--warning-hue), var(--warning-sat), 20%); + --val-color--warning-link-active: hsl(var(--warning-hue), var(--warning-sat), 38%); + + --danger-hue: 348; + --danger-sat: 86%; + --val-color--danger: hsl(var(--danger-hue), var(--danger-sat), 50%); + --val-color--danger-light: hsl(var(--danger-hue), var(--danger-sat), 60%); + --val-color--danger-dark: hsl(var(--danger-hue), var(--danger-sat), 35%); + --val-color--danger-link: hsl(var(--danger-hue), var(--danger-sat), 25%); + --val-color--danger-link-hover: hsl(var(--danger-hue), var(--danger-sat), 10%); + --val-color--danger-link-active: hsl(var(--danger-hue), var(--danger-sat), 30%); + + --light-hue: 0; + --light-sat: 0%; + --val-color--light: hsl(var(--light-hue), var(--light-sat), 96%); + --val-color--light-light: hsl(var(--light-hue), var(--light-sat), 98%); + --val-color--light-dark: hsl(var(--light-hue), var(--light-sat), 92%); + + --dark-hue: 0; + --dark-sat: 0%; + --val-color--dark: hsl(var(--dark-hue), var(--dark-sat), 25%); + --val-color--dark-light: hsl(var(--dark-hue), var(--dark-sat), 40%); + --val-color--dark-dark: hsl(var(--dark-hue), var(--dark-sat), 8%); + --val-color--dark-link: hsl(var(--dark-hue), var(--dark-sat), 90%); + --val-color--dark-link-hover: hsl(var(--dark-hue), var(--dark-sat), 100%); + --val-color--dark-link-active: hsl(var(--dark-hue), var(--dark-sat), 70%); + + + + + --gray-hue: 201; + --gray-sat: 15%; + --val-color--gray-5: hsl(var(--gray-hue), var(--gray-sat), 5%); + --val-color--gray-10: hsl(var(--gray-hue), var(--gray-sat) ,11%); + --val-color--gray-20: hsl(var(--gray-hue), var(--gray-sat),20%); + --val-color--gray-45: hsl(var(--gray-hue), var(--gray-sat), 44%); + --val-color--gray-60: hsl(var(--gray-hue), var(--gray-sat), 57%); + --val-color--gray-65: hsl(var(--gray-hue), var(--gray-sat), 63%); + --val-color--gray-70: hsl(var(--gray-hue), var(--gray-sat), 72%); + --val-color--gray-90: hsl(var(--gray-hue), var(--gray-sat), 88%); + --val-color--gray-95: hsl(var(--gray-hue), var(--gray-sat), 93%); + --val-color--gray-100: hsl(var(--gray-hue), var(--gray-sat), 97%); + + + + + --val-color--bg: #fafafa; + --val-color--text: #212529; + --val-color--white: #fff; + +/* + + + --color-text-neutral-soft: var(--color--gray-45); + --color-text-neutral-medium: var(--color--gray-20); + --color-text-neutral-loud: var(--color--gray-5); + --color-text-primary-medium: var(--val-color--primary-40); + --color-text-primary-loud: var(--val-color--primary-30); + --color--black: #000; +*/ +/* + --color--red: #e33f1e; + --color--gold: #fdca40; + --color--green: #3fa21c; + --header-height-wide-when-fixed: calc(6 * var(--val-gap)); + --mobile-nav-width: 31.25rem; +*/ + --val-border-radius: 0.375rem; + + /* Menu component */ + --val-menu--color-bg: var(--val-color--bg); + --val-menu--color-highlight: #e91e63; + --val-menu--color-border: rgba(0, 0, 0, 0.1); + --val-menu--color-shadow: rgba(0, 0, 0, 0.06); + --val-menu--line-padding: 0.625rem; + --val-menu--line-height: calc(1.875rem + 1px); + --val-menu--item-height: calc(var(--val-menu--line-padding) + var(--val-menu--line-height)); + --val-menu--item-width-min: 14rem; + --val-menu--item-width-max: 20rem; + --val-menu--item-gap: 1rem; + --val-menu--border-radius: 0.625rem; + --val-menu--trigger-width: var(--val-menu--item-height); + --val-menu--side-width: 20rem; +} diff --git a/static/js/menu.js b/static/js/menu.js new file mode 100644 index 00000000..2f3b332e --- /dev/null +++ b/static/js/menu.js @@ -0,0 +1,94 @@ +function menu__showChildren(nav, children) { + let submenu = children[0].querySelector('.menu__subs'); + submenu.classList.add('active'); + submenu.style.animation = 'slideLeft 0.5s ease forwards'; + + let title = children[0].querySelector('i').parentNode.childNodes[0].textContent; + nav.querySelector('.menu__title').innerHTML = title; + nav.querySelector('.menu__header').classList.add('active'); +} + +function menu__hideChildren(nav, children) { + let submenu = children[0].querySelector('.menu__subs'); + submenu.style.animation = 'slideRight 0.5s ease forwards'; + setTimeout(() => { + submenu.classList.remove('active'); + submenu.style.removeProperty('animation'); + }, 300); + + children.shift(); + if (children.length > 0) { + let title = children[0].querySelector('i').parentNode.childNodes[0].textContent; + nav.querySelector('.menu__title').innerHTML = title; + } else { + nav.querySelector('.menu__header').classList.remove('active'); + nav.querySelector('.menu__title').innerHTML = ''; + } +} + +function menu__toggle(nav, overlay) { + nav.classList.toggle('active'); + overlay.classList.toggle('active'); +} + +function menu__reset(menu, nav, overlay) { + menu__toggle(nav, overlay); + setTimeout(() => { + nav.querySelector('.menu__header').classList.remove('active'); + nav.querySelector('.menu__title').innerHTML = ''; + menu.querySelectorAll('.menu__subs').forEach(submenu => { + submenu.classList.remove('active'); + submenu.style.removeProperty('animation'); + }); + }, 300); + return []; +} + +document.querySelectorAll('.menu').forEach(menu => { + + let menuChildren = []; + const menuNav = menu.querySelector('.menu__nav'); + const menuOverlay = menu.querySelector('.menu__overlay'); + + menu.querySelector('.menu__list').addEventListener('click', (e) => { + if (menuNav.classList.contains('active')) { + let target = e.target.closest('.menu__children'); + if (target && target != menuChildren[0]) { + menuChildren.unshift(target); + menu__showChildren(menuNav, menuChildren); + } + } + }); + + menu.querySelector('.menu__back').addEventListener('click', () => { + menu__hideChildren(menuNav, menuChildren); + }); + + menu.querySelector('.menu__close').addEventListener('click', () => { + menuChildren = menu__reset(menu, menuNav, menuOverlay); + }); + + menu.querySelectorAll('.menu__link > a[target="_blank"]').forEach(link => { + link.addEventListener('click', (e) => { + menuChildren = menu__reset(menu, menuNav, menuOverlay); + e.target.blur(); + }); + }); + + menu.querySelector('.menu__trigger').addEventListener('click', () => { + menu__toggle(menuNav, menuOverlay); + }); + + menuOverlay.addEventListener('click', () => { + menu__toggle(menuNav, menuOverlay); + }); + + window.onresize = function () { + if (menuNav.classList.contains('active')) { + var fontSizeRoot = parseFloat(getComputedStyle(document.documentElement).fontSize); + if (this.innerWidth >= 62 * fontSizeRoot) { + menuChildren = menu__reset(menu, menuNav, menuOverlay); + } + } + }; +}); From 0f1296c216e6bd6110edb752f313d9a082775209 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 29 Sep 2025 02:07:10 +0200 Subject: [PATCH 137/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20la=20estructura?= =?UTF-8?q?=20y=20estilos=20del=20men=C3=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rrenombra clases, ajusta estilos CSS y actualiza la lógica de JavaScript para una mejor gestión de submenús. --- src/base/component/menu/group.rs | 2 +- src/base/component/menu/item.rs | 40 ++++++------ src/base/component/menu/megamenu.rs | 2 +- src/base/component/menu/menu_menu.rs | 4 +- src/base/component/menu/submenu.rs | 4 +- static/css/menu.css | 91 +++++++++++++--------------- static/css/root.css | 5 +- static/js/menu.js | 21 ++++--- 8 files changed, 84 insertions(+), 85 deletions(-) diff --git a/src/base/component/menu/group.rs b/src/base/component/menu/group.rs index 41279b4e..ba188934 100644 --- a/src/base/component/menu/group.rs +++ b/src/base/component/menu/group.rs @@ -18,7 +18,7 @@ impl Component for Group { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { PrepareMarkup::With(html! { - div id=[self.id()] class="menu-group" { + div id=[self.id()] class="menu__group" { (self.elements().render(cx)) } }) diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs index 07d629ae..7a36fbbf 100644 --- a/src/base/component/menu/item.rs +++ b/src/base/component/menu/item.rs @@ -1,11 +1,9 @@ use crate::prelude::*; -//use super::{Megamenu, Submenu}; - type Label = L10n; type Content = Typed; type SubmenuItems = Typed; -//type MegamenuGroups = Typed; +type MegamenuGroups = Typed; #[derive(AutoDefault)] pub enum ItemKind { @@ -16,7 +14,7 @@ pub enum ItemKind { LinkBlank(Label, FnPathByContext), Html(Content), Submenu(Label, SubmenuItems), - // Megamenu(Label, MegamenuGroups), + Megamenu(Label, MegamenuGroups), } #[rustfmt::skip] @@ -41,61 +39,65 @@ impl Component for Item { match self.item_kind() { ItemKind::Void => PrepareMarkup::None, ItemKind::Label(label) => PrepareMarkup::With(html! { - li class="menu__label" { + li class="menu__item menu__item--label" { span title=[description] { (left_icon) - (label.using(cx)) + span class="menu__label" { (label.using(cx)) } (right_icon) } } }), ItemKind::Link(label, path) => PrepareMarkup::With(html! { - li class="menu__link" { + li class="menu__item menu__item--link" { a href=(path(cx)) title=[description] { (left_icon) - (label.using(cx)) + span class="menu__label" { (label.using(cx)) } (right_icon) } } }), ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! { - li class="menu__link" { + li class="menu__item menu__item--link" { a href=(path(cx)) title=[description] target="_blank" { (left_icon) - (label.using(cx)) + span class="menu__label" { (label.using(cx)) } (right_icon) } } }), ItemKind::Html(content) => PrepareMarkup::With(html! { - li class="menu__html" { + li class="menu__item menu__item--html" { (content.render(cx)) } }), ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! { - li class="menu__children" { + li class="menu__item menu__item--children" { a href="#" title=[description] { (left_icon) - (label.using(cx)) i class="menu__icon bi-chevron-down" {} + span class="menu__label" { (label.using(cx)) } + (Icon::svg(html! { + path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} + }).render(cx)) } - div class="menu__subs" { + div class="menu__children menu__children--submenu" { (submenu.render(cx)) } } }), - /* ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! { - li class="menu__children" { + li class="menu__item menu__item--children" { a href="#" title=[description] { (left_icon) - (label.escaped(cx.langid())) i class="menu__icon bi-chevron-down" {} + span class="menu__label" { (label.using(cx)) } + (Icon::svg(html! { + path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} + }).render(cx)) } - div class="menu__subs menu__mega" { + div class="menu__children menu__children--mega" { (megamenu.render(cx)) } } }), - */ } } } diff --git a/src/base/component/menu/megamenu.rs b/src/base/component/menu/megamenu.rs index f22b184e..e435e753 100644 --- a/src/base/component/menu/megamenu.rs +++ b/src/base/component/menu/megamenu.rs @@ -18,7 +18,7 @@ impl Component for Megamenu { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { PrepareMarkup::With(html! { - div id=[self.id()] class="menu__groups" { + div id=[self.id()] class="menu__mega" { (self.groups().render(cx)) } }) diff --git a/src/base/component/menu/menu_menu.rs b/src/base/component/menu/menu_menu.rs index a7f91be1..58a4c21d 100644 --- a/src/base/component/menu/menu_menu.rs +++ b/src/base/component/menu/menu_menu.rs @@ -54,7 +54,9 @@ impl Component for Menu { class="menu__trigger" title=[L10n::l("menu_toggle").lookup(cx)] { - span {} span {} span {} + (Icon::svg(html! { + path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5" {} + }).render(cx)) } } } diff --git a/src/base/component/menu/submenu.rs b/src/base/component/menu/submenu.rs index 58770529..a5957ef9 100644 --- a/src/base/component/menu/submenu.rs +++ b/src/base/component/menu/submenu.rs @@ -19,9 +19,9 @@ impl Component for Submenu { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { PrepareMarkup::With(html! { - div id=[self.id()] class="menu__items" { + div id=[self.id()] class="menu__submenu" { @if let Some(title) = self.title().lookup(cx) { - h4 class="menu__title" { (title) } + h4 class="menu__submenu-title" { (title) } } ul { (self.items().render(cx)) diff --git a/static/css/menu.css b/static/css/menu.css index a8b2854c..5520e398 100644 --- a/static/css/menu.css +++ b/static/css/menu.css @@ -27,13 +27,13 @@ .menu__nav li { display: inline-block; margin: 0 0 0 1.5rem; - padding: var(--val-menu--line-padding) 0; - line-height: var(--val-menu--line-height); + padding: 0; + line-height: var(--val-menu--item-height); list-style: none; list-style-type: none; } -.menu__nav li.menu__label, +.menu__item--label, .menu__nav li > a { position: relative; font-weight: 500; @@ -48,19 +48,18 @@ .menu__nav li > a:focus { color: var(--val-menu--color-highlight); } -.menu__nav li > a > i.menu__icon { +.menu__nav li > a > svg.icon { margin-left: 0.25rem; } -.menu__nav li .menu__subs { +.menu__children { position: absolute; max-width: 100%; height: auto; - padding: 1rem 2rem; + padding: var(--val-gap-0-5) var(--val-gap-1-5); border: none; outline: none; background: var(--val-menu--color-bg); - border-radius: var(--val-menu--border-radius); border-top: 3px solid var(--val-menu--color-highlight); z-index: 500; opacity: 0; @@ -69,19 +68,19 @@ transition: all 0.5s ease-in-out; } -.menu__nav li.menu__children:hover > .menu__subs, -.menu__nav li.menu__children > a:focus + .menu__subs, -.menu__nav li.menu__children .menu__subs:focus-within { +.menu__item--children:hover > .menu__children, +.menu__item--children > a:focus + .menu__children, +.menu__item--children .menu__children:focus-within { margin-top: 0.4rem; opacity: 1; visibility: visible; } -.menu__nav li .menu__items { +.menu__submenu { min-width: var(--val-menu--item-width-min); max-width: var(--val-menu--item-width-max); } -.menu__nav li .menu__items .menu__title { +.menu__submenu-title { font-family: inherit; font-size: 1rem; font-weight: 500; @@ -94,17 +93,17 @@ text-transform: uppercase; text-rendering: optimizeLegibility; } -.menu__nav li .menu__items li { +.menu__submenu li { display: block; - margin-left: 0; + margin: 0; } -.menu__nav li .menu__mega { +.menu__children--mega { left: 50%; transform: translateX(-50%); } -.menu__nav li .menu__groups { +.menu__mega { display: flex; flex-wrap: nowrap; } @@ -116,15 +115,9 @@ /* Applies <= 992px */ @media only screen and (max-width: 62rem) { - .menu { - border-radius: var(--val-border-radius); - } .menu__wrapper { padding-right: var(--val-gap-0-5); } - .menu__wrapper button { - margin: var(--val-gap-0-5) 0 var(--val-gap-0-5) var(--val-gap-0-5); - } .menu__trigger { cursor: pointer; width: var(--val-menu--trigger-width); @@ -136,14 +129,10 @@ flex-direction: column; justify-content: center; } - .menu__trigger span { - width: 100%; - height: 2px; - margin: 12.675% 0; - border-radius: var(--val-border-radius); - background: var(--val-color--text); + .menu__trigger svg.icon { + width: 2rem; + height: 2rem; } - .menu__nav { position: fixed; top: 0; @@ -156,34 +145,36 @@ transform: translate(-100%); transition: all 0.5s ease-in-out; } - .menu__panel .menu__nav.active { + .menu__nav.active { transform: translate(0%); } .menu__nav li { display: block; margin: 0; - padding: 0; + line-height: var(--val-menu--line-height); } - .menu__nav li.menu__label, + + .menu__item--label, .menu__nav li > a { display: block; padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); border-bottom: 1px solid var(--val-menu--color-border); } - .menu__nav li ul li.menu__label, + .menu__nav li ul li.menu__item--label, .menu__nav li ul li > a { border-bottom: 0; } - .menu__nav li > a > i.menu__icon { + .menu__nav li > a > svg.icon { position: absolute; top: var(--val-menu--line-padding); right: var(--val-menu--line-padding); + height: var(--val-menu--line-height); font-size: 1.25rem; transform: rotate(-90deg); } - .menu__nav li .menu__subs { + .menu__children { position: absolute; display: none; top: 0; @@ -201,22 +192,22 @@ transform: translateX(0%); box-shadow: none; } - .menu__nav li .menu__subs.active { + .menu__children.active { display: block; } - .menu__nav li .menu__subs > :first-child { - margin-top: 4rem; + .menu__children > :first-child { + margin-top: 2.675rem; } - .menu__nav li .menu__items .menu__title { + .menu__submenu-title { padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); } - .menu__nav li .menu__groups { + .menu__mega { display: block; } - .menu__nav .menu__header { + .menu__header { position: sticky; display: flex; align-items: center; @@ -227,14 +218,14 @@ background: var(--val-menu--color-bg); z-index: 501; } - .menu__nav .menu__header .menu__title { + .menu__title { padding: var(--val-menu--line-padding); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } - .menu__nav .menu__header .menu__close, - .menu__nav .menu__header .menu__back { + .menu__close, + .menu__back { width: var(--val-menu--item-height); min-width: var(--val-menu--item-height); height: var(--val-menu--item-height); @@ -245,20 +236,20 @@ align-items: center; justify-content: center; } - .menu__nav .menu__header .menu__close { + .menu__close { font-size: 2.25rem; - border-left: 1px solid var(--val-menu--color-border); + border-left: 1px solid var(--val-menu--color-border) !important; } - .menu__nav .menu__header .menu__back { + .menu__back { font-size: 1.25rem; - border-right: 1px solid var(--val-menu--color-border); + border-right: 1px solid var(--val-menu--color-border) !important; display: none; } - .menu__nav .menu__header.active .menu__back { + .menu__header.active .menu__back { display: flex; } - .menu__nav .menu__list { + .menu__list { height: 100%; overflow-y: auto; overflow-x: hidden; diff --git a/static/css/root.css b/static/css/root.css index bc55a443..aeab1c67 100644 --- a/static/css/root.css +++ b/static/css/root.css @@ -191,6 +191,8 @@ --color--green: #3fa21c; --header-height-wide-when-fixed: calc(6 * var(--val-gap)); --mobile-nav-width: 31.25rem; + + --val-menu--border-radius: 0.625rem; */ --val-border-radius: 0.375rem; @@ -205,7 +207,6 @@ --val-menu--item-width-min: 14rem; --val-menu--item-width-max: 20rem; --val-menu--item-gap: 1rem; - --val-menu--border-radius: 0.625rem; - --val-menu--trigger-width: var(--val-menu--item-height); + --val-menu--trigger-width: 2.675rem; --val-menu--side-width: 20rem; } diff --git a/static/js/menu.js b/static/js/menu.js index 2f3b332e..6b5ae1b0 100644 --- a/static/js/menu.js +++ b/static/js/menu.js @@ -1,15 +1,17 @@ function menu__showChildren(nav, children) { - let submenu = children[0].querySelector('.menu__subs'); + const li = children[0]; + const submenu = li.querySelector('.menu__children'); submenu.classList.add('active'); submenu.style.animation = 'slideLeft 0.5s ease forwards'; - let title = children[0].querySelector('i').parentNode.childNodes[0].textContent; + const labelEl = li.querySelector('.menu__label'); + const title = labelEl ? labelEl.textContent.trim() : (li.querySelector('a')?.textContent?.trim() ?? ''); nav.querySelector('.menu__title').innerHTML = title; nav.querySelector('.menu__header').classList.add('active'); } function menu__hideChildren(nav, children) { - let submenu = children[0].querySelector('.menu__subs'); + const submenu = children[0].querySelector('.menu__children'); submenu.style.animation = 'slideRight 0.5s ease forwards'; setTimeout(() => { submenu.classList.remove('active'); @@ -18,11 +20,12 @@ function menu__hideChildren(nav, children) { children.shift(); if (children.length > 0) { - let title = children[0].querySelector('i').parentNode.childNodes[0].textContent; - nav.querySelector('.menu__title').innerHTML = title; + const a = children[0].querySelector('a'); + const title = (a && a.textContent ? a.textContent.trim() : ''); + nav.querySelector('.menu__title').textContent = title; } else { nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').innerHTML = ''; + nav.querySelector('.menu__title').textContent = ''; } } @@ -36,7 +39,7 @@ function menu__reset(menu, nav, overlay) { setTimeout(() => { nav.querySelector('.menu__header').classList.remove('active'); nav.querySelector('.menu__title').innerHTML = ''; - menu.querySelectorAll('.menu__subs').forEach(submenu => { + menu.querySelectorAll('.menu__children').forEach(submenu => { submenu.classList.remove('active'); submenu.style.removeProperty('animation'); }); @@ -52,7 +55,7 @@ document.querySelectorAll('.menu').forEach(menu => { menu.querySelector('.menu__list').addEventListener('click', (e) => { if (menuNav.classList.contains('active')) { - let target = e.target.closest('.menu__children'); + let target = e.target.closest('.menu__item--children'); if (target && target != menuChildren[0]) { menuChildren.unshift(target); menu__showChildren(menuNav, menuChildren); @@ -68,7 +71,7 @@ document.querySelectorAll('.menu').forEach(menu => { menuChildren = menu__reset(menu, menuNav, menuOverlay); }); - menu.querySelectorAll('.menu__link > a[target="_blank"]').forEach(link => { + menu.querySelectorAll('.menu__item--link > a[target="_blank"]').forEach(link => { link.addEventListener('click', (e) => { menuChildren = menu__reset(menu, menuNav, menuOverlay); e.target.blur(); From 6b08b92635c697674a8bcb81e75a470a188a09d9 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 30 Sep 2025 20:21:06 +0200 Subject: [PATCH 138/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`AssetsOp`=20?= =?UTF-8?q?por=20`ContextOp`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/theme/basic.rs | 14 +++++++------- src/html.rs | 3 ++- src/html/context.rs | 40 ++++++++++++++++++++++------------------ src/response/page.rs | 4 ++-- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 20f51994..ecd64856 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -52,32 +52,32 @@ impl Theme for Basic { _ => "/css/basic.css", }; let pkg_version = env!("CARGO_PKG_VERSION"); - page.alter_assets(AssetsOp::AddStyleSheet( + page.alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/normalize.css") .with_version("8.0.1") .with_weight(-99), )) - .alter_assets(AssetsOp::AddStyleSheet( + .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/root.css") .with_version(pkg_version) .with_weight(-99), )) - .alter_assets(AssetsOp::AddStyleSheet( + .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/components.css") .with_version(pkg_version) .with_weight(-99), )) - .alter_assets(AssetsOp::AddStyleSheet( + .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/menu.css") .with_version(pkg_version) .with_weight(-99), )) - .alter_assets(AssetsOp::AddStyleSheet( + .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from(styles) .with_version(pkg_version) .with_weight(-99), )) - .alter_assets(AssetsOp::AddJavaScript( + .alter_assets(ContextOp::AddJavaScript( JavaScript::defer("/js/menu.js") .with_version(pkg_version) .with_weight(-99), @@ -169,7 +169,7 @@ fn render_intro(page: &mut Page) -> Markup { } fn render_pagetop_intro(page: &mut Page) -> Markup { - page.alter_assets(AssetsOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| + page.alter_assets(ContextOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| util::indoc!(r#" try { const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); diff --git a/src/html.rs b/src/html.rs index c4195a9d..679d433a 100644 --- a/src/html.rs +++ b/src/html.rs @@ -14,7 +14,8 @@ pub use assets::{Asset, Assets}; // **< HTML DOCUMENT CONTEXT >********************************************************************** mod context; -pub use context::{AssetsOp, Context, Contextual, ErrorParam}; +#[allow(deprecated)] +pub use context::{AssetsOp, Context, ContextOp, Contextual, ErrorParam}; pub type FnPathByContext = fn(cx: &Context) -> &str; // **< HTML ATTRIBUTES >**************************************************************************** diff --git a/src/html/context.rs b/src/html/context.rs index 355ccead..94f24bf7 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -10,8 +10,12 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// Operaciones para modificar el contexto ([`Context`]) de un documento. -pub enum AssetsOp { +/// **Obsoleto desde la versión 0.4.0**: usar [`ContextOp`] en su lugar. +#[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] +pub type AssetsOp = ContextOp; + +/// Operaciones para modificar los recursos asociados al contexto ([`Context`]) de un documento. +pub enum ContextOp { // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. SetFavicon(Option), @@ -55,7 +59,7 @@ pub enum ErrorParam { /// - Almacenar la **solicitud HTTP** de origen. /// - Seleccionar **tema** y **composición** (*layout*) de renderizado. /// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo -/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`AssetsOp`]. +/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`ContextOp`]. /// - Leer y mantener **parámetros dinámicos tipados** de contexto. /// - Generar **identificadores únicos** por tipo de componente. /// @@ -71,9 +75,9 @@ pub enum ErrorParam { /// cx.with_langid(&LangMatch::resolve("es-ES")) /// .with_theme("aliner") /// .with_layout("default") -/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) -/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) -/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js"))) +/// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) +/// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) +/// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/app.js"))) /// .with_param("usuario_id", 42_i32) /// } /// ``` @@ -100,9 +104,9 @@ pub trait Contextual: LangId { #[builder_fn] fn with_param(self, key: &'static str, value: T) -> Self; - /// Define los recursos del contexto usando [`AssetsOp`]. + /// Define los recursos del contexto usando [`ContextOp`]. #[builder_fn] - fn with_assets(self, op: AssetsOp) -> Self; + fn with_assets(self, op: ContextOp) -> Self; // **< Contextual GETTERS >********************************************************************* @@ -172,11 +176,11 @@ pub trait Contextual: LangId { /// // Selecciona un tema (por su nombre corto). /// .with_theme("aliner") /// // Asigna un favicon. -/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) +/// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// // Añade una hoja de estilo externa. -/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/style.css"))) +/// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/style.css"))) /// // Añade un script JavaScript. -/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/main.js"))) +/// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/main.js"))) /// // Añade un parámetro dinámico al contexto. /// .with_param("usuario_id", 42) /// } @@ -442,29 +446,29 @@ impl Contextual for Context { } #[builder_fn] - fn with_assets(mut self, op: AssetsOp) -> Self { + fn with_assets(mut self, op: ContextOp) -> Self { match op { // Favicon. - AssetsOp::SetFavicon(favicon) => { + ContextOp::SetFavicon(favicon) => { self.favicon = favicon; } - AssetsOp::SetFaviconIfNone(icon) => { + ContextOp::SetFaviconIfNone(icon) => { if self.favicon.is_none() { self.favicon = Some(icon); } } // Stylesheets. - AssetsOp::AddStyleSheet(css) => { + ContextOp::AddStyleSheet(css) => { self.stylesheets.add(css); } - AssetsOp::RemoveStyleSheet(path) => { + ContextOp::RemoveStyleSheet(path) => { self.stylesheets.remove(path); } // JavaScripts. - AssetsOp::AddJavaScript(js) => { + ContextOp::AddJavaScript(js) => { self.javascripts.add(js); } - AssetsOp::RemoveJavaScript(path) => { + ContextOp::RemoveJavaScript(path) => { self.javascripts.remove(path); } } diff --git a/src/response/page.rs b/src/response/page.rs index e9d30671..135ab19e 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -8,9 +8,9 @@ use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::html::{AssetsOp, Context, Contextual}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; +use crate::html::{Context, ContextOp, Contextual}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; use crate::{builder_fn, AutoDefault}; @@ -286,7 +286,7 @@ impl Contextual for Page { } #[builder_fn] - fn with_assets(mut self, op: AssetsOp) -> Self { + fn with_assets(mut self, op: ContextOp) -> Self { self.context.alter_assets(op); self } From 075f61bf09ce4e52b03182ac81e2dd11e7f005b2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 30 Sep 2025 23:36:09 +0200 Subject: [PATCH 139/224] =?UTF-8?q?=F0=9F=9A=A7=20(core):=20Mueve=20`Conte?= =?UTF-8?q?xt`=20al=20=C3=A1mbito=20de=20componentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component.rs | 6 +++++ src/core/component/children.rs | 4 ++-- src/{html => core/component}/context.rs | 10 +++----- src/core/component/definition.rs | 3 ++- src/global.rs | 10 ++++---- src/html.rs | 32 +++++++++++++++++++++---- src/html/assets.rs | 3 ++- src/html/assets/favicon.rs | 3 ++- src/html/assets/javascript.rs | 3 ++- src/html/assets/stylesheet.rs | 3 ++- src/prelude.rs | 9 ++++++- src/response/page.rs | 3 +-- src/response/page/error.rs | 2 +- 13 files changed, 64 insertions(+), 27 deletions(-) rename src/{html => core/component}/context.rs (97%) diff --git a/src/core/component.rs b/src/core/component.rs index 17b9b735..a002e3d5 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -7,3 +7,9 @@ mod children; pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; + +// **< HTML DOCUMENT CONTEXT >********************************************************************** + +mod context; +pub use context::{Context, ContextOp, Contextual, ErrorParam}; +pub type FnPathByContext = fn(cx: &Context) -> &str; diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 920dacf8..c0c8841e 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -1,5 +1,5 @@ -use crate::core::component::Component; -use crate::html::{html, Context, Markup}; +use crate::core::component::{Component, Context}; +use crate::html::{html, Markup}; use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; diff --git a/src/html/context.rs b/src/core/component/context.rs similarity index 97% rename from src/html/context.rs rename to src/core/component/context.rs index 94f24bf7..9333e484 100644 --- a/src/html/context.rs +++ b/src/core/component/context.rs @@ -10,11 +10,7 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// **Obsoleto desde la versión 0.4.0**: usar [`ContextOp`] en su lugar. -#[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] -pub type AssetsOp = ContextOp; - -/// Operaciones para modificar los recursos asociados al contexto ([`Context`]) de un documento. +/// Operaciones para modificar recursos asociados al contexto ([`Context`]) de un documento. pub enum ContextOp { // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. @@ -64,7 +60,7 @@ pub enum ErrorParam { /// - Generar **identificadores únicos** por tipo de componente. /// /// Lo implementan, típicamente, estructuras que representan el contexto de renderizado, como -/// [`Context`](crate::html::Context) o [`Page`](crate::response::page::Page). +/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page). /// /// # Ejemplo /// @@ -375,7 +371,7 @@ impl Context { } } -/// Permite a [`Context`](crate::html::Context) actuar como proveedor de idioma. +/// Permite a [`Context`](crate::core::component::Context) actuar como proveedor de idioma. /// /// Devuelve un [`LanguageIdentifier`] siguiendo este orden de prioridad: /// diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index 333cf694..c0573b44 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -1,6 +1,7 @@ use crate::base::action; +use crate::core::component::Context; use crate::core::{AnyInfo, TypeInfo}; -use crate::html::{html, Context, Markup, PrepareMarkup}; +use crate::html::{html, Markup, PrepareMarkup}; /// Define la función de renderizado para todos los componentes. /// diff --git a/src/global.rs b/src/global.rs index ccc6d9de..c8805a30 100644 --- a/src/global.rs +++ b/src/global.rs @@ -50,11 +50,11 @@ pub struct App { pub theme: String, /// Idioma por defecto para la aplicación. /// - /// Si no está definido o no es válido, el idioma efectivo para el renderizado se resolverá - /// según la implementación de [`LangId`](crate::locale::LangId) en este orden: primero intenta - /// con el establecido en [`Contextual::with_langid()`](crate::html::Contextual::with_langid); - /// pero si no se ha definido explícitamente, usará el indicado en la cabecera `Accept-Language` - /// del navegador; y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). + /// Si no está definido o no es válido, [`LangId`](crate::locale::LangId) determinará el idioma + /// efectivo para el renderizado en este orden: primero intentará usar el establecido mediante + /// [`Contextual::with_langid()`](crate::core::component::Contextual::with_langid); si no se ha + /// definido explícitamente, probará el indicado en la cabecera `Accept-Language` del navegador; + /// y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html.rs b/src/html.rs index 679d433a..079811a2 100644 --- a/src/html.rs +++ b/src/html.rs @@ -13,10 +13,34 @@ pub use assets::{Asset, Assets}; // **< HTML DOCUMENT CONTEXT >********************************************************************** -mod context; -#[allow(deprecated)] -pub use context::{AssetsOp, Context, ContextOp, Contextual, ErrorParam}; -pub type FnPathByContext = fn(cx: &Context) -> &str; +/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Context`] en su lugar. +#[deprecated(since = "0.5.0", note = "Moved to `pagetop::core::component::Context`")] +pub type Context = crate::core::component::Context; + +/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextOp`] en su lugar. +#[deprecated( + since = "0.5.0", + note = "Moved to `pagetop::core::component::ContextOp`" +)] +pub type ContextOp = crate::core::component::ContextOp; + +/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Contextual`] en su lugar. +#[deprecated( + since = "0.5.0", + note = "Moved to `pagetop::core::component::Contextual`" +)] +pub trait Contextual: crate::core::component::Contextual {} + +/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ErrorParam`] en su lugar. +#[deprecated( + since = "0.5.0", + note = "Moved to `pagetop::core::component::ErrorParam`" +)] +pub type ErrorParam = crate::core::component::ErrorParam; + +/// **Obsoleto desde la versión 0.5.0**: usar [`ContextOp`] en su lugar. +#[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] +pub type AssetsOp = crate::core::component::ContextOp; // **< HTML ATTRIBUTES >**************************************************************************** diff --git a/src/html/assets.rs b/src/html/assets.rs index 41cd4710..fe5f5b7c 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -2,7 +2,8 @@ pub mod favicon; pub mod javascript; pub mod stylesheet; -use crate::html::{html, Context, Markup}; +use crate::core::component::Context; +use crate::html::{html, Markup}; use crate::{AutoDefault, Weight}; /// Representación genérica de un script [`JavaScript`](crate::html::JavaScript) o una hoja de diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 56c39056..c2280aab 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -1,4 +1,5 @@ -use crate::html::{html, Context, Markup}; +use crate::core::component::Context; +use crate::html::{html, Markup}; use crate::AutoDefault; /// Un **Favicon** es un recurso gráfico que usa el navegador como icono asociado al sitio. diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 4649cdb4..dde5f945 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,5 +1,6 @@ +use crate::core::component::Context; use crate::html::assets::Asset; -use crate::html::{html, Context, Markup, PreEscaped}; +use crate::html::{html, Markup, PreEscaped}; use crate::{join, join_pair, AutoDefault, Weight}; // Define el origen del recurso JavaScript y cómo debe cargarse en el navegador. diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index b54f4cf3..49cb9911 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,5 +1,6 @@ +use crate::core::component::Context; use crate::html::assets::Asset; -use crate::html::{html, Context, Markup, PreEscaped}; +use crate::html::{html, Markup, PreEscaped}; use crate::{join_pair, AutoDefault, Weight}; // Define el origen del recurso CSS y cómo se incluye en el documento. diff --git a/src/prelude.rs b/src/prelude.rs index 484e53c1..a71375e1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -28,7 +28,14 @@ pub use crate::global; pub use crate::trace; -pub use crate::html::*; +// No se usa `pub use crate::html::*;` para evitar duplicar alias marcados como obsoletos +// (*deprecated*) porque han sido trasladados a `crate::core::component`. Cuando se retiren estos +// alias obsoletos se volverá a declarar como `pub use crate::html::*;`. +pub use crate::html::{ + display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, + ClassesOp, Escaper, Favicon, JavaScript, Markup, PreEscaped, PrepareMarkup, StyleSheet, + TargetMedia, DOCTYPE, +}; pub use crate::locale::*; diff --git a/src/response/page.rs b/src/response/page.rs index 135ab19e..f81c9806 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -4,13 +4,12 @@ pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::core::component::{Child, ChildOp, Component}; +use crate::core::component::{Child, ChildOp, Component, Context, ContextOp, Contextual}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; -use crate::html::{Context, ContextOp, Contextual}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; use crate::{builder_fn, AutoDefault}; diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 2355d234..50e1c77b 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,5 +1,5 @@ use crate::base::component::Html; -use crate::html::Contextual; +use crate::core::component::Contextual; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode}; From 843aed86c7acc2566b9786b183adc7290e8826a2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 30 Sep 2025 23:45:13 +0200 Subject: [PATCH 140/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`ErrorParam`?= =?UTF-8?q?=20por=20`ContextError`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component.rs | 4 +--- src/core/component/context.rs | 34 +++++++++++++++++----------------- src/html.rs | 6 +++--- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/core/component.rs b/src/core/component.rs index a002e3d5..be9bbad1 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -8,8 +8,6 @@ pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; -// **< HTML DOCUMENT CONTEXT >********************************************************************** - mod context; -pub use context::{Context, ContextOp, Contextual, ErrorParam}; +pub use context::{Context, ContextError, ContextOp, Contextual}; pub type FnPathByContext = fn(cx: &Context) -> &str; diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 9333e484..9dad7f5f 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -33,14 +33,14 @@ pub enum ContextOp { /// Errores de acceso a parámetros dinámicos del contexto. /// -/// - [`ErrorParam::NotFound`]: la clave no existe. -/// - [`ErrorParam::TypeMismatch`]: la clave existe, pero el valor guardado no coincide con el tipo -/// solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo realmente -/// guardado (`saved`) para facilitar el diagnóstico. +/// - [`ContextError::ParamNotFound`]: la clave no existe. +/// - [`ContextError::ParamTypeMismatch`]: la clave existe, pero el valor guardado no coincide con +/// el tipo solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo +/// realmente guardado (`saved`) para facilitar el diagnóstico. #[derive(Debug)] -pub enum ErrorParam { - NotFound, - TypeMismatch { +pub enum ContextError { + ParamNotFound, + ParamTypeMismatch { key: &'static str, expected: &'static str, saved: &'static str, @@ -290,8 +290,8 @@ impl Context { /// Devuelve: /// /// - `Ok(&T)` si la clave existe y el tipo coincide. - /// - `Err(ErrorParam::NotFound)` si la clave no existe. - /// - `Err(ErrorParam::TypeMismatch)` si la clave existe pero el tipo no coincide. + /// - `Err(ContextError::ParamNotFound)` si la clave no existe. + /// - `Err(ContextError::ParamTypeMismatch)` si la clave existe pero el tipo no coincide. /// /// # Ejemplos /// @@ -308,10 +308,10 @@ impl Context { /// // Error de tipo: /// assert!(cx.get_param::("usuario_id").is_err()); /// ``` - pub fn get_param(&self, key: &'static str) -> Result<&T, ErrorParam> { - let (any, type_name) = self.params.get(key).ok_or(ErrorParam::NotFound)?; + pub fn get_param(&self, key: &'static str) -> Result<&T, ContextError> { + let (any, type_name) = self.params.get(key).ok_or(ContextError::ParamNotFound)?; any.downcast_ref::() - .ok_or_else(|| ErrorParam::TypeMismatch { + .ok_or_else(|| ContextError::ParamTypeMismatch { key, expected: TypeInfo::FullName.of::(), saved: type_name, @@ -323,8 +323,8 @@ impl Context { /// Devuelve: /// /// - `Ok(T)` si la clave existía y el tipo coincide. - /// - `Err(ErrorParam::NotFound)` si la clave no existe. - /// - `Err(ErrorParam::TypeMismatch)` si el tipo no coincide. + /// - `Err(ContextError::ParamNotFound)` si la clave no existe. + /// - `Err(ContextError::ParamTypeMismatch)` si el tipo no coincide. /// /// # Ejemplos /// @@ -341,12 +341,12 @@ impl Context { /// // Error de tipo: /// assert!(cx.take_param::("titulo").is_err()); /// ``` - pub fn take_param(&mut self, key: &'static str) -> Result { - let (boxed, saved) = self.params.remove(key).ok_or(ErrorParam::NotFound)?; + pub fn take_param(&mut self, key: &'static str) -> Result { + let (boxed, saved) = self.params.remove(key).ok_or(ContextError::ParamNotFound)?; boxed .downcast::() .map(|b| *b) - .map_err(|_| ErrorParam::TypeMismatch { + .map_err(|_| ContextError::ParamTypeMismatch { key, expected: TypeInfo::FullName.of::(), saved, diff --git a/src/html.rs b/src/html.rs index 079811a2..a86c9f76 100644 --- a/src/html.rs +++ b/src/html.rs @@ -31,12 +31,12 @@ pub type ContextOp = crate::core::component::ContextOp; )] pub trait Contextual: crate::core::component::Contextual {} -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ErrorParam`] en su lugar. +/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextError`] en su lugar. #[deprecated( since = "0.5.0", - note = "Moved to `pagetop::core::component::ErrorParam`" + note = "Moved to `pagetop::core::component::ContextError`" )] -pub type ErrorParam = crate::core::component::ErrorParam; +pub type ContextError = crate::core::component::ContextError; /// **Obsoleto desde la versión 0.5.0**: usar [`ContextOp`] en su lugar. #[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] From 200f8c0f4f5fb1206cd932de931c155b831f95ab Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 2 Oct 2025 18:48:20 +0200 Subject: [PATCH 141/224] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Dep?= =?UTF-8?q?ura=20atributos=20`#[inline]`=20en=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 5af5f9c9..709ce572 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -266,10 +266,13 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { v }; - // Extrae atributos descartando la documentación para incluir en `alter_...()`. - let non_doc_attrs: Vec<_> = attrs + // Filtra los atributos descartando `#[doc]` y `#[inline]` para el método `alter_...()`. + let non_doc_or_inline_attrs: Vec<_> = attrs .iter() - .filter(|&a| !a.path().is_ident("doc")) + .filter(|a| { + let p = a.path(); + !p.is_ident("doc") && !p.is_ident("inline") + }) .cloned() .collect(); @@ -284,14 +287,21 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { #(#attrs)* fn #with_name #generics (self, #(#args),*) -> Self #where_clause; - #(#non_doc_attrs)* + #(#non_doc_or_inline_attrs)* #[doc = #alter_doc] fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause; } } Some(body) => { + // Si no se indicó ninguna forma de `inline`, fuerza `#[inline]` para `with_...()`. + let force_inline = if attrs.iter().any(|a| a.path().is_ident("inline")) { + quote! {} + } else { + quote! { #[inline] } + }; let with_fn = if is_trait { quote! { + #force_inline #vis_pub fn #with_name #generics (self, #(#args),*) -> Self #where_clause { let mut s = self; s.#alter_ident(#(#call_idents),*); @@ -300,6 +310,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { } } else { quote! { + #force_inline #vis_pub fn #with_name #generics (mut self, #(#args),*) -> Self #where_clause { self.#alter_ident(#(#call_idents),*); self @@ -310,7 +321,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { #(#attrs)* #with_fn - #(#non_doc_attrs)* + #(#non_doc_or_inline_attrs)* #[doc = #alter_doc] #vis_pub fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause { #body From e23bd0294ce9f795760b587cfa406b3d0801c60b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 2 Oct 2025 21:24:19 +0200 Subject: [PATCH 142/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20uso=20de=20las?= =?UTF-8?q?=20regiones=20en=20contexto=20y=20p=C3=A1gina?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/theme/basic.rs | 116 +++++++++++++++++----------------- src/core/component/context.rs | 22 ++++++- src/core/theme/definition.rs | 86 ++++++++++++------------- src/core/theme/regions.rs | 2 +- src/response/page.rs | 37 ++++++----- 5 files changed, 142 insertions(+), 121 deletions(-) diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index ecd64856..28d93890 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -93,76 +93,74 @@ fn render_intro(page: &mut Page) -> Markup { let intro_button_lnk: Option<&String> = page.param("intro_button_lnk"); html! { - body id=[page.body_id().get()] class=[page.body_classes().get()] { - header class="intro-header" { - section class="intro-header__body" { - h1 class="intro-header__title" { - span { (title) } - (intro) - } + header class="intro-header" { + section class="intro-header__body" { + h1 class="intro-header__title" { + span { (title) } + (intro) } - aside class="intro-header__image" aria-hidden="true" { - div class="intro-header__monster" { - picture { - source - type="image/avif" - src="/img/monster-pagetop_250.avif" - srcset="/img/monster-pagetop_500.avif 1.5x"; - source - type="image/webp" - src="/img/monster-pagetop_250.webp" - srcset="/img/monster-pagetop_500.webp 1.5x"; - img - src="/img/monster-pagetop_250.png" - srcset="/img/monster-pagetop_500.png 1.5x" - alt="Monster PageTop"; - } + } + aside class="intro-header__image" aria-hidden="true" { + div class="intro-header__monster" { + picture { + source + type="image/avif" + src="/img/monster-pagetop_250.avif" + srcset="/img/monster-pagetop_500.avif 1.5x"; + source + type="image/webp" + src="/img/monster-pagetop_250.webp" + srcset="/img/monster-pagetop_500.webp 1.5x"; + img + src="/img/monster-pagetop_250.png" + srcset="/img/monster-pagetop_500.png 1.5x" + alt="Monster PageTop"; } } } - main class="intro-content" { - section class="intro-content__body" { - @if intro_button_lnk.is_some() { - div class="intro-button" { - a - class="intro-button__link" - href=[intro_button_lnk] - target="_blank" - rel="noreferrer" - { - span {} span {} span {} - div class="intro-button__text" { - (intro_button_txt.using(page)) - } + } + main class="intro-content" { + section class="intro-content__body" { + @if intro_button_lnk.is_some() { + div class="intro-button" { + a + class="intro-button__link" + href=[intro_button_lnk] + target="_blank" + rel="noreferrer" + { + span {} span {} span {} + div class="intro-button__text" { + (intro_button_txt.using(page)) } } } - div class="intro-text" { (page.render_region("content")) } } + div class="intro-text" { (page.render_region("content")) } } - footer class="intro-footer" { - section class="intro-footer__body" { - div class="intro-footer__logo" { - svg - viewBox="0 0 1614 1614" - xmlns="http://www.w3.org/2000/svg" - role="img" - aria-label=[L10n::l("pagetop_logo").lookup(page)] - preserveAspectRatio="xMidYMid slice" - focusable="false" - { - path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} - path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} - path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} - } - } - div class="intro-footer__links" { - a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } - a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(page)) } - em { (L10n::l("intro_have_fun").using(page)) } + } + footer class="intro-footer" { + section class="intro-footer__body" { + div class="intro-footer__logo" { + svg + viewBox="0 0 1614 1614" + xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label=[L10n::l("pagetop_logo").lookup(page)] + preserveAspectRatio="xMidYMid slice" + focusable="false" + { + path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} } } + div class="intro-footer__links" { + a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } + a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(page)) } + em { (L10n::l("intro_have_fun").using(page)) } + } } } } diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 9dad7f5f..b344c860 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -1,5 +1,6 @@ +use crate::core::component::ChildOp; use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME}; -use crate::core::theme::ThemeRef; +use crate::core::theme::{ChildrenInRegions, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; @@ -104,6 +105,10 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: ContextOp) -> Self; + /// Opera con [`ChildOp`] en una región (`region_name`) de la página. + #[builder_fn] + fn with_child_in(self, region_name: &'static str, op: ChildOp) -> Self; + // **< Contextual GETTERS >********************************************************************* /// Devuelve una referencia a la solicitud HTTP asociada, si existe. @@ -211,6 +216,7 @@ pub struct Context { favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. + regions : ChildrenInRegions, // Regiones de componentes para renderizar. params : HashMap<&'static str, (Box, &'static str)>, // Parámetros en ejecución. id_counter : usize, // Contador para generar identificadores únicos. } @@ -250,6 +256,7 @@ impl Context { favicon : None, stylesheets: Assets::::new(), javascripts: Assets::::new(), + regions : ChildrenInRegions::default(), params : HashMap::default(), id_counter : 0, } @@ -283,6 +290,13 @@ impl Context { markup } + /// Renderiza los componentes de una región (`region_name`). + pub fn render_region(&mut self, region_name: &'static str) -> Markup { + self.regions + .merge_all_components(self.theme, region_name) + .render(self) + } + // **< Context PARAMS >************************************************************************* /// Recupera una *referencia tipada* al parámetro solicitado. @@ -471,6 +485,12 @@ impl Contextual for Context { self } + #[builder_fn] + fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { + self.regions.alter_child_in(region_name, op); + self + } + // **< Contextual GETTERS >********************************************************************* fn request(&self) -> Option<&HttpRequest> { diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 38a0bfc4..643bfffd 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -26,65 +26,65 @@ pub type ThemeRef = &'static dyn Theme; /// - `::render_body(self, page, self.page_regions())` /// - `::render_head(self, page)` pub trait ThemePage { - /// Renderiza el contenido del `` de la página. + /// Renderiza el **contenido interior** del `` de la página. /// /// Recorre `regions` en el **orden declarado** y, para cada región con contenido, genera un - /// contenedor con `role="region"` y un `aria-label` localizado. Se asume que cada identificador - /// de región es **único** dentro de la página. + /// contenedor con `role="region"` y un `aria-label` localizado. + /// Se asume que cada identificador de región es **único** dentro de la página. + /// + /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. fn render_body(&self, page: &mut Page, regions: &[(Region, L10n)]) -> Markup { html! { - body id=[page.body_id().get()] class=[page.body_classes().get()] { - @for (region, region_label) in regions { - @let output = page.render_region(region.key()); - @if !output.is_empty() { - @let region_name = region.name(); - div - id=(region_name) - class={ "region region--" (region_name) } - role="region" - aria-label=[region_label.lookup(page)] - { - (output) - } + @for (region, region_label) in regions { + @let output = page.render_region(region.key()); + @if !output.is_empty() { + @let region_name = region.name(); + div + id=(region_name) + class={ "region region--" (region_name) } + role="region" + aria-label=[region_label.lookup(page)] + { + (output) } } } } } - /// Renderiza el contenido del `` de la página. + /// Renderiza el **contenido interior** del `` de la página. /// - /// Por defecto incluye las etiquetas básicas (`charset`, `title`, `description`, `viewport`, - /// `X-UA-Compatible`), los metadatos (`name/content`) y propiedades (`property/content`), - /// además de los recursos CSS/JS de la página. + /// Recorre y genera por defecto las etiquetas básicas (`charset`, `title`, `description`, + /// `viewport`, `X-UA-Compatible`), los metadatos (`name/content`) y propiedades + /// (`property/content`), además de los recursos CSS/JS de la página. + /// + /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. fn render_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { - head { - meta charset="utf-8"; + meta charset="utf-8"; - @if let Some(title) = page.title() { - title { (global::SETTINGS.app.name) (" | ") (title) } - } @else { - title { (global::SETTINGS.app.name) } - } - - @if let Some(description) = page.description() { - meta name="description" content=(description); - } - - meta name="viewport" content=(viewport); - @for (name, content) in page.metadata() { - meta name=(name) content=(content) {} - } - - meta http-equiv="X-UA-Compatible" content="IE=edge"; - @for (property, content) in page.properties() { - meta property=(property) content=(content) {} - } - - (page.render_assets()) + @if let Some(title) = page.title() { + title { (global::SETTINGS.app.name) (" | ") (title) } + } @else { + title { (global::SETTINGS.app.name) } } + + @if let Some(description) = page.description() { + meta name="description" content=(description); + } + + meta name="viewport" content=(viewport); + @for (name, content) in page.metadata() { + meta name=(name) content=(content) {} + } + + meta http-equiv="X-UA-Compatible" content="IE=edge"; + @for (property, content) in page.properties() { + meta property=(property) content=(content) {} + } + + (page.render_assets()) } } } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 8082aac9..f14645c0 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -64,7 +64,7 @@ impl Region { self.key } - /// Devuelve el nombre normalizado de la región (para atributos y búsquedas). + /// Devuelve el nombre normalizado de la región (para identificadores y atributos HTML). #[inline] pub fn name(&self) -> &str { &self.name diff --git a/src/response/page.rs b/src/response/page.rs index f81c9806..deab452b 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -5,7 +5,7 @@ pub use actix_web::Result as ResultPage; use crate::base::action; use crate::core::component::{Child, ChildOp, Component, Context, ContextOp, Contextual}; -use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; +use crate::core::theme::{ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{AttrClasses, ClassesOp}; @@ -29,7 +29,6 @@ pub struct Page { body_id : AttrId, body_classes: AttrClasses, context : Context, - regions : ChildrenInRegions, } impl Page { @@ -47,7 +46,6 @@ impl Page { body_id : AttrId::default(), body_classes: AttrClasses::default(), context : Context::new(Some(request)), - regions : ChildrenInRegions::default(), } } @@ -111,7 +109,7 @@ impl Page { /// Añade un componente a la región de contenido por defecto. pub fn add_component(mut self, component: impl Component) -> Self { - self.regions + self.context .alter_child_in(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } @@ -122,7 +120,7 @@ impl Page { region_name: &'static str, component: impl Component, ) -> Self { - self.regions + self.context .alter_child_in(region_name, ChildOp::Add(Child::with(component))); self } @@ -143,13 +141,6 @@ impl Page { self } - /// Opera con [`ChildOp`] en una región (`region_name`) de la página. - #[builder_fn] - pub fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { - self.regions.alter_child_in(region_name, op); - self - } - // **< Page GETTERS >*************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. @@ -194,13 +185,13 @@ impl Page { // **< Page RENDER >**************************************************************************** /// Renderiza los componentes de una región (`region_name`) de la página. + #[inline] pub fn render_region(&mut self, region_name: &'static str) -> Markup { - self.regions - .merge_all_components(self.context.theme(), region_name) - .render(&mut self.context) + self.context.render_region(region_name) } /// Renderiza los recursos de la página. + #[inline] pub fn render_assets(&mut self) -> Markup { self.context.render_assets() } @@ -238,8 +229,14 @@ impl Page { Ok(html! { (DOCTYPE) html lang=(lang) dir=(dir) { - (head) - (body) + head { + (head) + } + body id=[self.body_id().get()] class=[self.body_classes().get()] { + (self.render_region("page-top")) + (body) + (self.render_region("page-bottom")) + } } }) } @@ -290,6 +287,12 @@ impl Contextual for Page { self } + #[builder_fn] + fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { + self.context.alter_child_in(region_name, op); + self + } + // **< Contextual GETTERS >********************************************************************* fn request(&self) -> Option<&HttpRequest> { From 7b941cb02c1e6f7fa4b61f3514bca48889ad6c56 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 3 Oct 2025 01:55:03 +0200 Subject: [PATCH 143/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`region=5Fnam?= =?UTF-8?q?e`=20a=20`region=5Fkey`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/theme/basic.rs | 4 +++- src/core/component/context.rs | 14 ++++++------ src/core/theme/definition.rs | 4 ++-- src/core/theme/regions.rs | 38 +++++++++++++++---------------- src/response/page.rs | 42 +++++++++++------------------------ 5 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 28d93890..54660a96 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -136,7 +136,9 @@ fn render_intro(page: &mut Page) -> Markup { } } } - div class="intro-text" { (page.render_region("content")) } + div class="intro-text" { + (page.context().render_components_of("content")) + } } } footer class="intro-footer" { diff --git a/src/core/component/context.rs b/src/core/component/context.rs index b344c860..f58d3816 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -105,9 +105,9 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: ContextOp) -> Self; - /// Opera con [`ChildOp`] en una región (`region_name`) de la página. + /// Opera con [`ChildOp`] en una región (`region_key`) de la página. #[builder_fn] - fn with_child_in(self, region_name: &'static str, op: ChildOp) -> Self; + fn with_child_in(self, region_key: &'static str, op: ChildOp) -> Self; // **< Contextual GETTERS >********************************************************************* @@ -290,10 +290,10 @@ impl Context { markup } - /// Renderiza los componentes de una región (`region_name`). - pub fn render_region(&mut self, region_name: &'static str) -> Markup { + /// Renderiza los componentes de una región (`region_key`). + pub fn render_components_of(&mut self, region_key: &'static str) -> Markup { self.regions - .merge_all_components(self.theme, region_name) + .merge_all_components(self.theme, region_key) .render(self) } @@ -486,8 +486,8 @@ impl Contextual for Context { } #[builder_fn] - fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { - self.regions.alter_child_in(region_name, op); + fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { + self.regions.alter_child_in(region_key, op); self } diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 643bfffd..4478ad0b 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -36,7 +36,7 @@ pub trait ThemePage { fn render_body(&self, page: &mut Page, regions: &[(Region, L10n)]) -> Markup { html! { @for (region, region_label) in regions { - @let output = page.render_region(region.key()); + @let output = page.context().render_components_of(region.key()); @if !output.is_empty() { @let region_name = region.name(); div @@ -84,7 +84,7 @@ pub trait ThemePage { meta property=(property) content=(content) {} } - (page.render_assets()) + (page.context().render_assets()) } } } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index f14645c0..3fdfe099 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -76,30 +76,30 @@ impl Region { pub struct ChildrenInRegions(HashMap<&'static str, Children>); impl ChildrenInRegions { - pub fn with(region_name: &'static str, child: Child) -> Self { - ChildrenInRegions::default().with_child_in(region_name, ChildOp::Add(child)) + pub fn with(region_key: &'static str, child: Child) -> Self { + ChildrenInRegions::default().with_child_in(region_key, ChildOp::Add(child)) } #[builder_fn] - pub fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { - if let Some(region) = self.0.get_mut(region_name) { + pub fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { + if let Some(region) = self.0.get_mut(region_key) { region.alter_child(op); } else { - self.0.insert(region_name, Children::new().with_child(op)); + self.0.insert(region_key, Children::new().with_child(op)); } self } - pub fn merge_all_components(&self, theme_ref: ThemeRef, region_name: &'static str) -> Children { + pub fn merge_all_components(&self, theme_ref: ThemeRef, region_key: &'static str) -> Children { let common = COMMON_REGIONS.read(); if let Some(r) = THEME_REGIONS.read().get(&theme_ref.type_id()) { Children::merge(&[ - common.0.get(region_name), - self.0.get(region_name), - r.0.get(region_name), + common.0.get(region_key), + self.0.get(region_key), + r.0.get(region_key), ]) } else { - Children::merge(&[common.0.get(region_name), self.0.get(region_name)]) + Children::merge(&[common.0.get(region_key), self.0.get(region_key)]) } } } @@ -114,9 +114,9 @@ impl ChildrenInRegions { pub enum InRegion { /// Región de contenido por defecto. Content, - /// Región identificada por el nombre proporcionado. - Named(&'static str), - /// Región identificada por un nombre y asociada a un tema concreto. + /// Región identificada por la clave proporcionado. + Key(&'static str), + /// Región identificada por una clave para un tema concreto. OfTheme(&'static str, ThemeRef), } @@ -134,7 +134,7 @@ impl InRegion { /// ))); /// /// // Texto en la región "sidebar". - /// InRegion::Named("sidebar").add(Child::with(Html::with(|_| + /// InRegion::Key("sidebar").add(Child::with(Html::with(|_| /// html! { ("Publicidad") } /// ))); /// ``` @@ -145,19 +145,19 @@ impl InRegion { .write() .alter_child_in(REGION_CONTENT, ChildOp::Add(child)); } - InRegion::Named(region_name) => { + InRegion::Key(region_key) => { COMMON_REGIONS .write() - .alter_child_in(region_name, ChildOp::Add(child)); + .alter_child_in(region_key, ChildOp::Add(child)); } - InRegion::OfTheme(region_name, theme_ref) => { + InRegion::OfTheme(region_key, theme_ref) => { let mut regions = THEME_REGIONS.write(); if let Some(r) = regions.get_mut(&theme_ref.type_id()) { - r.alter_child_in(region_name, ChildOp::Add(child)); + r.alter_child_in(region_key, ChildOp::Add(child)); } else { regions.insert( theme_ref.type_id(), - ChildrenInRegions::with(region_name, child), + ChildrenInRegions::with(region_key, child), ); } } diff --git a/src/response/page.rs b/src/response/page.rs index deab452b..1649d543 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -103,8 +103,8 @@ impl Page { /// **Obsoleto desde la versión 0.4.0**: usar [`add_component_in()`](Self::add_component_in) en /// su lugar. #[deprecated(since = "0.4.0", note = "Use `add_component_in()` instead")] - pub fn with_component_in(self, region_name: &'static str, component: impl Component) -> Self { - self.add_component_in(region_name, component) + pub fn with_component_in(self, region_key: &'static str, component: impl Component) -> Self { + self.add_component_in(region_key, component) } /// Añade un componente a la región de contenido por defecto. @@ -114,30 +114,26 @@ impl Page { self } - /// Añade un componente en una región (`region_name`) de la página. - pub fn add_component_in( - mut self, - region_name: &'static str, - component: impl Component, - ) -> Self { + /// Añade un componente en una región (`region_key`) de la página. + pub fn add_component_in(mut self, region_key: &'static str, component: impl Component) -> Self { self.context - .alter_child_in(region_name, ChildOp::Add(Child::with(component))); + .alter_child_in(region_key, ChildOp::Add(Child::with(component))); self } /// **Obsoleto desde la versión 0.4.0**: usar [`with_child_in()`](Self::with_child_in) en su /// lugar. #[deprecated(since = "0.4.0", note = "Use `with_child_in()` instead")] - pub fn with_child_in_region(mut self, region_name: &'static str, op: ChildOp) -> Self { - self.alter_child_in(region_name, op); + pub fn with_child_in_region(mut self, region_key: &'static str, op: ChildOp) -> Self { + self.alter_child_in(region_key, op); self } /// **Obsoleto desde la versión 0.4.0**: usar [`alter_child_in()`](Self::alter_child_in) en su /// lugar. #[deprecated(since = "0.4.0", note = "Use `alter_child_in()` instead")] - pub fn alter_child_in_region(&mut self, region_name: &'static str, op: ChildOp) -> &mut Self { - self.alter_child_in(region_name, op); + pub fn alter_child_in_region(&mut self, region_key: &'static str, op: ChildOp) -> &mut Self { + self.alter_child_in(region_key, op); self } @@ -184,18 +180,6 @@ impl Page { // **< Page RENDER >**************************************************************************** - /// Renderiza los componentes de una región (`region_name`) de la página. - #[inline] - pub fn render_region(&mut self, region_name: &'static str) -> Markup { - self.context.render_region(region_name) - } - - /// Renderiza los recursos de la página. - #[inline] - pub fn render_assets(&mut self) -> Markup { - self.context.render_assets() - } - /// Renderiza la página completa en formato HTML. /// /// Ejecuta las acciones correspondientes antes y después de renderizar el ``, @@ -233,9 +217,9 @@ impl Page { (head) } body id=[self.body_id().get()] class=[self.body_classes().get()] { - (self.render_region("page-top")) + (self.context.render_components_of("page-top")) (body) - (self.render_region("page-bottom")) + (self.context.render_components_of("page-bottom")) } } }) @@ -288,8 +272,8 @@ impl Contextual for Page { } #[builder_fn] - fn with_child_in(mut self, region_name: &'static str, op: ChildOp) -> Self { - self.context.alter_child_in(region_name, op); + fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { + self.context.alter_child_in(region_key, op); self } From 5f64d500808858b3baa39fc8baa3277707678b73 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 4 Oct 2025 08:25:04 +0200 Subject: [PATCH 144/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20`Region`=20para?= =?UTF-8?q?=20declarar=20las=20regiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/theme/definition.rs | 32 ++++++++++++++++---------------- src/core/theme/regions.rs | 12 +++++++++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 4478ad0b..0322a31e 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -28,14 +28,14 @@ pub type ThemeRef = &'static dyn Theme; pub trait ThemePage { /// Renderiza el **contenido interior** del `` de la página. /// - /// Recorre `regions` en el **orden declarado** y, para cada región con contenido, genera un - /// contenedor con `role="region"` y un `aria-label` localizado. + /// Esta implementación recorre `regions` en el **orden declarado** y, para cada región con + /// contenido, genera un contenedor con `role="region"` y un `aria-label` localizado. /// Se asume que cada identificador de región es **único** dentro de la página. /// /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. - fn render_body(&self, page: &mut Page, regions: &[(Region, L10n)]) -> Markup { + fn render_body(&self, page: &mut Page, regions: &[Region]) -> Markup { html! { - @for (region, region_label) in regions { + @for region in regions { @let output = page.context().render_components_of(region.key()); @if !output.is_empty() { @let region_name = region.name(); @@ -43,7 +43,7 @@ pub trait ThemePage { id=(region_name) class={ "region region--" (region_name) } role="region" - aria-label=[region_label.lookup(page)] + aria-label=[region.label().lookup(page)] { (output) } @@ -125,31 +125,31 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// Declaración ordenada de las regiones disponibles en la página. /// - /// Devuelve una **lista estática** de pares `(Region, L10n)` que se usará para renderizar todas - /// las regiones que componen una página en el orden indicado . + /// Devuelve una **lista estática** de regiones ([`Region`](crate::core::theme::Region)) con la + /// información necesaria para renderizar el contenedor de cada región. /// /// Si un tema necesita un conjunto distinto de regiones, se puede **sobrescribir** este método /// con los siguientes requisitos y recomendaciones: /// /// - Los identificadores deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). - /// - La región `"content"` es **obligatoria**. Se puede usar [`Region::default()`] para - /// declararla. - /// - La etiqueta `L10n` se evalúa con el idioma activo de la página. + /// - La región `"content"` es **obligatoria** porque se usa por defecto para añadir componentes + /// para renderizar. Se puede utilizar [`Region::default()`] para declararla. + /// - La etiqueta `L10n` se evaluará con el idioma activo de la página. /// /// Por defecto devuelve: /// /// - `"header"`: cabecera. /// - `"content"`: contenido principal (**obligatoria**). /// - `"footer"`: pie. - fn page_regions(&self) -> &'static [(Region, L10n)] { - static REGIONS: LazyLock<[(Region, L10n); 3]> = LazyLock::new(|| { + fn page_regions(&self) -> &'static [Region] { + static REGIONS: LazyLock<[Region; 3]> = LazyLock::new(|| { [ - (Region::declare("header"), L10n::l("region_header")), - (Region::default(), L10n::l("region_content")), - (Region::declare("footer"), L10n::l("region_footer")), + Region::declare("header", L10n::l("region_header")), + Region::default(), + Region::declare("footer", L10n::l("region_footer")), ] }); - ®IONS[..] + &*REGIONS } /// Acciones específicas del tema antes de renderizar el `` de la página. diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 3fdfe099..00ff60cb 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -1,5 +1,6 @@ use crate::core::component::{Child, ChildOp, Children}; use crate::core::theme::ThemeRef; +use crate::locale::L10n; use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; @@ -29,6 +30,7 @@ pub const REGION_CONTENT: &str = "content"; pub struct Region { key: &'static str, name: String, + label: L10n, } impl Default for Region { @@ -37,6 +39,7 @@ impl Default for Region { Self { key: REGION_CONTENT, name: REGION_CONTENT.to_string(), + label: L10n::l("region_content"), } } } @@ -51,10 +54,11 @@ impl Region { /// sencillos, limitando los caracteres a `[a-z0-9-]` (p.ej., `"sidebar"` o `"main-menu"`), cuyo /// nombre normalizado coincidirá con la clave. #[inline] - pub fn declare(key: &'static str) -> Self { + pub fn declare(key: &'static str, label: L10n) -> Self { Self { key, name: key.trim().to_ascii_lowercase().replace(' ', "-"), + label, } } @@ -69,6 +73,12 @@ impl Region { pub fn name(&self) -> &str { &self.name } + + /// Devuelve la etiqueta localizada asociada a la región. + #[inline] + pub fn label(&self) -> &L10n { + &self.label + } } // Contenedor interno de componentes agrupados por región. From cac50b21cb6f724b06070d2dc5f24582bb3a42b7 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 6 Oct 2025 04:09:26 +0200 Subject: [PATCH 145/224] =?UTF-8?q?=F0=9F=9A=A7=20Depura=20la=20estructura?= =?UTF-8?q?=20y=20estilos=20del=20men=C3=BA=20e=20intro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/menu/item.rs | 8 +- src/base/theme/basic.rs | 55 +++++----- src/core/theme/definition.rs | 83 +++++++++++----- static/css/basic.css | 16 +++ static/css/intro.css | 112 +++++++++------------ static/css/menu.css | 171 +++++++++++++++++++++++--------- static/css/root.css | 19 ++++ static/js/menu.js | 1 - 8 files changed, 302 insertions(+), 163 deletions(-) diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs index 7a36fbbf..c9f79033 100644 --- a/src/base/component/menu/item.rs +++ b/src/base/component/menu/item.rs @@ -49,7 +49,7 @@ impl Component for Item { }), ItemKind::Link(label, path) => PrepareMarkup::With(html! { li class="menu__item menu__item--link" { - a href=(path(cx)) title=[description] { + a class="menu__link" href=(path(cx)) title=[description] { (left_icon) span class="menu__label" { (label.using(cx)) } (right_icon) @@ -58,7 +58,7 @@ impl Component for Item { }), ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! { li class="menu__item menu__item--link" { - a href=(path(cx)) title=[description] target="_blank" { + a class="menu__link" href=(path(cx)) title=[description] target="_blank" { (left_icon) span class="menu__label" { (label.using(cx)) } (right_icon) @@ -72,7 +72,7 @@ impl Component for Item { }), ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! { li class="menu__item menu__item--children" { - a href="#" title=[description] { + button type="button" class="menu__link" title=[description] { (left_icon) span class="menu__label" { (label.using(cx)) } (Icon::svg(html! { @@ -86,7 +86,7 @@ impl Component for Item { }), ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! { li class="menu__item menu__item--children" { - a href="#" title=[description] { + button type="button" class="menu__link" title=[description] { (left_icon) span class="menu__label" { (label.using(cx)) } (Icon::svg(html! { diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 54660a96..97d28b82 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -46,12 +46,8 @@ impl Theme for Basic { } fn after_render_page_body(&self, page: &mut Page) { - let styles = match page.layout() { - "Intro" => "/css/intro.css", - "PageTopIntro" => "/css/intro.css", - _ => "/css/basic.css", - }; let pkg_version = env!("CARGO_PKG_VERSION"); + page.alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/normalize.css") .with_version("8.0.1") @@ -62,6 +58,11 @@ impl Theme for Basic { .with_version(pkg_version) .with_weight(-99), )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(pkg_version) + .with_weight(-99), + )) .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/css/components.css") .with_version(pkg_version) @@ -72,11 +73,6 @@ impl Theme for Basic { .with_version(pkg_version) .with_weight(-99), )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from(styles) - .with_version(pkg_version) - .with_weight(-99), - )) .alter_assets(ContextOp::AddJavaScript( JavaScript::defer("/js/menu.js") .with_version(pkg_version) @@ -86,9 +82,18 @@ impl Theme for Basic { } fn render_intro(page: &mut Page) -> Markup { + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/intro.css").with_version(env!("CARGO_PKG_VERSION")), + )); + let title = page.title().unwrap_or_default(); let intro = page.description().unwrap_or_default(); + let theme = page.context().theme(); + let h = theme.render_page_region(page, "header"); + let c = theme.render_page_region(page, "content"); + let f = theme.render_page_region(page, "footer"); + let intro_button_txt: L10n = page.param_or_default("intro_button_txt"); let intro_button_lnk: Option<&String> = page.param("intro_button_lnk"); @@ -118,26 +123,27 @@ fn render_intro(page: &mut Page) -> Markup { } } } + (h) } main class="intro-content" { section class="intro-content__body" { - @if intro_button_lnk.is_some() { - div class="intro-button" { - a - class="intro-button__link" - href=[intro_button_lnk] - target="_blank" - rel="noreferrer" - { - span {} span {} span {} - div class="intro-button__text" { - (intro_button_txt.using(page)) + div class="intro-text" { + @if intro_button_lnk.is_some() { + div class="intro-button" { + a + class="intro-button__link" + href=[intro_button_lnk] + target="_blank" + rel="noreferrer" + { + span {} span {} span {} + div class="intro-button__text" { + (intro_button_txt.using(page)) + } } } } - } - div class="intro-text" { - (page.context().render_components_of("content")) + (c) } } } @@ -164,6 +170,7 @@ fn render_intro(page: &mut Page) -> Markup { em { (L10n::l("intro_have_fun").using(page)) } } } + (f) } } } diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 0322a31e..5756fb23 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -15,50 +15,70 @@ pub type ThemeRef = &'static dyn Theme; /// Métodos predefinidos de renderizado para las páginas de un tema. /// -/// Contiene las implementaciones base de las **secciones** `` y ``. Se implementa -/// automáticamente para cualquier tipo que implemente [`Theme`], por lo que normalmente no requiere -/// implementación explícita. +/// Contiene las implementaciones base para renderizar las **secciones** `` y ``. Se +/// implementa automáticamente para cualquier tipo que implemente [`Theme`], por lo que normalmente +/// no requiere implementación explícita. /// -/// Si un tema **sobrescribe** [`render_page_head()`](Theme::render_page_head) o -/// [`render_page_body()`](Theme::render_page_body), se puede volver al comportamiento por defecto -/// cuando se necesite usando FQS (*Fully Qualified Syntax*): +/// Si un tema **sobrescribe** uno o más de estos métodos de [`Theme`]: +/// +/// - [`render_page_region()`](Theme::render_page_region), +/// - [`render_page_head()`](Theme::render_page_head), o +/// - [`render_page_body()`](Theme::render_page_body); +/// +/// es posible volver al comportamiento por defecto usando FQS (*Fully Qualified Syntax*): /// /// - `::render_body(self, page, self.page_regions())` /// - `::render_head(self, page)` pub trait ThemePage { + /// Renderiza el **contenedor** de una región concreta del `` de la página. + /// + /// Obtiene los componentes asociados a `region.key()` desde el contexto de la página y, si hay + /// salida, envuelve el contenido en un contenedor `
` predefinido. + /// + /// Si la región **no produce contenido**, devuelve un `Markup` vacío. + #[inline] + fn render_region(&self, page: &mut Page, region: &Region) -> Markup { + html! { + @let output = page.context().render_components_of(region.key()); + @if !output.is_empty() { + @let region_name = region.name(); + div + id=(region_name) + class={ "region region--" (region_name) } + role="region" + aria-label=[region.label().lookup(page)] + { + (output) + } + } + } + } + /// Renderiza el **contenido interior** del `` de la página. /// - /// Esta implementación recorre `regions` en el **orden declarado** y, para cada región con - /// contenido, genera un contenedor con `role="region"` y un `aria-label` localizado. - /// Se asume que cada identificador de región es **único** dentro de la página. + /// Recorre `regions` en el **orden declarado** y, para cada región con contenido, delega en + /// [`render_region()`](Self::render_region) la generación del contenedor. Las regiones sin + /// contenido **no** producen salida. Se asume que cada identificador de región es **único** + /// dentro de la página. /// /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. + #[inline] fn render_body(&self, page: &mut Page, regions: &[Region]) -> Markup { html! { @for region in regions { - @let output = page.context().render_components_of(region.key()); - @if !output.is_empty() { - @let region_name = region.name(); - div - id=(region_name) - class={ "region region--" (region_name) } - role="region" - aria-label=[region.label().lookup(page)] - { - (output) - } - } + (self.render_region(page, region)) } } } /// Renderiza el **contenido interior** del `` de la página. /// - /// Recorre y genera por defecto las etiquetas básicas (`charset`, `title`, `description`, - /// `viewport`, `X-UA-Compatible`), los metadatos (`name/content`) y propiedades - /// (`property/content`), además de los recursos CSS/JS de la página. + /// Incluye por defecto las etiquetas básicas (`charset`, `title`, `description`, `viewport`, + /// `X-UA-Compatible`), los metadatos (`name/content`) y propiedades (`property/content`), + /// además de los recursos CSS/JS de la página. /// - /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. + /// La etiqueta `` no se incluye aquí; únicamente se renderiza su contenido. + #[inline] fn render_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { @@ -152,6 +172,19 @@ pub trait Theme: Extension + ThemePage + Send + Sync { &*REGIONS } + /// Renderiza una región de la página **por clave**. + /// + /// Busca en [`page_regions()`](Self::page_regions) la región asociada a una clave y, si existe, + /// delega en [`ThemePage::render_region()`] su renderizado. Si no se encuentra la clave o la + /// región no produce contenido, devuelve un `Markup` vacío. + fn render_page_region(&self, page: &mut Page, key: &str) -> Markup { + html! { + @if let Some(region) = self.page_regions().iter().find(|r| r.key() == key) { + (self.render_region(page, region)) + } + } + } + /// Acciones específicas del tema antes de renderizar el `` de la página. /// /// Útil para preparar clases, inyectar recursos o ajustar metadatos. diff --git a/static/css/basic.css b/static/css/basic.css index 04801dd0..058e1736 100644 --- a/static/css/basic.css +++ b/static/css/basic.css @@ -1,3 +1,19 @@ +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + font-family: var(--val-font-family); + font-size: var(--val-fs--base); + font-weight: var(--val-fw--base); + line-height: var(--val-lh--base); + color: var(--val-color--text); + background-color: var(--val-color--bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + /* Page layout */ .region--footer { diff --git a/static/css/intro.css b/static/css/intro.css index 39c9d6ad..774bbb2a 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -1,37 +1,15 @@ -:root { - --bg-img: url('/img/intro-header.jpg'); - --bg-img-set: image-set(url('/img/intro-header.avif') type('image/avif'), url('/img/intro-header.webp') type('image/webp'), var(--bg-img) type('image/jpeg')); - --bg-img-sm: url('/img/intro-header-sm.jpg'); - --bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--bg-img-sm) type('image/jpeg')); - --bg-color: #8c5919; - --color: #1a202c; - --color-gray: #e4e4e7; - --color-link: #1e4eae; - --color-block-1: #b689ff; - --color-block-2: #fecaca; - --color-block-3: #e6a9e2; - --color-block-4: #ffedca; - --color-block-5: #ffffff; - --focus-outline: 2px solid var(--color-link); - --focus-outline-offset: 2px; - --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); -} - html { min-height: 100%; background-color: black; } + body { margin: auto; position: relative; min-height: 100%; min-width: 350px; - background-color: var(--bg-color); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - font-size: 1.125rem; - font-weight: 300; - color: var(--color); - line-height: 1.6; + color: var(--intro-color); + background-color: var(--intro-bg-color); width: 100%; display: flex; @@ -51,12 +29,12 @@ a { transition: font-size 0.2s, text-decoration-color 0.2s; } a:focus-visible { - outline: var(--focus-outline); - outline-offset: var(--focus-outline-offset); + outline: var(--intro-focus-outline); + outline-offset: var(--intro-focus-outline-offset); } a:hover, a:hover:visited { - text-decoration-color: var(--color-link); + text-decoration-color: var(--intro-color-link); } /* @@ -69,9 +47,9 @@ a:hover:visited { width: 100%; max-width: 80rem; margin: 0 auto; - padding-bottom: 9rem; - background-image: var(--bg-img-sm); - background-image: var(--bg-img-sm-set); + padding-bottom: 4rem; + background-image: var(--intro-bg-img-sm); + background-image: var(--intro-bg-img-sm-set); background-position: top center; background-position-y: -1rem; background-size: contain; @@ -119,8 +97,8 @@ a:hover:visited { } @media (min-width: 64rem) { .intro-header { - background-image: var(--bg-img); - background-image: var(--bg-img-set); + background-image: var(--intro-bg-img); + background-image: var(--intro-bg-img-set); } .intro-header__title { padding: 1.2rem 2rem 2.6rem 2rem; @@ -180,8 +158,7 @@ a:hover:visited { .intro-button { width: 100%; - margin: 0 auto 3rem; - z-index: 10; + margin: 0 auto; } .intro-button__link { background: #7f1d1d; @@ -306,6 +283,9 @@ a:hover:visited { animation-play-state: paused; } @media (max-width: 48rem) { + .intro-header { + padding-bottom: 9rem;; + } .intro-button__link { height: 6.25rem; min-width: auto; @@ -342,17 +322,15 @@ a:hover:visited { font-size: 1.3125rem; font-weight: 400; line-height: 1.5; - margin-top: -6rem; margin-bottom: 0; background: #fff; position: relative; +} +.region--content { padding: 2.5rem 1.063rem 0.75rem; overflow: hidden; } -.intro-button + .intro-text { - padding-top: 6rem; -} -.intro-text p { +.region--content p { width: 100%; line-height: 150%; font-weight: 400; @@ -364,31 +342,39 @@ a:hover:visited { font-size: 1.375rem; line-height: 2rem; } - .intro-button + .intro-text { + .intro-button + .region--content { padding-top: 7rem; } } @media (min-width: 64rem) { - .intro-text { + .intro-header { + padding-bottom: 9rem;; + } + .intro-text, + .region--content { border-radius: 0.75rem; - box-shadow: var(--shadow); + } + .intro-text { + box-shadow: var(--intro-shadow); max-width: 60rem; margin: 0 auto 6rem; + } + .region--content { padding-left: 4.5rem; padding-right: 4.5rem; } } -.intro-text .block { +.region--content .block { position: relative; } -.intro-text .block__title { +.region--content .block__title { margin: 1em 0 .8em; } -.intro-text .block__title span { +.region--content .block__title span { display: inline-block; padding: 10px 30px 14px; - margin: 0 0 0 20px; + margin: 30px 0 0 20px; background: white; border: 5px solid; border-radius: 30px; @@ -396,7 +382,7 @@ a:hover:visited { border-color: orangered; transform: rotate(-3deg) translateY(-25%); } -.intro-text .block__title:before { +.region--content .block__title:before { content: ""; height: 5px; position: absolute; @@ -409,7 +395,7 @@ a:hover:visited { transform: rotate(2deg) translateY(-50%); transform-origin: top left; } -.intro-text .block__title:after { +.region--content .block__title:after { content: ""; height: 70rem; position: absolute; @@ -417,23 +403,23 @@ a:hover:visited { left: -15%; width: 130%; z-index: -10; - background: var(--color-block-1); + background: var(--intro-bg-block-1); transform: rotate(2deg); } -.intro-text .block:nth-of-type(5n+1) .block__title:after { - background: var(--color-block-1); +.region--content .block:nth-of-type(5n+1) .block__title:after { + background: var(--intro-bg-block-1); } -.intro-text .block:nth-of-type(5n+2) .block__title:after { - background: var(--color-block-2); +.region--content .block:nth-of-type(5n+2) .block__title:after { + background: var(--intro-bg-block-2); } -.intro-text .block:nth-of-type(5n+3) .block__title:after { - background: var(--color-block-3); +.region--content .block:nth-of-type(5n+3) .block__title:after { + background: var(--intro-bg-block-3); } -.intro-text .block:nth-of-type(5n+4) .block__title:after { - background: var(--color-block-4); +.region--content .block:nth-of-type(5n+4) .block__title:after { + background: var(--intro-bg-block-4); } -.intro-text .block:nth-of-type(5n+5) .block__title:after { - background: var(--color-block-5); +.region--content .block:nth-of-type(5n+5) .block__title:after { + background: var(--intro-bg-block-5); } /* @@ -443,7 +429,7 @@ a:hover:visited { .intro-footer { width: 100%; background-color: black; - color: var(--color-gray); + color: var(--intro-color-gray); padding-bottom: 2rem; } @@ -459,7 +445,7 @@ a:hover:visited { line-height: 100%; } .intro-footer__body a:visited { - color: var(--color-gray); + color: var(--intro-color-gray); } .intro-footer__logo, .intro-footer__links { @@ -492,5 +478,5 @@ a:hover:visited { /* PoweredBy component */ .poweredby a:visited { - color: var(--color-gray); + color: var(--intro-color-gray); } diff --git a/static/css/menu.css b/static/css/menu.css index 5520e398..6522f4a7 100644 --- a/static/css/menu.css +++ b/static/css/menu.css @@ -1,54 +1,92 @@ +/* Aislamiento & normalización */ + .menu { + isolation: isolate; +} +@supports (all: revert) { + .menu { + all: revert; + display: block; } +} +.menu { + box-sizing: border-box; + line-height: var(--val-menu--line-height, 1.5); + color: var(--val-color--text); + text-align: left; + text-transform: none; + letter-spacing: normal; + word-spacing: normal; + white-space: normal; + cursor: default; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 100%; height: auto; margin: 0; padding: 0; z-index: 9999; - border: none; - outline: none; + border: 0; background: var(--val-menu--color-bg); } +.menu *, +.menu *::before, +.menu *::after { + box-sizing: inherit; +} +.menu :where(a, button) { + appearance: none; + background: none; + border: 0; + font: inherit; + color: inherit; + text-decoration: none; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} +.menu :where(a, button):focus-visible { + outline: 2px solid var(--val-menu--color-highlight); + outline-offset: 2px; +} +.menu :where(ul, ol) { + list-style: none; + margin: 0; + padding: 0; +} +.menu svg { + fill: currentColor; +} + +/* Estructura */ .menu__wrapper { padding-right: var(--val-gap); } -.menu__wrapper a, -.menu__wrapper button { - cursor: pointer; - border: none; - background: none; - text-decoration: none; -} -.menu__nav ul { - margin: 0; - padding: 0; -} .menu__nav li { display: inline-block; - margin: 0 0 0 1.5rem; + margin: 0; + margin-inline-start: 1.5rem; padding: 0; line-height: var(--val-menu--item-height); list-style: none; - list-style-type: none; } .menu__item--label, -.menu__nav li > a { +.menu__nav li > .menu__link { position: relative; - font-weight: 500; - color: var(--val-color--text); + font-weight: normal; text-rendering: optimizeLegibility; + font-size: 1.45rem; } -.menu__nav li > a { - border: none; +.menu__nav li > .menu__link { transition: color 0.3s ease-in-out; } -.menu__nav li:hover > a, -.menu__nav li > a:focus { +.menu__nav li:hover > .menu__link, +.menu__nav li > .menu__link:focus { color: var(--val-menu--color-highlight); } -.menu__nav li > a > svg.icon { +.menu__nav li > .menu__link > svg.icon { margin-left: 0.25rem; } @@ -57,19 +95,18 @@ max-width: 100%; height: auto; padding: var(--val-gap-0-5) var(--val-gap-1-5); - border: none; - outline: none; + border: 0; background: var(--val-menu--color-bg); border-top: 3px solid var(--val-menu--color-highlight); z-index: 500; opacity: 0; visibility: hidden; box-shadow: 0 4px 6px -1px var(--val-menu--color-border), 0 2px 4px -1px var(--val-menu--color-shadow); - transition: all 0.5s ease-in-out; + transition: all 0.3s ease-in-out; } .menu__item--children:hover > .menu__children, -.menu__item--children > a:focus + .menu__children, +.menu__item--children > .menu__link:focus + .menu__children, .menu__item--children .menu__children:focus-within { margin-top: 0.4rem; opacity: 1; @@ -81,14 +118,12 @@ max-width: var(--val-menu--item-width-max); } .menu__submenu-title { - font-family: inherit; font-size: 1rem; - font-weight: 500; + font-weight: normal; margin: 0; padding: var(--val-menu--line-padding) 0; line-height: var(--val-menu--line-height); - border: none; - outline: none; + border: 0; color: var(--val-menu--color-highlight); text-transform: uppercase; text-rendering: optimizeLegibility; @@ -113,18 +148,15 @@ display: none; } -/* Applies <= 992px */ -@media only screen and (max-width: 62rem) { +/* Responsive <= 62rem (992px) */ + +@media (max-width: 62rem) { .menu__wrapper { padding-right: var(--val-gap-0-5); } .menu__trigger { - cursor: pointer; width: var(--val-menu--trigger-width); height: var(--val-menu--item-height); - border: none; - outline: none; - background: none; display: flex; flex-direction: column; justify-content: center; @@ -133,6 +165,13 @@ width: 2rem; height: 2rem; } + + .menu__nav, + .menu__children { + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + } + .menu__nav { position: fixed; top: 0; @@ -143,10 +182,16 @@ overflow: hidden; background: var(--val-menu--color-bg); transform: translate(-100%); - transition: all 0.5s ease-in-out; + transition: transform .5s ease-in-out, opacity .5s ease-in-out; + will-change: transform; + backface-visibility: hidden; + visibility: hidden; + pointer-events: none; } .menu__nav.active { transform: translate(0%); + visibility: visible; + pointer-events: auto; } .menu__nav li { @@ -156,16 +201,18 @@ } .menu__item--label, - .menu__nav li > a { + .menu__nav li > .menu__link { display: block; + text-align: inherit; + width: 100%; padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); border-bottom: 1px solid var(--val-menu--color-border); } .menu__nav li ul li.menu__item--label, - .menu__nav li ul li > a { + .menu__nav li ul li > .menu__link { border-bottom: 0; } - .menu__nav li > a > svg.icon { + .menu__nav li > .menu__link > svg.icon { position: absolute; top: var(--val-menu--line-padding); right: var(--val-menu--line-padding); @@ -191,6 +238,7 @@ visibility: visible; transform: translateX(0%); box-shadow: none; + transition: opacity .5s ease-in-out, transform .5s ease-in-out, margin-top .5s ease-in-out; } .menu__children.active { display: block; @@ -223,6 +271,16 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: 1.45rem; + font-weight: normal; + opacity: 0; + transform: translateY(.25rem); + transition: opacity .5s ease-in-out, transform .5s ease-in-out; + will-change: opacity, transform; + } + .menu__header.active .menu__title { + opacity: 1; + transform: translateY(0); } .menu__close, .menu__back { @@ -231,18 +289,20 @@ height: var(--val-menu--item-height); line-height: var(--val-menu--item-height); color: var(--val-color--text); - cursor: pointer; display: flex; align-items: center; justify-content: center; + background: var(--val-menu--color-bg); } .menu__close { font-size: 2.25rem; - border-left: 1px solid var(--val-menu--color-border) !important; + border: 1px solid var(--val-menu--color-border) !important; + border-width: 0 0 1px 1px !important; } .menu__back { font-size: 1.25rem; - border-right: 1px solid var(--val-menu--color-border) !important; + border: 1px solid var(--val-menu--color-border) !important; + border-width: 0 1px 1px 0 !important; display: none; } .menu__header.active .menu__back { @@ -267,15 +327,34 @@ opacity: 0; visibility: hidden; background: rgba(0, 0, 0, 0.55); - transition: all 0.5s ease-in-out; + transition: opacity .5s ease-in-out, visibility 0s linear .5s; } .menu__overlay.active { opacity: 1; visibility: visible; + transition-delay: 0s, 0s; + } +} + +@media (hover: hover) and (pointer: fine) { + .menu__item--children:hover > .menu__children { + margin-top: 0.4rem; + opacity: 1; + visibility: visible; } } -/* ANIMATIONS */ +@media (prefers-reduced-motion: reduce) { + .menu__nav, + .menu__children, + .menu__title, + .menu__overlay { + transition: none !important; + animation: none !important; + } +} + +/* Animaciones */ @keyframes slideLeft { 0% { diff --git a/static/css/root.css b/static/css/root.css index aeab1c67..270c1b3f 100644 --- a/static/css/root.css +++ b/static/css/root.css @@ -1,3 +1,22 @@ +:root { + --intro-bg-img: url('/img/intro-header.jpg'); + --intro-bg-img-set: image-set(url('/img/intro-header.avif') type('image/avif'), url('/img/intro-header.webp') type('image/webp'), var(--intro-bg-img) type('image/jpeg')); + --intro-bg-img-sm: url('/img/intro-header-sm.jpg'); + --intro-bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--intro-bg-img-sm) type('image/jpeg')); + --intro-bg-color: #8c5919; + --intro-bg-block-1: #b689ff; + --intro-bg-block-2: #fecaca; + --intro-bg-block-3: #e6a9e2; + --intro-bg-block-4: #ffedca; + --intro-bg-block-5: #ffffff; + --intro-color: #1a202c; + --intro-color-gray: #e4e4e7; + --intro-color-link: #1e4eae; + --intro-focus-outline: 2px solid var(--intro-color-link); + --intro-focus-outline-offset: 2px; + --intro-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + :root { --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; --val-font-serif: "Lora","georgia",serif; diff --git a/static/js/menu.js b/static/js/menu.js index 6b5ae1b0..1f09bfe5 100644 --- a/static/js/menu.js +++ b/static/js/menu.js @@ -48,7 +48,6 @@ function menu__reset(menu, nav, overlay) { } document.querySelectorAll('.menu').forEach(menu => { - let menuChildren = []; const menuNav = menu.querySelector('.menu__nav'); const menuOverlay = menu.querySelector('.menu__overlay'); From b076e22c2bdd0727f053313ec5e973a9b7341f3c Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Tue, 7 Oct 2025 05:56:32 +0200 Subject: [PATCH 146/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20el=20uso=20de?= =?UTF-8?q?=20regiones=20y=20a=C3=B1ade=20`BasicRegion`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/theme.rs | 2 +- src/base/theme/basic.rs | 9 ++- src/core/theme.rs | 4 +- src/core/theme/definition.rs | 121 ++++++++++++++++++++++++----------- src/core/theme/regions.rs | 110 ++++++++++++++++--------------- 5 files changed, 148 insertions(+), 98 deletions(-) diff --git a/src/base/theme.rs b/src/base/theme.rs index 40129bfa..a4b2df5b 100644 --- a/src/base/theme.rs +++ b/src/base/theme.rs @@ -1,4 +1,4 @@ //! Temas básicos soportados por PageTop. mod basic; -pub use basic::Basic; +pub use basic::{Basic, BasicRegion}; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 97d28b82..b6a982f6 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -1,6 +1,9 @@ /// Es el tema básico que incluye PageTop por defecto. use crate::prelude::*; +/// El tema básico usa las mismas regiones predefinidas por [`ThemeRegion`]. +pub type BasicRegion = ThemeRegion; + /// Tema básico por defecto. /// /// Ofrece las siguientes composiciones (*layouts*): @@ -90,9 +93,9 @@ fn render_intro(page: &mut Page) -> Markup { let intro = page.description().unwrap_or_default(); let theme = page.context().theme(); - let h = theme.render_page_region(page, "header"); - let c = theme.render_page_region(page, "content"); - let f = theme.render_page_region(page, "footer"); + let h = theme.render_page_region(page, &BasicRegion::Header); + let c = theme.render_page_region(page, &BasicRegion::Content); + let f = theme.render_page_region(page, &BasicRegion::Footer); let intro_button_txt: L10n = page.param_or_default("intro_button_txt"); let intro_button_lnk: Option<&String> = page.param("intro_button_lnk"); diff --git a/src/core/theme.rs b/src/core/theme.rs index 61d820b4..64f40f33 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -15,10 +15,10 @@ //! [`Theme`]. mod definition; -pub use definition::{Theme, ThemePage, ThemeRef}; +pub use definition::{Theme, ThemePage, ThemeRef, ThemeRegion}; mod regions; pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT}; -pub use regions::{InRegion, Region}; +pub use regions::{InRegion, Region, RegionRef}; pub(crate) mod all; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 5756fb23..7ef95c46 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,9 +1,9 @@ use crate::core::extension::Extension; -use crate::core::theme::Region; -use crate::global; +use crate::core::theme::{Region, RegionRef, REGION_CONTENT}; use crate::html::{html, Markup}; use crate::locale::L10n; use crate::response::page::Page; +use crate::{global, join}; use std::sync::LazyLock; @@ -13,6 +13,46 @@ use std::sync::LazyLock; /// implementen [`Theme`] y, a su vez, [`Extension`]. pub type ThemeRef = &'static dyn Theme; +/// Conjunto de regiones que los temas pueden exponer para el renderizado. +/// +/// `ThemeRegion` define un conjunto de regiones predefinidas para estructurar un documento HTML. +/// Proporciona **identificadores estables** (vía [`Region::key()`]) y **etiquetas localizables** +/// (vía [`Region::label()`]) a las regiones donde se añadirán los componentes. +/// +/// Se usa por defecto en [`Theme::page_regions()`](crate::core::theme::Theme::page_regions) y sus +/// variantes representan el conjunto mínimo recomendado para cualquier tema. Sin embargo, cada tema +/// podría exponer su propio conjunto de regiones. +pub enum ThemeRegion { + /// Cabecera de la página. + /// + /// Clave: `"header"`. Suele contener *branding*, navegación principal o avisos globales. + Header, + + /// Contenido principal de la página (**obligatoria**). + /// + /// Clave: `"content"`. Es el destino por defecto para insertar componentes a nivel de página. + Content, + + /// Pie de página. + /// + /// Clave: `"footer"`. Suele contener enlaces legales, créditos o navegación secundaria. + Footer, +} + +impl Region for ThemeRegion { + fn key(&self) -> &str { + match self { + ThemeRegion::Header => "header", + ThemeRegion::Content => REGION_CONTENT, + ThemeRegion::Footer => "footer", + } + } + + fn label(&self) -> L10n { + L10n::l(join!("region_", self.key())) + } +} + /// Métodos predefinidos de renderizado para las páginas de un tema. /// /// Contiene las implementaciones base para renderizar las **secciones** `` y ``. Se @@ -37,14 +77,14 @@ pub trait ThemePage { /// /// Si la región **no produce contenido**, devuelve un `Markup` vacío. #[inline] - fn render_region(&self, page: &mut Page, region: &Region) -> Markup { + fn render_region(&self, page: &mut Page, region: RegionRef) -> Markup { html! { - @let output = page.context().render_components_of(region.key()); + @let key = region.key(); + @let output = page.context().render_components_of(key); @if !output.is_empty() { - @let region_name = region.name(); div - id=(region_name) - class={ "region region--" (region_name) } + id=(key) + class={ "region region--" (key) } role="region" aria-label=[region.label().lookup(page)] { @@ -63,10 +103,10 @@ pub trait ThemePage { /// /// La etiqueta `` no se incluye aquí; únicamente renderiza su contenido. #[inline] - fn render_body(&self, page: &mut Page, regions: &[Region]) -> Markup { + fn render_body(&self, page: &mut Page, regions: &[RegionRef]) -> Markup { html! { @for region in regions { - (self.render_region(page, region)) + (self.render_region(page, *region)) } } } @@ -145,44 +185,53 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// Declaración ordenada de las regiones disponibles en la página. /// - /// Devuelve una **lista estática** de regiones ([`Region`](crate::core::theme::Region)) con la - /// información necesaria para renderizar el contenedor de cada región. + /// Retorna una **lista estática** de referencias ([`RegionRef`](crate::core::theme::RegionRef)) + /// que representan las regiones que el tema admite dentro del ``. /// - /// Si un tema necesita un conjunto distinto de regiones, se puede **sobrescribir** este método - /// con los siguientes requisitos y recomendaciones: + /// Cada referencia apunta a una instancia que implementa [`Region`](crate::core::theme::Region) + /// para definir cada región de forma segura y estable. Y si un tema necesita un conjunto + /// distinto de regiones, puede **sobrescribir** este método siguiendo estas recomendaciones: /// - /// - Los identificadores deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). - /// - La región `"content"` es **obligatoria** porque se usa por defecto para añadir componentes - /// para renderizar. Se puede utilizar [`Region::default()`] para declararla. - /// - La etiqueta `L10n` se evaluará con el idioma activo de la página. + /// - Los identificadores devueltos por [`Region::key()`](crate::core::theme::Region::key) + /// deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). + /// - La región `"content"` es **obligatoria**, ya que se usa como destino por defecto para + /// insertar componentes y renderizarlos. + /// - El orden de la lista podría tener relevancia como **orden de renderizado** dentro del + /// `` segun la implementación de [`render_page_body()`](Self::render_page_body). + /// - Las etiquetas (`L10n`) de cada región se evaluarán con el idioma activo de la página. /// - /// Por defecto devuelve: + /// # Ejemplo /// - /// - `"header"`: cabecera. - /// - `"content"`: contenido principal (**obligatoria**). - /// - `"footer"`: pie. - fn page_regions(&self) -> &'static [Region] { - static REGIONS: LazyLock<[Region; 3]> = LazyLock::new(|| { + /// ```rust,ignore + /// fn page_regions(&self) -> &'static [RegionRef] { + /// static REGIONS: LazyLock<[RegionRef; 4]> = LazyLock::new(|| { + /// [ + /// &ThemeRegion::Header, + /// &ThemeRegion::Content, + /// &ThemeRegion::Footer, + /// ] + /// }); + /// &*REGIONS + /// } + /// ``` + fn page_regions(&self) -> &'static [RegionRef] { + static REGIONS: LazyLock<[RegionRef; 3]> = LazyLock::new(|| { [ - Region::declare("header", L10n::l("region_header")), - Region::default(), - Region::declare("footer", L10n::l("region_footer")), + &ThemeRegion::Header, + &ThemeRegion::Content, + &ThemeRegion::Footer, ] }); &*REGIONS } - /// Renderiza una región de la página **por clave**. + /// Renderiza una región de la página. /// - /// Busca en [`page_regions()`](Self::page_regions) la región asociada a una clave y, si existe, - /// delega en [`ThemePage::render_region()`] su renderizado. Si no se encuentra la clave o la - /// región no produce contenido, devuelve un `Markup` vacío. - fn render_page_region(&self, page: &mut Page, key: &str) -> Markup { - html! { - @if let Some(region) = self.page_regions().iter().find(|r| r.key() == key) { - (self.render_region(page, region)) - } - } + /// Si se sobrescribe este método, se puede volver al comportamiento base con: + /// `::render_region(self, page, region)`. + #[inline] + fn render_page_region(&self, page: &mut Page, region: RegionRef) -> Markup { + ::render_region(self, page, region) } /// Acciones específicas del tema antes de renderizar el `` de la página. diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 00ff60cb..ecb5eb50 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -19,68 +19,66 @@ static COMMON_REGIONS: LazyLock> = /// Nombre de la región de contenido por defecto (`"content"`). pub const REGION_CONTENT: &str = "content"; -/// Identificador de una región de página. +/// Define la interfaz mínima que describe una **región de renderizado** dentro de una página. /// -/// Incluye una **clave estática** ([`key()`](Self::key)) que identifica la región en el tema, y un -/// **nombre normalizado** ([`name()`](Self::name)) en minúsculas para su uso en atributos HTML -/// (p.ej., clases `region__{name}`). +/// Una *región* representa una zona del documento HTML (por ejemplo: `"header"`, `"content"` o +/// `"sidebar-left"`), en la que se pueden incluir y renderizar componentes dinámicamente. /// -/// Se utiliza para declarar las regiones que componen una página en un tema (ver -/// [`page_regions()`](crate::core::theme::Theme::page_regions)). -pub struct Region { - key: &'static str, - name: String, - label: L10n, -} - -impl Default for Region { - #[inline] - fn default() -> Self { - Self { - key: REGION_CONTENT, - name: REGION_CONTENT.to_string(), - label: L10n::l("region_content"), - } - } -} - -impl Region { - /// Declara una región a partir de su clave estática. +/// Este `trait` abstrae los metadatos básicos de cada región, esencialmente: +/// +/// - su **clave interna** (`key()`), que la identifica de forma única dentro de la página, y +/// - su **etiqueta localizada** (`label()`), que se usa como texto accesible (por ejemplo en +/// `aria-label` o en descripciones semánticas del contenedor). +/// +/// Las implementaciones típicas son *enumeraciones estáticas* declaradas por cada tema (ver como +/// ejemplo [`ThemeRegion`](crate::core::theme::ThemeRegion)), de modo que las claves y etiquetas +/// permanecen inmutables y fácilmente referenciables. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// pub enum MyThemeRegion { +/// Header, +/// Content, +/// Footer, +/// } +/// +/// impl Region for MyThemeRegion { +/// fn key(&self) -> &str { +/// match self { +/// MyThemeRegion::Header => "header", +/// MyThemeRegion::Content => "content", +/// MyThemeRegion::Footer => "footer", +/// } +/// } +/// +/// fn label(&self) -> L10n { +/// L10n::l(join!("region__", self.key())) +/// } +/// } +/// ``` +pub trait Region: Send + Sync { + /// Devuelve la **clave interna** que identifica de forma única una región. /// - /// Genera además un nombre normalizado de la clave, eliminando espacios iniciales y finales, - /// convirtiendo a minúsculas y sustituyendo los espacios intermedios por guiones (`-`). + /// La clave se utiliza para asociar los componentes de la región con su contenedor HTML + /// correspondiente. Por convención, se emplean nombres en minúsculas y con guiones (`"header"`, + /// `"main"`, `"sidebar-right"`, etc.), y la región `"content"` es **obligatoria** en todos los + /// temas. + fn key(&self) -> &str; + + /// Devuelve la **etiqueta localizada** (`L10n`) asociada a la región. /// - /// Esta clave se usará para añadir componentes a la región; por ello se recomiendan nombres - /// sencillos, limitando los caracteres a `[a-z0-9-]` (p.ej., `"sidebar"` o `"main-menu"`), cuyo - /// nombre normalizado coincidirá con la clave. - #[inline] - pub fn declare(key: &'static str, label: L10n) -> Self { - Self { - key, - name: key.trim().to_ascii_lowercase().replace(' ', "-"), - label, - } - } - - /// Devuelve la clave estática asignada a la región. - #[inline] - pub fn key(&self) -> &'static str { - self.key - } - - /// Devuelve el nombre normalizado de la región (para identificadores y atributos HTML). - #[inline] - pub fn name(&self) -> &str { - &self.name - } - - /// Devuelve la etiqueta localizada asociada a la región. - #[inline] - pub fn label(&self) -> &L10n { - &self.label - } + /// Esta etiqueta se evalúa en el idioma activo de la página y se utiliza principalmente para + /// accesibilidad, como el valor de `aria-label` en el contenedor generado por + /// [`ThemePage::render_region()`](crate::core::theme::ThemePage::render_region). + fn label(&self) -> L10n; } +/// Referencia estática a una región. +pub type RegionRef = &'static dyn Region; + // Contenedor interno de componentes agrupados por región. #[derive(AutoDefault)] pub struct ChildrenInRegions(HashMap<&'static str, Children>); From fe1a6d1baa0a8c5a1d01a5db405ff8cca043bf23 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 10 Oct 2025 10:55:51 +0200 Subject: [PATCH 147/224] =?UTF-8?q?=F0=9F=9A=A7=20Revisi=C3=B3n=20del=20es?= =?UTF-8?q?tado=20de=20los=20men=C3=BAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/menu/item.rs | 6 +++--- static/css/menu.css | 5 +++++ static/js/menu.js | 23 +++++++++++------------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs index c9f79033..c6342186 100644 --- a/src/base/component/menu/item.rs +++ b/src/base/component/menu/item.rs @@ -137,14 +137,14 @@ impl Item { ..Default::default() } } - /* - pub fn megamenu(label: L10n, megamenu: Megamenu) -> Self { + + pub fn megamenu(label: L10n, megamenu: menu::Megamenu) -> Self { Item { item_kind: ItemKind::Megamenu(label, MegamenuGroups::with(megamenu)), ..Default::default() } } - */ + // **< Item BUILDER >*************************************************************************** #[builder_fn] diff --git a/static/css/menu.css b/static/css/menu.css index 6522f4a7..428ba157 100644 --- a/static/css/menu.css +++ b/static/css/menu.css @@ -342,6 +342,11 @@ opacity: 1; visibility: visible; } + .menu.menu--closing .menu__children { + margin-top: 0 !important; + opacity: 0 !important; + visibility: hidden !important; + } } @media (prefers-reduced-motion: reduce) { diff --git a/static/js/menu.js b/static/js/menu.js index 1f09bfe5..dca8e4df 100644 --- a/static/js/menu.js +++ b/static/js/menu.js @@ -1,12 +1,12 @@ +const getTitle = (li) => li.querySelector('.menu__label')?.textContent.trim() ?? ''; + function menu__showChildren(nav, children) { const li = children[0]; const submenu = li.querySelector('.menu__children'); submenu.classList.add('active'); submenu.style.animation = 'slideLeft 0.5s ease forwards'; - const labelEl = li.querySelector('.menu__label'); - const title = labelEl ? labelEl.textContent.trim() : (li.querySelector('a')?.textContent?.trim() ?? ''); - nav.querySelector('.menu__title').innerHTML = title; + nav.querySelector('.menu__title').textContent = getTitle(li);; nav.querySelector('.menu__header').classList.add('active'); } @@ -20,9 +20,7 @@ function menu__hideChildren(nav, children) { children.shift(); if (children.length > 0) { - const a = children[0].querySelector('a'); - const title = (a && a.textContent ? a.textContent.trim() : ''); - nav.querySelector('.menu__title').textContent = title; + nav.querySelector('.menu__title').textContent = getTitle(children[0]); } else { nav.querySelector('.menu__header').classList.remove('active'); nav.querySelector('.menu__title').textContent = ''; @@ -38,7 +36,7 @@ function menu__reset(menu, nav, overlay) { menu__toggle(nav, overlay); setTimeout(() => { nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').innerHTML = ''; + nav.querySelector('.menu__title').textContent = ''; menu.querySelectorAll('.menu__children').forEach(submenu => { submenu.classList.remove('active'); submenu.style.removeProperty('animation'); @@ -85,12 +83,13 @@ document.querySelectorAll('.menu').forEach(menu => { menu__toggle(menuNav, menuOverlay); }); - window.onresize = function () { + let resizeTimeout; + window.addEventListener('resize', () => { if (menuNav.classList.contains('active')) { - var fontSizeRoot = parseFloat(getComputedStyle(document.documentElement).fontSize); - if (this.innerWidth >= 62 * fontSizeRoot) { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { menuChildren = menu__reset(menu, menuNav, menuOverlay); - } + }, 150); } - }; + }); }); From 8eafa436cdb1a0a221aecfa8a0cf3ca9b071d7f2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 11 Oct 2025 21:36:06 +0200 Subject: [PATCH 148/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20nuevo=20tema?= =?UTF-8?q?=20para=20pruebas=20llamado=20`Aliner`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 289 +++++++------- Cargo.toml | 12 +- extensions/pagetop-aliner/Cargo.toml | 21 ++ extensions/pagetop-aliner/README.md | 100 +++++ extensions/pagetop-aliner/build.rs | 7 + extensions/pagetop-aliner/src/lib.rs | 118 ++++++ .../pagetop-aliner/static/css/styles.css | 356 ++++++++++++++++++ 7 files changed, 761 insertions(+), 142 deletions(-) create mode 100644 extensions/pagetop-aliner/Cargo.toml create mode 100644 extensions/pagetop-aliner/README.md create mode 100644 extensions/pagetop-aliner/build.rs create mode 100644 extensions/pagetop-aliner/src/lib.rs create mode 100644 extensions/pagetop-aliner/static/css/styles.css diff --git a/Cargo.lock b/Cargo.lock index e0d497c8..cab741ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.1" +version = "3.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" dependencies = [ "actix-codec", "actix-rt", @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -328,9 +328,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -390,9 +390,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -400,7 +400,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -484,9 +484,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.38" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -520,7 +520,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -589,9 +589,9 @@ checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609" [[package]] name = "config" -version = "0.15.16" +version = "0.15.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef036f0ecf99baef11555578630e2cca559909b4c50822dbba828c252d21c49" +checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918" dependencies = [ "pathdiff", "serde_core", @@ -703,9 +703,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] @@ -788,7 +788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -805,15 +805,15 @@ checksum = "4742a071cd9694fc86f9fa1a08fa3e53d40cc899d7ee532295da2d085639fbc5" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -851,7 +851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.16", + "thiserror 2.0.17", ] [[package]] @@ -994,9 +994,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -1387,9 +1387,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "linux-raw-sys" @@ -1422,11 +1422,10 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1447,9 +1446,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -1474,6 +1473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1520,9 +1520,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1560,6 +1560,7 @@ dependencies = [ "fluent-templates", "indoc", "itoa", + "pagetop-aliner", "pagetop-build", "pagetop-macros", "pagetop-statics", @@ -1576,6 +1577,14 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "pagetop-aliner" +version = "0.0.9" +dependencies = [ + "pagetop", + "pagetop-build", +] + [[package]] name = "pagetop-build" version = "0.3.1" @@ -1608,9 +1617,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1618,15 +1627,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1813,9 +1822,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1887,18 +1896,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -1908,9 +1917,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -1960,7 +1969,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2004,9 +2013,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -2014,18 +2023,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2047,9 +2056,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ "serde_core", ] @@ -2112,6 +2121,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "1.0.1" @@ -2161,9 +2176,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -2210,15 +2225,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2242,11 +2257,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2262,9 +2277,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2355,9 +2370,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "serde_core", "serde_spanned", @@ -2368,18 +2383,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -2495,9 +2510,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unic-langid" @@ -2659,9 +2674,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -2672,9 +2687,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2686,9 +2701,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2696,9 +2711,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2709,9 +2724,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -2722,27 +2737,27 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -2751,9 +2766,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -2762,32 +2777,26 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -2814,16 +2823,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -2844,19 +2853,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2867,9 +2876,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2879,9 +2888,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2891,9 +2900,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2903,9 +2912,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2915,9 +2924,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2927,9 +2936,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2939,9 +2948,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2951,9 +2960,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 913f3d7c..fe101436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,8 @@ default = [] testing = [] [dev-dependencies] -tempfile = "3.22" +tempfile = "3.23" +pagetop-aliner.workspace = true [build-dependencies] pagetop-build.workspace = true @@ -58,9 +59,12 @@ pagetop-build.workspace = true [workspace] resolver = "2" members = [ + # Helpers "helpers/pagetop-build", "helpers/pagetop-macros", "helpers/pagetop-statics", + # Extensions + "extensions/pagetop-aliner", ] [workspace.package] @@ -71,7 +75,11 @@ authors = ["Manuel Cillero "] [workspace.dependencies] actix-web = { version = "4.11", default-features = false } - +# Helpers pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } +# Extensions +pagetop-aliner = { version = "0.0", path = "extensions/pagetop-aliner" } +# PageTop +pagetop = { version = "0.4", path = "." } diff --git a/extensions/pagetop-aliner/Cargo.toml b/extensions/pagetop-aliner/Cargo.toml new file mode 100644 index 00000000..1c1101fb --- /dev/null +++ b/extensions/pagetop-aliner/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pagetop-aliner" +version = "0.0.9" +edition = "2021" + +description = """ + Tema para PageTop que muestra esquemáticamente la composición de las páginas HTML +""" +categories = ["web-programming", "gui"] +keywords = ["pagetop", "theme", "css"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +pagetop.workspace = true + +[build-dependencies] +pagetop-build.workspace = true diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md new file mode 100644 index 00000000..88e3d782 --- /dev/null +++ b/extensions/pagetop-aliner/README.md @@ -0,0 +1,100 @@ +
+ +

PageTop Aliner

+ +

Tema para PageTop que muestra esquemáticamente la composición de las páginas HTML.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) +[![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) +[![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) + +
+
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# ⚡️ Guía rápida + +Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: + +```toml +[dependencies] +pagetop-aliner = "..." +``` + +**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en +`dependencies()` determina la prioridad relativa frente a las otras extensiones: + +```rust,no_run +use pagetop::prelude::*; + +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec { + vec![ + // ... + &pagetop_aliner::Aliner, + // ... + ] + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +Y **selecciona el tema en la configuración** de la aplicación: + +```toml +[app] +theme = "Aliner" +``` + +…o **fuerza el tema por código** en una página concreta: + +```rust,no_run +use pagetop::prelude::*; + +async fn homepage(request: HttpRequest) -> ResultPage { + Page::new(request) + .with_theme("Aliner") + .add_component( + Block::new() + .with_title(L10n::l("sample_title")) + .add_component(Html::with(|cx| html! { + p { (L10n::l("sample_content").using(cx)) } + })), + ) + .render() +} +``` + + +# 🚧 Advertencia + +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +# 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/extensions/pagetop-aliner/build.rs b/extensions/pagetop-aliner/build.rs new file mode 100644 index 00000000..26713f52 --- /dev/null +++ b/extensions/pagetop-aliner/build.rs @@ -0,0 +1,7 @@ +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_dir("./static", None) + .with_name("aliner") + .build() +} diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs new file mode 100644 index 00000000..8e9b03ab --- /dev/null +++ b/extensions/pagetop-aliner/src/lib.rs @@ -0,0 +1,118 @@ +/*! +
+ +

PageTop Aliner

+ +

Tema para PageTop que muestra esquemáticamente la composición de las páginas HTML.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) +[![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) +[![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) + +
+
+ +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# ⚡️ Guía rápida + +Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: + +```toml +[dependencies] +pagetop-aliner = "..." +``` + +**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en +`dependencies()` determina la prioridad relativa frente a las otras extensiones: + +```rust,no_run +use pagetop::prelude::*; + +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec { + vec![ + // ... + &pagetop_aliner::Aliner, + // ... + ] + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +Y **selecciona el tema en la configuración** de la aplicación: + +```toml +[app] +theme = "Aliner" +``` + +…o **fuerza el tema por código** en una página concreta: + +```rust,no_run +use pagetop::prelude::*; + +async fn homepage(request: HttpRequest) -> ResultPage { + Page::new(request) + .with_theme("Aliner") + .add_component( + Block::new() + .with_title(L10n::l("sample_title")) + .add_component(Html::with(|cx| html! { + p { (L10n::l("sample_content").using(cx)) } + })), + ) + .render() +} +``` +*/ + +use pagetop::prelude::*; + +/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. +pub type AlinerRegion = ThemeRegion; + +/// Implementa el tema para usar en pruebas que muestran el esquema de páginas HTML. +/// +/// Tema mínimo ideal para **pruebas y demos** que renderiza el **esqueleto HTML** con las mismas +/// regiones básicas definidas por [`ThemeRegion`]. No pretende ser un tema para producción, está +/// pensado para: +/// +/// - Verificar integración de componentes y composiciones (*layouts*) sin estilos complejos. +/// - Realizar pruebas de renderizado rápido con salida estable y predecible. +/// - Preparar ejemplos y documentación, sin dependencias visuales (CSS/JS) innecesarias. +pub struct Aliner; + +impl Extension for Aliner { + fn theme(&self) -> Option { + Some(&Self) + } + + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + static_files_service!(scfg, [aliner] => "/aliner"); + } +} + +impl Theme for Aliner { + fn before_render_page_body(&self, page: &mut Page) { + page.alter_param("include_basic_css", true) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/aliner/css/styles.css") + .with_version(env!("CARGO_PKG_VERSION")) + .with_weight(-90), + )); + } +} diff --git a/extensions/pagetop-aliner/static/css/styles.css b/extensions/pagetop-aliner/static/css/styles.css new file mode 100644 index 00000000..1cc2f5dc --- /dev/null +++ b/extensions/pagetop-aliner/static/css/styles.css @@ -0,0 +1,356 @@ +html { + background-color: white; + padding: 1px 3px; +} +body { + padding: 1px 3px; +} +div { + padding: 1px 3px; + margin: 5px; +} +h1, h2, h3, h4,h5, h6, p { + background-color: snow; +} +* * { + outline: 5px solid rgba(255,0,0,.1); +} +* * * { + outline: 3px dashed rgba(255,0,0,.4); +} +* * * * { + outline: 2px dotted rgba(255,0,0,.6); +} +* * * * * { + outline: 1px dotted rgba(255,0,0,.9); +} +* * * * * * { + outline-color: gray; +} + +*::before, *::after { + background: #faa; + border-radius: 3px; + font: normal normal 400 10px/1.2 monospace; + vertical-align: middle; + padding: 1px 3px; + margin: 0 3px; +} +*::before { + content: "("; +} +*::after { + content: ")"; +} + +a::before { content: ""; } +a::after { content: ""; } +abbr::before { content: ""; } +abbr::after { content: ""; } +acronym::before { content: ""; } +acronym::after { content: ""; } +address::before { content: "
"; } +address::after { content: "
"; } +applet::before { content: ""; } +applet::after { content: ""; } +area::before { content: ""; } +area::after { content: ""; } +article::before { content: "
"; } +article::after { content: "
"; } +aside::before { content: ""; } +audio::before { content: ""; } + +b::before { content: ""; } +b::after { content: ""; } +base::before { content: ""; } +base::after { content: ""; } +basefont::before { content: ""; } +basefont::after { content: ""; } +bdi::before { content: ""; } +bdi::after { content: ""; } +bdo::before { content: ""; } +bdo::after { content: ""; } +bgsound::before { content: ""; } +bgsound::after { content: ""; } +big::before { content: ""; } +big::after { content: ""; } +blink::before { content: ""; } +blink::after { content: ""; } +blockquote::before { content: "
"; } +blockquote::after { content: "
"; } +body::before { content: ""; } +body::after { content: ""; } +br::before { content: "
"; } +br::after { content: "
"; } +button::before { content: ""; } + +caption::before { content: ""; } +caption::after { content: ""; } +canvas::before { content: ""; } +canvas::after { content: ""; } +center::before { content: "
"; } +center::after { content: "
"; } +cite::before { content: ""; } +cite::after { content: ""; } +code::before { content: ""; } +code::after { content: ""; } +col::before { content: ""; } +col::after { content: ""; } +colgroup::before { content: ""; } +colgroup::after { content: ""; } +command::before { content: ""; } +command::after { content: ""; } +content::before { content: ""; } +content::after { content: ""; } + +data::before { content: ""; } +data::after { content: ""; } +datalist::before { content: ""; } +datalist::after { content: ""; } +dd::before { content: "
"; } +dd::after { content: "
"; } +del::before { content: ""; } +del::after { content: ""; } +details::before { content: "
"; } +details::after { content: "
"; } +dfn::before { content: ""; } +dfn::after { content: ""; } +dialog::before { content: ""; } +dialog::after { content: ""; } +dir::before { content: ""; } +dir::after { content: ""; } +div::before { content: "
"; } +div::after { content: "
"; } +dl::before { content: "
"; } +dl::after { content: "
"; } +dt::before { content: "
"; } +dt::after { content: "
"; } + +element::before { content: ""; } +element::after { content: ""; } +em::before { content: ""; } +em::after { content: ""; } +embed::before { content: ""; } +embed::after { content: ""; } + +fieldset::before { content: "
"; } +fieldset::after { content: "
"; } +figcaption::before { content: "
"; } +figcaption::after { content: "
"; } +figure::before { content: "
"; } +figure::after { content: "
"; } +font::before { content: ""; } +font::after { content: ""; } +footer::before { content: "
"; } +footer::after { content: "
"; } +form::before { content: "
"; } +form::after { content: "
"; } +frame::before { content: ""; } +frame::after { content: ""; } +frameset::before { content: ""; } +frameset::after { content: ""; } + +h1::before { content: "

"; } +h1::after { content: "

"; } +h2::before { content: "

"; } +h2::after { content: "

"; } +h3::before { content: "

"; } +h3::after { content: "

"; } +h4::before { content: "

"; } +h4::after { content: "

"; } +h5::before { content: "
"; } +h5::after { content: "
"; } +h6::before { content: "
"; } +h6::after { content: "
"; } +head::before { content: ""; } +head::after { content: ""; } +header::before { content: "
"; } +header::after { content: "
"; } +hgroup::before { content: "
"; } +hgroup::after { content: "
"; } +hr::before { content: "
"; } +hr::after { content: ""; } +html::before { content: ""; } +html::after { content: ""; } + +i::before { content: ""; } +i::after { content: ""; } +iframe::before { content: ""; } +image::before { content: ""; } +image::after { content: ""; } +img::before { content: ""; } +img::after { content: ""; } +input::before { content: ""; } +input::after { content: ""; } +ins::before { content: ""; } +ins::after { content: ""; } +isindex::before { content: ""; } +isindex::after { content: ""; } + +kbd::before { content: ""; } +kbd::after { content: ""; } +keygen::before { content: ""; } +keygen::after { content: ""; } + +label::before { content: ""; } +legend::before { content: ""; } +legend::after { content: ""; } +li::before { content: "
  • "; } +li::after { content: "
  • "; } +link::before { content: ""; } +link::after { content: ""; } +listing::before { content: ""; } +listing::after { content: ""; } + +main::before { content: "
    "; } +main::after { content: "
    "; } +map::before { content: ""; } +map::after { content: ""; } +mark::before { content: ""; } +mark::after { content: ""; } +marquee::before { content: ""; } +marquee::after { content: ""; } +menu::before { content: ""; } +menu::after { content: ""; } +menuitem::before { content: ""; } +menuitem::after { content: ""; } +meta::before { content: ""; } +meta::after { content: ""; } +meter::before { content: ""; } +meter::after { content: ""; } +multicol::before { content: ""; } +multicol::after { content: ""; } + +nav::before { content: ""; } +nextid::before { content: ""; } +nextid::after { content: ""; } +nobr::before { content: ""; } +nobr::after { content: ""; } +noembed::before { content: ""; } +noembed::after { content: ""; } +noframes::before { content: ""; } +noframes::after { content: ""; } +noscript::before { content: ""; } + +object::before { content: ""; } +object::after { content: ""; } +ol::before { content: "
      "; } +ol::after { content: "
    "; } +optgroup::before { content: ""; } +optgroup::after { content: ""; } +option::before { content: ""; } +output::before { content: ""; } +output::after { content: ""; } + +p::before { content: "

    "; } +p::after { content: "

    "; } +param::before { content: ""; } +param::after { content: ""; } +picture::before { content: ""; } +picture::after { content: ""; } +plaintext::before { content: ""; } +plaintext::after { content: "</plaintext>"; } +pre::before { content: "<pre>"; } +pre::after { content: "</pre>"; } +progress::before { content: "<progress>"; } +progress::after { content: "</progress>"; } + +q::before { content: "<q>"; } +q::after { content: "</q>"; } + +rb::before { content: "<rb>"; } +rb::after { content: "</rb>"; } +rp::before { content: "<rp>"; } +rp::after { content: "</rp>"; } +rt::before { content: "<rt>"; } +rt::after { content: "</rt>"; } +rtc::before { content: "<rtc>"; } +rtc::after { content: "</rtc>"; } +ruby::before { content: "<ruby>"; } +ruby::after { content: "</ruby>"; } + +s::before { content: "<s>"; } +s::after { content: "</s>"; } +samp::before { content: "<samp>"; } +samp::after { content: "</samp>"; } +script::before { content: "<script>"; } +script::after { content: "</script>"; } +section::before { content: "<section>"; } +section::after { content: "</section>"; } +select::before { content: "<select>"; } +select::after { content: "</select>"; } +shadow::before { content: "<shadow>"; } +shadow::after { content: "</shadow>"; } +slot::before { content: "<slot>"; } +slot::after { content: "</slot>"; } +small::before { content: "<small>"; } +small::after { content: "</small>"; } +source::before { content: "<source>"; } +source::after { content: "</source>"; } +spacer::before { content: "<spacer>"; } +spacer::after { content: "</spacer>"; } +span::before { content: "<span>"; } +span::after { content: "</span>"; } +strike::before { content: "<strike>"; } +strike::after { content: "</strike>"; } +strong::before { content: "<strong>"; } +strong::after { content: "</strong>"; } +style::before { content: "<style>"; } +style::after { content: "<\/style>"; } +sub::before { content: "<sub>"; } +sub::after { content: "</sub>"; } +summary::before { content: "<summary>"; } +summary::after { content: "</summary>"; } +sup::before { content: "<sup>"; } +sup::after { content: "</sup>"; } + +table::before { content: "<table>"; } +table::after { content: "</table>"; } +tbody::before { content: "<tbody>"; } +tbody::after { content: "</tbody>"; } +td::before { content: "<td>"; } +td::after { content: "</td>"; } +template::before { content: "<template>"; } +template::after { content: "</template>"; } +textarea::before { content: "<textarea>"; } +textarea::after { content: "</textarea>"; } +tfoot::before { content: "<tfoot>"; } +tfoot::after { content: "</tfoot>"; } +th::before { content: "<th>"; } +th::after { content: "</th>"; } +thead::before { content: "<thead>"; } +thead::after { content: "</thead>"; } +time::before { content: "<time>"; } +time::after { content: "</time>"; } +title::before { content: "<title>"; } +title::after { content: "</title>"; } +tr::before { content: "<tr>"; } +tr::after { content: "</tr>"; } +track::before { content: "<track>"; } +track::after { content: "</track>"; } +tt::before { content: "<tt>"; } +tt::after { content: "</tt>"; } + +u::before { content: "<u>"; } +u::after { content: "</u>"; } +ul::before { content: "<ul>"; } +ul::after { content: "</ul>"; } + +var::before { content: "<var>"; } +var::after { content: "</var>"; } +video::before { content: "<video>"; } +video::after { content: "</video>"; } + +wbr::before { content: "<wbr>"; } +wbr::after { content: "</wbr>"; } + +xmp::before { content: "<xmp>"; } +xmp::after { content: "</xmp>"; } From f25f62dd3c5194a2028b6c1af5d6cf3f77b9226d Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 06:57:04 +0200 Subject: [PATCH 149/224] =?UTF-8?q?=F0=9F=93=9D=20Evita=20en=20los=20ejemp?= =?UTF-8?q?los=20`use=20pagetop::prelude::*;`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/html.rs | 6 ++---- src/base/component/poweredby.rs | 3 +-- src/core.rs | 3 +-- src/core/action.rs | 3 +-- src/core/component/context.rs | 24 ++++++++---------------- src/core/extension/definition.rs | 9 +++------ src/core/theme/definition.rs | 3 +-- src/core/theme/regions.rs | 6 ++---- src/html.rs | 3 +-- src/html/assets/favicon.rs | 3 +-- src/html/assets/javascript.rs | 3 +-- src/html/assets/stylesheet.rs | 3 +-- src/html/attr_classes.rs | 3 +-- src/html/attr_id.rs | 3 +-- src/html/attr_l10n.rs | 3 +-- src/html/attr_name.rs | 3 +-- src/html/attr_value.rs | 3 +-- src/locale.rs | 24 ++++++++---------------- src/response/json.rs | 6 ++---- src/service.rs | 3 +-- src/util.rs | 15 +++++---------- 21 files changed, 44 insertions(+), 88 deletions(-) diff --git a/src/base/component/html.rs b/src/base/component/html.rs index b8c4aaaa..a60d30f9 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -8,8 +8,7 @@ use crate::prelude::*; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let component = Html::with(|_| { /// html! { /// div class="example" { @@ -22,8 +21,7 @@ use crate::prelude::*; /// Para renderizar contenido que dependa del contexto, se puede acceder a él dentro del *closure*: /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let component = Html::with(|cx| { /// let user = cx.param::<String>("username").cloned().unwrap_or("visitor".to_string()); /// html! { diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 4b54af36..d77d65c6 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -47,8 +47,7 @@ impl PoweredBy { /// eliminará, pero en este caso es necesario especificar el tipo explícitamente: /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let p1 = PoweredBy::default().with_copyright(Some("2001 © Foo Inc.")); /// let p2 = PoweredBy::new().with_copyright(None::<String>); /// ``` diff --git a/src/core.rs b/src/core.rs index 79d92074..9ecbd2e9 100644 --- a/src/core.rs +++ b/src/core.rs @@ -122,8 +122,7 @@ impl TypeInfo { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let n = 3u32; /// assert_eq!(n.type_name(), "u32"); /// ``` diff --git a/src/core/action.rs b/src/core/action.rs index cbbe79c0..9f81cd52 100644 --- a/src/core/action.rs +++ b/src/core/action.rs @@ -22,8 +22,7 @@ pub use all::dispatch_actions; /// # Ejemplo /// /// ```rust,ignore -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// impl Extension for MyTheme { /// fn actions(&self) -> Vec<ActionBox> { /// actions_boxed![ diff --git a/src/core/component/context.rs b/src/core/component/context.rs index f58d3816..8c4e47e1 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -66,8 +66,7 @@ pub enum ContextError { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// fn prepare_context<C: Contextual>(cx: C) -> C { /// cx.with_langid(&LangMatch::resolve("es-ES")) /// .with_theme("aliner") @@ -168,8 +167,7 @@ pub trait Contextual: LangId { /// Crea un nuevo contexto asociado a una solicitud HTTP: /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// fn new_context(request: HttpRequest) -> Context { /// Context::new(Some(request)) /// // Establece el idioma del documento a español. @@ -190,8 +188,7 @@ pub trait Contextual: LangId { /// Y hace operaciones con un contexto dado: /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// fn use_context(cx: &mut Context) { /// // Recupera el tema seleccionado. /// let active_theme = cx.theme(); @@ -310,8 +307,7 @@ impl Context { /// # Ejemplos /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let cx = Context::new(None) /// .with_param("usuario_id", 42_i32) /// .with_param("titulo", "Hola".to_string()); @@ -343,8 +339,7 @@ impl Context { /// # Ejemplos /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let mut cx = Context::new(None) /// .with_param("contador", 7_i32) /// .with_param("titulo", "Hola".to_string()); @@ -374,8 +369,7 @@ impl Context { /// # Ejemplos /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let mut cx = Context::new(None).with_param("temp", 1u8); /// assert!(cx.remove_param("temp")); /// assert!(!cx.remove_param("temp")); // ya no existe @@ -441,8 +435,7 @@ impl Contextual for Context { /// # Ejemplos /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let cx = Context::new(None) /// .with_param("usuario_id", 42_i32) /// .with_param("titulo", "Hola".to_string()) @@ -517,8 +510,7 @@ impl Contextual for Context { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let cx = Context::new(None).with_param("username", "Alice".to_string()); /// /// // Devuelve Some(&String) si existe y coincide el tipo. diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 90bdbad2..5699130d 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -16,8 +16,7 @@ pub type ExtensionRef = &'static dyn Extension; /// extensión y sobreescribir los métodos que sea necesario. /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// pub struct Blog; /// /// impl Extension for Blog { @@ -45,8 +44,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// la extensión no es un tema, este método devuelve `None` por defecto. /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// pub struct MyTheme; /// /// impl Extension for MyTheme { @@ -88,8 +86,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// estáticos, etc., usando [`ServiceConfig`](crate::service::web::ServiceConfig). /// /// ```rust,ignore - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// pub struct ExtensionSample; /// /// impl Extension for ExtensionSample { diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 7ef95c46..4e7db776 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -155,8 +155,7 @@ pub trait ThemePage { /// **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme). /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// pub struct MyTheme; /// /// impl Extension for MyTheme { diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index ecb5eb50..8e386f55 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -37,8 +37,7 @@ pub const REGION_CONTENT: &str = "content"; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// pub enum MyThemeRegion { /// Header, /// Content, @@ -134,8 +133,7 @@ impl InRegion { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// // Banner global, en la región por defecto de cualquier página. /// InRegion::Content.add(Child::with(Html::with(|_| /// html! { ("🎉 ¡Bienvenido!") } diff --git a/src/html.rs b/src/html.rs index a86c9f76..abc8e8cf 100644 --- a/src/html.rs +++ b/src/html.rs @@ -93,8 +93,7 @@ pub type OptionComponent<C: core::component::Component> = core::component::Typed /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Texto normal, se escapa automáticamente para evitar inyección de HTML. /// let fragment = PrepareMarkup::Escaped("Hola <b>mundo</b>".to_string()); /// assert_eq!(fragment.render().into_string(), "Hola &lt;b&gt;mundo&lt;/b&gt;"); diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index c2280aab..dce3e1bd 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -18,8 +18,7 @@ use crate::AutoDefault; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let favicon = Favicon::new() /// // Estándar de facto admitido por todos los navegadores. /// .with_icon("/icons/favicon.ico") diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index dde5f945..6dc9b852 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -46,8 +46,7 @@ enum Source { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Script externo con carga diferida, versión de caché y prioridad en el renderizado. /// let script = JavaScript::defer("/assets/js/app.js") /// .with_version("1.2.3") diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 49cb9911..8d1bf29f 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -62,8 +62,7 @@ impl TargetMedia { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Crea una hoja de estilos externa con control de versión y medio específico (`screen`). /// let stylesheet = StyleSheet::from("/assets/css/main.css") /// .with_version("2.0.1") diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 80fdad79..bb88f587 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -31,8 +31,7 @@ pub enum ClassesOp { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let classes = AttrClasses::new("Btn btn-primary") /// .with_value(ClassesOp::Add, "Active") /// .with_value(ClassesOp::Remove, "btn-primary"); diff --git a/src/html/attr_id.rs b/src/html/attr_id.rs index 3d5f3eb4..a1d8a1da 100644 --- a/src/html/attr_id.rs +++ b/src/html/attr_id.rs @@ -12,8 +12,7 @@ use crate::{builder_fn, AutoDefault}; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let id = AttrId::new(" main Section "); /// assert_eq!(id.as_str(), Some("main_section")); /// diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 37fc80fa..86d1c4a3 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -9,8 +9,7 @@ use crate::{builder_fn, AutoDefault}; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Traducción por clave en las locales por defecto de PageTop. /// let hello = AttrL10n::new(L10n::l("test-hello-world")); /// diff --git a/src/html/attr_name.rs b/src/html/attr_name.rs index 9bc9659e..1741695c 100644 --- a/src/html/attr_name.rs +++ b/src/html/attr_name.rs @@ -12,8 +12,7 @@ use crate::{builder_fn, AutoDefault}; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let name = AttrName::new(" DISplay name "); /// assert_eq!(name.as_str(), Some("display_name")); /// diff --git a/src/html/attr_value.rs b/src/html/attr_value.rs index eff80660..b20dec3d 100644 --- a/src/html/attr_value.rs +++ b/src/html/attr_value.rs @@ -10,8 +10,7 @@ use crate::{builder_fn, AutoDefault}; /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let s = AttrValue::new(" a new string "); /// assert_eq!(s.as_str(), Some("a new string")); /// diff --git a/src/locale.rs b/src/locale.rs index 2bf0da90..7c913ec9 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -76,8 +76,7 @@ //! que declarar: //! //! ```rust -//! use pagetop::prelude::*; -//! +//! # use pagetop::prelude::*; //! include_locales!(LOCALES_SAMPLE); //! ``` //! @@ -149,8 +148,7 @@ pub trait LangId { /// # Ejemplos /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Coincidencia exacta. /// let lang = LangMatch::resolve("es-ES"); /// assert_eq!(lang.langid().to_string(), "es-ES"); @@ -173,8 +171,7 @@ pub trait LangId { /// respaldo ("en-US"): /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Idioma por defecto o de respaldo si no resuelve. /// let lang = LangMatch::resolve("it-IT"); /// let langid = lang.langid(); @@ -236,8 +233,7 @@ impl LangMatch { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let lang = LangMatch::resolve("es-ES").as_option(); /// assert_eq!(lang.unwrap().to_string(), "es-ES"); /// @@ -327,8 +323,7 @@ enum L10nOp { /// Los argumentos dinámicos se añaden con `with_arg()` o `with_args()`. /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Texto literal sin traducción. /// let raw = L10n::n("© 2025 PageTop").get(); /// @@ -407,8 +402,7 @@ impl L10n { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); /// ``` pub fn get(&self) -> Option<String> { @@ -422,8 +416,7 @@ impl L10n { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// struct ResourceLang; /// /// impl LangId for ResourceLang { @@ -464,8 +457,7 @@ impl L10n { /// # Ejemplo /// /// ```rust - /// use pagetop::prelude::*; - /// + /// # use pagetop::prelude::*; /// let html = L10n::l("welcome.message").using(&LangMatch::resolve("es")); /// ``` pub fn using(&self, language: &impl LangId) -> Markup { diff --git a/src/response/json.rs b/src/response/json.rs index fee4c121..0878999f 100644 --- a/src/response/json.rs +++ b/src/response/json.rs @@ -6,8 +6,7 @@ //! tipo Rust fuertemente tipado, validando el formato y deserializando con *serde*. //! //! ```rust -//! use pagetop::prelude::*; -//! +//! # use pagetop::prelude::*; //! #[derive(serde::Deserialize)] //! struct NuevoUsuario { nombre: String, email: String } //! @@ -25,8 +24,7 @@ //! `application/json; charset=utf-8`, todo con una llamada compacta. //! //! ```rust -//! use pagetop::prelude::*; -//! +//! # use pagetop::prelude::*; //! #[derive(serde::Serialize)] //! struct Usuario { id: u32, nombre: String } //! diff --git a/src/service.rs b/src/service.rs index 09dc6183..9a936fc3 100644 --- a/src/service.rs +++ b/src/service.rs @@ -102,8 +102,7 @@ macro_rules! include_files_service { /// # Ejemplos /// /// ```rust,ignore -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// pub struct MyExtension; /// /// impl Extension for MyExtension { diff --git a/src/util.rs b/src/util.rs index a4daf677..f73cf78a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -50,8 +50,7 @@ macro_rules! hm { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Concatena todos los fragmentos directamente. /// let result = join!("Hello", " ", "World"); /// assert_eq!(result, "Hello World".to_string()); @@ -81,8 +80,7 @@ macro_rules! join { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Concatena los fragmentos no vacíos con un espacio como separador. /// let result_with_separator = join_opt!(["Hello", "", "World"]; " "); /// assert_eq!(result_with_separator, Some("Hello World".to_string())); @@ -121,8 +119,7 @@ macro_rules! join_opt { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// let first = "Hello"; /// let separator = "-"; /// let second = "World"; @@ -164,8 +161,7 @@ macro_rules! join_pair { /// # Ejemplo /// /// ```rust -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Concatena los fragmentos. /// let result = join_strict!(["Hello", "World"]); /// assert_eq!(result, Some("HelloWorld".to_string())); @@ -211,8 +207,7 @@ macro_rules! join_strict { /// # Ejemplos /// /// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// # use pagetop::prelude::*; /// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). /// println!("{:#?}", util::resolve_absolute_dir("documents")); /// From e38d7a3c4fae6d9006a63f076cf5c2bbd0311f09 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 08:43:17 +0200 Subject: [PATCH 150/224] =?UTF-8?q?=F0=9F=94=A8=20A=C3=B1ade=20soporte=20p?= =?UTF-8?q?ara=20el=20tema=20`pagetop-aliner`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ tools/changelog.sh | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index c6c12e0e..0e635189 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,11 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, proporciona una colección de macros que mejoran la experiencia de desarrollo con PageTop. +## Extensiones + + * **[pagetop-aliner](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner)**, + es un tema para demos y pruebas que muestra esquemáticamente la composición de las páginas HTML. + # 🧪 Pruebas diff --git a/tools/changelog.sh b/tools/changelog.sh index 722cdb7f..253f22bc 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -55,6 +55,10 @@ case "$CRATE" in --exclude-path "helpers/pagetop-macros/**/*" ) ;; + pagetop-aliner) + CHANGELOG_FILE="extensions/pagetop-aliner/CHANGELOG.md" + PATH_FLAGS=(--include-path "extensions/pagetop-aliner/**/*") + ;; *) echo "Error: unsupported crate '$CRATE'" >&2 exit 1 From ec60c4ce6f50b84a231dbfa3d240dc2107118a5a Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 09:15:50 +0200 Subject: [PATCH 151/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactoriza=20p?= =?UTF-8?q?=C3=A1gina=20de=20bienvenida=20y=20tema=20`Basic`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Actualiza `Welcome` para usar el nuevo componente `Intro`. - Simplifica el tema `Basic` apoyándose en la lógica de `Theme`. - Predefine los *assets* básicos como recursos de `Theme`. - Refactoriza archivos de localicación para reflejar los cambios de los componentes. --- extensions/pagetop-aliner/src/lib.rs | 2 +- src/base/component.rs | 3 + src/base/component/intro.rs | 343 +++++++++++++++++++++++++++ src/base/extension/welcome.rs | 43 ++-- src/base/theme/basic.rs | 205 +--------------- src/core/theme/definition.rs | 26 +- src/locale/en-US/base.ftl | 6 +- src/locale/en-US/welcome.ftl | 8 +- src/locale/es-ES/base.ftl | 6 +- src/locale/es-ES/welcome.ftl | 8 +- static/css/intro.css | 82 ++++--- static/css/root.css | 19 -- 12 files changed, 455 insertions(+), 296 deletions(-) create mode 100644 src/base/component/intro.rs diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 8e9b03ab..084678d7 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -108,7 +108,7 @@ impl Extension for Aliner { impl Theme for Aliner { fn before_render_page_body(&self, page: &mut Page) { - page.alter_param("include_basic_css", true) + page.alter_param("include_basic_assets", true) .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/aliner/css/styles.css") .with_version(env!("CARGO_PKG_VERSION")) diff --git a/src/base/component.rs b/src/base/component.rs index 4edfc9a9..6c3b028f 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -54,6 +54,9 @@ pub use html::Html; mod block; pub use block::Block; +mod intro; +pub use intro::{Intro, IntroOpening}; + mod poweredby; pub use poweredby::PoweredBy; diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs new file mode 100644 index 00000000..5d3440e9 --- /dev/null +++ b/src/base/component/intro.rs @@ -0,0 +1,343 @@ +use crate::prelude::*; + +/// Tipo de apertura que se mostrará en la introducción del componente [`Intro`]. +/// +/// Permite elegir entre una apertura con textos predefinidos sobre PageTop (como hace la página de +/// bienvenida [`Welcome`](crate::base::extension::Welcome)) o una introducción completamente +/// personalizada. +#[derive(AutoDefault, Copy, Clone, Debug, Eq, PartialEq)] +pub enum IntroOpening { + /// Modo por defecto. Muestra una introducción estándar de PageTop e incluye automáticamente + /// *badges* con información de la última versión liberada, fecha del último lanzamiento y + /// licencia de uso. + #[default] + PageTop, + /// Modo totalmente personalizado. No añade *badges* ni textos predefinidos. Usa la imagen de + /// PageTop pero el contenido lo define el propio desarrollador. + Custom, +} + +/// Componente para presentar PageTop (como [`Welcome`](crate::base::extension::Welcome)), o mostrar +/// introducciones. +/// +/// Usa la imagen de PageTop para presentar contenidos con: +/// +/// - Una **imagen decorativa** (el *monster* de PageTop) antecediendo al contenido. +/// - Una vista destacada con **título + eslogan**. +/// - Un **botón opcional** de llamada a la acción con texto y enlace configurables. +/// - El **área de textos** con *badges* predefinidos (en modo [`IntroOpening::PageTop`]) y bloques +/// ([`Block`](crate::base::component::Block)) para crear párrafos vistosos de texto. Aunque +/// admite todo tipo de componentes. +/// +/// ### Ejemplos +/// +/// **Intro mínima por defecto** +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let intro = Intro::default(); +/// ``` +/// +/// **Título, eslogan y botón personalizados** +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let intro = Intro::default() +/// .with_title(L10n::l("intro_custom_title")) +/// .with_slogan(L10n::l("intro_custom_slogan")) +/// .with_button(Some(( +/// L10n::l("intro_learn_more"), +/// |_| "/learn-more" +/// ))); +/// ``` +/// +/// **Sin botón + modo *Custom* (sin *badges* predefinidos)** +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let intro = Intro::default() +/// .with_button(None::<(L10n, FnPathByContext)>) +/// .with_opening(IntroOpening::Custom); +/// ``` +/// +/// **Añadir contenidos hijo** +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let intro = Intro::default() +/// .add_component( +/// Block::new() +/// .with_title(L10n::l("intro_custom_block_title")) +/// .add_component(Html::with(move |cx| { +/// html! { +/// p { (L10n::l("intro_custom_paragraph_1").using(cx)) } +/// p { (L10n::l("intro_custom_paragraph_2").using(cx)) } +/// } +/// })), +/// ); +/// ``` +#[rustfmt::skip] +pub struct Intro { + title : L10n, + slogan : L10n, + button : Option<(L10n, FnPathByContext)>, + opening : IntroOpening, + children: Children, +} + +impl Default for Intro { + #[rustfmt::skip] + fn default() -> Self { + Intro { + title : L10n::l("intro_default_title"), + slogan : L10n::l("intro_default_slogan").with_arg("app", &global::SETTINGS.app.name), + button : Some((L10n::l("intro_default_button"), |_| "https://pagetop.cillero.es")), + opening : IntroOpening::default(), + children: Children::default(), + } + } +} + +impl Component for Intro { + fn new() -> Self { + Intro::default() + } + + fn setup_before_prepare(&mut self, cx: &mut Context) { + cx.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/intro.css").with_version(env!("CARGO_PKG_VERSION")), + )); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + if self.opening() == IntroOpening::PageTop { + cx.alter_assets(ContextOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| + util::indoc!(r#" + try { + const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); + const data = await resp.json(); + const date = new Date(data.versions[0].created_at); + const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" }); + document.getElementById("intro-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`; + document.getElementById("intro-badges").style.display = "block"; + } catch (e) { + console.error("Failed to fetch release date from crates.io:", e); + } + "#) + .replace("LANGID", cx.langid().to_string().as_str()) + .replace("LABEL", L10n::l("intro_release_label").using(cx).as_str()) + ))); + } + + PrepareMarkup::With(html! { + div class="intro" { + div class="intro-header" { + section class="intro-header__body" { + h1 class="intro-header__title" { + span { (self.title().using(cx)) } + (self.slogan().using(cx)) + } + } + aside class="intro-header__image" aria-hidden="true" { + div class="intro-header__monster" { + picture { + source + type="image/avif" + src="/img/monster-pagetop_250.avif" + srcset="/img/monster-pagetop_500.avif 1.5x"; + source + type="image/webp" + src="/img/monster-pagetop_250.webp" + srcset="/img/monster-pagetop_500.webp 1.5x"; + img + src="/img/monster-pagetop_250.png" + srcset="/img/monster-pagetop_500.png 1.5x" + alt="Monster PageTop"; + } + } + } + } + div class="intro-content" { + section class="intro-content__body" { + div class="intro-text" { + @if let Some((txt, lnk)) = self.button() { + div class="intro-button" { + a + class="intro-button__link" + href=((lnk)(cx)) + target="_blank" + rel="noreferrer" + { + span {} span {} span {} + div class="intro-button__text" { + (txt.using(cx)) + } + } + } + } + div class="intro-text__children" { + @if self.opening() == IntroOpening::PageTop { + p { (L10n::l("intro_text1").using(cx)) } + div id="intro-badges" { + img + src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge" + alt=[L10n::l("intro_pagetop_label").lookup(cx)] {} (" ") + img + id="intro-release" + alt=[L10n::l("intro_release_label").lookup(cx)] {} (" ") + img + src=(format!( + "https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge", + L10n::l("intro_license_label").lookup(cx).unwrap_or_default() + )) + alt=[L10n::l("intro_license_label").lookup(cx)] {} + } + p { (L10n::l("intro_text2").using(cx)) } + } + (self.children().render(cx)) + } + } + } + } + div class="intro-footer" { + section class="intro-footer__body" { + div class="intro-footer__logo" { + svg + viewBox="0 0 1614 1614" + xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label=[L10n::l("pagetop_logo").lookup(cx)] + preserveAspectRatio="xMidYMid slice" + focusable="false" + { + path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} + } + } + div class="intro-footer__links" { + a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } + a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(cx)) } + em { (L10n::l("intro_have_fun").using(cx)) } + } + } + } + } + }) + } +} + +impl Intro { + // **< Intro BUILDER >************************************************************************** + + /// Establece el título de entrada. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let intro = Intro::default().with_title(L10n::n("Título de entrada")); + /// ``` + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; + self + } + + /// Establece el eslogan de entrada (línea secundaria del título). + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let intro = Intro::default().with_slogan(L10n::n("Un eslogan para la entrada")); + /// ``` + #[builder_fn] + pub fn with_slogan(mut self, slogan: L10n) -> Self { + self.slogan = slogan; + self + } + + /// Configura el botón opcional de llamada a la acción. + /// + /// - Usa `Some((texto, closure_url))` para mostrarlo, donde [`FnPathByContext`] recibe el + /// [`Context`] y devuelve la ruta o URL final al pulsar el botón. + /// - Usa `None` para ocultarlo. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// // Define un botón con texto y una URL fija. + /// let intro = Intro::default().with_button(Some((L10n::n("Pulsa este botón"), |_| "/start"))); + /// // Descarta el botón de la intro. + /// let intro_no_button = Intro::default().with_button(None); + /// ``` + #[builder_fn] + pub fn with_button(mut self, button: Option<(L10n, FnPathByContext)>) -> Self { + self.button = button; + self + } + + /// Selecciona el tipo de apertura: [`IntroOpening::PageTop`] (por defecto) o + /// [`IntroOpening::Custom`]. + /// + /// - `PageTop`: añade *badges* automáticos y una presentación de lo que es PageTop. + /// - `Custom`: introducción en blanco para añadir cualquier contenido. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let intro = Intro::default().with_opening(IntroOpening::Custom); + /// ``` + #[builder_fn] + pub fn with_opening(mut self, opening: IntroOpening) -> Self { + self.opening = opening; + self + } + + /// Añade un nuevo componente hijo a la intro. + /// + /// Si es un bloque ([`Block`]) aplica estilos específicos para destacarlo. + pub fn add_component(mut self, component: impl Component) -> Self { + self.children + .alter_child(ChildOp::Add(Child::with(component))); + self + } + + /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // **< Intro GETTERS >************************************************************************** + + /// Devuelve el título de entrada. + pub fn title(&self) -> &L10n { + &self.title + } + + /// Devuelve el eslogan de la entrada. + pub fn slogan(&self) -> &L10n { + &self.slogan + } + + /// Devuelve el botón de llamada a la acción, si existe. + pub fn button(&self) -> Option<(&L10n, &FnPathByContext)> { + self.button.as_ref().map(|(txt, lnk)| (txt, lnk)) + } + + /// Devuelve el modo de apertura configurado. + pub fn opening(&self) -> IntroOpening { + self.opening + } + + /// Devuelve la lista de hijos (`children`) de la intro. + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 5c6fec5a..865807a1 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -26,30 +26,29 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_theme("Basic") - .with_layout("PageTopIntro") .with_title(L10n::l("welcome_title")) - .with_description(L10n::l("welcome_intro").with_arg("app", app)) - .with_param("intro_button_txt", L10n::l("welcome_powered")) - .with_param("intro_button_lnk", "https://pagetop.cillero.es".to_string()) .add_component( - Block::new() - .with_title(L10n::l("welcome_status_title")) - .add_component(Html::with(move |cx| { - html! { - p { (L10n::l("welcome_status_1").using(cx)) } - p { (L10n::l("welcome_status_2").using(cx)) } - } - })), - ) - .add_component( - Block::new() - .with_title(L10n::l("welcome_support_title")) - .add_component(Html::with(move |cx| { - html! { - p { (L10n::l("welcome_support_1").using(cx)) } - p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } - } - })), + Intro::new() + .add_component( + Block::new() + .with_title(L10n::l("welcome_status_title")) + .add_component(Html::with(move |cx| { + html! { + p { (L10n::l("welcome_status_1").using(cx)) } + p { (L10n::l("welcome_status_2").using(cx)) } + } + })), + ) + .add_component( + Block::new() + .with_title(L10n::l("welcome_support_title")) + .add_component(Html::with(move |cx| { + html! { + p { (L10n::l("welcome_support_1").using(cx)) } + p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } + } + })), + ), ) .render() } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index b6a982f6..a6711859 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -4,33 +4,7 @@ use crate::prelude::*; /// El tema básico usa las mismas regiones predefinidas por [`ThemeRegion`]. pub type BasicRegion = ThemeRegion; -/// Tema básico por defecto. -/// -/// Ofrece las siguientes composiciones (*layouts*): -/// -/// - **Composición predeterminada** -/// - Renderizado genérico con -/// [`ThemePage::render_body()`](crate::core::theme::ThemePage::render_body) usando las regiones -/// predefinidas en [`page_regions()`](crate::core::theme::Theme::page_regions). -/// -/// - **`Intro`** -/// - Página de entrada con cabecera visual, título y descripción y un botón opcional de llamada a -/// la acción. Ideal para una página de inicio o bienvenida en el contexto de PageTop. -/// - **Regiones:** `content` (se renderiza dentro de `.intro-content__body`). -/// - **Parámetros:** -/// - `intro_button_txt` (`L10n`) – Texto del botón. -/// - `intro_button_lnk` (`Option<String>`) – URL del botón; si no se indica, el botón no se -/// muestra. -/// -/// - **`PageTopIntro`** -/// - Variante de `Intro` con textos predefinidos sobre PageTop al inicio del contenido. Añade una -/// banda de *badges* con la versión de [PageTop en crates.io](https://crates.io/crates/pagetop) -/// más la fecha de la última versión publicada y la licencia de uso. -/// - **Regiones:** `content` (igual que `Intro`). -/// - **Parámetros:** los mismos que `Intro`. -/// -/// **Nota:** si no se especifica `layout` o el valor no coincide con ninguno de los anteriores, se -/// aplica la composición predeterminada. +/// Tema básico por defecto que extiende el funcionamiento predeterminado de [`Theme`]. pub struct Basic; impl Extension for Basic { @@ -40,180 +14,7 @@ impl Extension for Basic { } impl Theme for Basic { - fn render_page_body(&self, page: &mut Page) -> Markup { - match page.layout() { - "Intro" => render_intro(page), - "PageTopIntro" => render_pagetop_intro(page), - _ => <Self as ThemePage>::render_body(self, page, self.page_regions()), - } - } - - fn after_render_page_body(&self, page: &mut Page) { - let pkg_version = env!("CARGO_PKG_VERSION"); - - page.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/normalize.css") - .with_version("8.0.1") - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/root.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/basic.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/components.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/menu.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddJavaScript( - JavaScript::defer("/js/menu.js") - .with_version(pkg_version) - .with_weight(-99), - )); + fn before_render_page_body(&self, page: &mut Page) { + page.alter_param("include_basic_assets", true); } } - -fn render_intro(page: &mut Page) -> Markup { - page.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/intro.css").with_version(env!("CARGO_PKG_VERSION")), - )); - - let title = page.title().unwrap_or_default(); - let intro = page.description().unwrap_or_default(); - - let theme = page.context().theme(); - let h = theme.render_page_region(page, &BasicRegion::Header); - let c = theme.render_page_region(page, &BasicRegion::Content); - let f = theme.render_page_region(page, &BasicRegion::Footer); - - let intro_button_txt: L10n = page.param_or_default("intro_button_txt"); - let intro_button_lnk: Option<&String> = page.param("intro_button_lnk"); - - html! { - header class="intro-header" { - section class="intro-header__body" { - h1 class="intro-header__title" { - span { (title) } - (intro) - } - } - aside class="intro-header__image" aria-hidden="true" { - div class="intro-header__monster" { - picture { - source - type="image/avif" - src="/img/monster-pagetop_250.avif" - srcset="/img/monster-pagetop_500.avif 1.5x"; - source - type="image/webp" - src="/img/monster-pagetop_250.webp" - srcset="/img/monster-pagetop_500.webp 1.5x"; - img - src="/img/monster-pagetop_250.png" - srcset="/img/monster-pagetop_500.png 1.5x" - alt="Monster PageTop"; - } - } - } - (h) - } - main class="intro-content" { - section class="intro-content__body" { - div class="intro-text" { - @if intro_button_lnk.is_some() { - div class="intro-button" { - a - class="intro-button__link" - href=[intro_button_lnk] - target="_blank" - rel="noreferrer" - { - span {} span {} span {} - div class="intro-button__text" { - (intro_button_txt.using(page)) - } - } - } - } - (c) - } - } - } - footer class="intro-footer" { - section class="intro-footer__body" { - div class="intro-footer__logo" { - svg - viewBox="0 0 1614 1614" - xmlns="http://www.w3.org/2000/svg" - role="img" - aria-label=[L10n::l("pagetop_logo").lookup(page)] - preserveAspectRatio="xMidYMid slice" - focusable="false" - { - path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} - path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} - path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} - } - } - div class="intro-footer__links" { - a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } - a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(page)) } - em { (L10n::l("intro_have_fun").using(page)) } - } - } - (f) - } - } -} - -fn render_pagetop_intro(page: &mut Page) -> Markup { - page.alter_assets(ContextOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| - util::indoc!(r#" - try { - const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); - const data = await resp.json(); - const date = new Date(data.versions[0].created_at); - const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" }); - document.getElementById("intro-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`; - document.getElementById("intro-badges").style.display = "block"; - } catch (e) { - console.error("Failed to fetch release date from crates.io:", e); - } - "#) - .replace("LANGID", cx.langid().to_string().as_str()) - .replace("LABEL", L10n::l("intro_release_label").using(cx).as_str()) - .to_string(), - ))) - .alter_child_in("content", ChildOp::Prepend(Child::with(Html::with(|cx| html! { - p { (L10n::l("intro_text1").using(cx)) } - div id="intro-badges" style="display: none; margin-bottom: 1.1rem;" { - img - src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge" - alt=[L10n::l("intro_pagetop_label").lookup(cx)] {} (" ") - img - id="intro-release" - alt=[L10n::l("intro_release_label").lookup(cx)] {} (" ") - img - src=(format!( - "https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge", - L10n::l("intro_license_label").lookup(cx).unwrap_or_default() - )) - alt=[L10n::l("intro_license_label").lookup(cx)] {} - } - p { (L10n::l("intro_text2").using(cx)) } - })))); - - render_intro(page) -} diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 4e7db776..1eb7b226 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,6 +1,7 @@ +use crate::core::component::{ContextOp, Contextual}; use crate::core::extension::Extension; use crate::core::theme::{Region, RegionRef, REGION_CONTENT}; -use crate::html::{html, Markup}; +use crate::html::{html, Markup, StyleSheet}; use crate::locale::L10n; use crate::response::page::Page; use crate::{global, join}; @@ -241,7 +242,7 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// Renderiza el contenido del `<body>` de la página. /// - /// Si se sobrescribe este método, se puede volver al comportamiento base con: + /// Si se sobrescribe este método, se puede volver al renderizado base con: /// `<Self as ThemePage>::render_body(self, page, self.page_regions())`. #[inline] fn render_page_body(&self, page: &mut Page) -> Markup { @@ -256,10 +257,29 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// Renderiza el contenido del `<head>` de la página. /// - /// Si se sobrescribe este método, se puede volver al comportamiento base con: + /// Si se sobrescribe este método, se puede volver al renderizado base con: /// `<Self as ThemePage>::render_head(self, page)`. #[inline] fn render_page_head(&self, page: &mut Page) -> Markup { + if page.param_or("include_basic_assets", false) { + let pkg_version = env!("CARGO_PKG_VERSION"); + + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/normalize.css") + .with_version("8.0.1") + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/root.css") + .with_version(pkg_version) + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(pkg_version) + .with_weight(-99), + )); + } <Self as ThemePage>::render_head(self, page) } diff --git a/src/locale/en-US/base.ftl b/src/locale/en-US/base.ftl index 16b1a3ef..76baa120 100644 --- a/src/locale/en-US/base.ftl +++ b/src/locale/en-US/base.ftl @@ -1,4 +1,8 @@ -# Basic theme, intro layout. +# Intro component. +intro_default_title = Hello, world! +intro_default_slogan = Discover⚡{ $app } +intro_default_button = A web solution powered by <strong>PageTop</strong> + intro_pagetop_label = PageTop version on Crates.io intro_release_label = Release date intro_license_label = License diff --git a/src/locale/en-US/welcome.ftl b/src/locale/en-US/welcome.ftl index 20faf964..34028804 100644 --- a/src/locale/en-US/welcome.ftl +++ b/src/locale/en-US/welcome.ftl @@ -1,16 +1,12 @@ welcome_extension_name = Default Homepage welcome_extension_description = Displays a default homepage when none is configured. -welcome_page = Welcome page -welcome_title = Hello, world! - -welcome_intro = Discover⚡{ $app } -welcome_powered = A web solution powered by <strong>PageTop</strong> +welcome_title = Welcome page welcome_status_title = Status welcome_status_1 = If you can see this page, it means the <strong>PageTop</strong> server is running correctly, but the application is not fully configured. This may be due to routine maintenance or a temporary issue. welcome_status_2 = If the issue persists, please <strong>contact the system administrator</strong>. welcome_support_title = Support -welcome_support_1 = To report issues with the <strong>PageTop</strong> framework, use <a href="https://git.cillero.es/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">SoloGit</a>. Remember, before opening a new issue, review the existing ones to avoid duplicates. +welcome_support_1 = To report issues with the <strong>PageTop</strong> framework, use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">GitHub</a>. Remember, before opening a new issue, review the existing ones to avoid duplicates. welcome_support_2 = For issues specific to the application (<strong>{ $app }</strong>), please use its official repository or support channel. diff --git a/src/locale/es-ES/base.ftl b/src/locale/es-ES/base.ftl index fee21a93..09867d13 100644 --- a/src/locale/es-ES/base.ftl +++ b/src/locale/es-ES/base.ftl @@ -1,4 +1,8 @@ -# Basic theme, intro layout. +# Intro component. +intro_default_title = ¡Hola, mundo! +intro_default_slogan = Descubre⚡{ $app } +intro_default_button = Una solución web creada con <strong>PageTop</strong> + intro_pagetop_label = Versión de PageTop en Crates.io intro_release_label = Lanzamiento intro_license_label = Licencia diff --git a/src/locale/es-ES/welcome.ftl b/src/locale/es-ES/welcome.ftl index 13330dbf..605d1e2c 100644 --- a/src/locale/es-ES/welcome.ftl +++ b/src/locale/es-ES/welcome.ftl @@ -1,16 +1,12 @@ welcome_extension_name = Página de inicio predeterminada welcome_extension_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada. -welcome_page = Página de bienvenida -welcome_title = ¡Hola, mundo! - -welcome_intro = Descubre⚡{ $app } -welcome_powered = Una solución web creada con <strong>PageTop</strong> +welcome_title = Página de bienvenida welcome_status_title = Estado welcome_status_1 = Si puedes ver esta página, es porque el servidor de <strong>PageTop</strong> está funcionando correctamente, pero la aplicación no está completamente configurada. Esto puede deberse a tareas de mantenimiento o a una incidencia temporal. welcome_status_2 = Si el problema persiste, por favor, <strong>contacta con el administrador del sistema</strong>. welcome_support_title = Soporte -welcome_support_1 = Para comunicar incidencias del propio entorno <strong>PageTop</strong>, utiliza <a href="https://git.cillero.es/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">SoloGit</a>. Recuerda, antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. +welcome_support_1 = Para comunicar incidencias del propio entorno <strong>PageTop</strong>, utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">GitHub</a>. Recuerda, antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. welcome_support_2 = Para fallos específicos de la aplicación (<strong>{ $app }</strong>), utiliza su repositorio oficial o su canal de soporte. diff --git a/static/css/intro.css b/static/css/intro.css index 774bbb2a..17ab5bef 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -1,12 +1,24 @@ -html { - min-height: 100%; - background-color: black; +:root { + --intro-bg-img: url('/img/intro-header.jpg'); + --intro-bg-img-set: image-set(url('/img/intro-header.avif') type('image/avif'), url('/img/intro-header.webp') type('image/webp'), var(--intro-bg-img) type('image/jpeg')); + --intro-bg-img-sm: url('/img/intro-header-sm.jpg'); + --intro-bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--intro-bg-img-sm) type('image/jpeg')); + --intro-bg-color: #8c5919; + --intro-bg-block-1: #b689ff; + --intro-bg-block-2: #fecaca; + --intro-bg-block-3: #e6a9e2; + --intro-bg-block-4: #ffedca; + --intro-bg-block-5: #ffffff; + --intro-color: #1a202c; + --intro-color-gray: #e4e4e7; + --intro-color-link: #1e4eae; + --intro-focus-outline: 2px solid var(--intro-color-link); + --intro-focus-outline-offset: 2px; + --intro-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } -body { - margin: auto; +.intro { position: relative; - min-height: 100%; min-width: 350px; color: var(--intro-color); background-color: var(--intro-bg-color); @@ -18,22 +30,22 @@ body { align-items: center; } -section { +.intro section { position: relative; text-align: center; } -a { +.intro a { color: currentColor; text-decoration: underline; transition: font-size 0.2s, text-decoration-color 0.2s; } -a:focus-visible { +.intro a:focus-visible { outline: var(--intro-focus-outline); outline-offset: var(--intro-focus-outline-offset); } -a:hover, -a:hover:visited { +.intro a:hover, +.intro a:hover:visited { text-decoration-color: var(--intro-color-link); } @@ -172,8 +184,8 @@ a:hover:visited { justify-content: space-between; font-size: 1.5rem; line-height: 1.3; - text-decoration: none; - transition: transform 0.3s ease-in-out; + text-decoration: none !important; + transition: transform 0.3s ease-in-out !important; position: relative; overflow: hidden; min-width: 28.875rem; @@ -307,7 +319,7 @@ a:hover:visited { } .intro-button__link:hover { transition: all .5s; - transform: rotate(-3deg) scale(1.1); + transform: rotate(-3deg) scale(1.125); } } @@ -326,11 +338,11 @@ a:hover:visited { background: #fff; position: relative; } -.region--content { +.intro-text__children { padding: 2.5rem 1.063rem 0.75rem; overflow: hidden; } -.region--content p { +.intro-text__children p { width: 100%; line-height: 150%; font-weight: 400; @@ -342,7 +354,7 @@ a:hover:visited { font-size: 1.375rem; line-height: 2rem; } - .intro-button + .region--content { + .intro-button + .intro-text__children { padding-top: 7rem; } } @@ -351,7 +363,7 @@ a:hover:visited { padding-bottom: 9rem;; } .intro-text, - .region--content { + .intro-text__children { border-radius: 0.75rem; } .intro-text { @@ -359,19 +371,19 @@ a:hover:visited { max-width: 60rem; margin: 0 auto 6rem; } - .region--content { + .intro-text__children { padding-left: 4.5rem; padding-right: 4.5rem; } } -.region--content .block { +.intro-text__children .block { position: relative; } -.region--content .block__title { +.intro-text__children .block__title { margin: 1em 0 .8em; } -.region--content .block__title span { +.intro-text__children .block__title span { display: inline-block; padding: 10px 30px 14px; margin: 30px 0 0 20px; @@ -382,7 +394,7 @@ a:hover:visited { border-color: orangered; transform: rotate(-3deg) translateY(-25%); } -.region--content .block__title:before { +.intro-text__children .block__title:before { content: ""; height: 5px; position: absolute; @@ -395,7 +407,7 @@ a:hover:visited { transform: rotate(2deg) translateY(-50%); transform-origin: top left; } -.region--content .block__title:after { +.intro-text__children .block__title:after { content: ""; height: 70rem; position: absolute; @@ -406,22 +418,28 @@ a:hover:visited { background: var(--intro-bg-block-1); transform: rotate(2deg); } -.region--content .block:nth-of-type(5n+1) .block__title:after { +.intro-text__children .block:nth-of-type(5n+1) .block__title:after { background: var(--intro-bg-block-1); } -.region--content .block:nth-of-type(5n+2) .block__title:after { +.intro-text__children .block:nth-of-type(5n+2) .block__title:after { background: var(--intro-bg-block-2); } -.region--content .block:nth-of-type(5n+3) .block__title:after { +.intro-text__children .block:nth-of-type(5n+3) .block__title:after { background: var(--intro-bg-block-3); } -.region--content .block:nth-of-type(5n+4) .block__title:after { +.intro-text__children .block:nth-of-type(5n+4) .block__title:after { background: var(--intro-bg-block-4); } -.region--content .block:nth-of-type(5n+5) .block__title:after { +.intro-text__children .block:nth-of-type(5n+5) .block__title:after { background: var(--intro-bg-block-5); } +#intro-badges { + display: none; + margin-bottom: 1.1rem; + text-align: center; +} + /* * Footer */ @@ -474,9 +492,3 @@ a:hover:visited { padding: 0 1rem 2rem; } } - -/* PoweredBy component */ - -.poweredby a:visited { - color: var(--intro-color-gray); -} diff --git a/static/css/root.css b/static/css/root.css index 270c1b3f..aeab1c67 100644 --- a/static/css/root.css +++ b/static/css/root.css @@ -1,22 +1,3 @@ -:root { - --intro-bg-img: url('/img/intro-header.jpg'); - --intro-bg-img-set: image-set(url('/img/intro-header.avif') type('image/avif'), url('/img/intro-header.webp') type('image/webp'), var(--intro-bg-img) type('image/jpeg')); - --intro-bg-img-sm: url('/img/intro-header-sm.jpg'); - --intro-bg-img-sm-set: image-set(url('/img/intro-header-sm.avif') type('image/avif'), url('/img/intro-header-sm.webp') type('image/webp'), var(--intro-bg-img-sm) type('image/jpeg')); - --intro-bg-color: #8c5919; - --intro-bg-block-1: #b689ff; - --intro-bg-block-2: #fecaca; - --intro-bg-block-3: #e6a9e2; - --intro-bg-block-4: #ffedca; - --intro-bg-block-5: #ffffff; - --intro-color: #1a202c; - --intro-color-gray: #e4e4e7; - --intro-color-link: #1e4eae; - --intro-focus-outline: 2px solid var(--intro-color-link); - --intro-focus-outline-offset: 2px; - --intro-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); -} - :root { --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; --val-font-serif: "Lora","georgia",serif; From 3e3903b2c72d4264cb2a73915c2b8adeb61bc389 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 12:07:02 +0200 Subject: [PATCH 152/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20tema=20`Bootsi?= =?UTF-8?q?er`=20basado=20en=20`Bootstrap`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 9 + Cargo.toml | 3 + extensions/pagetop-aliner/Cargo.toml | 2 +- extensions/pagetop-aliner/README.md | 2 +- extensions/pagetop-aliner/src/lib.rs | 2 +- extensions/pagetop-bootsier/Cargo.toml | 21 + extensions/pagetop-bootsier/README.md | 100 + extensions/pagetop-bootsier/build.rs | 22 + extensions/pagetop-bootsier/src/lib.rs | 121 + .../static/bootstrap-5.3.3/_accordion.scss | 158 + .../static/bootstrap-5.3.3/_alert.scss | 68 + .../static/bootstrap-5.3.3/_badge.scss | 38 + .../static/bootstrap-5.3.3/_breadcrumb.scss | 40 + .../static/bootstrap-5.3.3/_button-group.scss | 142 + .../static/bootstrap-5.3.3/_buttons.scss | 216 + .../static/bootstrap-5.3.3/_card.scss | 239 + .../static/bootstrap-5.3.3/_carousel.scss | 236 + .../static/bootstrap-5.3.3/_close.scss | 63 + .../static/bootstrap-5.3.3/_containers.scss | 41 + .../static/bootstrap-5.3.3/_dropdown.scss | 250 + .../static/bootstrap-5.3.3/_forms.scss | 9 + .../static/bootstrap-5.3.3/_functions.scss | 302 ++ .../static/bootstrap-5.3.3/_grid.scss | 39 + .../static/bootstrap-5.3.3/_helpers.scss | 12 + .../static/bootstrap-5.3.3/_images.scss | 42 + .../static/bootstrap-5.3.3/_list-group.scss | 197 + .../static/bootstrap-5.3.3/_maps.scss | 174 + .../static/bootstrap-5.3.3/_mixins.scss | 42 + .../static/bootstrap-5.3.3/_modal.scss | 236 + .../static/bootstrap-5.3.3/_nav.scss | 197 + .../static/bootstrap-5.3.3/_navbar.scss | 289 ++ .../static/bootstrap-5.3.3/_offcanvas.scss | 143 + .../static/bootstrap-5.3.3/_pagination.scss | 109 + .../static/bootstrap-5.3.3/_placeholders.scss | 51 + .../static/bootstrap-5.3.3/_popover.scss | 196 + .../static/bootstrap-5.3.3/_progress.scss | 68 + .../static/bootstrap-5.3.3/_reboot.scss | 611 +++ .../static/bootstrap-5.3.3/_root.scss | 187 + .../static/bootstrap-5.3.3/_spinners.scss | 85 + .../static/bootstrap-5.3.3/_tables.scss | 171 + .../static/bootstrap-5.3.3/_toasts.scss | 73 + .../static/bootstrap-5.3.3/_tooltip.scss | 119 + .../static/bootstrap-5.3.3/_transitions.scss | 27 + .../static/bootstrap-5.3.3/_type.scss | 106 + .../static/bootstrap-5.3.3/_utilities.scss | 806 +++ .../bootstrap-5.3.3/_variables-dark.scss | 87 + .../static/bootstrap-5.3.3/_variables.scss | 1751 +++++++ .../bootstrap-5.3.3/bootstrap-grid.scss | 62 + .../bootstrap-5.3.3/bootstrap-reboot.scss | 10 + .../bootstrap-5.3.3/bootstrap-utilities.scss | 19 + .../static/bootstrap-5.3.3/bootstrap.scss | 52 + .../forms/_floating-labels.scss | 95 + .../bootstrap-5.3.3/forms/_form-check.scss | 189 + .../bootstrap-5.3.3/forms/_form-control.scss | 214 + .../bootstrap-5.3.3/forms/_form-range.scss | 91 + .../bootstrap-5.3.3/forms/_form-select.scss | 80 + .../bootstrap-5.3.3/forms/_form-text.scss | 11 + .../bootstrap-5.3.3/forms/_input-group.scss | 132 + .../static/bootstrap-5.3.3/forms/_labels.scss | 36 + .../bootstrap-5.3.3/forms/_validation.scss | 12 + .../bootstrap-5.3.3/helpers/_clearfix.scss | 3 + .../bootstrap-5.3.3/helpers/_color-bg.scss | 7 + .../helpers/_colored-links.scss | 30 + .../bootstrap-5.3.3/helpers/_focus-ring.scss | 5 + .../bootstrap-5.3.3/helpers/_icon-link.scss | 25 + .../bootstrap-5.3.3/helpers/_position.scss | 36 + .../bootstrap-5.3.3/helpers/_ratio.scss | 26 + .../bootstrap-5.3.3/helpers/_stacks.scss | 15 + .../helpers/_stretched-link.scss | 15 + .../helpers/_text-truncation.scss | 7 + .../helpers/_visually-hidden.scss | 8 + .../static/bootstrap-5.3.3/helpers/_vr.scss | 8 + .../static/bootstrap-5.3.3/mixins/_alert.scss | 18 + .../bootstrap-5.3.3/mixins/_backdrop.scss | 14 + .../bootstrap-5.3.3/mixins/_banner.scss | 7 + .../mixins/_border-radius.scss | 78 + .../bootstrap-5.3.3/mixins/_box-shadow.scss | 18 + .../bootstrap-5.3.3/mixins/_breakpoints.scss | 127 + .../bootstrap-5.3.3/mixins/_buttons.scss | 70 + .../static/bootstrap-5.3.3/mixins/_caret.scss | 69 + .../bootstrap-5.3.3/mixins/_clearfix.scss | 9 + .../bootstrap-5.3.3/mixins/_color-mode.scss | 21 + .../bootstrap-5.3.3/mixins/_color-scheme.scss | 7 + .../bootstrap-5.3.3/mixins/_container.scss | 11 + .../bootstrap-5.3.3/mixins/_deprecate.scss | 10 + .../static/bootstrap-5.3.3/mixins/_forms.scss | 163 + .../bootstrap-5.3.3/mixins/_gradients.scss | 47 + .../static/bootstrap-5.3.3/mixins/_grid.scss | 151 + .../static/bootstrap-5.3.3/mixins/_image.scss | 16 + .../bootstrap-5.3.3/mixins/_list-group.scss | 26 + .../static/bootstrap-5.3.3/mixins/_lists.scss | 7 + .../bootstrap-5.3.3/mixins/_pagination.scss | 10 + .../bootstrap-5.3.3/mixins/_reset-text.scss | 17 + .../bootstrap-5.3.3/mixins/_resize.scss | 6 + .../mixins/_table-variants.scss | 24 + .../mixins/_text-truncate.scss | 8 + .../bootstrap-5.3.3/mixins/_transition.scss | 26 + .../bootstrap-5.3.3/mixins/_utilities.scss | 97 + .../mixins/_visually-hidden.scss | 33 + .../static/bootstrap-5.3.3/tests/jasmine.js | 16 + .../_auto-import-of-variables-dark.test.scss | 7 + .../tests/mixins/_color-modes.test.scss | 69 + .../_media-query-color-mode-full.test.scss | 8 + .../tests/mixins/_utilities.test.scss | 393 ++ .../tests/sass-true/register.js | 14 + .../bootstrap-5.3.3/tests/sass-true/runner.js | 17 + .../tests/utilities/_api.test.scss | 75 + .../bootstrap-5.3.3/utilities/_api.scss | 47 + .../static/bootstrap-5.3.3/vendor/_rfs.scss | 348 ++ .../pagetop-bootsier/static/js/bootstrap.js | 4494 +++++++++++++++++ .../static/js/bootstrap.js.map | 1 + .../static/js/bootstrap.min.js | 7 + .../static/js/bootstrap.min.js.map | 1 + src/base/extension/welcome.rs | 1 - tools/changelog.sh | 8 + 115 files changed, 15682 insertions(+), 4 deletions(-) create mode 100644 extensions/pagetop-bootsier/Cargo.toml create mode 100644 extensions/pagetop-bootsier/README.md create mode 100644 extensions/pagetop-bootsier/build.rs create mode 100644 extensions/pagetop-bootsier/src/lib.rs create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.js create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.js.map create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.min.js create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.min.js.map diff --git a/Cargo.lock b/Cargo.lock index cab741ef..ebcebd39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1561,6 +1561,7 @@ dependencies = [ "indoc", "itoa", "pagetop-aliner", + "pagetop-bootsier", "pagetop-build", "pagetop-macros", "pagetop-statics", @@ -1585,6 +1586,14 @@ dependencies = [ "pagetop-build", ] +[[package]] +name = "pagetop-bootsier" +version = "0.0.18" +dependencies = [ + "pagetop", + "pagetop-build", +] + [[package]] name = "pagetop-build" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index fe101436..103f6a94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ testing = [] [dev-dependencies] tempfile = "3.23" pagetop-aliner.workspace = true +pagetop-bootsier.workspace = true [build-dependencies] pagetop-build.workspace = true @@ -65,6 +66,7 @@ members = [ "helpers/pagetop-statics", # Extensions "extensions/pagetop-aliner", + "extensions/pagetop-bootsier", ] [workspace.package] @@ -81,5 +83,6 @@ pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } # Extensions pagetop-aliner = { version = "0.0", path = "extensions/pagetop-aliner" } +pagetop-bootsier = { version = "0.0", path = "extensions/pagetop-bootsier" } # PageTop pagetop = { version = "0.4", path = "." } diff --git a/extensions/pagetop-aliner/Cargo.toml b/extensions/pagetop-aliner/Cargo.toml index 1c1101fb..603a6309 100644 --- a/extensions/pagetop-aliner/Cargo.toml +++ b/extensions/pagetop-aliner/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.9" edition = "2021" description = """ - Tema para PageTop que muestra esquemáticamente la composición de las páginas HTML + Tema de PageTop que muestra esquemáticamente la composición de las páginas HTML """ categories = ["web-programming", "gui"] keywords = ["pagetop", "theme", "css"] diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 88e3d782..ac6da910 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -2,7 +2,7 @@ <h1>PageTop Aliner</h1> -<p>Tema para <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p> +<p>Tema de <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p> [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 084678d7..92f9aa41 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -107,7 +107,7 @@ impl Extension for Aliner { } impl Theme for Aliner { - fn before_render_page_body(&self, page: &mut Page) { + fn after_render_page_body(&self, page: &mut Page) { page.alter_param("include_basic_assets", true) .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/aliner/css/styles.css") diff --git a/extensions/pagetop-bootsier/Cargo.toml b/extensions/pagetop-bootsier/Cargo.toml new file mode 100644 index 00000000..8945e5a8 --- /dev/null +++ b/extensions/pagetop-bootsier/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pagetop-bootsier" +version = "0.0.18" +edition = "2021" + +description = """ + Tema de PageTop basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles. +""" +categories = ["web-programming", "gui"] +keywords = ["pagetop", "theme", "bootstrap", "css", "js"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +pagetop.workspace = true + +[build-dependencies] +pagetop-build.workspace = true diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md new file mode 100644 index 00000000..87bd3c7b --- /dev/null +++ b/extensions/pagetop-bootsier/README.md @@ -0,0 +1,100 @@ +<div align="center"> + +<h1>PageTop Bootsier</h1> + +<p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> + +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) +[![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) +[![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) + +<br> +</div> + +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# ⚡️ Guía rápida + +Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: + +```toml +[dependencies] +pagetop-bootsier = "..." +``` + +**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en +`dependencies()` determina la prioridad relativa frente a las otras extensiones: + +```rust,no_run +use pagetop::prelude::*; + +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec<ExtensionRef> { + vec![ + // ... + &pagetop_bootsier::Bootsier, + // ... + ] + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +Y **selecciona el tema en la configuración** de la aplicación: + +```toml +[app] +theme = "Bootsier" +``` + +…o **fuerza el tema por código** en una página concreta: + +```rust,no_run +use pagetop::prelude::*; + +async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { + Page::new(request) + .with_theme("Bootsier") + .add_component( + Block::new() + .with_title(L10n::l("sample_title")) + .add_component(Html::with(|cx| html! { + p { (L10n::l("sample_content").using(cx)) } + })), + ) + .render() +} +``` + + +# 🚧 Advertencia + +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +# 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs new file mode 100644 index 00000000..47cb4740 --- /dev/null +++ b/extensions/pagetop-bootsier/build.rs @@ -0,0 +1,22 @@ +use pagetop_build::StaticFilesBundle; + +use std::env; +use std::path::Path; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss( + "./static/bootstrap-5.3.3/bootstrap.scss", + "bootstrap.min.css", + ) + .with_name("bootsier") + .build()?; + StaticFilesBundle::from_dir("./static/js", Some(bootstrap_js_files)) + .with_name("bootsier_js") + .build() +} + +fn bootstrap_js_files(path: &Path) -> bool { + // No filtering during development, only on "release" compilation. + env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release" + || path.file_name().map_or(false, |n| n == "bootstrap.min.js") +} diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs new file mode 100644 index 00000000..8157ec2d --- /dev/null +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -0,0 +1,121 @@ +/*! +<div align="center"> + +<h1>PageTop Bootsier</h1> + +<p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> + +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) +[![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) +[![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) + +<br> +</div> + +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +# ⚡️ Guía rápida + +Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: + +```toml +[dependencies] +pagetop-bootsier = "..." +``` + +**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en +`dependencies()` determina la prioridad relativa frente a las otras extensiones: + +```rust,no_run +use pagetop::prelude::*; + +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec<ExtensionRef> { + vec![ + // ... + &pagetop_bootsier::Bootsier, + // ... + ] + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +Y **selecciona el tema en la configuración** de la aplicación: + +```toml +[app] +theme = "Bootsier" +``` + +…o **fuerza el tema por código** en una página concreta: + +```rust,no_run +use pagetop::prelude::*; + +async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { + Page::new(request) + .with_theme("Bootsier") + .add_component( + Block::new() + .with_title(L10n::l("sample_title")) + .add_component(Html::with(|cx| html! { + p { (L10n::l("sample_content").using(cx)) } + })), + ) + .render() +} +``` +*/ + +use pagetop::prelude::*; + +/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. +pub type BootsierRegion = ThemeRegion; + +// Versión de la librería Bootstrap. +const BOOTSTRAP_VERSION: &str = "5.3.3"; + +/// Tema basado en [Bootstrap](https://getbootstrap.com/) para los componentes base de PageTop. +/// +/// Ofrece composición de páginas *responsive*, utilidades y componentes listos para usar, con +/// estilos coherentes y enfoque en accesibilidad. +pub struct Bootsier; + +impl Extension for Bootsier { + fn theme(&self) -> Option<ThemeRef> { + Some(&Self) + } + + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + static_files_service!(scfg, [bootsier] => "/bootsier/css"); + static_files_service!(scfg, [bootsier_js] => "/bootsier/js"); + } +} + +impl Theme for Bootsier { + fn after_render_page_body(&self, page: &mut Page) { + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/bootsier/css/bootstrap.min.css") + .with_version(BOOTSTRAP_VERSION) + .with_weight(-90), + )) + .alter_assets(ContextOp::AddJavaScript( + JavaScript::defer("/bootsier/js/bootstrap.min.js") + .with_version(BOOTSTRAP_VERSION) + .with_weight(-90), + )); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss new file mode 100644 index 00000000..17e5436e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss @@ -0,0 +1,158 @@ +// +// Base styles +// + +.accordion { + // scss-docs-start accordion-css-vars + --#{$prefix}accordion-color: #{$accordion-color}; + --#{$prefix}accordion-bg: #{$accordion-bg}; + --#{$prefix}accordion-transition: #{$accordion-transition}; + --#{$prefix}accordion-border-color: #{$accordion-border-color}; + --#{$prefix}accordion-border-width: #{$accordion-border-width}; + --#{$prefix}accordion-border-radius: #{$accordion-border-radius}; + --#{$prefix}accordion-inner-border-radius: #{$accordion-inner-border-radius}; + --#{$prefix}accordion-btn-padding-x: #{$accordion-button-padding-x}; + --#{$prefix}accordion-btn-padding-y: #{$accordion-button-padding-y}; + --#{$prefix}accordion-btn-color: #{$accordion-button-color}; + --#{$prefix}accordion-btn-bg: #{$accordion-button-bg}; + --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon)}; + --#{$prefix}accordion-btn-icon-width: #{$accordion-icon-width}; + --#{$prefix}accordion-btn-icon-transform: #{$accordion-icon-transform}; + --#{$prefix}accordion-btn-icon-transition: #{$accordion-icon-transition}; + --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon)}; + --#{$prefix}accordion-btn-focus-box-shadow: #{$accordion-button-focus-box-shadow}; + --#{$prefix}accordion-body-padding-x: #{$accordion-body-padding-x}; + --#{$prefix}accordion-body-padding-y: #{$accordion-body-padding-y}; + --#{$prefix}accordion-active-color: #{$accordion-button-active-color}; + --#{$prefix}accordion-active-bg: #{$accordion-button-active-bg}; + // scss-docs-end accordion-css-vars +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--#{$prefix}accordion-btn-padding-y) var(--#{$prefix}accordion-btn-padding-x); + @include font-size($font-size-base); + color: var(--#{$prefix}accordion-btn-color); + text-align: left; // Reset button style + background-color: var(--#{$prefix}accordion-btn-bg); + border: 0; + @include border-radius(0); + overflow-anchor: none; + @include transition(var(--#{$prefix}accordion-transition)); + + &:not(.collapsed) { + color: var(--#{$prefix}accordion-active-color); + background-color: var(--#{$prefix}accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--#{$prefix}accordion-border-width)) 0 var(--#{$prefix}accordion-border-color); // stylelint-disable-line function-disallowed-list + + &::after { + background-image: var(--#{$prefix}accordion-btn-active-icon); + transform: var(--#{$prefix}accordion-btn-icon-transform); + } + } + + // Accordion icon + &::after { + flex-shrink: 0; + width: var(--#{$prefix}accordion-btn-icon-width); + height: var(--#{$prefix}accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--#{$prefix}accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--#{$prefix}accordion-btn-icon-width); + @include transition(var(--#{$prefix}accordion-btn-icon-transition)); + } + + &:hover { + z-index: 2; + } + + &:focus { + z-index: 3; + outline: 0; + box-shadow: var(--#{$prefix}accordion-btn-focus-box-shadow); + } +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + color: var(--#{$prefix}accordion-color); + background-color: var(--#{$prefix}accordion-bg); + border: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color); + + &:first-of-type { + @include border-top-radius(var(--#{$prefix}accordion-border-radius)); + + > .accordion-header .accordion-button { + @include border-top-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + &:not(:first-of-type) { + border-top: 0; + } + + // Only set a border-radius on the last item if the accordion is collapsed + &:last-of-type { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + + > .accordion-header .accordion-button { + &.collapsed { + @include border-bottom-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + > .accordion-collapse { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + } + } +} + +.accordion-body { + padding: var(--#{$prefix}accordion-body-padding-y) var(--#{$prefix}accordion-body-padding-x); +} + + +// Flush accordion items +// +// Remove borders and border-radius to keep accordion items edge-to-edge. + +.accordion-flush { + > .accordion-item { + border-right: 0; + border-left: 0; + @include border-radius(0); + + &:first-child { border-top: 0; } + &:last-child { border-bottom: 0; } + + // stylelint-disable selector-max-class + > .accordion-header .accordion-button { + &, + &.collapsed { + @include border-radius(0); + } + } + // stylelint-enable selector-max-class + + > .accordion-collapse { + @include border-radius(0); + } + } +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .accordion-button::after { + --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon-dark)}; + --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon-dark)}; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss new file mode 100644 index 00000000..b8cff9b7 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss @@ -0,0 +1,68 @@ +// +// Base styles +// + +.alert { + // scss-docs-start alert-css-vars + --#{$prefix}alert-bg: transparent; + --#{$prefix}alert-padding-x: #{$alert-padding-x}; + --#{$prefix}alert-padding-y: #{$alert-padding-y}; + --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom}; + --#{$prefix}alert-color: inherit; + --#{$prefix}alert-border-color: transparent; + --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color); + --#{$prefix}alert-border-radius: #{$alert-border-radius}; + --#{$prefix}alert-link-color: inherit; + // scss-docs-end alert-css-vars + + position: relative; + padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x); + margin-bottom: var(--#{$prefix}alert-margin-bottom); + color: var(--#{$prefix}alert-color); + background-color: var(--#{$prefix}alert-bg); + border: var(--#{$prefix}alert-border); + @include border-radius(var(--#{$prefix}alert-border-radius)); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; + color: var(--#{$prefix}alert-link-color); +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: $alert-dismissible-padding-r; + + // Adjust close link position + .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: $stretched-link-z-index + 1; + padding: $alert-padding-y * 1.25 $alert-padding-x; + } +} + + +// scss-docs-start alert-modifiers +// Generate contextual modifier classes for colorizing the alert +@each $state in map-keys($theme-colors) { + .alert-#{$state} { + --#{$prefix}alert-color: var(--#{$prefix}#{$state}-text-emphasis); + --#{$prefix}alert-bg: var(--#{$prefix}#{$state}-bg-subtle); + --#{$prefix}alert-border-color: var(--#{$prefix}#{$state}-border-subtle); + --#{$prefix}alert-link-color: var(--#{$prefix}#{$state}-text-emphasis); + } +} +// scss-docs-end alert-modifiers diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss new file mode 100644 index 00000000..cc3d2695 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss @@ -0,0 +1,38 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + // scss-docs-start badge-css-vars + --#{$prefix}badge-padding-x: #{$badge-padding-x}; + --#{$prefix}badge-padding-y: #{$badge-padding-y}; + @include rfs($badge-font-size, --#{$prefix}badge-font-size); + --#{$prefix}badge-font-weight: #{$badge-font-weight}; + --#{$prefix}badge-color: #{$badge-color}; + --#{$prefix}badge-border-radius: #{$badge-border-radius}; + // scss-docs-end badge-css-vars + + display: inline-block; + padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x); + @include font-size(var(--#{$prefix}badge-font-size)); + font-weight: var(--#{$prefix}badge-font-weight); + line-height: 1; + color: var(--#{$prefix}badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius(var(--#{$prefix}badge-border-radius)); + @include gradient-bg(); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss new file mode 100644 index 00000000..b8252ff2 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss @@ -0,0 +1,40 @@ +.breadcrumb { + // scss-docs-start breadcrumb-css-vars + --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x}; + --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y}; + --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; + @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size); + --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg}; + --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius}; + --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color}; + --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x}; + --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color}; + // scss-docs-end breadcrumb-css-vars + + display: flex; + flex-wrap: wrap; + padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x); + margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom); + @include font-size(var(--#{$prefix}breadcrumb-font-size)); + list-style: none; + background-color: var(--#{$prefix}breadcrumb-bg); + @include border-radius(var(--#{$prefix}breadcrumb-border-radius)); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item { + padding-left: var(--#{$prefix}breadcrumb-item-padding-x); + + &::before { + float: left; // Suppress inline spacings and underlining of the separator + padding-right: var(--#{$prefix}breadcrumb-item-padding-x); + color: var(--#{$prefix}breadcrumb-divider-color); + content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; + } + } + + &.active { + color: var(--#{$prefix}breadcrumb-item-active-color); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss new file mode 100644 index 00000000..55ae3f65 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss @@ -0,0 +1,142 @@ +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; // match .btn alignment given font-size hack above + + > .btn { + position: relative; + flex: 1 1 auto; + } + + // Bring the hover, focused, and "active" buttons to the front to overlay + // the borders properly + > .btn-check:checked + .btn, + > .btn-check:focus + .btn, + > .btn:hover, + > .btn:focus, + > .btn:active, + > .btn.active { + z-index: 1; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + + .input-group { + width: auto; + } +} + +.btn-group { + @include border-radius($btn-border-radius); + + // Prevent double borders when buttons are next to each other + > :not(.btn-check:first-child) + .btn, + > .btn-group:not(:first-child) { + margin-left: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn.dropdown-toggle-split:first-child, + > .btn-group:not(:last-child) > .btn { + @include border-end-radius(0); + } + + // The left radius should be 0 if the button is: + // - the "third or more" child + // - the second child and the previous element isn't `.btn-check` (making it the first child visually) + // - part of a btn-group which isn't the first child + > .btn:nth-child(n + 3), + > :not(.btn-check) + .btn, + > .btn-group:not(:first-child) > .btn { + @include border-start-radius(0); + } +} + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + + +// +// Split button dropdowns +// + +.dropdown-toggle-split { + padding-right: $btn-padding-x * .75; + padding-left: $btn-padding-x * .75; + + &::after, + .dropup &::after, + .dropend &::after { + margin-left: 0; + } + + .dropstart &::before { + margin-right: 0; + } +} + +.btn-sm + .dropdown-toggle-split { + padding-right: $btn-padding-x-sm * .75; + padding-left: $btn-padding-x-sm * .75; +} + +.btn-lg + .dropdown-toggle-split { + padding-right: $btn-padding-x-lg * .75; + padding-left: $btn-padding-x-lg * .75; +} + + +// The clickable button for toggling the menu +// Set the same inset shadow as the :active state +.btn-group.show .dropdown-toggle { + @include box-shadow($btn-active-box-shadow); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + + +// +// Vertical button groups +// + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; + + > .btn, + > .btn-group { + width: 100%; + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-top: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-bottom-radius(0); + } + + > .btn ~ .btn, + > .btn-group:not(:first-child) > .btn { + @include border-top-radius(0); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss new file mode 100644 index 00000000..caa4518a --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss @@ -0,0 +1,216 @@ +// +// Base styles +// + +.btn { + // scss-docs-start btn-css-vars + --#{$prefix}btn-padding-x: #{$btn-padding-x}; + --#{$prefix}btn-padding-y: #{$btn-padding-y}; + --#{$prefix}btn-font-family: #{$btn-font-family}; + @include rfs($btn-font-size, --#{$prefix}btn-font-size); + --#{$prefix}btn-font-weight: #{$btn-font-weight}; + --#{$prefix}btn-line-height: #{$btn-line-height}; + --#{$prefix}btn-color: #{$btn-color}; + --#{$prefix}btn-bg: transparent; + --#{$prefix}btn-border-width: #{$btn-border-width}; + --#{$prefix}btn-border-color: transparent; + --#{$prefix}btn-border-radius: #{$btn-border-radius}; + --#{$prefix}btn-hover-border-color: transparent; + --#{$prefix}btn-box-shadow: #{$btn-box-shadow}; + --#{$prefix}btn-disabled-opacity: #{$btn-disabled-opacity}; + --#{$prefix}btn-focus-box-shadow: 0 0 0 #{$btn-focus-width} rgba(var(--#{$prefix}btn-focus-shadow-rgb), .5); + // scss-docs-end btn-css-vars + + display: inline-block; + padding: var(--#{$prefix}btn-padding-y) var(--#{$prefix}btn-padding-x); + font-family: var(--#{$prefix}btn-font-family); + @include font-size(var(--#{$prefix}btn-font-size)); + font-weight: var(--#{$prefix}btn-font-weight); + line-height: var(--#{$prefix}btn-line-height); + color: var(--#{$prefix}btn-color); + text-align: center; + text-decoration: if($link-decoration == none, null, none); + white-space: $btn-white-space; + vertical-align: middle; + cursor: if($enable-button-pointers, pointer, null); + user-select: none; + border: var(--#{$prefix}btn-border-width) solid var(--#{$prefix}btn-border-color); + @include border-radius(var(--#{$prefix}btn-border-radius)); + @include gradient-bg(var(--#{$prefix}btn-bg)); + @include box-shadow(var(--#{$prefix}btn-box-shadow)); + @include transition($btn-transition); + + &:hover { + color: var(--#{$prefix}btn-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + background-color: var(--#{$prefix}btn-hover-bg); + border-color: var(--#{$prefix}btn-hover-border-color); + } + + .btn-check + &:hover { + // override for the checkbox/radio buttons + color: var(--#{$prefix}btn-color); + background-color: var(--#{$prefix}btn-bg); + border-color: var(--#{$prefix}btn-border-color); + } + + &:focus-visible { + color: var(--#{$prefix}btn-hover-color); + @include gradient-bg(var(--#{$prefix}btn-hover-bg)); + border-color: var(--#{$prefix}btn-hover-border-color); + outline: 0; + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + + .btn-check:focus-visible + & { + border-color: var(--#{$prefix}btn-hover-border-color); + outline: 0; + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + + .btn-check:checked + &, + :not(.btn-check) + &:active, + &:first-child:active, + &.active, + &.show { + color: var(--#{$prefix}btn-active-color); + background-color: var(--#{$prefix}btn-active-bg); + // Remove CSS gradients if they're enabled + background-image: if($enable-gradients, none, null); + border-color: var(--#{$prefix}btn-active-border-color); + @include box-shadow(var(--#{$prefix}btn-active-shadow)); + + &:focus-visible { + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-active-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + } + + .btn-check:checked:focus-visible + & { + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-active-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + + &:disabled, + &.disabled, + fieldset:disabled & { + color: var(--#{$prefix}btn-disabled-color); + pointer-events: none; + background-color: var(--#{$prefix}btn-disabled-bg); + background-image: if($enable-gradients, none, null); + border-color: var(--#{$prefix}btn-disabled-border-color); + opacity: var(--#{$prefix}btn-disabled-opacity); + @include box-shadow(none); + } +} + + +// +// Alternate buttons +// + +// scss-docs-start btn-variant-loops +@each $color, $value in $theme-colors { + .btn-#{$color} { + @if $color == "light" { + @include button-variant( + $value, + $value, + $hover-background: shade-color($value, $btn-hover-bg-shade-amount), + $hover-border: shade-color($value, $btn-hover-border-shade-amount), + $active-background: shade-color($value, $btn-active-bg-shade-amount), + $active-border: shade-color($value, $btn-active-border-shade-amount) + ); + } @else if $color == "dark" { + @include button-variant( + $value, + $value, + $hover-background: tint-color($value, $btn-hover-bg-tint-amount), + $hover-border: tint-color($value, $btn-hover-border-tint-amount), + $active-background: tint-color($value, $btn-active-bg-tint-amount), + $active-border: tint-color($value, $btn-active-border-tint-amount) + ); + } @else { + @include button-variant($value, $value); + } + } +} + +@each $color, $value in $theme-colors { + .btn-outline-#{$color} { + @include button-outline-variant($value); + } +} +// scss-docs-end btn-variant-loops + + +// +// Link buttons +// + +// Make a button look and behave like a link +.btn-link { + --#{$prefix}btn-font-weight: #{$font-weight-normal}; + --#{$prefix}btn-color: #{$btn-link-color}; + --#{$prefix}btn-bg: transparent; + --#{$prefix}btn-border-color: transparent; + --#{$prefix}btn-hover-color: #{$btn-link-hover-color}; + --#{$prefix}btn-hover-border-color: transparent; + --#{$prefix}btn-active-color: #{$btn-link-hover-color}; + --#{$prefix}btn-active-border-color: transparent; + --#{$prefix}btn-disabled-color: #{$btn-link-disabled-color}; + --#{$prefix}btn-disabled-border-color: transparent; + --#{$prefix}btn-box-shadow: 0 0 0 #000; // Can't use `none` as keyword negates all values when used with multiple shadows + --#{$prefix}btn-focus-shadow-rgb: #{$btn-link-focus-shadow-rgb}; + + text-decoration: $link-decoration; + @if $enable-gradients { + background-image: none; + } + + &:hover, + &:focus-visible { + text-decoration: $link-hover-decoration; + } + + &:focus-visible { + color: var(--#{$prefix}btn-color); + } + + &:hover { + color: var(--#{$prefix}btn-hover-color); + } + + // No need for an active state here +} + + +// +// Button Sizes +// + +.btn-lg { + @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg); +} + +.btn-sm { + @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss new file mode 100644 index 00000000..d3535a98 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss @@ -0,0 +1,239 @@ +// +// Base styles +// + +.card { + // scss-docs-start card-css-vars + --#{$prefix}card-spacer-y: #{$card-spacer-y}; + --#{$prefix}card-spacer-x: #{$card-spacer-x}; + --#{$prefix}card-title-spacer-y: #{$card-title-spacer-y}; + --#{$prefix}card-title-color: #{$card-title-color}; + --#{$prefix}card-subtitle-color: #{$card-subtitle-color}; + --#{$prefix}card-border-width: #{$card-border-width}; + --#{$prefix}card-border-color: #{$card-border-color}; + --#{$prefix}card-border-radius: #{$card-border-radius}; + --#{$prefix}card-box-shadow: #{$card-box-shadow}; + --#{$prefix}card-inner-border-radius: #{$card-inner-border-radius}; + --#{$prefix}card-cap-padding-y: #{$card-cap-padding-y}; + --#{$prefix}card-cap-padding-x: #{$card-cap-padding-x}; + --#{$prefix}card-cap-bg: #{$card-cap-bg}; + --#{$prefix}card-cap-color: #{$card-cap-color}; + --#{$prefix}card-height: #{$card-height}; + --#{$prefix}card-color: #{$card-color}; + --#{$prefix}card-bg: #{$card-bg}; + --#{$prefix}card-img-overlay-padding: #{$card-img-overlay-padding}; + --#{$prefix}card-group-margin: #{$card-group-margin}; + // scss-docs-end card-css-vars + + position: relative; + display: flex; + flex-direction: column; + min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 + height: var(--#{$prefix}card-height); + color: var(--#{$prefix}body-color); + word-wrap: break-word; + background-color: var(--#{$prefix}card-bg); + background-clip: border-box; + border: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + @include border-radius(var(--#{$prefix}card-border-radius)); + @include box-shadow(var(--#{$prefix}card-box-shadow)); + + > hr { + margin-right: 0; + margin-left: 0; + } + + > .list-group { + border-top: inherit; + border-bottom: inherit; + + &:first-child { + border-top-width: 0; + @include border-top-radius(var(--#{$prefix}card-inner-border-radius)); + } + + &:last-child { + border-bottom-width: 0; + @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius)); + } + } + + // Due to specificity of the above selector (`.card > .list-group`), we must + // use a child selector here to prevent double borders. + > .card-header + .list-group, + > .list-group + .card-footer { + border-top: 0; + } +} + +.card-body { + // Enable `flex-grow: 1` for decks and groups so that card blocks take up + // as much space as possible, ensuring footers are aligned to the bottom. + flex: 1 1 auto; + padding: var(--#{$prefix}card-spacer-y) var(--#{$prefix}card-spacer-x); + color: var(--#{$prefix}card-color); +} + +.card-title { + margin-bottom: var(--#{$prefix}card-title-spacer-y); + color: var(--#{$prefix}card-title-color); +} + +.card-subtitle { + margin-top: calc(-.5 * var(--#{$prefix}card-title-spacer-y)); // stylelint-disable-line function-disallowed-list + margin-bottom: 0; + color: var(--#{$prefix}card-subtitle-color); +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link { + &:hover { + text-decoration: if($link-hover-decoration == underline, none, null); + } + + + .card-link { + margin-left: var(--#{$prefix}card-spacer-x); + } +} + +// +// Optional textual caps +// + +.card-header { + padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x); + margin-bottom: 0; // Removes the default margin-bottom of <hN> + color: var(--#{$prefix}card-cap-color); + background-color: var(--#{$prefix}card-cap-bg); + border-bottom: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + + &:first-child { + @include border-radius(var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius) 0 0); + } +} + +.card-footer { + padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x); + color: var(--#{$prefix}card-cap-color); + background-color: var(--#{$prefix}card-cap-bg); + border-top: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + + &:last-child { + @include border-radius(0 0 var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius)); + } +} + + +// +// Header navs +// + +.card-header-tabs { + margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + margin-bottom: calc(-1 * var(--#{$prefix}card-cap-padding-y)); // stylelint-disable-line function-disallowed-list + margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + border-bottom: 0; + + .nav-link.active { + background-color: var(--#{$prefix}card-bg); + border-bottom-color: var(--#{$prefix}card-bg); + } +} + +.card-header-pills { + margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list +} + +// Card image +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--#{$prefix}card-img-overlay-padding); + @include border-radius(var(--#{$prefix}card-inner-border-radius)); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch +} + +.card-img, +.card-img-top { + @include border-top-radius(var(--#{$prefix}card-inner-border-radius)); +} + +.card-img, +.card-img-bottom { + @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius)); +} + + +// +// Card groups +// + +.card-group { + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + margin-bottom: var(--#{$prefix}card-group-margin); + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0%; + margin-bottom: 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:not(:last-child) { + @include border-end-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-right-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-right-radius: 0; + } + } + + &:not(:first-child) { + @include border-start-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-left-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-left-radius: 0; + } + } + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss new file mode 100644 index 00000000..3a135220 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss @@ -0,0 +1,236 @@ +// Notes on the classes: +// +// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically) +// even when their scroll action started on a carousel, but for compatibility (with Firefox) +// we're preventing all actions instead +// 2. The .carousel-item-start and .carousel-item-end is used to indicate where +// the active slide is heading. +// 3. .active.carousel-item is the current slide. +// 4. .active.carousel-item-start and .active.carousel-item-end is the current +// slide in its in-transition state. Only one of these occurs at a time. +// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end +// is the upcoming slide in transition. + +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; + @include clearfix(); +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; + @include transition($carousel-transition); +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + transform: translateX(-100%); +} + + +// +// Alternate transitions +// + +.carousel-fade { + .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; + } + + .carousel-item.active, + .carousel-item-next.carousel-item-start, + .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; + } + + .active.carousel-item-start, + .active.carousel-item-end { + z-index: 0; + opacity: 0; + @include transition(opacity 0s $carousel-transition-duration); + } +} + + +// +// Left/right controls for nav +// + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + // Use flex for alignment (1-3) + display: flex; // 1. allow flex styles + align-items: center; // 2. vertically center contents + justify-content: center; // 3. horizontally center contents + width: $carousel-control-width; + padding: 0; + color: $carousel-control-color; + text-align: center; + background: none; + border: 0; + opacity: $carousel-control-opacity; + @include transition($carousel-control-transition); + + // Hover/focus state + &:hover, + &:focus { + color: $carousel-control-color; + text-decoration: none; + outline: 0; + opacity: $carousel-control-hover-opacity; + } +} +.carousel-control-prev { + left: 0; + background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null); +} +.carousel-control-next { + right: 0; + background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null); +} + +// Icons for within +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: $carousel-control-icon-width; + height: $carousel-control-icon-width; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: escape-svg($carousel-control-prev-icon-bg) #{"/*rtl:" + escape-svg($carousel-control-next-icon-bg) + "*/"}; +} +.carousel-control-next-icon { + background-image: escape-svg($carousel-control-next-icon-bg) #{"/*rtl:" + escape-svg($carousel-control-prev-icon-bg) + "*/"}; +} + +// Optional indicator pips/controls +// +// Add a container (such as a list) with the following class and add an item (ideally a focusable control, +// like a button) with data-bs-target for each slide your carousel holds. + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + // Use the .carousel-control's width as margin so we don't overlay those + margin-right: $carousel-control-width; + margin-bottom: 1rem; + margin-left: $carousel-control-width; + + [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: $carousel-indicator-width; + height: $carousel-indicator-height; + padding: 0; + margin-right: $carousel-indicator-spacer; + margin-left: $carousel-indicator-spacer; + text-indent: -999px; + cursor: pointer; + background-color: $carousel-indicator-active-bg; + background-clip: padding-box; + border: 0; + // Use transparent borders to increase the hit area by 10px on top and bottom. + border-top: $carousel-indicator-hit-area-height solid transparent; + border-bottom: $carousel-indicator-hit-area-height solid transparent; + opacity: $carousel-indicator-opacity; + @include transition($carousel-indicator-transition); + } + + .active { + opacity: $carousel-indicator-active-opacity; + } +} + + +// Optional captions +// +// + +.carousel-caption { + position: absolute; + right: (100% - $carousel-caption-width) * .5; + bottom: $carousel-caption-spacer; + left: (100% - $carousel-caption-width) * .5; + padding-top: $carousel-caption-padding-y; + padding-bottom: $carousel-caption-padding-y; + color: $carousel-caption-color; + text-align: center; +} + +// Dark mode carousel + +@mixin carousel-dark() { + .carousel-control-prev-icon, + .carousel-control-next-icon { + filter: $carousel-dark-control-icon-filter; + } + + .carousel-indicators [data-bs-target] { + background-color: $carousel-dark-indicator-active-bg; + } + + .carousel-caption { + color: $carousel-dark-caption-color; + } +} + +.carousel-dark { + @include carousel-dark(); +} + +@if $enable-dark-mode { + @include color-mode(dark) { + @if $color-mode-type == "media-query" { + .carousel { + @include carousel-dark(); + } + } @else { + .carousel, + &.carousel { + @include carousel-dark(); + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss new file mode 100644 index 00000000..4d6e73c1 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss @@ -0,0 +1,63 @@ +// Transparent background and border properties included for button version. +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +.btn-close { + // scss-docs-start close-css-vars + --#{$prefix}btn-close-color: #{$btn-close-color}; + --#{$prefix}btn-close-bg: #{ escape-svg($btn-close-bg) }; + --#{$prefix}btn-close-opacity: #{$btn-close-opacity}; + --#{$prefix}btn-close-hover-opacity: #{$btn-close-hover-opacity}; + --#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow}; + --#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity}; + --#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity}; + --#{$prefix}btn-close-white-filter: #{$btn-close-white-filter}; + // scss-docs-end close-css-vars + + box-sizing: content-box; + width: $btn-close-width; + height: $btn-close-height; + padding: $btn-close-padding-y $btn-close-padding-x; + color: var(--#{$prefix}btn-close-color); + background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements + border: 0; // for button elements + @include border-radius(); + opacity: var(--#{$prefix}btn-close-opacity); + + // Override <a>'s hover style + &:hover { + color: var(--#{$prefix}btn-close-color); + text-decoration: none; + opacity: var(--#{$prefix}btn-close-hover-opacity); + } + + &:focus { + outline: 0; + box-shadow: var(--#{$prefix}btn-close-focus-shadow); + opacity: var(--#{$prefix}btn-close-focus-opacity); + } + + &:disabled, + &.disabled { + pointer-events: none; + user-select: none; + opacity: var(--#{$prefix}btn-close-disabled-opacity); + } +} + +@mixin btn-close-white() { + filter: var(--#{$prefix}btn-close-white-filter); +} + +.btn-close-white { + @include btn-close-white(); +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .btn-close { + @include btn-close-white(); + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss new file mode 100644 index 00000000..83b31381 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss @@ -0,0 +1,41 @@ +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +@if $enable-container-classes { + // Single container class with breakpoint max-widths + .container, + // 100% wide container at all breakpoints + .container-fluid { + @include make-container(); + } + + // Responsive containers that are 100% wide until a breakpoint + @each $breakpoint, $container-max-width in $container-max-widths { + .container-#{$breakpoint} { + @extend .container-fluid; + } + + @include media-breakpoint-up($breakpoint, $grid-breakpoints) { + %responsive-container-#{$breakpoint} { + max-width: $container-max-width; + } + + // Extend each breakpoint which is smaller or equal to the current breakpoint + $extend-breakpoint: true; + + @each $name, $width in $grid-breakpoints { + @if ($extend-breakpoint) { + .container#{breakpoint-infix($name, $grid-breakpoints)} { + @extend %responsive-container-#{$breakpoint}; + } + + // Once the current breakpoint is reached, stop extending + @if ($breakpoint == $name) { + $extend-breakpoint: false; + } + } + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss new file mode 100644 index 00000000..587ebb48 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss @@ -0,0 +1,250 @@ +// The dropdown wrapper (`<div>`) +.dropup, +.dropend, +.dropdown, +.dropstart, +.dropup-center, +.dropdown-center { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; + + // Generate the caret automatically + @include caret(); +} + +// The dropdown menu +.dropdown-menu { + // scss-docs-start dropdown-css-vars + --#{$prefix}dropdown-zindex: #{$zindex-dropdown}; + --#{$prefix}dropdown-min-width: #{$dropdown-min-width}; + --#{$prefix}dropdown-padding-x: #{$dropdown-padding-x}; + --#{$prefix}dropdown-padding-y: #{$dropdown-padding-y}; + --#{$prefix}dropdown-spacer: #{$dropdown-spacer}; + @include rfs($dropdown-font-size, --#{$prefix}dropdown-font-size); + --#{$prefix}dropdown-color: #{$dropdown-color}; + --#{$prefix}dropdown-bg: #{$dropdown-bg}; + --#{$prefix}dropdown-border-color: #{$dropdown-border-color}; + --#{$prefix}dropdown-border-radius: #{$dropdown-border-radius}; + --#{$prefix}dropdown-border-width: #{$dropdown-border-width}; + --#{$prefix}dropdown-inner-border-radius: #{$dropdown-inner-border-radius}; + --#{$prefix}dropdown-divider-bg: #{$dropdown-divider-bg}; + --#{$prefix}dropdown-divider-margin-y: #{$dropdown-divider-margin-y}; + --#{$prefix}dropdown-box-shadow: #{$dropdown-box-shadow}; + --#{$prefix}dropdown-link-color: #{$dropdown-link-color}; + --#{$prefix}dropdown-link-hover-color: #{$dropdown-link-hover-color}; + --#{$prefix}dropdown-link-hover-bg: #{$dropdown-link-hover-bg}; + --#{$prefix}dropdown-link-active-color: #{$dropdown-link-active-color}; + --#{$prefix}dropdown-link-active-bg: #{$dropdown-link-active-bg}; + --#{$prefix}dropdown-link-disabled-color: #{$dropdown-link-disabled-color}; + --#{$prefix}dropdown-item-padding-x: #{$dropdown-item-padding-x}; + --#{$prefix}dropdown-item-padding-y: #{$dropdown-item-padding-y}; + --#{$prefix}dropdown-header-color: #{$dropdown-header-color}; + --#{$prefix}dropdown-header-padding-x: #{$dropdown-header-padding-x}; + --#{$prefix}dropdown-header-padding-y: #{$dropdown-header-padding-y}; + // scss-docs-end dropdown-css-vars + + position: absolute; + z-index: var(--#{$prefix}dropdown-zindex); + display: none; // none by default, but block on "open" of the menu + min-width: var(--#{$prefix}dropdown-min-width); + padding: var(--#{$prefix}dropdown-padding-y) var(--#{$prefix}dropdown-padding-x); + margin: 0; // Override default margin of ul + @include font-size(var(--#{$prefix}dropdown-font-size)); + color: var(--#{$prefix}dropdown-color); + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + list-style: none; + background-color: var(--#{$prefix}dropdown-bg); + background-clip: padding-box; + border: var(--#{$prefix}dropdown-border-width) solid var(--#{$prefix}dropdown-border-color); + @include border-radius(var(--#{$prefix}dropdown-border-radius)); + @include box-shadow(var(--#{$prefix}dropdown-box-shadow)); + + &[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--#{$prefix}dropdown-spacer); + } + + @if $dropdown-padding-y == 0 { + > .dropdown-item:first-child, + > li:first-child .dropdown-item { + @include border-top-radius(var(--#{$prefix}dropdown-inner-border-radius)); + } + > .dropdown-item:last-child, + > li:last-child .dropdown-item { + @include border-bottom-radius(var(--#{$prefix}dropdown-inner-border-radius)); + } + + } +} + +// scss-docs-start responsive-breakpoints +// We deliberately hardcode the `bs-` prefix because we check +// this custom property in JS to determine Popper's positioning + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .dropdown-menu#{$infix}-start { + --bs-position: start; + + &[data-bs-popper] { + right: auto; + left: 0; + } + } + + .dropdown-menu#{$infix}-end { + --bs-position: end; + + &[data-bs-popper] { + right: 0; + left: auto; + } + } + } +} +// scss-docs-end responsive-breakpoints + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// Just add .dropup after the standard .dropdown class and you're set. +.dropup { + .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(up); + } +} + +.dropend { + .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(end); + &::after { + vertical-align: 0; + } + } +} + +.dropstart { + .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(start); + &::before { + vertical-align: 0; + } + } +} + + +// Dividers (basically an `<hr>`) within the dropdown +.dropdown-divider { + height: 0; + margin: var(--#{$prefix}dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--#{$prefix}dropdown-divider-bg); + opacity: 1; // Revisit in v6 to de-dupe styles that conflict with <hr> element +} + +// Links, buttons, and more within the dropdown menu +// +// `<button>`-specific styles are denoted with `// For <button>s` +.dropdown-item { + display: block; + width: 100%; // For `<button>`s + padding: var(--#{$prefix}dropdown-item-padding-y) var(--#{$prefix}dropdown-item-padding-x); + clear: both; + font-weight: $font-weight-normal; + color: var(--#{$prefix}dropdown-link-color); + text-align: inherit; // For `<button>`s + text-decoration: if($link-decoration == none, null, none); + white-space: nowrap; // prevent links from randomly breaking onto new lines + background-color: transparent; // For `<button>`s + border: 0; // For `<button>`s + @include border-radius(var(--#{$prefix}dropdown-item-border-radius, 0)); + + &:hover, + &:focus { + color: var(--#{$prefix}dropdown-link-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + @include gradient-bg(var(--#{$prefix}dropdown-link-hover-bg)); + } + + &.active, + &:active { + color: var(--#{$prefix}dropdown-link-active-color); + text-decoration: none; + @include gradient-bg(var(--#{$prefix}dropdown-link-active-bg)); + } + + &.disabled, + &:disabled { + color: var(--#{$prefix}dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent; + // Remove CSS gradients if they're enabled + background-image: if($enable-gradients, none, null); + } +} + +.dropdown-menu.show { + display: block; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: var(--#{$prefix}dropdown-header-padding-y) var(--#{$prefix}dropdown-header-padding-x); + margin-bottom: 0; // for use with heading elements + @include font-size($font-size-sm); + color: var(--#{$prefix}dropdown-header-color); + white-space: nowrap; // as with > li > a +} + +// Dropdown text +.dropdown-item-text { + display: block; + padding: var(--#{$prefix}dropdown-item-padding-y) var(--#{$prefix}dropdown-item-padding-x); + color: var(--#{$prefix}dropdown-link-color); +} + +// Dark dropdowns +.dropdown-menu-dark { + // scss-docs-start dropdown-dark-css-vars + --#{$prefix}dropdown-color: #{$dropdown-dark-color}; + --#{$prefix}dropdown-bg: #{$dropdown-dark-bg}; + --#{$prefix}dropdown-border-color: #{$dropdown-dark-border-color}; + --#{$prefix}dropdown-box-shadow: #{$dropdown-dark-box-shadow}; + --#{$prefix}dropdown-link-color: #{$dropdown-dark-link-color}; + --#{$prefix}dropdown-link-hover-color: #{$dropdown-dark-link-hover-color}; + --#{$prefix}dropdown-divider-bg: #{$dropdown-dark-divider-bg}; + --#{$prefix}dropdown-link-hover-bg: #{$dropdown-dark-link-hover-bg}; + --#{$prefix}dropdown-link-active-color: #{$dropdown-dark-link-active-color}; + --#{$prefix}dropdown-link-active-bg: #{$dropdown-dark-link-active-bg}; + --#{$prefix}dropdown-link-disabled-color: #{$dropdown-dark-link-disabled-color}; + --#{$prefix}dropdown-header-color: #{$dropdown-dark-header-color}; + // scss-docs-end dropdown-dark-css-vars +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss new file mode 100644 index 00000000..7b17d849 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss @@ -0,0 +1,9 @@ +@import "forms/labels"; +@import "forms/form-text"; +@import "forms/form-control"; +@import "forms/form-select"; +@import "forms/form-check"; +@import "forms/form-range"; +@import "forms/floating-labels"; +@import "forms/input-group"; +@import "forms/validation"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss new file mode 100644 index 00000000..90296586 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss @@ -0,0 +1,302 @@ +// Bootstrap functions +// +// Utility mixins and functions for evaluating source code across our variables, maps, and mixins. + +// Ascending +// Used to evaluate Sass maps like our grid breakpoints. +@mixin _assert-ascending($map, $map-name) { + $prev-key: null; + $prev-num: null; + @each $key, $num in $map { + @if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" { + // Do nothing + } @else if not comparable($prev-num, $num) { + @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } @else if $prev-num >= $num { + @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; + } + $prev-key: $key; + $prev-num: $num; + } +} + +// Starts at zero +// Used to ensure the min-width of the lowest breakpoint starts at 0. +@mixin _assert-starts-at-zero($map, $map-name: "$grid-breakpoints") { + @if length($map) > 0 { + $values: map-values($map); + $first-value: nth($values, 1); + @if $first-value != 0 { + @warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}."; + } + } +} + +// Colors +@function to-rgb($value) { + @return red($value), green($value), blue($value); +} + +// stylelint-disable scss/dollar-variable-pattern +@function rgba-css-var($identifier, $target) { + @if $identifier == "body" and $target == "bg" { + @return rgba(var(--#{$prefix}#{$identifier}-bg-rgb), var(--#{$prefix}#{$target}-opacity)); + } @if $identifier == "body" and $target == "text" { + @return rgba(var(--#{$prefix}#{$identifier}-color-rgb), var(--#{$prefix}#{$target}-opacity)); + } @else { + @return rgba(var(--#{$prefix}#{$identifier}-rgb), var(--#{$prefix}#{$target}-opacity)); + } +} + +@function map-loop($map, $func, $args...) { + $_map: (); + + @each $key, $value in $map { + // allow to pass the $key and $value of the map as an function argument + $_args: (); + @each $arg in $args { + $_args: append($_args, if($arg == "$key", $key, if($arg == "$value", $value, $arg))); + } + + $_map: map-merge($_map, ($key: call(get-function($func), $_args...))); + } + + @return $_map; +} +// stylelint-enable scss/dollar-variable-pattern + +@function varify($list) { + $result: null; + @each $entry in $list { + $result: append($result, var(--#{$prefix}#{$entry}), space); + } + @return $result; +} + +// Internal Bootstrap function to turn maps into its negative variant. +// It prefixes the keys with `n` and makes the value negative. +@function negativify-map($map) { + $result: (); + @each $key, $value in $map { + @if $key != 0 { + $result: map-merge($result, ("n" + $key: (-$value))); + } + } + @return $result; +} + +// Get multiple keys from a sass map +@function map-get-multiple($map, $values) { + $result: (); + @each $key, $value in $map { + @if (index($values, $key) != null) { + $result: map-merge($result, ($key: $value)); + } + } + @return $result; +} + +// Merge multiple maps +@function map-merge-multiple($maps...) { + $merged-maps: (); + + @each $map in $maps { + $merged-maps: map-merge($merged-maps, $map); + } + @return $merged-maps; +} + +// Replace `$search` with `$replace` in `$string` +// Used on our SVG icon backgrounds for custom forms. +// +// @author Kitty Giraudel +// @param {String} $string - Initial string +// @param {String} $search - Substring to replace +// @param {String} $replace ('') - New value +// @return {String} - Updated string +@function str-replace($string, $search, $replace: "") { + $index: str-index($string, $search); + + @if $index { + @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); + } + + @return $string; +} + +// See https://codepen.io/kevinweber/pen/dXWoRw +// +// Requires the use of quotes around data URIs. + +@function escape-svg($string) { + @if str-index($string, "data:image/svg+xml") { + @each $char, $encoded in $escaped-characters { + // Do not escape the url brackets + @if str-index($string, "url(") == 1 { + $string: url("#{str-replace(str-slice($string, 6, -3), $char, $encoded)}"); + } @else { + $string: str-replace($string, $char, $encoded); + } + } + } + + @return $string; +} + +// Color contrast +// See https://github.com/twbs/bootstrap/pull/30168 + +// A list of pre-calculated numbers of pow(divide((divide($value, 255) + .055), 1.055), 2.4). (from 0 to 255) +// stylelint-disable-next-line scss/dollar-variable-default, scss/dollar-variable-pattern +$_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003 .0033 .0037 .004 .0044 .0048 .0052 .0056 .006 .0065 .007 .0075 .008 .0086 .0091 .0097 .0103 .011 .0116 .0123 .013 .0137 .0144 .0152 .016 .0168 .0176 .0185 .0194 .0203 .0212 .0222 .0232 .0242 .0252 .0262 .0273 .0284 .0296 .0307 .0319 .0331 .0343 .0356 .0369 .0382 .0395 .0409 .0423 .0437 .0452 .0467 .0482 .0497 .0513 .0529 .0545 .0561 .0578 .0595 .0612 .063 .0648 .0666 .0685 .0704 .0723 .0742 .0762 .0782 .0802 .0823 .0844 .0865 .0887 .0908 .0931 .0953 .0976 .0999 .1022 .1046 .107 .1095 .1119 .1144 .117 .1195 .1221 .1248 .1274 .1301 .1329 .1356 .1384 .1413 .1441 .147 .15 .1529 .1559 .159 .162 .1651 .1683 .1714 .1746 .1779 .1812 .1845 .1878 .1912 .1946 .1981 .2016 .2051 .2086 .2122 .2159 .2195 .2232 .227 .2307 .2346 .2384 .2423 .2462 .2502 .2542 .2582 .2623 .2664 .2705 .2747 .2789 .2831 .2874 .2918 .2961 .3005 .305 .3095 .314 .3185 .3231 .3278 .3325 .3372 .3419 .3467 .3515 .3564 .3613 .3663 .3712 .3763 .3813 .3864 .3916 .3968 .402 .4072 .4125 .4179 .4233 .4287 .4342 .4397 .4452 .4508 .4564 .4621 .4678 .4735 .4793 .4851 .491 .4969 .5029 .5089 .5149 .521 .5271 .5333 .5395 .5457 .552 .5583 .5647 .5711 .5776 .5841 .5906 .5972 .6038 .6105 .6172 .624 .6308 .6376 .6445 .6514 .6584 .6654 .6724 .6795 .6867 .6939 .7011 .7084 .7157 .7231 .7305 .7379 .7454 .7529 .7605 .7682 .7758 .7835 .7913 .7991 .807 .8148 .8228 .8308 .8388 .8469 .855 .8632 .8714 .8796 .8879 .8963 .9047 .9131 .9216 .9301 .9387 .9473 .956 .9647 .9734 .9823 .9911 1; + +@function color-contrast($background, $color-contrast-dark: $color-contrast-dark, $color-contrast-light: $color-contrast-light, $min-contrast-ratio: $min-contrast-ratio) { + $foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black; + $max-ratio: 0; + $max-ratio-color: null; + + @each $color in $foregrounds { + $contrast-ratio: contrast-ratio($background, $color); + @if $contrast-ratio > $min-contrast-ratio { + @return $color; + } @else if $contrast-ratio > $max-ratio { + $max-ratio: $contrast-ratio; + $max-ratio-color: $color; + } + } + + @warn "Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}..."; + + @return $max-ratio-color; +} + +@function contrast-ratio($background, $foreground: $color-contrast-light) { + $l1: luminance($background); + $l2: luminance(opaque($background, $foreground)); + + @return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05)); +} + +// Return WCAG2.1 relative luminance +// See https://www.w3.org/TR/WCAG/#dfn-relative-luminance +// See https://www.w3.org/TR/WCAG/#dfn-contrast-ratio +@function luminance($color) { + $rgb: ( + "r": red($color), + "g": green($color), + "b": blue($color) + ); + + @each $name, $value in $rgb { + $value: if(divide($value, 255) < .04045, divide(divide($value, 255), 12.92), nth($_luminance-list, $value + 1)); + $rgb: map-merge($rgb, ($name: $value)); + } + + @return (map-get($rgb, "r") * .2126) + (map-get($rgb, "g") * .7152) + (map-get($rgb, "b") * .0722); +} + +// Return opaque color +// opaque(#fff, rgba(0, 0, 0, .5)) => #808080 +@function opaque($background, $foreground) { + @return mix(rgba($foreground, 1), $background, opacity($foreground) * 100%); +} + +// scss-docs-start color-functions +// Tint a color: mix a color with white +@function tint-color($color, $weight) { + @return mix(white, $color, $weight); +} + +// Shade a color: mix a color with black +@function shade-color($color, $weight) { + @return mix(black, $color, $weight); +} + +// Shade the color if the weight is positive, else tint it +@function shift-color($color, $weight) { + @return if($weight > 0, shade-color($color, $weight), tint-color($color, -$weight)); +} +// scss-docs-end color-functions + +// Return valid calc +@function add($value1, $value2, $return-calc: true) { + @if $value1 == null { + @return $value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 + $value2; + } + + @return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + unquote(" + ") + $value2); +} + +@function subtract($value1, $value2, $return-calc: true) { + @if $value1 == null and $value2 == null { + @return null; + } + + @if $value1 == null { + @return -$value2; + } + + @if $value2 == null { + @return $value1; + } + + @if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) { + @return $value1 - $value2; + } + + @if type-of($value2) != number { + $value2: unquote("(") + $value2 + unquote(")"); + } + + @return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + unquote(" - ") + $value2); +} + +@function divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + @if $dividend == 0 { + @return 0; + } + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + $remainder: $dividend; + $result: 0; + $factor: 10; + @while ($remainder > 0 and $precision >= 0) { + $quotient: 0; + @while ($remainder >= $divisor) { + $remainder: $remainder - $divisor; + $quotient: $quotient + 1; + } + $result: $result * 10 + $quotient; + $factor: $factor * .1; + $remainder: $remainder * 10; + $precision: $precision - 1; + @if ($precision < 0 and $remainder >= $divisor * 5) { + $result: $result + 1; + } + } + $result: $result * $factor * $sign; + $dividend-unit: unit($dividend); + $divisor-unit: unit($divisor); + $unit-map: ( + "px": 1px, + "rem": 1rem, + "em": 1em, + "%": 1% + ); + @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) { + $result: $result * map-get($unit-map, $dividend-unit); + } + @return $result; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss new file mode 100644 index 00000000..048f8009 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss @@ -0,0 +1,39 @@ +// Row +// +// Rows contain your columns. + +:root { + @each $name, $value in $grid-breakpoints { + --#{$prefix}breakpoint-#{$name}: #{$value}; + } +} + +@if $enable-grid-classes { + .row { + @include make-row(); + + > * { + @include make-col-ready(); + } + } +} + +@if $enable-cssgrid { + .grid { + display: grid; + grid-template-rows: repeat(var(--#{$prefix}rows, 1), 1fr); + grid-template-columns: repeat(var(--#{$prefix}columns, #{$grid-columns}), 1fr); + gap: var(--#{$prefix}gap, #{$grid-gutter-width}); + + @include make-cssgrid(); + } +} + + +// Columns +// +// Common styles for small and large grid columns + +@if $enable-grid-classes { + @include make-grid-columns(); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss new file mode 100644 index 00000000..13f2752c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss @@ -0,0 +1,12 @@ +@import "helpers/clearfix"; +@import "helpers/color-bg"; +@import "helpers/colored-links"; +@import "helpers/focus-ring"; +@import "helpers/icon-link"; +@import "helpers/ratio"; +@import "helpers/position"; +@import "helpers/stacks"; +@import "helpers/visually-hidden"; +@import "helpers/stretched-link"; +@import "helpers/text-truncation"; +@import "helpers/vr"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss new file mode 100644 index 00000000..3d6a1014 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss @@ -0,0 +1,42 @@ +// Responsive images (ensure images don't scale beyond their parents) +// +// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s. +// We previously tried the "images are responsive by default" approach in Bootstrap v2, +// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps) +// which weren't expecting the images within themselves to be involuntarily resized. +// See also https://github.com/twbs/bootstrap/issues/18178 +.img-fluid { + @include img-fluid(); +} + + +// Image thumbnails +.img-thumbnail { + padding: $thumbnail-padding; + background-color: $thumbnail-bg; + border: $thumbnail-border-width solid $thumbnail-border-color; + @include border-radius($thumbnail-border-radius); + @include box-shadow($thumbnail-box-shadow); + + // Keep them at most 100% wide + @include img-fluid(); +} + +// +// Figures +// + +.figure { + // Ensures the caption's text aligns with the image. + display: inline-block; +} + +.figure-img { + margin-bottom: $spacer * .5; + line-height: 1; +} + +.figure-caption { + @include font-size($figure-caption-font-size); + color: $figure-caption-color; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss new file mode 100644 index 00000000..455531ee --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss @@ -0,0 +1,197 @@ +// Base class +// +// Easily usable on <ul>, <ol>, or <div>. + +.list-group { + // scss-docs-start list-group-css-vars + --#{$prefix}list-group-color: #{$list-group-color}; + --#{$prefix}list-group-bg: #{$list-group-bg}; + --#{$prefix}list-group-border-color: #{$list-group-border-color}; + --#{$prefix}list-group-border-width: #{$list-group-border-width}; + --#{$prefix}list-group-border-radius: #{$list-group-border-radius}; + --#{$prefix}list-group-item-padding-x: #{$list-group-item-padding-x}; + --#{$prefix}list-group-item-padding-y: #{$list-group-item-padding-y}; + --#{$prefix}list-group-action-color: #{$list-group-action-color}; + --#{$prefix}list-group-action-hover-color: #{$list-group-action-hover-color}; + --#{$prefix}list-group-action-hover-bg: #{$list-group-hover-bg}; + --#{$prefix}list-group-action-active-color: #{$list-group-action-active-color}; + --#{$prefix}list-group-action-active-bg: #{$list-group-action-active-bg}; + --#{$prefix}list-group-disabled-color: #{$list-group-disabled-color}; + --#{$prefix}list-group-disabled-bg: #{$list-group-disabled-bg}; + --#{$prefix}list-group-active-color: #{$list-group-active-color}; + --#{$prefix}list-group-active-bg: #{$list-group-active-bg}; + --#{$prefix}list-group-active-border-color: #{$list-group-active-border-color}; + // scss-docs-end list-group-css-vars + + display: flex; + flex-direction: column; + + // No need to set list-style: none; since .list-group-item is block level + padding-left: 0; // reset padding because ul and ol + margin-bottom: 0; + @include border-radius(var(--#{$prefix}list-group-border-radius)); +} + +.list-group-numbered { + list-style-type: none; + counter-reset: section; + + > .list-group-item::before { + // Increments only this instance of the section counter + content: counters(section, ".") ". "; + counter-increment: section; + } +} + +// Interactive list items +// +// Use anchor or button elements instead of `li`s or `div`s to create interactive +// list items. Includes an extra `.active` modifier class for selected items. + +.list-group-item-action { + width: 100%; // For `<button>`s (anchors become 100% by default though) + color: var(--#{$prefix}list-group-action-color); + text-align: inherit; // For `<button>`s (anchors inherit) + + // Hover state + &:hover, + &:focus { + z-index: 1; // Place hover/focus items above their siblings for proper border styling + color: var(--#{$prefix}list-group-action-hover-color); + text-decoration: none; + background-color: var(--#{$prefix}list-group-action-hover-bg); + } + + &:active { + color: var(--#{$prefix}list-group-action-active-color); + background-color: var(--#{$prefix}list-group-action-active-bg); + } +} + +// Individual list items +// +// Use on `li`s or `div`s within the `.list-group` parent. + +.list-group-item { + position: relative; + display: block; + padding: var(--#{$prefix}list-group-item-padding-y) var(--#{$prefix}list-group-item-padding-x); + color: var(--#{$prefix}list-group-color); + text-decoration: if($link-decoration == none, null, none); + background-color: var(--#{$prefix}list-group-bg); + border: var(--#{$prefix}list-group-border-width) solid var(--#{$prefix}list-group-border-color); + + &:first-child { + @include border-top-radius(inherit); + } + + &:last-child { + @include border-bottom-radius(inherit); + } + + &.disabled, + &:disabled { + color: var(--#{$prefix}list-group-disabled-color); + pointer-events: none; + background-color: var(--#{$prefix}list-group-disabled-bg); + } + + // Include both here for `<a>`s and `<button>`s + &.active { + z-index: 2; // Place active items above their siblings for proper border styling + color: var(--#{$prefix}list-group-active-color); + background-color: var(--#{$prefix}list-group-active-bg); + border-color: var(--#{$prefix}list-group-active-border-color); + } + + // stylelint-disable-next-line scss/selector-no-redundant-nesting-selector + & + .list-group-item { + border-top-width: 0; + + &.active { + margin-top: calc(-1 * var(--#{$prefix}list-group-border-width)); // stylelint-disable-line function-disallowed-list + border-top-width: var(--#{$prefix}list-group-border-width); + } + } +} + +// Horizontal +// +// Change the layout of list group items from vertical (default) to horizontal. + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .list-group-horizontal#{$infix} { + flex-direction: row; + + > .list-group-item { + &:first-child:not(:last-child) { + @include border-bottom-start-radius(var(--#{$prefix}list-group-border-radius)); + @include border-top-end-radius(0); + } + + &:last-child:not(:first-child) { + @include border-top-end-radius(var(--#{$prefix}list-group-border-radius)); + @include border-bottom-start-radius(0); + } + + &.active { + margin-top: 0; + } + + + .list-group-item { + border-top-width: var(--#{$prefix}list-group-border-width); + border-left-width: 0; + + &.active { + margin-left: calc(-1 * var(--#{$prefix}list-group-border-width)); // stylelint-disable-line function-disallowed-list + border-left-width: var(--#{$prefix}list-group-border-width); + } + } + } + } + } +} + + +// Flush list items +// +// Remove borders and border-radius to keep list group items edge-to-edge. Most +// useful within other components (e.g., cards). + +.list-group-flush { + @include border-radius(0); + + > .list-group-item { + border-width: 0 0 var(--#{$prefix}list-group-border-width); + + &:last-child { + border-bottom-width: 0; + } + } +} + + +// scss-docs-start list-group-modifiers +// List group contextual variants +// +// Add modifier classes to change text and background color on individual items. +// Organizationally, this must come after the `:hover` states. + +@each $state in map-keys($theme-colors) { + .list-group-item-#{$state} { + --#{$prefix}list-group-color: var(--#{$prefix}#{$state}-text-emphasis); + --#{$prefix}list-group-bg: var(--#{$prefix}#{$state}-bg-subtle); + --#{$prefix}list-group-border-color: var(--#{$prefix}#{$state}-border-subtle); + --#{$prefix}list-group-action-hover-color: var(--#{$prefix}emphasis-color); + --#{$prefix}list-group-action-hover-bg: var(--#{$prefix}#{$state}-border-subtle); + --#{$prefix}list-group-action-active-color: var(--#{$prefix}emphasis-color); + --#{$prefix}list-group-action-active-bg: var(--#{$prefix}#{$state}-border-subtle); + --#{$prefix}list-group-active-color: var(--#{$prefix}#{$state}-bg-subtle); + --#{$prefix}list-group-active-bg: var(--#{$prefix}#{$state}-text-emphasis); + --#{$prefix}list-group-active-border-color: var(--#{$prefix}#{$state}-text-emphasis); + } +} +// scss-docs-end list-group-modifiers diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss new file mode 100644 index 00000000..68ee421c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss @@ -0,0 +1,174 @@ +// Re-assigned maps +// +// Placed here so that others can override the default Sass maps and see automatic updates to utilities and more. + +// scss-docs-start theme-colors-rgb +$theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default; +// scss-docs-end theme-colors-rgb + +// scss-docs-start theme-text-map +$theme-colors-text: ( + "primary": $primary-text-emphasis, + "secondary": $secondary-text-emphasis, + "success": $success-text-emphasis, + "info": $info-text-emphasis, + "warning": $warning-text-emphasis, + "danger": $danger-text-emphasis, + "light": $light-text-emphasis, + "dark": $dark-text-emphasis, +) !default; +// scss-docs-end theme-text-map + +// scss-docs-start theme-bg-subtle-map +$theme-colors-bg-subtle: ( + "primary": $primary-bg-subtle, + "secondary": $secondary-bg-subtle, + "success": $success-bg-subtle, + "info": $info-bg-subtle, + "warning": $warning-bg-subtle, + "danger": $danger-bg-subtle, + "light": $light-bg-subtle, + "dark": $dark-bg-subtle, +) !default; +// scss-docs-end theme-bg-subtle-map + +// scss-docs-start theme-border-subtle-map +$theme-colors-border-subtle: ( + "primary": $primary-border-subtle, + "secondary": $secondary-border-subtle, + "success": $success-border-subtle, + "info": $info-border-subtle, + "warning": $warning-border-subtle, + "danger": $danger-border-subtle, + "light": $light-border-subtle, + "dark": $dark-border-subtle, +) !default; +// scss-docs-end theme-border-subtle-map + +$theme-colors-text-dark: null !default; +$theme-colors-bg-subtle-dark: null !default; +$theme-colors-border-subtle-dark: null !default; + +@if $enable-dark-mode { + // scss-docs-start theme-text-dark-map + $theme-colors-text-dark: ( + "primary": $primary-text-emphasis-dark, + "secondary": $secondary-text-emphasis-dark, + "success": $success-text-emphasis-dark, + "info": $info-text-emphasis-dark, + "warning": $warning-text-emphasis-dark, + "danger": $danger-text-emphasis-dark, + "light": $light-text-emphasis-dark, + "dark": $dark-text-emphasis-dark, + ) !default; + // scss-docs-end theme-text-dark-map + + // scss-docs-start theme-bg-subtle-dark-map + $theme-colors-bg-subtle-dark: ( + "primary": $primary-bg-subtle-dark, + "secondary": $secondary-bg-subtle-dark, + "success": $success-bg-subtle-dark, + "info": $info-bg-subtle-dark, + "warning": $warning-bg-subtle-dark, + "danger": $danger-bg-subtle-dark, + "light": $light-bg-subtle-dark, + "dark": $dark-bg-subtle-dark, + ) !default; + // scss-docs-end theme-bg-subtle-dark-map + + // scss-docs-start theme-border-subtle-dark-map + $theme-colors-border-subtle-dark: ( + "primary": $primary-border-subtle-dark, + "secondary": $secondary-border-subtle-dark, + "success": $success-border-subtle-dark, + "info": $info-border-subtle-dark, + "warning": $warning-border-subtle-dark, + "danger": $danger-border-subtle-dark, + "light": $light-border-subtle-dark, + "dark": $dark-border-subtle-dark, + ) !default; + // scss-docs-end theme-border-subtle-dark-map +} + +// Utilities maps +// +// Extends the default `$theme-colors` maps to help create our utilities. + +// Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign. +// scss-docs-start utilities-colors +$utilities-colors: $theme-colors-rgb !default; +// scss-docs-end utilities-colors + +// scss-docs-start utilities-text-colors +$utilities-text: map-merge( + $utilities-colors, + ( + "black": to-rgb($black), + "white": to-rgb($white), + "body": to-rgb($body-color) + ) +) !default; +$utilities-text-colors: map-loop($utilities-text, rgba-css-var, "$key", "text") !default; + +$utilities-text-emphasis-colors: ( + "primary-emphasis": var(--#{$prefix}primary-text-emphasis), + "secondary-emphasis": var(--#{$prefix}secondary-text-emphasis), + "success-emphasis": var(--#{$prefix}success-text-emphasis), + "info-emphasis": var(--#{$prefix}info-text-emphasis), + "warning-emphasis": var(--#{$prefix}warning-text-emphasis), + "danger-emphasis": var(--#{$prefix}danger-text-emphasis), + "light-emphasis": var(--#{$prefix}light-text-emphasis), + "dark-emphasis": var(--#{$prefix}dark-text-emphasis) +) !default; +// scss-docs-end utilities-text-colors + +// scss-docs-start utilities-bg-colors +$utilities-bg: map-merge( + $utilities-colors, + ( + "black": to-rgb($black), + "white": to-rgb($white), + "body": to-rgb($body-bg) + ) +) !default; +$utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, "$key", "bg") !default; + +$utilities-bg-subtle: ( + "primary-subtle": var(--#{$prefix}primary-bg-subtle), + "secondary-subtle": var(--#{$prefix}secondary-bg-subtle), + "success-subtle": var(--#{$prefix}success-bg-subtle), + "info-subtle": var(--#{$prefix}info-bg-subtle), + "warning-subtle": var(--#{$prefix}warning-bg-subtle), + "danger-subtle": var(--#{$prefix}danger-bg-subtle), + "light-subtle": var(--#{$prefix}light-bg-subtle), + "dark-subtle": var(--#{$prefix}dark-bg-subtle) +) !default; +// scss-docs-end utilities-bg-colors + +// scss-docs-start utilities-border-colors +$utilities-border: map-merge( + $utilities-colors, + ( + "black": to-rgb($black), + "white": to-rgb($white) + ) +) !default; +$utilities-border-colors: map-loop($utilities-border, rgba-css-var, "$key", "border") !default; + +$utilities-border-subtle: ( + "primary-subtle": var(--#{$prefix}primary-border-subtle), + "secondary-subtle": var(--#{$prefix}secondary-border-subtle), + "success-subtle": var(--#{$prefix}success-border-subtle), + "info-subtle": var(--#{$prefix}info-border-subtle), + "warning-subtle": var(--#{$prefix}warning-border-subtle), + "danger-subtle": var(--#{$prefix}danger-border-subtle), + "light-subtle": var(--#{$prefix}light-border-subtle), + "dark-subtle": var(--#{$prefix}dark-border-subtle) +) !default; +// scss-docs-end utilities-border-colors + +$utilities-links-underline: map-loop($utilities-colors, rgba-css-var, "$key", "link-underline") !default; + +$negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default; + +$gutters: $spacers !default; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss new file mode 100644 index 00000000..e1e130b1 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss @@ -0,0 +1,42 @@ +// Toggles +// +// Used in conjunction with global variables to enable certain theme features. + +// Vendor +@import "vendor/rfs"; + +// Deprecate +@import "mixins/deprecate"; + +// Helpers +@import "mixins/breakpoints"; +@import "mixins/color-mode"; +@import "mixins/color-scheme"; +@import "mixins/image"; +@import "mixins/resize"; +@import "mixins/visually-hidden"; +@import "mixins/reset-text"; +@import "mixins/text-truncate"; + +// Utilities +@import "mixins/utilities"; + +// Components +@import "mixins/backdrop"; +@import "mixins/buttons"; +@import "mixins/caret"; +@import "mixins/pagination"; +@import "mixins/lists"; +@import "mixins/forms"; +@import "mixins/table-variants"; + +// Skins +@import "mixins/border-radius"; +@import "mixins/box-shadow"; +@import "mixins/gradients"; +@import "mixins/transition"; + +// Layout +@import "mixins/clearfix"; +@import "mixins/container"; +@import "mixins/grid"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss new file mode 100644 index 00000000..494db94e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss @@ -0,0 +1,236 @@ +// stylelint-disable function-disallowed-list + +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and stuff + + +// Container that the modal scrolls within +.modal { + // scss-docs-start modal-css-vars + --#{$prefix}modal-zindex: #{$zindex-modal}; + --#{$prefix}modal-width: #{$modal-md}; + --#{$prefix}modal-padding: #{$modal-inner-padding}; + --#{$prefix}modal-margin: #{$modal-dialog-margin}; + --#{$prefix}modal-color: #{$modal-content-color}; + --#{$prefix}modal-bg: #{$modal-content-bg}; + --#{$prefix}modal-border-color: #{$modal-content-border-color}; + --#{$prefix}modal-border-width: #{$modal-content-border-width}; + --#{$prefix}modal-border-radius: #{$modal-content-border-radius}; + --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-xs}; + --#{$prefix}modal-inner-border-radius: #{$modal-content-inner-border-radius}; + --#{$prefix}modal-header-padding-x: #{$modal-header-padding-x}; + --#{$prefix}modal-header-padding-y: #{$modal-header-padding-y}; + --#{$prefix}modal-header-padding: #{$modal-header-padding}; // Todo in v6: Split this padding into x and y + --#{$prefix}modal-header-border-color: #{$modal-header-border-color}; + --#{$prefix}modal-header-border-width: #{$modal-header-border-width}; + --#{$prefix}modal-title-line-height: #{$modal-title-line-height}; + --#{$prefix}modal-footer-gap: #{$modal-footer-margin-between}; + --#{$prefix}modal-footer-bg: #{$modal-footer-bg}; + --#{$prefix}modal-footer-border-color: #{$modal-footer-border-color}; + --#{$prefix}modal-footer-border-width: #{$modal-footer-border-width}; + // scss-docs-end modal-css-vars + + position: fixed; + top: 0; + left: 0; + z-index: var(--#{$prefix}modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a + // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342 + // See also https://github.com/twbs/bootstrap/issues/17695 +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: var(--#{$prefix}modal-margin); + // allow clicks to pass through for custom click handling to close modal + pointer-events: none; + + // When fading in the modal, animate it to slide down + .modal.fade & { + @include transition($modal-transition); + transform: $modal-fade-transform; + } + .modal.show & { + transform: $modal-show-transform; + } + + // When trying to close, animate focus to scale + .modal.modal-static & { + transform: $modal-scale-transform; + } +} + +.modal-dialog-scrollable { + height: calc(100% - var(--#{$prefix}modal-margin) * 2); + + .modal-content { + max-height: 100%; + overflow: hidden; + } + + .modal-body { + overflow-y: auto; + } +} + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--#{$prefix}modal-margin) * 2); +} + +// Actual modal +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog` + // counteract the pointer-events: none; in the .modal-dialog + color: var(--#{$prefix}modal-color); + pointer-events: auto; + background-color: var(--#{$prefix}modal-bg); + background-clip: padding-box; + border: var(--#{$prefix}modal-border-width) solid var(--#{$prefix}modal-border-color); + @include border-radius(var(--#{$prefix}modal-border-radius)); + @include box-shadow(var(--#{$prefix}modal-box-shadow)); + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + // scss-docs-start modal-backdrop-css-vars + --#{$prefix}backdrop-zindex: #{$zindex-modal-backdrop}; + --#{$prefix}backdrop-bg: #{$modal-backdrop-bg}; + --#{$prefix}backdrop-opacity: #{$modal-backdrop-opacity}; + // scss-docs-end modal-backdrop-css-vars + + @include overlay-backdrop(var(--#{$prefix}backdrop-zindex), var(--#{$prefix}backdrop-bg), var(--#{$prefix}backdrop-opacity)); +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + padding: var(--#{$prefix}modal-header-padding); + border-bottom: var(--#{$prefix}modal-header-border-width) solid var(--#{$prefix}modal-header-border-color); + @include border-top-radius(var(--#{$prefix}modal-inner-border-radius)); + + .btn-close { + padding: calc(var(--#{$prefix}modal-header-padding-y) * .5) calc(var(--#{$prefix}modal-header-padding-x) * .5); + margin: calc(-.5 * var(--#{$prefix}modal-header-padding-y)) calc(-.5 * var(--#{$prefix}modal-header-padding-x)) calc(-.5 * var(--#{$prefix}modal-header-padding-y)) auto; + } +} + +// Title text within header +.modal-title { + margin-bottom: 0; + line-height: var(--#{$prefix}modal-title-line-height); +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + // Enable `flex-grow: 1` so that the body take up as much space as possible + // when there should be a fixed height on `.modal-dialog`. + flex: 1 1 auto; + padding: var(--#{$prefix}modal-padding); +} + +// Footer (for actions) +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; // vertically center + justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items + padding: calc(var(--#{$prefix}modal-padding) - var(--#{$prefix}modal-footer-gap) * .5); + background-color: var(--#{$prefix}modal-footer-bg); + border-top: var(--#{$prefix}modal-footer-border-width) solid var(--#{$prefix}modal-footer-border-color); + @include border-bottom-radius(var(--#{$prefix}modal-inner-border-radius)); + + // Place margin between footer elements + // This solution is far from ideal because of the universal selector usage, + // but is needed to fix https://github.com/twbs/bootstrap/issues/24800 + > * { + margin: calc(var(--#{$prefix}modal-footer-gap) * .5); // Todo in v6: replace with gap on parent class + } +} + +// Scale up the modal +@include media-breakpoint-up(sm) { + .modal { + --#{$prefix}modal-margin: #{$modal-dialog-margin-y-sm-up}; + --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-sm-up}; + } + + // Automatically set modal's width for larger viewports + .modal-dialog { + max-width: var(--#{$prefix}modal-width); + margin-right: auto; + margin-left: auto; + } + + .modal-sm { + --#{$prefix}modal-width: #{$modal-sm}; + } +} + +@include media-breakpoint-up(lg) { + .modal-lg, + .modal-xl { + --#{$prefix}modal-width: #{$modal-lg}; + } +} + +@include media-breakpoint-up(xl) { + .modal-xl { + --#{$prefix}modal-width: #{$modal-xl}; + } +} + +// scss-docs-start modal-fullscreen-loop +@each $breakpoint in map-keys($grid-breakpoints) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + $postfix: if($infix != "", $infix + "-down", ""); + + @include media-breakpoint-down($breakpoint) { + .modal-fullscreen#{$postfix} { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + + .modal-content { + height: 100%; + border: 0; + @include border-radius(0); + } + + .modal-header, + .modal-footer { + @include border-radius(0); + } + + .modal-body { + overflow-y: auto; + } + } + } +} +// scss-docs-end modal-fullscreen-loop diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss new file mode 100644 index 00000000..ff073d36 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss @@ -0,0 +1,197 @@ +// Base class +// +// Kickstart any navigation component with a set of style resets. Works with +// `<nav>`s, `<ul>`s or `<ol>`s. + +.nav { + // scss-docs-start nav-css-vars + --#{$prefix}nav-link-padding-x: #{$nav-link-padding-x}; + --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y}; + @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size); + --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight}; + --#{$prefix}nav-link-color: #{$nav-link-color}; + --#{$prefix}nav-link-hover-color: #{$nav-link-hover-color}; + --#{$prefix}nav-link-disabled-color: #{$nav-link-disabled-color}; + // scss-docs-end nav-css-vars + + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: var(--#{$prefix}nav-link-padding-y) var(--#{$prefix}nav-link-padding-x); + @include font-size(var(--#{$prefix}nav-link-font-size)); + font-weight: var(--#{$prefix}nav-link-font-weight); + color: var(--#{$prefix}nav-link-color); + text-decoration: if($link-decoration == none, null, none); + background: none; + border: 0; + @include transition($nav-link-transition); + + &:hover, + &:focus { + color: var(--#{$prefix}nav-link-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + } + + &:focus-visible { + outline: 0; + box-shadow: $nav-link-focus-box-shadow; + } + + // Disabled state lightens text + &.disabled, + &:disabled { + color: var(--#{$prefix}nav-link-disabled-color); + pointer-events: none; + cursor: default; + } +} + +// +// Tabs +// + +.nav-tabs { + // scss-docs-start nav-tabs-css-vars + --#{$prefix}nav-tabs-border-width: #{$nav-tabs-border-width}; + --#{$prefix}nav-tabs-border-color: #{$nav-tabs-border-color}; + --#{$prefix}nav-tabs-border-radius: #{$nav-tabs-border-radius}; + --#{$prefix}nav-tabs-link-hover-border-color: #{$nav-tabs-link-hover-border-color}; + --#{$prefix}nav-tabs-link-active-color: #{$nav-tabs-link-active-color}; + --#{$prefix}nav-tabs-link-active-bg: #{$nav-tabs-link-active-bg}; + --#{$prefix}nav-tabs-link-active-border-color: #{$nav-tabs-link-active-border-color}; + // scss-docs-end nav-tabs-css-vars + + border-bottom: var(--#{$prefix}nav-tabs-border-width) solid var(--#{$prefix}nav-tabs-border-color); + + .nav-link { + margin-bottom: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list + border: var(--#{$prefix}nav-tabs-border-width) solid transparent; + @include border-top-radius(var(--#{$prefix}nav-tabs-border-radius)); + + &:hover, + &:focus { + // Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link + isolation: isolate; + border-color: var(--#{$prefix}nav-tabs-link-hover-border-color); + } + } + + .nav-link.active, + .nav-item.show .nav-link { + color: var(--#{$prefix}nav-tabs-link-active-color); + background-color: var(--#{$prefix}nav-tabs-link-active-bg); + border-color: var(--#{$prefix}nav-tabs-link-active-border-color); + } + + .dropdown-menu { + // Make dropdown border overlap tab border + margin-top: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list + // Remove the top rounded corners here since there is a hard edge above the menu + @include border-top-radius(0); + } +} + + +// +// Pills +// + +.nav-pills { + // scss-docs-start nav-pills-css-vars + --#{$prefix}nav-pills-border-radius: #{$nav-pills-border-radius}; + --#{$prefix}nav-pills-link-active-color: #{$nav-pills-link-active-color}; + --#{$prefix}nav-pills-link-active-bg: #{$nav-pills-link-active-bg}; + // scss-docs-end nav-pills-css-vars + + .nav-link { + @include border-radius(var(--#{$prefix}nav-pills-border-radius)); + } + + .nav-link.active, + .show > .nav-link { + color: var(--#{$prefix}nav-pills-link-active-color); + @include gradient-bg(var(--#{$prefix}nav-pills-link-active-bg)); + } +} + + +// +// Underline +// + +.nav-underline { + // scss-docs-start nav-underline-css-vars + --#{$prefix}nav-underline-gap: #{$nav-underline-gap}; + --#{$prefix}nav-underline-border-width: #{$nav-underline-border-width}; + --#{$prefix}nav-underline-link-active-color: #{$nav-underline-link-active-color}; + // scss-docs-end nav-underline-css-vars + + gap: var(--#{$prefix}nav-underline-gap); + + .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--#{$prefix}nav-underline-border-width) solid transparent; + + &:hover, + &:focus { + border-bottom-color: currentcolor; + } + } + + .nav-link.active, + .show > .nav-link { + font-weight: $font-weight-bold; + color: var(--#{$prefix}nav-underline-link-active-color); + border-bottom-color: currentcolor; + } +} + + +// +// Justified variants +// + +.nav-fill { + > .nav-link, + .nav-item { + flex: 1 1 auto; + text-align: center; + } +} + +.nav-justified { + > .nav-link, + .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; + } +} + +.nav-fill, +.nav-justified { + .nav-item .nav-link { + width: 100%; // Make sure button will grow + } +} + + +// Tabbable tabs +// +// Hide tabbable panes to start, show them when `.active` + +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss new file mode 100644 index 00000000..71619382 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss @@ -0,0 +1,289 @@ +// Navbar +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + // scss-docs-start navbar-css-vars + --#{$prefix}navbar-padding-x: #{if($navbar-padding-x == null, 0, $navbar-padding-x)}; + --#{$prefix}navbar-padding-y: #{$navbar-padding-y}; + --#{$prefix}navbar-color: #{$navbar-light-color}; + --#{$prefix}navbar-hover-color: #{$navbar-light-hover-color}; + --#{$prefix}navbar-disabled-color: #{$navbar-light-disabled-color}; + --#{$prefix}navbar-active-color: #{$navbar-light-active-color}; + --#{$prefix}navbar-brand-padding-y: #{$navbar-brand-padding-y}; + --#{$prefix}navbar-brand-margin-end: #{$navbar-brand-margin-end}; + --#{$prefix}navbar-brand-font-size: #{$navbar-brand-font-size}; + --#{$prefix}navbar-brand-color: #{$navbar-light-brand-color}; + --#{$prefix}navbar-brand-hover-color: #{$navbar-light-brand-hover-color}; + --#{$prefix}navbar-nav-link-padding-x: #{$navbar-nav-link-padding-x}; + --#{$prefix}navbar-toggler-padding-y: #{$navbar-toggler-padding-y}; + --#{$prefix}navbar-toggler-padding-x: #{$navbar-toggler-padding-x}; + --#{$prefix}navbar-toggler-font-size: #{$navbar-toggler-font-size}; + --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-light-toggler-icon-bg)}; + --#{$prefix}navbar-toggler-border-color: #{$navbar-light-toggler-border-color}; + --#{$prefix}navbar-toggler-border-radius: #{$navbar-toggler-border-radius}; + --#{$prefix}navbar-toggler-focus-width: #{$navbar-toggler-focus-width}; + --#{$prefix}navbar-toggler-transition: #{$navbar-toggler-transition}; + // scss-docs-end navbar-css-vars + + position: relative; + display: flex; + flex-wrap: wrap; // allow us to do the line break for collapsing content + align-items: center; + justify-content: space-between; // space out brand from logo + padding: var(--#{$prefix}navbar-padding-y) var(--#{$prefix}navbar-padding-x); + @include gradient-bg(); + + // Because flex properties aren't inherited, we need to redeclare these first + // few properties so that content nested within behave properly. + // The `flex-wrap` property is inherited to simplify the expanded navbars + %container-flex-properties { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; + } + + > .container, + > .container-fluid { + @extend %container-flex-properties; + } + + @each $breakpoint, $container-max-width in $container-max-widths { + > .container#{breakpoint-infix($breakpoint, $container-max-widths)} { + @extend %container-flex-properties; + } + } +} + + +// Navbar brand +// +// Used for brand, project, or site names. + +.navbar-brand { + padding-top: var(--#{$prefix}navbar-brand-padding-y); + padding-bottom: var(--#{$prefix}navbar-brand-padding-y); + margin-right: var(--#{$prefix}navbar-brand-margin-end); + @include font-size(var(--#{$prefix}navbar-brand-font-size)); + color: var(--#{$prefix}navbar-brand-color); + text-decoration: if($link-decoration == none, null, none); + white-space: nowrap; + + &:hover, + &:focus { + color: var(--#{$prefix}navbar-brand-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + } +} + + +// Navbar nav +// +// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`). + +.navbar-nav { + // scss-docs-start navbar-nav-css-vars + --#{$prefix}nav-link-padding-x: 0; + --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y}; + @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size); + --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight}; + --#{$prefix}nav-link-color: var(--#{$prefix}navbar-color); + --#{$prefix}nav-link-hover-color: var(--#{$prefix}navbar-hover-color); + --#{$prefix}nav-link-disabled-color: var(--#{$prefix}navbar-disabled-color); + // scss-docs-end navbar-nav-css-vars + + display: flex; + flex-direction: column; // cannot use `inherit` to get the `.navbar`s value + padding-left: 0; + margin-bottom: 0; + list-style: none; + + .nav-link { + &.active, + &.show { + color: var(--#{$prefix}navbar-active-color); + } + } + + .dropdown-menu { + position: static; + } +} + + +// Navbar text +// +// + +.navbar-text { + padding-top: $nav-link-padding-y; + padding-bottom: $nav-link-padding-y; + color: var(--#{$prefix}navbar-color); + + a, + a:hover, + a:focus { + color: var(--#{$prefix}navbar-active-color); + } +} + + +// Responsive navbar +// +// Custom styles for responsive collapsing and toggling of navbar contents. +// Powered by the collapse Bootstrap JavaScript plugin. + +// When collapsed, prevent the toggleable navbar contents from appearing in +// the default flexbox row orientation. Requires the use of `flex-wrap: wrap` +// on the `.navbar` parent. +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + // For always expanded or extra full navbars, ensure content aligns itself + // properly vertically. Can be easily overridden with flex utilities. + align-items: center; +} + +// Button for toggling the navbar when in its collapsed state +.navbar-toggler { + padding: var(--#{$prefix}navbar-toggler-padding-y) var(--#{$prefix}navbar-toggler-padding-x); + @include font-size(var(--#{$prefix}navbar-toggler-font-size)); + line-height: 1; + color: var(--#{$prefix}navbar-color); + background-color: transparent; // remove default button style + border: var(--#{$prefix}border-width) solid var(--#{$prefix}navbar-toggler-border-color); // remove default button style + @include border-radius(var(--#{$prefix}navbar-toggler-border-radius)); + @include transition(var(--#{$prefix}navbar-toggler-transition)); + + &:hover { + text-decoration: none; + } + + &:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--#{$prefix}navbar-toggler-focus-width); + } +} + +// Keep as a separate element so folks can easily override it with another icon +// or image file as needed. +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--#{$prefix}navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.navbar-nav-scroll { + max-height: var(--#{$prefix}scroll-height, 75vh); + overflow-y: auto; +} + +// scss-docs-start navbar-expand-loop +// Generate series of `.navbar-expand-*` responsive classes for configuring +// where your navbar collapses. +.navbar-expand { + @each $breakpoint in map-keys($grid-breakpoints) { + $next: breakpoint-next($breakpoint, $grid-breakpoints); + $infix: breakpoint-infix($next, $grid-breakpoints); + + // stylelint-disable-next-line scss/selector-no-union-class-name + &#{$infix} { + @include media-breakpoint-up($next) { + flex-wrap: nowrap; + justify-content: flex-start; + + .navbar-nav { + flex-direction: row; + + .dropdown-menu { + position: absolute; + } + + .nav-link { + padding-right: var(--#{$prefix}navbar-nav-link-padding-x); + padding-left: var(--#{$prefix}navbar-nav-link-padding-x); + } + } + + .navbar-nav-scroll { + overflow: visible; + } + + .navbar-collapse { + display: flex !important; // stylelint-disable-line declaration-no-important + flex-basis: auto; + } + + .navbar-toggler { + display: none; + } + + .offcanvas { + // stylelint-disable declaration-no-important + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + @include box-shadow(none); + @include transition(none); + // stylelint-enable declaration-no-important + + .offcanvas-header { + display: none; + } + + .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } + } + } + } + } +} +// scss-docs-end navbar-expand-loop + +// Navbar themes +// +// Styles for switching between navbars with light or dark background. + +.navbar-light { + @include deprecate("`.navbar-light`", "v5.2.0", "v6.0.0", true); +} + +.navbar-dark, +.navbar[data-bs-theme="dark"] { + // scss-docs-start navbar-dark-css-vars + --#{$prefix}navbar-color: #{$navbar-dark-color}; + --#{$prefix}navbar-hover-color: #{$navbar-dark-hover-color}; + --#{$prefix}navbar-disabled-color: #{$navbar-dark-disabled-color}; + --#{$prefix}navbar-active-color: #{$navbar-dark-active-color}; + --#{$prefix}navbar-brand-color: #{$navbar-dark-brand-color}; + --#{$prefix}navbar-brand-hover-color: #{$navbar-dark-brand-hover-color}; + --#{$prefix}navbar-toggler-border-color: #{$navbar-dark-toggler-border-color}; + --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)}; + // scss-docs-end navbar-dark-css-vars +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .navbar-toggler-icon { + --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)}; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss new file mode 100644 index 00000000..eb2c97ab --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss @@ -0,0 +1,143 @@ +// stylelint-disable function-disallowed-list + +%offcanvas-css-vars { + // scss-docs-start offcanvas-css-vars + --#{$prefix}offcanvas-zindex: #{$zindex-offcanvas}; + --#{$prefix}offcanvas-width: #{$offcanvas-horizontal-width}; + --#{$prefix}offcanvas-height: #{$offcanvas-vertical-height}; + --#{$prefix}offcanvas-padding-x: #{$offcanvas-padding-x}; + --#{$prefix}offcanvas-padding-y: #{$offcanvas-padding-y}; + --#{$prefix}offcanvas-color: #{$offcanvas-color}; + --#{$prefix}offcanvas-bg: #{$offcanvas-bg-color}; + --#{$prefix}offcanvas-border-width: #{$offcanvas-border-width}; + --#{$prefix}offcanvas-border-color: #{$offcanvas-border-color}; + --#{$prefix}offcanvas-box-shadow: #{$offcanvas-box-shadow}; + --#{$prefix}offcanvas-transition: #{transform $offcanvas-transition-duration ease-in-out}; + --#{$prefix}offcanvas-title-line-height: #{$offcanvas-title-line-height}; + // scss-docs-end offcanvas-css-vars +} + +@each $breakpoint in map-keys($grid-breakpoints) { + $next: breakpoint-next($breakpoint, $grid-breakpoints); + $infix: breakpoint-infix($next, $grid-breakpoints); + + .offcanvas#{$infix} { + @extend %offcanvas-css-vars; + } +} + +@each $breakpoint in map-keys($grid-breakpoints) { + $next: breakpoint-next($breakpoint, $grid-breakpoints); + $infix: breakpoint-infix($next, $grid-breakpoints); + + .offcanvas#{$infix} { + @include media-breakpoint-down($next) { + position: fixed; + bottom: 0; + z-index: var(--#{$prefix}offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--#{$prefix}offcanvas-color); + visibility: hidden; + background-color: var(--#{$prefix}offcanvas-bg); + background-clip: padding-box; + outline: 0; + @include box-shadow(var(--#{$prefix}offcanvas-box-shadow)); + @include transition(var(--#{$prefix}offcanvas-transition)); + + &.offcanvas-start { + top: 0; + left: 0; + width: var(--#{$prefix}offcanvas-width); + border-right: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color); + transform: translateX(-100%); + } + + &.offcanvas-end { + top: 0; + right: 0; + width: var(--#{$prefix}offcanvas-width); + border-left: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color); + transform: translateX(100%); + } + + &.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--#{$prefix}offcanvas-height); + max-height: 100%; + border-bottom: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color); + transform: translateY(-100%); + } + + &.offcanvas-bottom { + right: 0; + left: 0; + height: var(--#{$prefix}offcanvas-height); + max-height: 100%; + border-top: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color); + transform: translateY(100%); + } + + &.showing, + &.show:not(.hiding) { + transform: none; + } + + &.showing, + &.hiding, + &.show { + visibility: visible; + } + } + + @if not ($infix == "") { + @include media-breakpoint-up($next) { + --#{$prefix}offcanvas-height: auto; + --#{$prefix}offcanvas-border-width: 0; + background-color: transparent !important; // stylelint-disable-line declaration-no-important + + .offcanvas-header { + display: none; + } + + .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + // Reset `background-color` in case `.bg-*` classes are used in offcanvas + background-color: transparent !important; // stylelint-disable-line declaration-no-important + } + } + } + } +} + +.offcanvas-backdrop { + @include overlay-backdrop($zindex-offcanvas-backdrop, $offcanvas-backdrop-bg, $offcanvas-backdrop-opacity); +} + +.offcanvas-header { + display: flex; + align-items: center; + padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x); + + .btn-close { + padding: calc(var(--#{$prefix}offcanvas-padding-y) * .5) calc(var(--#{$prefix}offcanvas-padding-x) * .5); + margin: calc(-.5 * var(--#{$prefix}offcanvas-padding-y)) calc(-.5 * var(--#{$prefix}offcanvas-padding-x)) calc(-.5 * var(--#{$prefix}offcanvas-padding-y)) auto; + } +} + +.offcanvas-title { + margin-bottom: 0; + line-height: var(--#{$prefix}offcanvas-title-line-height); +} + +.offcanvas-body { + flex-grow: 1; + padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x); + overflow-y: auto; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss new file mode 100644 index 00000000..f275a62e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss @@ -0,0 +1,109 @@ +.pagination { + // scss-docs-start pagination-css-vars + --#{$prefix}pagination-padding-x: #{$pagination-padding-x}; + --#{$prefix}pagination-padding-y: #{$pagination-padding-y}; + @include rfs($pagination-font-size, --#{$prefix}pagination-font-size); + --#{$prefix}pagination-color: #{$pagination-color}; + --#{$prefix}pagination-bg: #{$pagination-bg}; + --#{$prefix}pagination-border-width: #{$pagination-border-width}; + --#{$prefix}pagination-border-color: #{$pagination-border-color}; + --#{$prefix}pagination-border-radius: #{$pagination-border-radius}; + --#{$prefix}pagination-hover-color: #{$pagination-hover-color}; + --#{$prefix}pagination-hover-bg: #{$pagination-hover-bg}; + --#{$prefix}pagination-hover-border-color: #{$pagination-hover-border-color}; + --#{$prefix}pagination-focus-color: #{$pagination-focus-color}; + --#{$prefix}pagination-focus-bg: #{$pagination-focus-bg}; + --#{$prefix}pagination-focus-box-shadow: #{$pagination-focus-box-shadow}; + --#{$prefix}pagination-active-color: #{$pagination-active-color}; + --#{$prefix}pagination-active-bg: #{$pagination-active-bg}; + --#{$prefix}pagination-active-border-color: #{$pagination-active-border-color}; + --#{$prefix}pagination-disabled-color: #{$pagination-disabled-color}; + --#{$prefix}pagination-disabled-bg: #{$pagination-disabled-bg}; + --#{$prefix}pagination-disabled-border-color: #{$pagination-disabled-border-color}; + // scss-docs-end pagination-css-vars + + display: flex; + @include list-unstyled(); +} + +.page-link { + position: relative; + display: block; + padding: var(--#{$prefix}pagination-padding-y) var(--#{$prefix}pagination-padding-x); + @include font-size(var(--#{$prefix}pagination-font-size)); + color: var(--#{$prefix}pagination-color); + text-decoration: if($link-decoration == none, null, none); + background-color: var(--#{$prefix}pagination-bg); + border: var(--#{$prefix}pagination-border-width) solid var(--#{$prefix}pagination-border-color); + @include transition($pagination-transition); + + &:hover { + z-index: 2; + color: var(--#{$prefix}pagination-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + background-color: var(--#{$prefix}pagination-hover-bg); + border-color: var(--#{$prefix}pagination-hover-border-color); + } + + &:focus { + z-index: 3; + color: var(--#{$prefix}pagination-focus-color); + background-color: var(--#{$prefix}pagination-focus-bg); + outline: $pagination-focus-outline; + box-shadow: var(--#{$prefix}pagination-focus-box-shadow); + } + + &.active, + .active > & { + z-index: 3; + color: var(--#{$prefix}pagination-active-color); + @include gradient-bg(var(--#{$prefix}pagination-active-bg)); + border-color: var(--#{$prefix}pagination-active-border-color); + } + + &.disabled, + .disabled > & { + color: var(--#{$prefix}pagination-disabled-color); + pointer-events: none; + background-color: var(--#{$prefix}pagination-disabled-bg); + border-color: var(--#{$prefix}pagination-disabled-border-color); + } +} + +.page-item { + &:not(:first-child) .page-link { + margin-left: $pagination-margin-start; + } + + @if $pagination-margin-start == calc(#{$pagination-border-width} * -1) { + &:first-child { + .page-link { + @include border-start-radius(var(--#{$prefix}pagination-border-radius)); + } + } + + &:last-child { + .page-link { + @include border-end-radius(var(--#{$prefix}pagination-border-radius)); + } + } + } @else { + // Add border-radius to all pageLinks in case they have left margin + .page-link { + @include border-radius(var(--#{$prefix}pagination-border-radius)); + } + } +} + + +// +// Sizing +// + +.pagination-lg { + @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $pagination-border-radius-lg); +} + +.pagination-sm { + @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $pagination-border-radius-sm); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss new file mode 100644 index 00000000..6e32e1cd --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss @@ -0,0 +1,51 @@ +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: $placeholder-opacity-max; + + &.btn::before { + display: inline-block; + content: ""; + } +} + +// Sizing +.placeholder-xs { + min-height: .6em; +} + +.placeholder-sm { + min-height: .8em; +} + +.placeholder-lg { + min-height: 1.2em; +} + +// Animation +.placeholder-glow { + .placeholder { + animation: placeholder-glow 2s ease-in-out infinite; + } +} + +@keyframes placeholder-glow { + 50% { + opacity: $placeholder-opacity-min; + } +} + +.placeholder-wave { + mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%); + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite; +} + +@keyframes placeholder-wave { + 100% { + mask-position: -200% 0%; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss new file mode 100644 index 00000000..7b69f623 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss @@ -0,0 +1,196 @@ +.popover { + // scss-docs-start popover-css-vars + --#{$prefix}popover-zindex: #{$zindex-popover}; + --#{$prefix}popover-max-width: #{$popover-max-width}; + @include rfs($popover-font-size, --#{$prefix}popover-font-size); + --#{$prefix}popover-bg: #{$popover-bg}; + --#{$prefix}popover-border-width: #{$popover-border-width}; + --#{$prefix}popover-border-color: #{$popover-border-color}; + --#{$prefix}popover-border-radius: #{$popover-border-radius}; + --#{$prefix}popover-inner-border-radius: #{$popover-inner-border-radius}; + --#{$prefix}popover-box-shadow: #{$popover-box-shadow}; + --#{$prefix}popover-header-padding-x: #{$popover-header-padding-x}; + --#{$prefix}popover-header-padding-y: #{$popover-header-padding-y}; + @include rfs($popover-header-font-size, --#{$prefix}popover-header-font-size); + --#{$prefix}popover-header-color: #{$popover-header-color}; + --#{$prefix}popover-header-bg: #{$popover-header-bg}; + --#{$prefix}popover-body-padding-x: #{$popover-body-padding-x}; + --#{$prefix}popover-body-padding-y: #{$popover-body-padding-y}; + --#{$prefix}popover-body-color: #{$popover-body-color}; + --#{$prefix}popover-arrow-width: #{$popover-arrow-width}; + --#{$prefix}popover-arrow-height: #{$popover-arrow-height}; + --#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color); + // scss-docs-end popover-css-vars + + z-index: var(--#{$prefix}popover-zindex); + display: block; + max-width: var(--#{$prefix}popover-max-width); + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text(); + @include font-size(var(--#{$prefix}popover-font-size)); + // Allow breaking very long words so they don't overflow the popover's bounds + word-wrap: break-word; + background-color: var(--#{$prefix}popover-bg); + background-clip: padding-box; + border: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color); + @include border-radius(var(--#{$prefix}popover-border-radius)); + @include box-shadow(var(--#{$prefix}popover-box-shadow)); + + .popover-arrow { + display: block; + width: var(--#{$prefix}popover-arrow-width); + height: var(--#{$prefix}popover-arrow-height); + + &::before, + &::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; + } + } +} + +.bs-popover-top { + > .popover-arrow { + bottom: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list + + &::before, + &::after { + border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list + } + + &::before { + bottom: 0; + border-top-color: var(--#{$prefix}popover-arrow-border); + } + + &::after { + bottom: var(--#{$prefix}popover-border-width); + border-top-color: var(--#{$prefix}popover-bg); + } + } +} + +/* rtl:begin:ignore */ +.bs-popover-end { + > .popover-arrow { + left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list + width: var(--#{$prefix}popover-arrow-height); + height: var(--#{$prefix}popover-arrow-width); + + &::before, + &::after { + border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list + } + + &::before { + left: 0; + border-right-color: var(--#{$prefix}popover-arrow-border); + } + + &::after { + left: var(--#{$prefix}popover-border-width); + border-right-color: var(--#{$prefix}popover-bg); + } + } +} + +/* rtl:end:ignore */ + +.bs-popover-bottom { + > .popover-arrow { + top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list + + &::before, + &::after { + border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list + } + + &::before { + top: 0; + border-bottom-color: var(--#{$prefix}popover-arrow-border); + } + + &::after { + top: var(--#{$prefix}popover-border-width); + border-bottom-color: var(--#{$prefix}popover-bg); + } + } + + // This will remove the popover-header's border just below the arrow + .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--#{$prefix}popover-arrow-width); + margin-left: calc(-.5 * var(--#{$prefix}popover-arrow-width)); // stylelint-disable-line function-disallowed-list + content: ""; + border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg); + } +} + +/* rtl:begin:ignore */ +.bs-popover-start { + > .popover-arrow { + right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list + width: var(--#{$prefix}popover-arrow-height); + height: var(--#{$prefix}popover-arrow-width); + + &::before, + &::after { + border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list + } + + &::before { + right: 0; + border-left-color: var(--#{$prefix}popover-arrow-border); + } + + &::after { + right: var(--#{$prefix}popover-border-width); + border-left-color: var(--#{$prefix}popover-bg); + } + } +} + +/* rtl:end:ignore */ + +.bs-popover-auto { + &[data-popper-placement^="top"] { + @extend .bs-popover-top; + } + &[data-popper-placement^="right"] { + @extend .bs-popover-end; + } + &[data-popper-placement^="bottom"] { + @extend .bs-popover-bottom; + } + &[data-popper-placement^="left"] { + @extend .bs-popover-start; + } +} + +// Offset the popover to account for the popover arrow +.popover-header { + padding: var(--#{$prefix}popover-header-padding-y) var(--#{$prefix}popover-header-padding-x); + margin-bottom: 0; // Reset the default from Reboot + @include font-size(var(--#{$prefix}popover-header-font-size)); + color: var(--#{$prefix}popover-header-color); + background-color: var(--#{$prefix}popover-header-bg); + border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color); + @include border-top-radius(var(--#{$prefix}popover-inner-border-radius)); + + &:empty { + display: none; + } +} + +.popover-body { + padding: var(--#{$prefix}popover-body-padding-y) var(--#{$prefix}popover-body-padding-x); + color: var(--#{$prefix}popover-body-color); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss new file mode 100644 index 00000000..148c3815 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss @@ -0,0 +1,68 @@ +// Disable animation if transitions are disabled + +// scss-docs-start progress-keyframes +@if $enable-transitions { + @keyframes progress-bar-stripes { + 0% { background-position-x: $progress-height; } + } +} +// scss-docs-end progress-keyframes + +.progress, +.progress-stacked { + // scss-docs-start progress-css-vars + --#{$prefix}progress-height: #{$progress-height}; + @include rfs($progress-font-size, --#{$prefix}progress-font-size); + --#{$prefix}progress-bg: #{$progress-bg}; + --#{$prefix}progress-border-radius: #{$progress-border-radius}; + --#{$prefix}progress-box-shadow: #{$progress-box-shadow}; + --#{$prefix}progress-bar-color: #{$progress-bar-color}; + --#{$prefix}progress-bar-bg: #{$progress-bar-bg}; + --#{$prefix}progress-bar-transition: #{$progress-bar-transition}; + // scss-docs-end progress-css-vars + + display: flex; + height: var(--#{$prefix}progress-height); + overflow: hidden; // force rounded corners by cropping it + @include font-size(var(--#{$prefix}progress-font-size)); + background-color: var(--#{$prefix}progress-bg); + @include border-radius(var(--#{$prefix}progress-border-radius)); + @include box-shadow(var(--#{$prefix}progress-box-shadow)); +} + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--#{$prefix}progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--#{$prefix}progress-bar-bg); + @include transition(var(--#{$prefix}progress-bar-transition)); +} + +.progress-bar-striped { + @include gradient-striped(); + background-size: var(--#{$prefix}progress-height) var(--#{$prefix}progress-height); +} + +.progress-stacked > .progress { + overflow: visible; +} + +.progress-stacked > .progress > .progress-bar { + width: 100%; +} + +@if $enable-transitions { + .progress-bar-animated { + animation: $progress-bar-animation-timing progress-bar-stripes; + + @if $enable-reduced-motion { + @media (prefers-reduced-motion: reduce) { + animation: none; + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss new file mode 100644 index 00000000..18791753 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss @@ -0,0 +1,611 @@ +// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix + + +// Reboot +// +// Normalization of HTML elements, manually forked from Normalize.css to remove +// styles targeting irrelevant browsers while applying new styles. +// +// Normalize is licensed MIT. https://github.com/necolas/normalize.css + + +// Document +// +// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`. + +*, +*::before, +*::after { + box-sizing: border-box; +} + + +// Root +// +// Ability to the value of the root font sizes, affecting the value of `rem`. +// null by default, thus nothing is generated. + +:root { + @if $font-size-root != null { + @include font-size(var(--#{$prefix}root-font-size)); + } + + @if $enable-smooth-scroll { + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; + } + } +} + + +// Body +// +// 1. Remove the margin in all browsers. +// 2. As a best practice, apply a default `background-color`. +// 3. Prevent adjustments of font size after orientation changes in iOS. +// 4. Change the default tap highlight to be completely transparent in iOS. + +// scss-docs-start reboot-body-rules +body { + margin: 0; // 1 + font-family: var(--#{$prefix}body-font-family); + @include font-size(var(--#{$prefix}body-font-size)); + font-weight: var(--#{$prefix}body-font-weight); + line-height: var(--#{$prefix}body-line-height); + color: var(--#{$prefix}body-color); + text-align: var(--#{$prefix}body-text-align); + background-color: var(--#{$prefix}body-bg); // 2 + -webkit-text-size-adjust: 100%; // 3 + -webkit-tap-highlight-color: rgba($black, 0); // 4 +} +// scss-docs-end reboot-body-rules + + +// Content grouping +// +// 1. Reset Firefox's gray color + +hr { + margin: $hr-margin-y 0; + color: $hr-color; // 1 + border: 0; + border-top: $hr-border-width solid $hr-border-color; + opacity: $hr-opacity; +} + + +// Typography +// +// 1. Remove top margins from headings +// By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top +// margin for easier control within type scales as it avoids margin collapsing. + +%heading { + margin-top: 0; // 1 + margin-bottom: $headings-margin-bottom; + font-family: $headings-font-family; + font-style: $headings-font-style; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: var(--#{$prefix}heading-color); +} + +h1 { + @extend %heading; + @include font-size($h1-font-size); +} + +h2 { + @extend %heading; + @include font-size($h2-font-size); +} + +h3 { + @extend %heading; + @include font-size($h3-font-size); +} + +h4 { + @extend %heading; + @include font-size($h4-font-size); +} + +h5 { + @extend %heading; + @include font-size($h5-font-size); +} + +h6 { + @extend %heading; + @include font-size($h6-font-size); +} + + +// Reset margins on paragraphs +// +// Similarly, the top margin on `<p>`s get reset. However, we also reset the +// bottom margin to use `rem` units instead of `em`. + +p { + margin-top: 0; + margin-bottom: $paragraph-margin-bottom; +} + + +// Abbreviations +// +// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari. +// 2. Add explicit cursor to indicate changed behavior. +// 3. Prevent the text-decoration to be skipped. + +abbr[title] { + text-decoration: underline dotted; // 1 + cursor: help; // 2 + text-decoration-skip-ink: none; // 3 +} + + +// Address + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + + +// Lists + +ol, +ul { + padding-left: 2rem; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: $dt-font-weight; +} + +// 1. Undo browser default + +dd { + margin-bottom: .5rem; + margin-left: 0; // 1 +} + + +// Blockquote + +blockquote { + margin: 0 0 1rem; +} + + +// Strong +// +// Add the correct font weight in Chrome, Edge, and Safari + +b, +strong { + font-weight: $font-weight-bolder; +} + + +// Small +// +// Add the correct font size in all browsers + +small { + @include font-size($small-font-size); +} + + +// Mark + +mark { + padding: $mark-padding; + color: var(--#{$prefix}highlight-color); + background-color: var(--#{$prefix}highlight-bg); +} + + +// Sub and Sup +// +// Prevent `sub` and `sup` elements from affecting the line height in +// all browsers. + +sub, +sup { + position: relative; + @include font-size($sub-sup-font-size); + line-height: 0; + vertical-align: baseline; +} + +sub { bottom: -.25em; } +sup { top: -.5em; } + + +// Links + +a { + color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, 1)); + text-decoration: $link-decoration; + + &:hover { + --#{$prefix}link-color-rgb: var(--#{$prefix}link-hover-color-rgb); + text-decoration: $link-hover-decoration; + } +} + +// And undo these styles for placeholder links/named anchors (without href). +// It would be more straightforward to just use a[href] in previous block, but that +// causes specificity issues in many other styles that are too complex to fix. +// See https://github.com/twbs/bootstrap/issues/19402 + +a:not([href]):not([class]) { + &, + &:hover { + color: inherit; + text-decoration: none; + } +} + + +// Code + +pre, +code, +kbd, +samp { + font-family: $font-family-code; + @include font-size(1em); // Correct the odd `em` font sizing in all browsers. +} + +// 1. Remove browser default top margin +// 2. Reset browser default of `1em` to use `rem`s +// 3. Don't allow content to break outside + +pre { + display: block; + margin-top: 0; // 1 + margin-bottom: 1rem; // 2 + overflow: auto; // 3 + @include font-size($code-font-size); + color: $pre-color; + + // Account for some code outputs that place code tags in pre tags + code { + @include font-size(inherit); + color: inherit; + word-break: normal; + } +} + +code { + @include font-size($code-font-size); + color: var(--#{$prefix}code-color); + word-wrap: break-word; + + // Streamline the style when inside anchors to avoid broken underline and more + a > & { + color: inherit; + } +} + +kbd { + padding: $kbd-padding-y $kbd-padding-x; + @include font-size($kbd-font-size); + color: $kbd-color; + background-color: $kbd-bg; + @include border-radius($border-radius-sm); + + kbd { + padding: 0; + @include font-size(1em); + font-weight: $nested-kbd-font-weight; + } +} + + +// Figures +// +// Apply a consistent margin strategy (matches our type styles). + +figure { + margin: 0 0 1rem; +} + + +// Images and content + +img, +svg { + vertical-align: middle; +} + + +// Tables +// +// Prevent double borders + +table { + caption-side: bottom; + border-collapse: collapse; +} + +caption { + padding-top: $table-cell-padding-y; + padding-bottom: $table-cell-padding-y; + color: $table-caption-color; + text-align: left; +} + +// 1. Removes font-weight bold by inheriting +// 2. Matches default `<td>` alignment by inheriting `text-align`. +// 3. Fix alignment for Safari + +th { + font-weight: $table-th-font-weight; // 1 + text-align: inherit; // 2 + text-align: -webkit-match-parent; // 3 +} + +thead, +tbody, +tfoot, +tr, +td, +th { + border-color: inherit; + border-style: solid; + border-width: 0; +} + + +// Forms +// +// 1. Allow labels to use `margin` for spacing. + +label { + display: inline-block; // 1 +} + +// Remove the default `border-radius` that macOS Chrome adds. +// See https://github.com/twbs/bootstrap/issues/24093 + +button { + // stylelint-disable-next-line property-disallowed-list + border-radius: 0; +} + +// Explicitly remove focus outline in Chromium when it shouldn't be +// visible (e.g. as result of mouse click or touch tap). It already +// should be doing this automatically, but seems to currently be +// confused and applies its very visible two-tone outline anyway. + +button:focus:not(:focus-visible) { + outline: 0; +} + +// 1. Remove the margin in Firefox and Safari + +input, +button, +select, +optgroup, +textarea { + margin: 0; // 1 + font-family: inherit; + @include font-size(inherit); + line-height: inherit; +} + +// Remove the inheritance of text transform in Firefox +button, +select { + text-transform: none; +} +// Set the cursor for non-`<button>` buttons +// +// Details at https://github.com/twbs/bootstrap/pull/30562 +[role="button"] { + cursor: pointer; +} + +select { + // Remove the inheritance of word-wrap in Safari. + // See https://github.com/twbs/bootstrap/issues/24990 + word-wrap: normal; + + // Undo the opacity change from Chrome + &:disabled { + opacity: 1; + } +} + +// Remove the dropdown arrow only from text type inputs built with datalists in Chrome. +// See https://stackoverflow.com/a/54997118 + +[list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator { + display: none !important; +} + +// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` +// controls in Android 4. +// 2. Correct the inability to style clickable types in iOS and Safari. +// 3. Opinionated: add "hand" cursor to non-disabled button elements. + +button, +[type="button"], // 1 +[type="reset"], +[type="submit"] { + -webkit-appearance: button; // 2 + + @if $enable-button-pointers { + &:not(:disabled) { + cursor: pointer; // 3 + } + } +} + +// Remove inner border and padding from Firefox, but don't restore the outline like Normalize. + +::-moz-focus-inner { + padding: 0; + border-style: none; +} + +// 1. Textareas should really only resize vertically so they don't break their (horizontal) containers. + +textarea { + resize: vertical; // 1 +} + +// 1. Browsers set a default `min-width: min-content;` on fieldsets, +// unlike e.g. `<div>`s, which have `min-width: 0;` by default. +// So we reset that to ensure fieldsets behave more like a standard block element. +// See https://github.com/twbs/bootstrap/issues/12359 +// and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements +// 2. Reset the default outline behavior of fieldsets so they don't affect page layout. + +fieldset { + min-width: 0; // 1 + padding: 0; // 2 + margin: 0; // 2 + border: 0; // 2 +} + +// 1. By using `float: left`, the legend will behave like a block element. +// This way the border of a fieldset wraps around the legend if present. +// 2. Fix wrapping bug. +// See https://github.com/twbs/bootstrap/issues/29712 + +legend { + float: left; // 1 + width: 100%; + padding: 0; + margin-bottom: $legend-margin-bottom; + @include font-size($legend-font-size); + font-weight: $legend-font-weight; + line-height: inherit; + + + * { + clear: left; // 2 + } +} + +// Fix height of inputs with a type of datetime-local, date, month, week, or time +// See https://github.com/twbs/bootstrap/issues/18842 + +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-year-field { + padding: 0; +} + +::-webkit-inner-spin-button { + height: auto; +} + +// 1. This overrides the extra rounded corners on search inputs in iOS so that our +// `.form-control` class can properly style them. Note that this cannot simply +// be added to `.form-control` as it's not specific enough. For details, see +// https://github.com/twbs/bootstrap/issues/11586. +// 2. Correct the outline style in Safari. + +[type="search"] { + -webkit-appearance: textfield; // 1 + outline-offset: -2px; // 2 +} + +// 1. A few input types should stay LTR +// See https://rtlstyling.com/posts/rtl-styling#form-inputs +// 2. RTL only output +// See https://rtlcss.com/learn/usage-guide/control-directives/#raw + +/* rtl:raw: +[type="tel"], +[type="url"], +[type="email"], +[type="number"] { + direction: ltr; +} +*/ + +// Remove the inner padding in Chrome and Safari on macOS. + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +// Remove padding around color pickers in webkit browsers + +::-webkit-color-swatch-wrapper { + padding: 0; +} + + +// 1. Inherit font family and line height for file input buttons +// 2. Correct the inability to style clickable types in iOS and Safari. + +::file-selector-button { + font: inherit; // 1 + -webkit-appearance: button; // 2 +} + +// Correct element displays + +output { + display: inline-block; +} + +// Remove border from iframe + +iframe { + border: 0; +} + +// Summary +// +// 1. Add the correct display in all browsers + +summary { + display: list-item; // 1 + cursor: pointer; +} + + +// Progress +// +// Add the correct vertical alignment in Chrome, Firefox, and Opera. + +progress { + vertical-align: baseline; +} + + +// Hidden attribute +// +// Always hide an element with the `hidden` HTML attribute. + +[hidden] { + display: none !important; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss new file mode 100644 index 00000000..becddf14 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss @@ -0,0 +1,187 @@ +:root, +[data-bs-theme="light"] { + // Note: Custom variable values only support SassScript inside `#{}`. + + // Colors + // + // Generate palettes for full colors, grays, and theme colors. + + @each $color, $value in $colors { + --#{$prefix}#{$color}: #{$value}; + } + + @each $color, $value in $grays { + --#{$prefix}gray-#{$color}: #{$value}; + } + + @each $color, $value in $theme-colors { + --#{$prefix}#{$color}: #{$value}; + } + + @each $color, $value in $theme-colors-rgb { + --#{$prefix}#{$color}-rgb: #{$value}; + } + + @each $color, $value in $theme-colors-text { + --#{$prefix}#{$color}-text-emphasis: #{$value}; + } + + @each $color, $value in $theme-colors-bg-subtle { + --#{$prefix}#{$color}-bg-subtle: #{$value}; + } + + @each $color, $value in $theme-colors-border-subtle { + --#{$prefix}#{$color}-border-subtle: #{$value}; + } + + --#{$prefix}white-rgb: #{to-rgb($white)}; + --#{$prefix}black-rgb: #{to-rgb($black)}; + + // Fonts + + // Note: Use `inspect` for lists so that quoted items keep the quotes. + // See https://github.com/sass/sass/issues/2383#issuecomment-336349172 + --#{$prefix}font-sans-serif: #{inspect($font-family-sans-serif)}; + --#{$prefix}font-monospace: #{inspect($font-family-monospace)}; + --#{$prefix}gradient: #{$gradient}; + + // Root and body + // scss-docs-start root-body-variables + @if $font-size-root != null { + --#{$prefix}root-font-size: #{$font-size-root}; + } + --#{$prefix}body-font-family: #{inspect($font-family-base)}; + @include rfs($font-size-base, --#{$prefix}body-font-size); + --#{$prefix}body-font-weight: #{$font-weight-base}; + --#{$prefix}body-line-height: #{$line-height-base}; + @if $body-text-align != null { + --#{$prefix}body-text-align: #{$body-text-align}; + } + + --#{$prefix}body-color: #{$body-color}; + --#{$prefix}body-color-rgb: #{to-rgb($body-color)}; + --#{$prefix}body-bg: #{$body-bg}; + --#{$prefix}body-bg-rgb: #{to-rgb($body-bg)}; + + --#{$prefix}emphasis-color: #{$body-emphasis-color}; + --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color)}; + + --#{$prefix}secondary-color: #{$body-secondary-color}; + --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color)}; + --#{$prefix}secondary-bg: #{$body-secondary-bg}; + --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg)}; + + --#{$prefix}tertiary-color: #{$body-tertiary-color}; + --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color)}; + --#{$prefix}tertiary-bg: #{$body-tertiary-bg}; + --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg)}; + // scss-docs-end root-body-variables + + --#{$prefix}heading-color: #{$headings-color}; + + --#{$prefix}link-color: #{$link-color}; + --#{$prefix}link-color-rgb: #{to-rgb($link-color)}; + --#{$prefix}link-decoration: #{$link-decoration}; + + --#{$prefix}link-hover-color: #{$link-hover-color}; + --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color)}; + + @if $link-hover-decoration != null { + --#{$prefix}link-hover-decoration: #{$link-hover-decoration}; + } + + --#{$prefix}code-color: #{$code-color}; + --#{$prefix}highlight-color: #{$mark-color}; + --#{$prefix}highlight-bg: #{$mark-bg}; + + // scss-docs-start root-border-var + --#{$prefix}border-width: #{$border-width}; + --#{$prefix}border-style: #{$border-style}; + --#{$prefix}border-color: #{$border-color}; + --#{$prefix}border-color-translucent: #{$border-color-translucent}; + + --#{$prefix}border-radius: #{$border-radius}; + --#{$prefix}border-radius-sm: #{$border-radius-sm}; + --#{$prefix}border-radius-lg: #{$border-radius-lg}; + --#{$prefix}border-radius-xl: #{$border-radius-xl}; + --#{$prefix}border-radius-xxl: #{$border-radius-xxl}; + --#{$prefix}border-radius-2xl: var(--#{$prefix}border-radius-xxl); // Deprecated in v5.3.0 for consistency + --#{$prefix}border-radius-pill: #{$border-radius-pill}; + // scss-docs-end root-border-var + + --#{$prefix}box-shadow: #{$box-shadow}; + --#{$prefix}box-shadow-sm: #{$box-shadow-sm}; + --#{$prefix}box-shadow-lg: #{$box-shadow-lg}; + --#{$prefix}box-shadow-inset: #{$box-shadow-inset}; + + // Focus styles + // scss-docs-start root-focus-variables + --#{$prefix}focus-ring-width: #{$focus-ring-width}; + --#{$prefix}focus-ring-opacity: #{$focus-ring-opacity}; + --#{$prefix}focus-ring-color: #{$focus-ring-color}; + // scss-docs-end root-focus-variables + + // scss-docs-start root-form-validation-variables + --#{$prefix}form-valid-color: #{$form-valid-color}; + --#{$prefix}form-valid-border-color: #{$form-valid-border-color}; + --#{$prefix}form-invalid-color: #{$form-invalid-color}; + --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color}; + // scss-docs-end root-form-validation-variables +} + +@if $enable-dark-mode { + @include color-mode(dark, true) { + color-scheme: dark; + + // scss-docs-start root-dark-mode-vars + --#{$prefix}body-color: #{$body-color-dark}; + --#{$prefix}body-color-rgb: #{to-rgb($body-color-dark)}; + --#{$prefix}body-bg: #{$body-bg-dark}; + --#{$prefix}body-bg-rgb: #{to-rgb($body-bg-dark)}; + + --#{$prefix}emphasis-color: #{$body-emphasis-color-dark}; + --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color-dark)}; + + --#{$prefix}secondary-color: #{$body-secondary-color-dark}; + --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color-dark)}; + --#{$prefix}secondary-bg: #{$body-secondary-bg-dark}; + --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg-dark)}; + + --#{$prefix}tertiary-color: #{$body-tertiary-color-dark}; + --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color-dark)}; + --#{$prefix}tertiary-bg: #{$body-tertiary-bg-dark}; + --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg-dark)}; + + @each $color, $value in $theme-colors-text-dark { + --#{$prefix}#{$color}-text-emphasis: #{$value}; + } + + @each $color, $value in $theme-colors-bg-subtle-dark { + --#{$prefix}#{$color}-bg-subtle: #{$value}; + } + + @each $color, $value in $theme-colors-border-subtle-dark { + --#{$prefix}#{$color}-border-subtle: #{$value}; + } + + --#{$prefix}heading-color: #{$headings-color-dark}; + + --#{$prefix}link-color: #{$link-color-dark}; + --#{$prefix}link-hover-color: #{$link-hover-color-dark}; + --#{$prefix}link-color-rgb: #{to-rgb($link-color-dark)}; + --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color-dark)}; + + --#{$prefix}code-color: #{$code-color-dark}; + --#{$prefix}highlight-color: #{$mark-color-dark}; + --#{$prefix}highlight-bg: #{$mark-bg-dark}; + + --#{$prefix}border-color: #{$border-color-dark}; + --#{$prefix}border-color-translucent: #{$border-color-translucent-dark}; + + --#{$prefix}form-valid-color: #{$form-valid-color-dark}; + --#{$prefix}form-valid-border-color: #{$form-valid-border-color-dark}; + --#{$prefix}form-invalid-color: #{$form-invalid-color-dark}; + --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color-dark}; + // scss-docs-end root-dark-mode-vars + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss new file mode 100644 index 00000000..ec847320 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss @@ -0,0 +1,85 @@ +// +// Rotating border +// + +.spinner-grow, +.spinner-border { + display: inline-block; + width: var(--#{$prefix}spinner-width); + height: var(--#{$prefix}spinner-height); + vertical-align: var(--#{$prefix}spinner-vertical-align); + // stylelint-disable-next-line property-disallowed-list + border-radius: 50%; + animation: var(--#{$prefix}spinner-animation-speed) linear infinite var(--#{$prefix}spinner-animation-name); +} + +// scss-docs-start spinner-border-keyframes +@keyframes spinner-border { + to { transform: rotate(360deg) #{"/* rtl:ignore */"}; } +} +// scss-docs-end spinner-border-keyframes + +.spinner-border { + // scss-docs-start spinner-border-css-vars + --#{$prefix}spinner-width: #{$spinner-width}; + --#{$prefix}spinner-height: #{$spinner-height}; + --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align}; + --#{$prefix}spinner-border-width: #{$spinner-border-width}; + --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed}; + --#{$prefix}spinner-animation-name: spinner-border; + // scss-docs-end spinner-border-css-vars + + border: var(--#{$prefix}spinner-border-width) solid currentcolor; + border-right-color: transparent; +} + +.spinner-border-sm { + // scss-docs-start spinner-border-sm-css-vars + --#{$prefix}spinner-width: #{$spinner-width-sm}; + --#{$prefix}spinner-height: #{$spinner-height-sm}; + --#{$prefix}spinner-border-width: #{$spinner-border-width-sm}; + // scss-docs-end spinner-border-sm-css-vars +} + +// +// Growing circle +// + +// scss-docs-start spinner-grow-keyframes +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +// scss-docs-end spinner-grow-keyframes + +.spinner-grow { + // scss-docs-start spinner-grow-css-vars + --#{$prefix}spinner-width: #{$spinner-width}; + --#{$prefix}spinner-height: #{$spinner-height}; + --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align}; + --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed}; + --#{$prefix}spinner-animation-name: spinner-grow; + // scss-docs-end spinner-grow-css-vars + + background-color: currentcolor; + opacity: 0; +} + +.spinner-grow-sm { + --#{$prefix}spinner-width: #{$spinner-width-sm}; + --#{$prefix}spinner-height: #{$spinner-height-sm}; +} + +@if $enable-reduced-motion { + @media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed * 2}; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss new file mode 100644 index 00000000..276521a3 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss @@ -0,0 +1,171 @@ +// +// Basic Bootstrap table +// + +.table { + // Reset needed for nesting tables + --#{$prefix}table-color-type: initial; + --#{$prefix}table-bg-type: initial; + --#{$prefix}table-color-state: initial; + --#{$prefix}table-bg-state: initial; + // End of reset + --#{$prefix}table-color: #{$table-color}; + --#{$prefix}table-bg: #{$table-bg}; + --#{$prefix}table-border-color: #{$table-border-color}; + --#{$prefix}table-accent-bg: #{$table-accent-bg}; + --#{$prefix}table-striped-color: #{$table-striped-color}; + --#{$prefix}table-striped-bg: #{$table-striped-bg}; + --#{$prefix}table-active-color: #{$table-active-color}; + --#{$prefix}table-active-bg: #{$table-active-bg}; + --#{$prefix}table-hover-color: #{$table-hover-color}; + --#{$prefix}table-hover-bg: #{$table-hover-bg}; + + width: 100%; + margin-bottom: $spacer; + vertical-align: $table-cell-vertical-align; + border-color: var(--#{$prefix}table-border-color); + + // Target th & td + // We need the child combinator to prevent styles leaking to nested tables which doesn't have a `.table` class. + // We use the universal selectors here to simplify the selector (else we would need 6 different selectors). + // Another advantage is that this generates less code and makes the selector less specific making it easier to override. + // stylelint-disable-next-line selector-max-universal + > :not(caption) > * > * { + padding: $table-cell-padding-y $table-cell-padding-x; + // Following the precept of cascades: https://codepen.io/miriamsuzanne/full/vYNgodb + color: var(--#{$prefix}table-color-state, var(--#{$prefix}table-color-type, var(--#{$prefix}table-color))); + background-color: var(--#{$prefix}table-bg); + border-bottom-width: $table-border-width; + box-shadow: inset 0 0 0 9999px var(--#{$prefix}table-bg-state, var(--#{$prefix}table-bg-type, var(--#{$prefix}table-accent-bg))); + } + + > tbody { + vertical-align: inherit; + } + + > thead { + vertical-align: bottom; + } +} + +.table-group-divider { + border-top: calc(#{$table-border-width} * 2) solid $table-group-separator-color; // stylelint-disable-line function-disallowed-list +} + +// +// Change placement of captions with a class +// + +.caption-top { + caption-side: top; +} + + +// +// Condensed table w/ half padding +// + +.table-sm { + // stylelint-disable-next-line selector-max-universal + > :not(caption) > * > * { + padding: $table-cell-padding-y-sm $table-cell-padding-x-sm; + } +} + + +// Border versions +// +// Add or remove borders all around the table and between all the columns. +// +// When borders are added on all sides of the cells, the corners can render odd when +// these borders do not have the same color or if they are semi-transparent. +// Therefore we add top and border bottoms to the `tr`s and left and right borders +// to the `td`s or `th`s + +.table-bordered { + > :not(caption) > * { + border-width: $table-border-width 0; + + // stylelint-disable-next-line selector-max-universal + > * { + border-width: 0 $table-border-width; + } + } +} + +.table-borderless { + // stylelint-disable-next-line selector-max-universal + > :not(caption) > * > * { + border-bottom-width: 0; + } + + > :not(:first-child) { + border-top-width: 0; + } +} + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +// For rows +.table-striped { + > tbody > tr:nth-of-type(#{$table-striped-order}) > * { + --#{$prefix}table-color-type: var(--#{$prefix}table-striped-color); + --#{$prefix}table-bg-type: var(--#{$prefix}table-striped-bg); + } +} + +// For columns +.table-striped-columns { + > :not(caption) > tr > :nth-child(#{$table-striped-columns-order}) { + --#{$prefix}table-color-type: var(--#{$prefix}table-striped-color); + --#{$prefix}table-bg-type: var(--#{$prefix}table-striped-bg); + } +} + +// Active table +// +// The `.table-active` class can be added to highlight rows or cells + +.table-active { + --#{$prefix}table-color-state: var(--#{$prefix}table-active-color); + --#{$prefix}table-bg-state: var(--#{$prefix}table-active-bg); +} + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + > tbody > tr:hover > * { + --#{$prefix}table-color-state: var(--#{$prefix}table-hover-color); + --#{$prefix}table-bg-state: var(--#{$prefix}table-hover-bg); + } +} + + +// Table variants +// +// Table variants set the table cell backgrounds, border colors +// and the colors of the striped, hovered & active tables + +@each $color, $value in $table-variants { + @include table-variant($color, $value); +} + +// Responsive tables +// +// Generate series of `.table-responsive-*` classes for configuring the screen +// size of where your table will overflow. + +@each $breakpoint in map-keys($grid-breakpoints) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + @include media-breakpoint-down($breakpoint) { + .table-responsive#{$infix} { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss new file mode 100644 index 00000000..2ce378d5 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss @@ -0,0 +1,73 @@ +.toast { + // scss-docs-start toast-css-vars + --#{$prefix}toast-zindex: #{$zindex-toast}; + --#{$prefix}toast-padding-x: #{$toast-padding-x}; + --#{$prefix}toast-padding-y: #{$toast-padding-y}; + --#{$prefix}toast-spacing: #{$toast-spacing}; + --#{$prefix}toast-max-width: #{$toast-max-width}; + @include rfs($toast-font-size, --#{$prefix}toast-font-size); + --#{$prefix}toast-color: #{$toast-color}; + --#{$prefix}toast-bg: #{$toast-background-color}; + --#{$prefix}toast-border-width: #{$toast-border-width}; + --#{$prefix}toast-border-color: #{$toast-border-color}; + --#{$prefix}toast-border-radius: #{$toast-border-radius}; + --#{$prefix}toast-box-shadow: #{$toast-box-shadow}; + --#{$prefix}toast-header-color: #{$toast-header-color}; + --#{$prefix}toast-header-bg: #{$toast-header-background-color}; + --#{$prefix}toast-header-border-color: #{$toast-header-border-color}; + // scss-docs-end toast-css-vars + + width: var(--#{$prefix}toast-max-width); + max-width: 100%; + @include font-size(var(--#{$prefix}toast-font-size)); + color: var(--#{$prefix}toast-color); + pointer-events: auto; + background-color: var(--#{$prefix}toast-bg); + background-clip: padding-box; + border: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-border-color); + box-shadow: var(--#{$prefix}toast-box-shadow); + @include border-radius(var(--#{$prefix}toast-border-radius)); + + &.showing { + opacity: 0; + } + + &:not(.show) { + display: none; + } +} + +.toast-container { + --#{$prefix}toast-zindex: #{$zindex-toast}; + + position: absolute; + z-index: var(--#{$prefix}toast-zindex); + width: max-content; + max-width: 100%; + pointer-events: none; + + > :not(:last-child) { + margin-bottom: var(--#{$prefix}toast-spacing); + } +} + +.toast-header { + display: flex; + align-items: center; + padding: var(--#{$prefix}toast-padding-y) var(--#{$prefix}toast-padding-x); + color: var(--#{$prefix}toast-header-color); + background-color: var(--#{$prefix}toast-header-bg); + background-clip: padding-box; + border-bottom: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-header-border-color); + @include border-top-radius(calc(var(--#{$prefix}toast-border-radius) - var(--#{$prefix}toast-border-width))); + + .btn-close { + margin-right: calc(-.5 * var(--#{$prefix}toast-padding-x)); // stylelint-disable-line function-disallowed-list + margin-left: var(--#{$prefix}toast-padding-x); + } +} + +.toast-body { + padding: var(--#{$prefix}toast-padding-x); + word-wrap: break-word; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss new file mode 100644 index 00000000..85de90f5 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss @@ -0,0 +1,119 @@ +// Base class +.tooltip { + // scss-docs-start tooltip-css-vars + --#{$prefix}tooltip-zindex: #{$zindex-tooltip}; + --#{$prefix}tooltip-max-width: #{$tooltip-max-width}; + --#{$prefix}tooltip-padding-x: #{$tooltip-padding-x}; + --#{$prefix}tooltip-padding-y: #{$tooltip-padding-y}; + --#{$prefix}tooltip-margin: #{$tooltip-margin}; + @include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size); + --#{$prefix}tooltip-color: #{$tooltip-color}; + --#{$prefix}tooltip-bg: #{$tooltip-bg}; + --#{$prefix}tooltip-border-radius: #{$tooltip-border-radius}; + --#{$prefix}tooltip-opacity: #{$tooltip-opacity}; + --#{$prefix}tooltip-arrow-width: #{$tooltip-arrow-width}; + --#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height}; + // scss-docs-end tooltip-css-vars + + z-index: var(--#{$prefix}tooltip-zindex); + display: block; + margin: var(--#{$prefix}tooltip-margin); + @include deprecate("`$tooltip-margin`", "v5", "v5.x", true); + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + @include reset-text(); + @include font-size(var(--#{$prefix}tooltip-font-size)); + // Allow breaking very long words so they don't overflow the tooltip's bounds + word-wrap: break-word; + opacity: 0; + + &.show { opacity: var(--#{$prefix}tooltip-opacity); } + + .tooltip-arrow { + display: block; + width: var(--#{$prefix}tooltip-arrow-width); + height: var(--#{$prefix}tooltip-arrow-height); + + &::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; + } + } +} + +.bs-tooltip-top .tooltip-arrow { + bottom: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list + + &::before { + top: -1px; + border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list + border-top-color: var(--#{$prefix}tooltip-bg); + } +} + +/* rtl:begin:ignore */ +.bs-tooltip-end .tooltip-arrow { + left: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list + width: var(--#{$prefix}tooltip-arrow-height); + height: var(--#{$prefix}tooltip-arrow-width); + + &::before { + right: -1px; + border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list + border-right-color: var(--#{$prefix}tooltip-bg); + } +} + +/* rtl:end:ignore */ + +.bs-tooltip-bottom .tooltip-arrow { + top: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list + + &::before { + bottom: -1px; + border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list + border-bottom-color: var(--#{$prefix}tooltip-bg); + } +} + +/* rtl:begin:ignore */ +.bs-tooltip-start .tooltip-arrow { + right: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list + width: var(--#{$prefix}tooltip-arrow-height); + height: var(--#{$prefix}tooltip-arrow-width); + + &::before { + left: -1px; + border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list + border-left-color: var(--#{$prefix}tooltip-bg); + } +} + +/* rtl:end:ignore */ + +.bs-tooltip-auto { + &[data-popper-placement^="top"] { + @extend .bs-tooltip-top; + } + &[data-popper-placement^="right"] { + @extend .bs-tooltip-end; + } + &[data-popper-placement^="bottom"] { + @extend .bs-tooltip-bottom; + } + &[data-popper-placement^="left"] { + @extend .bs-tooltip-start; + } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: var(--#{$prefix}tooltip-max-width); + padding: var(--#{$prefix}tooltip-padding-y) var(--#{$prefix}tooltip-padding-x); + color: var(--#{$prefix}tooltip-color); + text-align: center; + background-color: var(--#{$prefix}tooltip-bg); + @include border-radius(var(--#{$prefix}tooltip-border-radius)); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss new file mode 100644 index 00000000..bfb26aa8 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss @@ -0,0 +1,27 @@ +.fade { + @include transition($transition-fade); + + &:not(.show) { + opacity: 0; + } +} + +// scss-docs-start collapse-classes +.collapse { + &:not(.show) { + display: none; + } +} + +.collapsing { + height: 0; + overflow: hidden; + @include transition($transition-collapse); + + &.collapse-horizontal { + width: 0; + height: auto; + @include transition($transition-collapse-width); + } +} +// scss-docs-end collapse-classes diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss new file mode 100644 index 00000000..37d64bf8 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss @@ -0,0 +1,106 @@ +// +// Headings +// +.h1 { + @extend h1; +} + +.h2 { + @extend h2; +} + +.h3 { + @extend h3; +} + +.h4 { + @extend h4; +} + +.h5 { + @extend h5; +} + +.h6 { + @extend h6; +} + + +.lead { + @include font-size($lead-font-size); + font-weight: $lead-font-weight; +} + +// Type display classes +@each $display, $font-size in $display-font-sizes { + .display-#{$display} { + @include font-size($font-size); + font-family: $display-font-family; + font-style: $display-font-style; + font-weight: $display-font-weight; + line-height: $display-line-height; + } +} + +// +// Emphasis +// +.small { + @extend small; +} + +.mark { + @extend mark; +} + +// +// Lists +// + +.list-unstyled { + @include list-unstyled(); +} + +// Inline turns list items into inline-block +.list-inline { + @include list-unstyled(); +} +.list-inline-item { + display: inline-block; + + &:not(:last-child) { + margin-right: $list-inline-padding; + } +} + + +// +// Misc +// + +// Builds on `abbr` +.initialism { + @include font-size($initialism-font-size); + text-transform: uppercase; +} + +// Blockquotes +.blockquote { + margin-bottom: $blockquote-margin-y; + @include font-size($blockquote-font-size); + + > :last-child { + margin-bottom: 0; + } +} + +.blockquote-footer { + margin-top: -$blockquote-margin-y; + margin-bottom: $blockquote-margin-y; + @include font-size($blockquote-footer-font-size); + color: $blockquote-footer-color; + + &::before { + content: "\2014\00A0"; // em dash, nbsp + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss new file mode 100644 index 00000000..696f906e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss @@ -0,0 +1,806 @@ +// Utilities + +$utilities: () !default; +// stylelint-disable-next-line scss/dollar-variable-default +$utilities: map-merge( + ( + // scss-docs-start utils-vertical-align + "align": ( + property: vertical-align, + class: align, + values: baseline top middle bottom text-bottom text-top + ), + // scss-docs-end utils-vertical-align + // scss-docs-start utils-float + "float": ( + responsive: true, + property: float, + values: ( + start: left, + end: right, + none: none, + ) + ), + // scss-docs-end utils-float + // Object Fit utilities + // scss-docs-start utils-object-fit + "object-fit": ( + responsive: true, + property: object-fit, + values: ( + contain: contain, + cover: cover, + fill: fill, + scale: scale-down, + none: none, + ) + ), + // scss-docs-end utils-object-fit + // Opacity utilities + // scss-docs-start utils-opacity + "opacity": ( + property: opacity, + values: ( + 0: 0, + 25: .25, + 50: .5, + 75: .75, + 100: 1, + ) + ), + // scss-docs-end utils-opacity + // scss-docs-start utils-overflow + "overflow": ( + property: overflow, + values: auto hidden visible scroll, + ), + "overflow-x": ( + property: overflow-x, + values: auto hidden visible scroll, + ), + "overflow-y": ( + property: overflow-y, + values: auto hidden visible scroll, + ), + // scss-docs-end utils-overflow + // scss-docs-start utils-display + "display": ( + responsive: true, + print: true, + property: display, + class: d, + values: inline inline-block block grid inline-grid table table-row table-cell flex inline-flex none + ), + // scss-docs-end utils-display + // scss-docs-start utils-shadow + "shadow": ( + property: box-shadow, + class: shadow, + values: ( + null: var(--#{$prefix}box-shadow), + sm: var(--#{$prefix}box-shadow-sm), + lg: var(--#{$prefix}box-shadow-lg), + none: none, + ) + ), + // scss-docs-end utils-shadow + // scss-docs-start utils-focus-ring + "focus-ring": ( + css-var: true, + css-variable-name: focus-ring-color, + class: focus-ring, + values: map-loop($theme-colors-rgb, rgba-css-var, "$key", "focus-ring") + ), + // scss-docs-end utils-focus-ring + // scss-docs-start utils-position + "position": ( + property: position, + values: static relative absolute fixed sticky + ), + "top": ( + property: top, + values: $position-values + ), + "bottom": ( + property: bottom, + values: $position-values + ), + "start": ( + property: left, + class: start, + values: $position-values + ), + "end": ( + property: right, + class: end, + values: $position-values + ), + "translate-middle": ( + property: transform, + class: translate-middle, + values: ( + null: translate(-50%, -50%), + x: translateX(-50%), + y: translateY(-50%), + ) + ), + // scss-docs-end utils-position + // scss-docs-start utils-borders + "border": ( + property: border, + values: ( + null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color), + 0: 0, + ) + ), + "border-top": ( + property: border-top, + values: ( + null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color), + 0: 0, + ) + ), + "border-end": ( + property: border-right, + class: border-end, + values: ( + null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color), + 0: 0, + ) + ), + "border-bottom": ( + property: border-bottom, + values: ( + null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color), + 0: 0, + ) + ), + "border-start": ( + property: border-left, + class: border-start, + values: ( + null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color), + 0: 0, + ) + ), + "border-color": ( + property: border-color, + class: border, + local-vars: ( + "border-opacity": 1 + ), + values: $utilities-border-colors + ), + "subtle-border-color": ( + property: border-color, + class: border, + values: $utilities-border-subtle + ), + "border-width": ( + property: border-width, + class: border, + values: $border-widths + ), + "border-opacity": ( + css-var: true, + class: border-opacity, + values: ( + 10: .1, + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), + // scss-docs-end utils-borders + // Sizing utilities + // scss-docs-start utils-sizing + "width": ( + property: width, + class: w, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + auto: auto + ) + ), + "max-width": ( + property: max-width, + class: mw, + values: (100: 100%) + ), + "viewport-width": ( + property: width, + class: vw, + values: (100: 100vw) + ), + "min-viewport-width": ( + property: min-width, + class: min-vw, + values: (100: 100vw) + ), + "height": ( + property: height, + class: h, + values: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100%, + auto: auto + ) + ), + "max-height": ( + property: max-height, + class: mh, + values: (100: 100%) + ), + "viewport-height": ( + property: height, + class: vh, + values: (100: 100vh) + ), + "min-viewport-height": ( + property: min-height, + class: min-vh, + values: (100: 100vh) + ), + // scss-docs-end utils-sizing + // Flex utilities + // scss-docs-start utils-flex + "flex": ( + responsive: true, + property: flex, + values: (fill: 1 1 auto) + ), + "flex-direction": ( + responsive: true, + property: flex-direction, + class: flex, + values: row column row-reverse column-reverse + ), + "flex-grow": ( + responsive: true, + property: flex-grow, + class: flex, + values: ( + grow-0: 0, + grow-1: 1, + ) + ), + "flex-shrink": ( + responsive: true, + property: flex-shrink, + class: flex, + values: ( + shrink-0: 0, + shrink-1: 1, + ) + ), + "flex-wrap": ( + responsive: true, + property: flex-wrap, + class: flex, + values: wrap nowrap wrap-reverse + ), + "justify-content": ( + responsive: true, + property: justify-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + evenly: space-evenly, + ) + ), + "align-items": ( + responsive: true, + property: align-items, + values: ( + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + "align-content": ( + responsive: true, + property: align-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + stretch: stretch, + ) + ), + "align-self": ( + responsive: true, + property: align-self, + values: ( + auto: auto, + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + "order": ( + responsive: true, + property: order, + values: ( + first: -1, + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + last: 6, + ), + ), + // scss-docs-end utils-flex + // Margin utilities + // scss-docs-start utils-spacing + "margin": ( + responsive: true, + property: margin, + class: m, + values: map-merge($spacers, (auto: auto)) + ), + "margin-x": ( + responsive: true, + property: margin-right margin-left, + class: mx, + values: map-merge($spacers, (auto: auto)) + ), + "margin-y": ( + responsive: true, + property: margin-top margin-bottom, + class: my, + values: map-merge($spacers, (auto: auto)) + ), + "margin-top": ( + responsive: true, + property: margin-top, + class: mt, + values: map-merge($spacers, (auto: auto)) + ), + "margin-end": ( + responsive: true, + property: margin-right, + class: me, + values: map-merge($spacers, (auto: auto)) + ), + "margin-bottom": ( + responsive: true, + property: margin-bottom, + class: mb, + values: map-merge($spacers, (auto: auto)) + ), + "margin-start": ( + responsive: true, + property: margin-left, + class: ms, + values: map-merge($spacers, (auto: auto)) + ), + // Negative margin utilities + "negative-margin": ( + responsive: true, + property: margin, + class: m, + values: $negative-spacers + ), + "negative-margin-x": ( + responsive: true, + property: margin-right margin-left, + class: mx, + values: $negative-spacers + ), + "negative-margin-y": ( + responsive: true, + property: margin-top margin-bottom, + class: my, + values: $negative-spacers + ), + "negative-margin-top": ( + responsive: true, + property: margin-top, + class: mt, + values: $negative-spacers + ), + "negative-margin-end": ( + responsive: true, + property: margin-right, + class: me, + values: $negative-spacers + ), + "negative-margin-bottom": ( + responsive: true, + property: margin-bottom, + class: mb, + values: $negative-spacers + ), + "negative-margin-start": ( + responsive: true, + property: margin-left, + class: ms, + values: $negative-spacers + ), + // Padding utilities + "padding": ( + responsive: true, + property: padding, + class: p, + values: $spacers + ), + "padding-x": ( + responsive: true, + property: padding-right padding-left, + class: px, + values: $spacers + ), + "padding-y": ( + responsive: true, + property: padding-top padding-bottom, + class: py, + values: $spacers + ), + "padding-top": ( + responsive: true, + property: padding-top, + class: pt, + values: $spacers + ), + "padding-end": ( + responsive: true, + property: padding-right, + class: pe, + values: $spacers + ), + "padding-bottom": ( + responsive: true, + property: padding-bottom, + class: pb, + values: $spacers + ), + "padding-start": ( + responsive: true, + property: padding-left, + class: ps, + values: $spacers + ), + // Gap utility + "gap": ( + responsive: true, + property: gap, + class: gap, + values: $spacers + ), + "row-gap": ( + responsive: true, + property: row-gap, + class: row-gap, + values: $spacers + ), + "column-gap": ( + responsive: true, + property: column-gap, + class: column-gap, + values: $spacers + ), + // scss-docs-end utils-spacing + // Text + // scss-docs-start utils-text + "font-family": ( + property: font-family, + class: font, + values: (monospace: var(--#{$prefix}font-monospace)) + ), + "font-size": ( + rfs: true, + property: font-size, + class: fs, + values: $font-sizes + ), + "font-style": ( + property: font-style, + class: fst, + values: italic normal + ), + "font-weight": ( + property: font-weight, + class: fw, + values: ( + lighter: $font-weight-lighter, + light: $font-weight-light, + normal: $font-weight-normal, + medium: $font-weight-medium, + semibold: $font-weight-semibold, + bold: $font-weight-bold, + bolder: $font-weight-bolder + ) + ), + "line-height": ( + property: line-height, + class: lh, + values: ( + 1: 1, + sm: $line-height-sm, + base: $line-height-base, + lg: $line-height-lg, + ) + ), + "text-align": ( + responsive: true, + property: text-align, + class: text, + values: ( + start: left, + end: right, + center: center, + ) + ), + "text-decoration": ( + property: text-decoration, + values: none underline line-through + ), + "text-transform": ( + property: text-transform, + class: text, + values: lowercase uppercase capitalize + ), + "white-space": ( + property: white-space, + class: text, + values: ( + wrap: normal, + nowrap: nowrap, + ) + ), + "word-wrap": ( + property: word-wrap word-break, + class: text, + values: (break: break-word), + rtl: false + ), + // scss-docs-end utils-text + // scss-docs-start utils-color + "color": ( + property: color, + class: text, + local-vars: ( + "text-opacity": 1 + ), + values: map-merge( + $utilities-text-colors, + ( + "muted": var(--#{$prefix}secondary-color), // deprecated + "black-50": rgba($black, .5), // deprecated + "white-50": rgba($white, .5), // deprecated + "body-secondary": var(--#{$prefix}secondary-color), + "body-tertiary": var(--#{$prefix}tertiary-color), + "body-emphasis": var(--#{$prefix}emphasis-color), + "reset": inherit, + ) + ) + ), + "text-opacity": ( + css-var: true, + class: text-opacity, + values: ( + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), + "text-color": ( + property: color, + class: text, + values: $utilities-text-emphasis-colors + ), + // scss-docs-end utils-color + // scss-docs-start utils-links + "link-opacity": ( + css-var: true, + class: link-opacity, + state: hover, + values: ( + 10: .1, + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), + "link-offset": ( + property: text-underline-offset, + class: link-offset, + state: hover, + values: ( + 1: .125em, + 2: .25em, + 3: .375em, + ) + ), + "link-underline": ( + property: text-decoration-color, + class: link-underline, + local-vars: ( + "link-underline-opacity": 1 + ), + values: map-merge( + $utilities-links-underline, + ( + null: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-underline-opacity, 1)), + ) + ) + ), + "link-underline-opacity": ( + css-var: true, + class: link-underline-opacity, + state: hover, + values: ( + 0: 0, + 10: .1, + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ), + ), + // scss-docs-end utils-links + // scss-docs-start utils-bg-color + "background-color": ( + property: background-color, + class: bg, + local-vars: ( + "bg-opacity": 1 + ), + values: map-merge( + $utilities-bg-colors, + ( + "transparent": transparent, + "body-secondary": rgba(var(--#{$prefix}secondary-bg-rgb), var(--#{$prefix}bg-opacity)), + "body-tertiary": rgba(var(--#{$prefix}tertiary-bg-rgb), var(--#{$prefix}bg-opacity)), + ) + ) + ), + "bg-opacity": ( + css-var: true, + class: bg-opacity, + values: ( + 10: .1, + 25: .25, + 50: .5, + 75: .75, + 100: 1 + ) + ), + "subtle-background-color": ( + property: background-color, + class: bg, + values: $utilities-bg-subtle + ), + // scss-docs-end utils-bg-color + "gradient": ( + property: background-image, + class: bg, + values: (gradient: var(--#{$prefix}gradient)) + ), + // scss-docs-start utils-interaction + "user-select": ( + property: user-select, + values: all auto none + ), + "pointer-events": ( + property: pointer-events, + class: pe, + values: none auto, + ), + // scss-docs-end utils-interaction + // scss-docs-start utils-border-radius + "rounded": ( + property: border-radius, + class: rounded, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-top": ( + property: border-top-left-radius border-top-right-radius, + class: rounded-top, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-end": ( + property: border-top-right-radius border-bottom-right-radius, + class: rounded-end, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-bottom": ( + property: border-bottom-right-radius border-bottom-left-radius, + class: rounded-bottom, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-start": ( + property: border-bottom-left-radius border-top-left-radius, + class: rounded-start, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + // scss-docs-end utils-border-radius + // scss-docs-start utils-visibility + "visibility": ( + property: visibility, + class: null, + values: ( + visible: visible, + invisible: hidden, + ) + ), + // scss-docs-end utils-visibility + // scss-docs-start utils-zindex + "z-index": ( + property: z-index, + class: z, + values: $zindex-levels, + ) + // scss-docs-end utils-zindex + ), + $utilities +); diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss new file mode 100644 index 00000000..6422b387 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss @@ -0,0 +1,87 @@ +// Dark color mode variables +// +// Custom variables for the `[data-bs-theme="dark"]` theme. Use this as a starting point for your own custom color modes by creating a new theme-specific file like `_variables-dark.scss` and adding the variables you need. + +// +// Global colors +// + +// scss-docs-start sass-dark-mode-vars +// scss-docs-start theme-text-dark-variables +$primary-text-emphasis-dark: tint-color($primary, 40%) !default; +$secondary-text-emphasis-dark: tint-color($secondary, 40%) !default; +$success-text-emphasis-dark: tint-color($success, 40%) !default; +$info-text-emphasis-dark: tint-color($info, 40%) !default; +$warning-text-emphasis-dark: tint-color($warning, 40%) !default; +$danger-text-emphasis-dark: tint-color($danger, 40%) !default; +$light-text-emphasis-dark: $gray-100 !default; +$dark-text-emphasis-dark: $gray-300 !default; +// scss-docs-end theme-text-dark-variables + +// scss-docs-start theme-bg-subtle-dark-variables +$primary-bg-subtle-dark: shade-color($primary, 80%) !default; +$secondary-bg-subtle-dark: shade-color($secondary, 80%) !default; +$success-bg-subtle-dark: shade-color($success, 80%) !default; +$info-bg-subtle-dark: shade-color($info, 80%) !default; +$warning-bg-subtle-dark: shade-color($warning, 80%) !default; +$danger-bg-subtle-dark: shade-color($danger, 80%) !default; +$light-bg-subtle-dark: $gray-800 !default; +$dark-bg-subtle-dark: mix($gray-800, $black) !default; +// scss-docs-end theme-bg-subtle-dark-variables + +// scss-docs-start theme-border-subtle-dark-variables +$primary-border-subtle-dark: shade-color($primary, 40%) !default; +$secondary-border-subtle-dark: shade-color($secondary, 40%) !default; +$success-border-subtle-dark: shade-color($success, 40%) !default; +$info-border-subtle-dark: shade-color($info, 40%) !default; +$warning-border-subtle-dark: shade-color($warning, 40%) !default; +$danger-border-subtle-dark: shade-color($danger, 40%) !default; +$light-border-subtle-dark: $gray-700 !default; +$dark-border-subtle-dark: $gray-800 !default; +// scss-docs-end theme-border-subtle-dark-variables + +$body-color-dark: $gray-300 !default; +$body-bg-dark: $gray-900 !default; +$body-secondary-color-dark: rgba($body-color-dark, .75) !default; +$body-secondary-bg-dark: $gray-800 !default; +$body-tertiary-color-dark: rgba($body-color-dark, .5) !default; +$body-tertiary-bg-dark: mix($gray-800, $gray-900, 50%) !default; +$body-emphasis-color-dark: $white !default; +$border-color-dark: $gray-700 !default; +$border-color-translucent-dark: rgba($white, .15) !default; +$headings-color-dark: inherit !default; +$link-color-dark: tint-color($primary, 40%) !default; +$link-hover-color-dark: shift-color($link-color-dark, -$link-shade-percentage) !default; +$code-color-dark: tint-color($code-color, 40%) !default; +$mark-color-dark: $body-color-dark !default; +$mark-bg-dark: $yellow-800 !default; + + +// +// Forms +// + +$form-select-indicator-color-dark: $body-color-dark !default; +$form-select-indicator-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color-dark}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default; + +$form-switch-color-dark: rgba($white, .25) !default; +$form-switch-bg-image-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color-dark}'/></svg>") !default; + +// scss-docs-start form-validation-colors-dark +$form-valid-color-dark: $green-300 !default; +$form-valid-border-color-dark: $green-300 !default; +$form-invalid-color-dark: $red-300 !default; +$form-invalid-border-color-dark: $red-300 !default; +// scss-docs-end form-validation-colors-dark + + +// +// Accordion +// + +$accordion-icon-color-dark: $primary-text-emphasis-dark !default; +$accordion-icon-active-color-dark: $primary-text-emphasis-dark !default; + +$accordion-button-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>") !default; +$accordion-button-active-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>") !default; +// scss-docs-end sass-dark-mode-vars diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss new file mode 100644 index 00000000..06531395 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss @@ -0,0 +1,1751 @@ +// Variables +// +// Variables should follow the `$component-state-property-size` formula for +// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs. + +// Color system + +// scss-docs-start gray-color-variables +$white: #fff !default; +$gray-100: #f8f9fa !default; +$gray-200: #e9ecef !default; +$gray-300: #dee2e6 !default; +$gray-400: #ced4da !default; +$gray-500: #adb5bd !default; +$gray-600: #6c757d !default; +$gray-700: #495057 !default; +$gray-800: #343a40 !default; +$gray-900: #212529 !default; +$black: #000 !default; +// scss-docs-end gray-color-variables + +// fusv-disable +// scss-docs-start gray-colors-map +$grays: ( + "100": $gray-100, + "200": $gray-200, + "300": $gray-300, + "400": $gray-400, + "500": $gray-500, + "600": $gray-600, + "700": $gray-700, + "800": $gray-800, + "900": $gray-900 +) !default; +// scss-docs-end gray-colors-map +// fusv-enable + +// scss-docs-start color-variables +$blue: #0d6efd !default; +$indigo: #6610f2 !default; +$purple: #6f42c1 !default; +$pink: #d63384 !default; +$red: #dc3545 !default; +$orange: #fd7e14 !default; +$yellow: #ffc107 !default; +$green: #198754 !default; +$teal: #20c997 !default; +$cyan: #0dcaf0 !default; +// scss-docs-end color-variables + +// scss-docs-start colors-map +$colors: ( + "blue": $blue, + "indigo": $indigo, + "purple": $purple, + "pink": $pink, + "red": $red, + "orange": $orange, + "yellow": $yellow, + "green": $green, + "teal": $teal, + "cyan": $cyan, + "black": $black, + "white": $white, + "gray": $gray-600, + "gray-dark": $gray-800 +) !default; +// scss-docs-end colors-map + +// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7. +// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast +$min-contrast-ratio: 4.5 !default; + +// Customize the light and dark text colors for use in our color contrast function. +$color-contrast-dark: $black !default; +$color-contrast-light: $white !default; + +// fusv-disable +$blue-100: tint-color($blue, 80%) !default; +$blue-200: tint-color($blue, 60%) !default; +$blue-300: tint-color($blue, 40%) !default; +$blue-400: tint-color($blue, 20%) !default; +$blue-500: $blue !default; +$blue-600: shade-color($blue, 20%) !default; +$blue-700: shade-color($blue, 40%) !default; +$blue-800: shade-color($blue, 60%) !default; +$blue-900: shade-color($blue, 80%) !default; + +$indigo-100: tint-color($indigo, 80%) !default; +$indigo-200: tint-color($indigo, 60%) !default; +$indigo-300: tint-color($indigo, 40%) !default; +$indigo-400: tint-color($indigo, 20%) !default; +$indigo-500: $indigo !default; +$indigo-600: shade-color($indigo, 20%) !default; +$indigo-700: shade-color($indigo, 40%) !default; +$indigo-800: shade-color($indigo, 60%) !default; +$indigo-900: shade-color($indigo, 80%) !default; + +$purple-100: tint-color($purple, 80%) !default; +$purple-200: tint-color($purple, 60%) !default; +$purple-300: tint-color($purple, 40%) !default; +$purple-400: tint-color($purple, 20%) !default; +$purple-500: $purple !default; +$purple-600: shade-color($purple, 20%) !default; +$purple-700: shade-color($purple, 40%) !default; +$purple-800: shade-color($purple, 60%) !default; +$purple-900: shade-color($purple, 80%) !default; + +$pink-100: tint-color($pink, 80%) !default; +$pink-200: tint-color($pink, 60%) !default; +$pink-300: tint-color($pink, 40%) !default; +$pink-400: tint-color($pink, 20%) !default; +$pink-500: $pink !default; +$pink-600: shade-color($pink, 20%) !default; +$pink-700: shade-color($pink, 40%) !default; +$pink-800: shade-color($pink, 60%) !default; +$pink-900: shade-color($pink, 80%) !default; + +$red-100: tint-color($red, 80%) !default; +$red-200: tint-color($red, 60%) !default; +$red-300: tint-color($red, 40%) !default; +$red-400: tint-color($red, 20%) !default; +$red-500: $red !default; +$red-600: shade-color($red, 20%) !default; +$red-700: shade-color($red, 40%) !default; +$red-800: shade-color($red, 60%) !default; +$red-900: shade-color($red, 80%) !default; + +$orange-100: tint-color($orange, 80%) !default; +$orange-200: tint-color($orange, 60%) !default; +$orange-300: tint-color($orange, 40%) !default; +$orange-400: tint-color($orange, 20%) !default; +$orange-500: $orange !default; +$orange-600: shade-color($orange, 20%) !default; +$orange-700: shade-color($orange, 40%) !default; +$orange-800: shade-color($orange, 60%) !default; +$orange-900: shade-color($orange, 80%) !default; + +$yellow-100: tint-color($yellow, 80%) !default; +$yellow-200: tint-color($yellow, 60%) !default; +$yellow-300: tint-color($yellow, 40%) !default; +$yellow-400: tint-color($yellow, 20%) !default; +$yellow-500: $yellow !default; +$yellow-600: shade-color($yellow, 20%) !default; +$yellow-700: shade-color($yellow, 40%) !default; +$yellow-800: shade-color($yellow, 60%) !default; +$yellow-900: shade-color($yellow, 80%) !default; + +$green-100: tint-color($green, 80%) !default; +$green-200: tint-color($green, 60%) !default; +$green-300: tint-color($green, 40%) !default; +$green-400: tint-color($green, 20%) !default; +$green-500: $green !default; +$green-600: shade-color($green, 20%) !default; +$green-700: shade-color($green, 40%) !default; +$green-800: shade-color($green, 60%) !default; +$green-900: shade-color($green, 80%) !default; + +$teal-100: tint-color($teal, 80%) !default; +$teal-200: tint-color($teal, 60%) !default; +$teal-300: tint-color($teal, 40%) !default; +$teal-400: tint-color($teal, 20%) !default; +$teal-500: $teal !default; +$teal-600: shade-color($teal, 20%) !default; +$teal-700: shade-color($teal, 40%) !default; +$teal-800: shade-color($teal, 60%) !default; +$teal-900: shade-color($teal, 80%) !default; + +$cyan-100: tint-color($cyan, 80%) !default; +$cyan-200: tint-color($cyan, 60%) !default; +$cyan-300: tint-color($cyan, 40%) !default; +$cyan-400: tint-color($cyan, 20%) !default; +$cyan-500: $cyan !default; +$cyan-600: shade-color($cyan, 20%) !default; +$cyan-700: shade-color($cyan, 40%) !default; +$cyan-800: shade-color($cyan, 60%) !default; +$cyan-900: shade-color($cyan, 80%) !default; + +$blues: ( + "blue-100": $blue-100, + "blue-200": $blue-200, + "blue-300": $blue-300, + "blue-400": $blue-400, + "blue-500": $blue-500, + "blue-600": $blue-600, + "blue-700": $blue-700, + "blue-800": $blue-800, + "blue-900": $blue-900 +) !default; + +$indigos: ( + "indigo-100": $indigo-100, + "indigo-200": $indigo-200, + "indigo-300": $indigo-300, + "indigo-400": $indigo-400, + "indigo-500": $indigo-500, + "indigo-600": $indigo-600, + "indigo-700": $indigo-700, + "indigo-800": $indigo-800, + "indigo-900": $indigo-900 +) !default; + +$purples: ( + "purple-100": $purple-100, + "purple-200": $purple-200, + "purple-300": $purple-300, + "purple-400": $purple-400, + "purple-500": $purple-500, + "purple-600": $purple-600, + "purple-700": $purple-700, + "purple-800": $purple-800, + "purple-900": $purple-900 +) !default; + +$pinks: ( + "pink-100": $pink-100, + "pink-200": $pink-200, + "pink-300": $pink-300, + "pink-400": $pink-400, + "pink-500": $pink-500, + "pink-600": $pink-600, + "pink-700": $pink-700, + "pink-800": $pink-800, + "pink-900": $pink-900 +) !default; + +$reds: ( + "red-100": $red-100, + "red-200": $red-200, + "red-300": $red-300, + "red-400": $red-400, + "red-500": $red-500, + "red-600": $red-600, + "red-700": $red-700, + "red-800": $red-800, + "red-900": $red-900 +) !default; + +$oranges: ( + "orange-100": $orange-100, + "orange-200": $orange-200, + "orange-300": $orange-300, + "orange-400": $orange-400, + "orange-500": $orange-500, + "orange-600": $orange-600, + "orange-700": $orange-700, + "orange-800": $orange-800, + "orange-900": $orange-900 +) !default; + +$yellows: ( + "yellow-100": $yellow-100, + "yellow-200": $yellow-200, + "yellow-300": $yellow-300, + "yellow-400": $yellow-400, + "yellow-500": $yellow-500, + "yellow-600": $yellow-600, + "yellow-700": $yellow-700, + "yellow-800": $yellow-800, + "yellow-900": $yellow-900 +) !default; + +$greens: ( + "green-100": $green-100, + "green-200": $green-200, + "green-300": $green-300, + "green-400": $green-400, + "green-500": $green-500, + "green-600": $green-600, + "green-700": $green-700, + "green-800": $green-800, + "green-900": $green-900 +) !default; + +$teals: ( + "teal-100": $teal-100, + "teal-200": $teal-200, + "teal-300": $teal-300, + "teal-400": $teal-400, + "teal-500": $teal-500, + "teal-600": $teal-600, + "teal-700": $teal-700, + "teal-800": $teal-800, + "teal-900": $teal-900 +) !default; + +$cyans: ( + "cyan-100": $cyan-100, + "cyan-200": $cyan-200, + "cyan-300": $cyan-300, + "cyan-400": $cyan-400, + "cyan-500": $cyan-500, + "cyan-600": $cyan-600, + "cyan-700": $cyan-700, + "cyan-800": $cyan-800, + "cyan-900": $cyan-900 +) !default; +// fusv-enable + +// scss-docs-start theme-color-variables +$primary: $blue !default; +$secondary: $gray-600 !default; +$success: $green !default; +$info: $cyan !default; +$warning: $yellow !default; +$danger: $red !default; +$light: $gray-100 !default; +$dark: $gray-900 !default; +// scss-docs-end theme-color-variables + +// scss-docs-start theme-colors-map +$theme-colors: ( + "primary": $primary, + "secondary": $secondary, + "success": $success, + "info": $info, + "warning": $warning, + "danger": $danger, + "light": $light, + "dark": $dark +) !default; +// scss-docs-end theme-colors-map + +// scss-docs-start theme-text-variables +$primary-text-emphasis: shade-color($primary, 60%) !default; +$secondary-text-emphasis: shade-color($secondary, 60%) !default; +$success-text-emphasis: shade-color($success, 60%) !default; +$info-text-emphasis: shade-color($info, 60%) !default; +$warning-text-emphasis: shade-color($warning, 60%) !default; +$danger-text-emphasis: shade-color($danger, 60%) !default; +$light-text-emphasis: $gray-700 !default; +$dark-text-emphasis: $gray-700 !default; +// scss-docs-end theme-text-variables + +// scss-docs-start theme-bg-subtle-variables +$primary-bg-subtle: tint-color($primary, 80%) !default; +$secondary-bg-subtle: tint-color($secondary, 80%) !default; +$success-bg-subtle: tint-color($success, 80%) !default; +$info-bg-subtle: tint-color($info, 80%) !default; +$warning-bg-subtle: tint-color($warning, 80%) !default; +$danger-bg-subtle: tint-color($danger, 80%) !default; +$light-bg-subtle: mix($gray-100, $white) !default; +$dark-bg-subtle: $gray-400 !default; +// scss-docs-end theme-bg-subtle-variables + +// scss-docs-start theme-border-subtle-variables +$primary-border-subtle: tint-color($primary, 60%) !default; +$secondary-border-subtle: tint-color($secondary, 60%) !default; +$success-border-subtle: tint-color($success, 60%) !default; +$info-border-subtle: tint-color($info, 60%) !default; +$warning-border-subtle: tint-color($warning, 60%) !default; +$danger-border-subtle: tint-color($danger, 60%) !default; +$light-border-subtle: $gray-200 !default; +$dark-border-subtle: $gray-500 !default; +// scss-docs-end theme-border-subtle-variables + +// Characters which are escaped by the escape-svg function +$escaped-characters: ( + ("<", "%3c"), + (">", "%3e"), + ("#", "%23"), + ("(", "%28"), + (")", "%29"), +) !default; + +// Options +// +// Quickly modify global styling by enabling or disabling optional features. + +$enable-caret: true !default; +$enable-rounded: true !default; +$enable-shadows: false !default; +$enable-gradients: false !default; +$enable-transitions: true !default; +$enable-reduced-motion: true !default; +$enable-smooth-scroll: true !default; +$enable-grid-classes: true !default; +$enable-container-classes: true !default; +$enable-cssgrid: false !default; +$enable-button-pointers: true !default; +$enable-rfs: true !default; +$enable-validation-icons: true !default; +$enable-negative-margins: false !default; +$enable-deprecation-messages: true !default; +$enable-important-utilities: true !default; + +$enable-dark-mode: true !default; +$color-mode-type: data !default; // `data` or `media-query` + +// Prefix for :root CSS variables + +$variable-prefix: bs- !default; // Deprecated in v5.2.0 for the shorter `$prefix` +$prefix: $variable-prefix !default; + +// Gradient +// +// The gradient which is added to components if `$enable-gradients` is `true` +// This gradient is also added to elements with `.bg-gradient` +// scss-docs-start variable-gradient +$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default; +// scss-docs-end variable-gradient + +// Spacing +// +// Control the default styling of most Bootstrap elements by modifying these +// variables. Mostly focused on spacing. +// You can add more entries to the $spacers map, should you need more variation. + +// scss-docs-start spacer-variables-maps +$spacer: 1rem !default; +$spacers: ( + 0: 0, + 1: $spacer * .25, + 2: $spacer * .5, + 3: $spacer, + 4: $spacer * 1.5, + 5: $spacer * 3, +) !default; +// scss-docs-end spacer-variables-maps + +// Position +// +// Define the edge positioning anchors of the position utilities. + +// scss-docs-start position-map +$position-values: ( + 0: 0, + 50: 50%, + 100: 100% +) !default; +// scss-docs-end position-map + +// Body +// +// Settings for the `<body>` element. + +$body-text-align: null !default; +$body-color: $gray-900 !default; +$body-bg: $white !default; + +$body-secondary-color: rgba($body-color, .75) !default; +$body-secondary-bg: $gray-200 !default; + +$body-tertiary-color: rgba($body-color, .5) !default; +$body-tertiary-bg: $gray-100 !default; + +$body-emphasis-color: $black !default; + +// Links +// +// Style anchor elements. + +$link-color: $primary !default; +$link-decoration: underline !default; +$link-shade-percentage: 20% !default; +$link-hover-color: shift-color($link-color, $link-shade-percentage) !default; +$link-hover-decoration: null !default; + +$stretched-link-pseudo-element: after !default; +$stretched-link-z-index: 1 !default; + +// Icon links +// scss-docs-start icon-link-variables +$icon-link-gap: .375rem !default; +$icon-link-underline-offset: .25em !default; +$icon-link-icon-size: 1em !default; +$icon-link-icon-transition: .2s ease-in-out transform !default; +$icon-link-icon-transform: translate3d(.25em, 0, 0) !default; +// scss-docs-end icon-link-variables + +// Paragraphs +// +// Style p element. + +$paragraph-margin-bottom: 1rem !default; + + +// Grid breakpoints +// +// Define the minimum dimensions at which your layout will change, +// adapting to different screen sizes, for use in media queries. + +// scss-docs-start grid-breakpoints +$grid-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px, + xxl: 1400px +) !default; +// scss-docs-end grid-breakpoints + +@include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); +@include _assert-starts-at-zero($grid-breakpoints, "$grid-breakpoints"); + + +// Grid containers +// +// Define the maximum width of `.container` for different screen sizes. + +// scss-docs-start container-max-widths +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 1140px, + xxl: 1320px +) !default; +// scss-docs-end container-max-widths + +@include _assert-ascending($container-max-widths, "$container-max-widths"); + + +// Grid columns +// +// Set the number of columns and specify the width of the gutters. + +$grid-columns: 12 !default; +$grid-gutter-width: 1.5rem !default; +$grid-row-columns: 6 !default; + +// Container padding + +$container-padding-x: $grid-gutter-width !default; + + +// Components +// +// Define common padding and border radius sizes and more. + +// scss-docs-start border-variables +$border-width: 1px !default; +$border-widths: ( + 1: 1px, + 2: 2px, + 3: 3px, + 4: 4px, + 5: 5px +) !default; +$border-style: solid !default; +$border-color: $gray-300 !default; +$border-color-translucent: rgba($black, .175) !default; +// scss-docs-end border-variables + +// scss-docs-start border-radius-variables +$border-radius: .375rem !default; +$border-radius-sm: .25rem !default; +$border-radius-lg: .5rem !default; +$border-radius-xl: 1rem !default; +$border-radius-xxl: 2rem !default; +$border-radius-pill: 50rem !default; +// scss-docs-end border-radius-variables +// fusv-disable +$border-radius-2xl: $border-radius-xxl !default; // Deprecated in v5.3.0 +// fusv-enable + +// scss-docs-start box-shadow-variables +$box-shadow: 0 .5rem 1rem rgba($black, .15) !default; +$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default; +$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default; +$box-shadow-inset: inset 0 1px 2px rgba($black, .075) !default; +// scss-docs-end box-shadow-variables + +$component-active-color: $white !default; +$component-active-bg: $primary !default; + +// scss-docs-start focus-ring-variables +$focus-ring-width: .25rem !default; +$focus-ring-opacity: .25 !default; +$focus-ring-color: rgba($primary, $focus-ring-opacity) !default; +$focus-ring-blur: 0 !default; +$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default; +// scss-docs-end focus-ring-variables + +// scss-docs-start caret-variables +$caret-width: .3em !default; +$caret-vertical-align: $caret-width * .85 !default; +$caret-spacing: $caret-width * .85 !default; +// scss-docs-end caret-variables + +$transition-base: all .2s ease-in-out !default; +$transition-fade: opacity .15s linear !default; +// scss-docs-start collapse-transition +$transition-collapse: height .35s ease !default; +$transition-collapse-width: width .35s ease !default; +// scss-docs-end collapse-transition + +// stylelint-disable function-disallowed-list +// scss-docs-start aspect-ratios +$aspect-ratios: ( + "1x1": 100%, + "4x3": calc(3 / 4 * 100%), + "16x9": calc(9 / 16 * 100%), + "21x9": calc(9 / 21 * 100%) +) !default; +// scss-docs-end aspect-ratios +// stylelint-enable function-disallowed-list + +// Typography +// +// Font, line-height, and color for body text, headings, and more. + +// scss-docs-start font-variables +// stylelint-disable value-keyword-case +$font-family-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; +$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; +// stylelint-enable value-keyword-case +$font-family-base: var(--#{$prefix}font-sans-serif) !default; +$font-family-code: var(--#{$prefix}font-monospace) !default; + +// $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins +// $font-size-base affects the font size of the body text +$font-size-root: null !default; +$font-size-base: 1rem !default; // Assumes the browser default, typically `16px` +$font-size-sm: $font-size-base * .875 !default; +$font-size-lg: $font-size-base * 1.25 !default; + +$font-weight-lighter: lighter !default; +$font-weight-light: 300 !default; +$font-weight-normal: 400 !default; +$font-weight-medium: 500 !default; +$font-weight-semibold: 600 !default; +$font-weight-bold: 700 !default; +$font-weight-bolder: bolder !default; + +$font-weight-base: $font-weight-normal !default; + +$line-height-base: 1.5 !default; +$line-height-sm: 1.25 !default; +$line-height-lg: 2 !default; + +$h1-font-size: $font-size-base * 2.5 !default; +$h2-font-size: $font-size-base * 2 !default; +$h3-font-size: $font-size-base * 1.75 !default; +$h4-font-size: $font-size-base * 1.5 !default; +$h5-font-size: $font-size-base * 1.25 !default; +$h6-font-size: $font-size-base !default; +// scss-docs-end font-variables + +// scss-docs-start font-sizes +$font-sizes: ( + 1: $h1-font-size, + 2: $h2-font-size, + 3: $h3-font-size, + 4: $h4-font-size, + 5: $h5-font-size, + 6: $h6-font-size +) !default; +// scss-docs-end font-sizes + +// scss-docs-start headings-variables +$headings-margin-bottom: $spacer * .5 !default; +$headings-font-family: null !default; +$headings-font-style: null !default; +$headings-font-weight: 500 !default; +$headings-line-height: 1.2 !default; +$headings-color: inherit !default; +// scss-docs-end headings-variables + +// scss-docs-start display-headings +$display-font-sizes: ( + 1: 5rem, + 2: 4.5rem, + 3: 4rem, + 4: 3.5rem, + 5: 3rem, + 6: 2.5rem +) !default; + +$display-font-family: null !default; +$display-font-style: null !default; +$display-font-weight: 300 !default; +$display-line-height: $headings-line-height !default; +// scss-docs-end display-headings + +// scss-docs-start type-variables +$lead-font-size: $font-size-base * 1.25 !default; +$lead-font-weight: 300 !default; + +$small-font-size: .875em !default; + +$sub-sup-font-size: .75em !default; + +// fusv-disable +$text-muted: var(--#{$prefix}secondary-color) !default; // Deprecated in 5.3.0 +// fusv-enable + +$initialism-font-size: $small-font-size !default; + +$blockquote-margin-y: $spacer !default; +$blockquote-font-size: $font-size-base * 1.25 !default; +$blockquote-footer-color: $gray-600 !default; +$blockquote-footer-font-size: $small-font-size !default; + +$hr-margin-y: $spacer !default; +$hr-color: inherit !default; + +// fusv-disable +$hr-bg-color: null !default; // Deprecated in v5.2.0 +$hr-height: null !default; // Deprecated in v5.2.0 +// fusv-enable + +$hr-border-color: null !default; // Allows for inherited colors +$hr-border-width: var(--#{$prefix}border-width) !default; +$hr-opacity: .25 !default; + +// scss-docs-start vr-variables +$vr-border-width: var(--#{$prefix}border-width) !default; +// scss-docs-end vr-variables + +$legend-margin-bottom: .5rem !default; +$legend-font-size: 1.5rem !default; +$legend-font-weight: null !default; + +$dt-font-weight: $font-weight-bold !default; + +$list-inline-padding: .5rem !default; + +$mark-padding: .1875em !default; +$mark-color: $body-color !default; +$mark-bg: $yellow-100 !default; +// scss-docs-end type-variables + + +// Tables +// +// Customizes the `.table` component with basic values, each used across all table variations. + +// scss-docs-start table-variables +$table-cell-padding-y: .5rem !default; +$table-cell-padding-x: .5rem !default; +$table-cell-padding-y-sm: .25rem !default; +$table-cell-padding-x-sm: .25rem !default; + +$table-cell-vertical-align: top !default; + +$table-color: var(--#{$prefix}emphasis-color) !default; +$table-bg: var(--#{$prefix}body-bg) !default; +$table-accent-bg: transparent !default; + +$table-th-font-weight: null !default; + +$table-striped-color: $table-color !default; +$table-striped-bg-factor: .05 !default; +$table-striped-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-striped-bg-factor) !default; + +$table-active-color: $table-color !default; +$table-active-bg-factor: .1 !default; +$table-active-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-active-bg-factor) !default; + +$table-hover-color: $table-color !default; +$table-hover-bg-factor: .075 !default; +$table-hover-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-hover-bg-factor) !default; + +$table-border-factor: .2 !default; +$table-border-width: var(--#{$prefix}border-width) !default; +$table-border-color: var(--#{$prefix}border-color) !default; + +$table-striped-order: odd !default; +$table-striped-columns-order: even !default; + +$table-group-separator-color: currentcolor !default; + +$table-caption-color: var(--#{$prefix}secondary-color) !default; + +$table-bg-scale: -80% !default; +// scss-docs-end table-variables + +// scss-docs-start table-loop +$table-variants: ( + "primary": shift-color($primary, $table-bg-scale), + "secondary": shift-color($secondary, $table-bg-scale), + "success": shift-color($success, $table-bg-scale), + "info": shift-color($info, $table-bg-scale), + "warning": shift-color($warning, $table-bg-scale), + "danger": shift-color($danger, $table-bg-scale), + "light": $light, + "dark": $dark, +) !default; +// scss-docs-end table-loop + + +// Buttons + Forms +// +// Shared variables that are reassigned to `$input-` and `$btn-` specific variables. + +// scss-docs-start input-btn-variables +$input-btn-padding-y: .375rem !default; +$input-btn-padding-x: .75rem !default; +$input-btn-font-family: null !default; +$input-btn-font-size: $font-size-base !default; +$input-btn-line-height: $line-height-base !default; + +$input-btn-focus-width: $focus-ring-width !default; +$input-btn-focus-color-opacity: $focus-ring-opacity !default; +$input-btn-focus-color: $focus-ring-color !default; +$input-btn-focus-blur: $focus-ring-blur !default; +$input-btn-focus-box-shadow: $focus-ring-box-shadow !default; + +$input-btn-padding-y-sm: .25rem !default; +$input-btn-padding-x-sm: .5rem !default; +$input-btn-font-size-sm: $font-size-sm !default; + +$input-btn-padding-y-lg: .5rem !default; +$input-btn-padding-x-lg: 1rem !default; +$input-btn-font-size-lg: $font-size-lg !default; + +$input-btn-border-width: var(--#{$prefix}border-width) !default; +// scss-docs-end input-btn-variables + + +// Buttons +// +// For each of Bootstrap's buttons, define text, background, and border color. + +// scss-docs-start btn-variables +$btn-color: var(--#{$prefix}body-color) !default; +$btn-padding-y: $input-btn-padding-y !default; +$btn-padding-x: $input-btn-padding-x !default; +$btn-font-family: $input-btn-font-family !default; +$btn-font-size: $input-btn-font-size !default; +$btn-line-height: $input-btn-line-height !default; +$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping + +$btn-padding-y-sm: $input-btn-padding-y-sm !default; +$btn-padding-x-sm: $input-btn-padding-x-sm !default; +$btn-font-size-sm: $input-btn-font-size-sm !default; + +$btn-padding-y-lg: $input-btn-padding-y-lg !default; +$btn-padding-x-lg: $input-btn-padding-x-lg !default; +$btn-font-size-lg: $input-btn-font-size-lg !default; + +$btn-border-width: $input-btn-border-width !default; + +$btn-font-weight: $font-weight-normal !default; +$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default; +$btn-focus-width: $input-btn-focus-width !default; +$btn-focus-box-shadow: $input-btn-focus-box-shadow !default; +$btn-disabled-opacity: .65 !default; +$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default; + +$btn-link-color: var(--#{$prefix}link-color) !default; +$btn-link-hover-color: var(--#{$prefix}link-hover-color) !default; +$btn-link-disabled-color: $gray-600 !default; +$btn-link-focus-shadow-rgb: to-rgb(mix(color-contrast($link-color), $link-color, 15%)) !default; + +// Allows for customizing button radius independently from global border radius +$btn-border-radius: var(--#{$prefix}border-radius) !default; +$btn-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; +$btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; + +$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; + +$btn-hover-bg-shade-amount: 15% !default; +$btn-hover-bg-tint-amount: 15% !default; +$btn-hover-border-shade-amount: 20% !default; +$btn-hover-border-tint-amount: 10% !default; +$btn-active-bg-shade-amount: 20% !default; +$btn-active-bg-tint-amount: 20% !default; +$btn-active-border-shade-amount: 25% !default; +$btn-active-border-tint-amount: 10% !default; +// scss-docs-end btn-variables + + +// Forms + +// scss-docs-start form-text-variables +$form-text-margin-top: .25rem !default; +$form-text-font-size: $small-font-size !default; +$form-text-font-style: null !default; +$form-text-font-weight: null !default; +$form-text-color: var(--#{$prefix}secondary-color) !default; +// scss-docs-end form-text-variables + +// scss-docs-start form-label-variables +$form-label-margin-bottom: .5rem !default; +$form-label-font-size: null !default; +$form-label-font-style: null !default; +$form-label-font-weight: null !default; +$form-label-color: null !default; +// scss-docs-end form-label-variables + +// scss-docs-start form-input-variables +$input-padding-y: $input-btn-padding-y !default; +$input-padding-x: $input-btn-padding-x !default; +$input-font-family: $input-btn-font-family !default; +$input-font-size: $input-btn-font-size !default; +$input-font-weight: $font-weight-base !default; +$input-line-height: $input-btn-line-height !default; + +$input-padding-y-sm: $input-btn-padding-y-sm !default; +$input-padding-x-sm: $input-btn-padding-x-sm !default; +$input-font-size-sm: $input-btn-font-size-sm !default; + +$input-padding-y-lg: $input-btn-padding-y-lg !default; +$input-padding-x-lg: $input-btn-padding-x-lg !default; +$input-font-size-lg: $input-btn-font-size-lg !default; + +$input-bg: var(--#{$prefix}body-bg) !default; +$input-disabled-color: null !default; +$input-disabled-bg: var(--#{$prefix}secondary-bg) !default; +$input-disabled-border-color: null !default; + +$input-color: var(--#{$prefix}body-color) !default; +$input-border-color: var(--#{$prefix}border-color) !default; +$input-border-width: $input-btn-border-width !default; +$input-box-shadow: var(--#{$prefix}box-shadow-inset) !default; + +$input-border-radius: var(--#{$prefix}border-radius) !default; +$input-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; +$input-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; + +$input-focus-bg: $input-bg !default; +$input-focus-border-color: tint-color($component-active-bg, 50%) !default; +$input-focus-color: $input-color !default; +$input-focus-width: $input-btn-focus-width !default; +$input-focus-box-shadow: $input-btn-focus-box-shadow !default; + +$input-placeholder-color: var(--#{$prefix}secondary-color) !default; +$input-plaintext-color: var(--#{$prefix}body-color) !default; + +$input-height-border: calc(#{$input-border-width} * 2) !default; // stylelint-disable-line function-disallowed-list + +$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default; +$input-height-inner-half: add($input-line-height * .5em, $input-padding-y) !default; +$input-height-inner-quarter: add($input-line-height * .25em, $input-padding-y * .5) !default; + +$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default; +$input-height-sm: add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default; +$input-height-lg: add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default; + +$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; + +$form-color-width: 3rem !default; +// scss-docs-end form-input-variables + +// scss-docs-start form-check-variables +$form-check-input-width: 1em !default; +$form-check-min-height: $font-size-base * $line-height-base !default; +$form-check-padding-start: $form-check-input-width + .5em !default; +$form-check-margin-bottom: .125rem !default; +$form-check-label-color: null !default; +$form-check-label-cursor: null !default; +$form-check-transition: null !default; + +$form-check-input-active-filter: brightness(90%) !default; + +$form-check-input-bg: $input-bg !default; +$form-check-input-border: var(--#{$prefix}border-width) solid var(--#{$prefix}border-color) !default; +$form-check-input-border-radius: .25em !default; +$form-check-radio-border-radius: 50% !default; +$form-check-input-focus-border: $input-focus-border-color !default; +$form-check-input-focus-box-shadow: $focus-ring-box-shadow !default; + +$form-check-input-checked-color: $component-active-color !default; +$form-check-input-checked-bg-color: $component-active-bg !default; +$form-check-input-checked-border-color: $form-check-input-checked-bg-color !default; +$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='#{$form-check-input-checked-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/></svg>") !default; +$form-check-radio-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='2' fill='#{$form-check-input-checked-color}'/></svg>") !default; + +$form-check-input-indeterminate-color: $component-active-color !default; +$form-check-input-indeterminate-bg-color: $component-active-bg !default; +$form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default; +$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='#{$form-check-input-indeterminate-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/></svg>") !default; + +$form-check-input-disabled-opacity: .5 !default; +$form-check-label-disabled-opacity: $form-check-input-disabled-opacity !default; +$form-check-btn-check-disabled-opacity: $btn-disabled-opacity !default; + +$form-check-inline-margin-end: 1rem !default; +// scss-docs-end form-check-variables + +// scss-docs-start form-switch-variables +$form-switch-color: rgba($black, .25) !default; +$form-switch-width: 2em !default; +$form-switch-padding-start: $form-switch-width + .5em !default; +$form-switch-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>") !default; +$form-switch-border-radius: $form-switch-width !default; +$form-switch-transition: background-position .15s ease-in-out !default; + +$form-switch-focus-color: $input-focus-border-color !default; +$form-switch-focus-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-focus-color}'/></svg>") !default; + +$form-switch-checked-color: $component-active-color !default; +$form-switch-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>") !default; +$form-switch-checked-bg-position: right center !default; +// scss-docs-end form-switch-variables + +// scss-docs-start input-group-variables +$input-group-addon-padding-y: $input-padding-y !default; +$input-group-addon-padding-x: $input-padding-x !default; +$input-group-addon-font-weight: $input-font-weight !default; +$input-group-addon-color: $input-color !default; +$input-group-addon-bg: var(--#{$prefix}tertiary-bg) !default; +$input-group-addon-border-color: $input-border-color !default; +// scss-docs-end input-group-variables + +// scss-docs-start form-select-variables +$form-select-padding-y: $input-padding-y !default; +$form-select-padding-x: $input-padding-x !default; +$form-select-font-family: $input-font-family !default; +$form-select-font-size: $input-font-size !default; +$form-select-indicator-padding: $form-select-padding-x * 3 !default; // Extra padding for background-image +$form-select-font-weight: $input-font-weight !default; +$form-select-line-height: $input-line-height !default; +$form-select-color: $input-color !default; +$form-select-bg: $input-bg !default; +$form-select-disabled-color: null !default; +$form-select-disabled-bg: $input-disabled-bg !default; +$form-select-disabled-border-color: $input-disabled-border-color !default; +$form-select-bg-position: right $form-select-padding-x center !default; +$form-select-bg-size: 16px 12px !default; // In pixels because image dimensions +$form-select-indicator-color: $gray-800 !default; +$form-select-indicator: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default; + +$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default; +$form-select-feedback-icon-position: center right $form-select-indicator-padding !default; +$form-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default; + +$form-select-border-width: $input-border-width !default; +$form-select-border-color: $input-border-color !default; +$form-select-border-radius: $input-border-radius !default; +$form-select-box-shadow: var(--#{$prefix}box-shadow-inset) !default; + +$form-select-focus-border-color: $input-focus-border-color !default; +$form-select-focus-width: $input-focus-width !default; +$form-select-focus-box-shadow: 0 0 0 $form-select-focus-width $input-btn-focus-color !default; + +$form-select-padding-y-sm: $input-padding-y-sm !default; +$form-select-padding-x-sm: $input-padding-x-sm !default; +$form-select-font-size-sm: $input-font-size-sm !default; +$form-select-border-radius-sm: $input-border-radius-sm !default; + +$form-select-padding-y-lg: $input-padding-y-lg !default; +$form-select-padding-x-lg: $input-padding-x-lg !default; +$form-select-font-size-lg: $input-font-size-lg !default; +$form-select-border-radius-lg: $input-border-radius-lg !default; + +$form-select-transition: $input-transition !default; +// scss-docs-end form-select-variables + +// scss-docs-start form-range-variables +$form-range-track-width: 100% !default; +$form-range-track-height: .5rem !default; +$form-range-track-cursor: pointer !default; +$form-range-track-bg: var(--#{$prefix}secondary-bg) !default; +$form-range-track-border-radius: 1rem !default; +$form-range-track-box-shadow: var(--#{$prefix}box-shadow-inset) !default; + +$form-range-thumb-width: 1rem !default; +$form-range-thumb-height: $form-range-thumb-width !default; +$form-range-thumb-bg: $component-active-bg !default; +$form-range-thumb-border: 0 !default; +$form-range-thumb-border-radius: 1rem !default; +$form-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default; +$form-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default; +$form-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in Edge +$form-range-thumb-active-bg: tint-color($component-active-bg, 70%) !default; +$form-range-thumb-disabled-bg: var(--#{$prefix}secondary-color) !default; +$form-range-thumb-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; +// scss-docs-end form-range-variables + +// scss-docs-start form-file-variables +$form-file-button-color: $input-color !default; +$form-file-button-bg: var(--#{$prefix}tertiary-bg) !default; +$form-file-button-hover-bg: var(--#{$prefix}secondary-bg) !default; +// scss-docs-end form-file-variables + +// scss-docs-start form-floating-variables +$form-floating-height: add(3.5rem, $input-height-border) !default; +$form-floating-line-height: 1.25 !default; +$form-floating-padding-x: $input-padding-x !default; +$form-floating-padding-y: 1rem !default; +$form-floating-input-padding-t: 1.625rem !default; +$form-floating-input-padding-b: .625rem !default; +$form-floating-label-height: 1.5em !default; +$form-floating-label-opacity: .65 !default; +$form-floating-label-transform: scale(.85) translateY(-.5rem) translateX(.15rem) !default; +$form-floating-label-disabled-color: $gray-600 !default; +$form-floating-transition: opacity .1s ease-in-out, transform .1s ease-in-out !default; +// scss-docs-end form-floating-variables + +// Form validation + +// scss-docs-start form-feedback-variables +$form-feedback-margin-top: $form-text-margin-top !default; +$form-feedback-font-size: $form-text-font-size !default; +$form-feedback-font-style: $form-text-font-style !default; +$form-feedback-valid-color: $success !default; +$form-feedback-invalid-color: $danger !default; + +$form-feedback-icon-valid-color: $form-feedback-valid-color !default; +$form-feedback-icon-valid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#{$form-feedback-icon-valid-color}' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>") !default; +$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default; +$form-feedback-icon-invalid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='#{$form-feedback-icon-invalid-color}'><circle cx='6' cy='6' r='4.5'/><path stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/><circle cx='6' cy='8.2' r='.6' fill='#{$form-feedback-icon-invalid-color}' stroke='none'/></svg>") !default; +// scss-docs-end form-feedback-variables + +// scss-docs-start form-validation-colors +$form-valid-color: $form-feedback-valid-color !default; +$form-valid-border-color: $form-feedback-valid-color !default; +$form-invalid-color: $form-feedback-invalid-color !default; +$form-invalid-border-color: $form-feedback-invalid-color !default; +// scss-docs-end form-validation-colors + +// scss-docs-start form-validation-states +$form-validation-states: ( + "valid": ( + "color": var(--#{$prefix}form-valid-color), + "icon": $form-feedback-icon-valid, + "tooltip-color": #fff, + "tooltip-bg-color": var(--#{$prefix}success), + "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}success-rgb), $input-btn-focus-color-opacity), + "border-color": var(--#{$prefix}form-valid-border-color), + ), + "invalid": ( + "color": var(--#{$prefix}form-invalid-color), + "icon": $form-feedback-icon-invalid, + "tooltip-color": #fff, + "tooltip-bg-color": var(--#{$prefix}danger), + "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}danger-rgb), $input-btn-focus-color-opacity), + "border-color": var(--#{$prefix}form-invalid-border-color), + ) +) !default; +// scss-docs-end form-validation-states + +// Z-index master list +// +// Warning: Avoid customizing these values. They're used for a bird's eye view +// of components dependent on the z-axis and are designed to all work together. + +// scss-docs-start zindex-stack +$zindex-dropdown: 1000 !default; +$zindex-sticky: 1020 !default; +$zindex-fixed: 1030 !default; +$zindex-offcanvas-backdrop: 1040 !default; +$zindex-offcanvas: 1045 !default; +$zindex-modal-backdrop: 1050 !default; +$zindex-modal: 1055 !default; +$zindex-popover: 1070 !default; +$zindex-tooltip: 1080 !default; +$zindex-toast: 1090 !default; +// scss-docs-end zindex-stack + +// scss-docs-start zindex-levels-map +$zindex-levels: ( + n1: -1, + 0: 0, + 1: 1, + 2: 2, + 3: 3 +) !default; +// scss-docs-end zindex-levels-map + + +// Navs + +// scss-docs-start nav-variables +$nav-link-padding-y: .5rem !default; +$nav-link-padding-x: 1rem !default; +$nav-link-font-size: null !default; +$nav-link-font-weight: null !default; +$nav-link-color: var(--#{$prefix}link-color) !default; +$nav-link-hover-color: var(--#{$prefix}link-hover-color) !default; +$nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default; +$nav-link-disabled-color: var(--#{$prefix}secondary-color) !default; +$nav-link-focus-box-shadow: $focus-ring-box-shadow !default; + +$nav-tabs-border-color: var(--#{$prefix}border-color) !default; +$nav-tabs-border-width: var(--#{$prefix}border-width) !default; +$nav-tabs-border-radius: var(--#{$prefix}border-radius) !default; +$nav-tabs-link-hover-border-color: var(--#{$prefix}secondary-bg) var(--#{$prefix}secondary-bg) $nav-tabs-border-color !default; +$nav-tabs-link-active-color: var(--#{$prefix}emphasis-color) !default; +$nav-tabs-link-active-bg: var(--#{$prefix}body-bg) !default; +$nav-tabs-link-active-border-color: var(--#{$prefix}border-color) var(--#{$prefix}border-color) $nav-tabs-link-active-bg !default; + +$nav-pills-border-radius: var(--#{$prefix}border-radius) !default; +$nav-pills-link-active-color: $component-active-color !default; +$nav-pills-link-active-bg: $component-active-bg !default; + +$nav-underline-gap: 1rem !default; +$nav-underline-border-width: .125rem !default; +$nav-underline-link-active-color: var(--#{$prefix}emphasis-color) !default; +// scss-docs-end nav-variables + + +// Navbar + +// scss-docs-start navbar-variables +$navbar-padding-y: $spacer * .5 !default; +$navbar-padding-x: null !default; + +$navbar-nav-link-padding-x: .5rem !default; + +$navbar-brand-font-size: $font-size-lg !default; +// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link +$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default; +$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default; +$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) * .5 !default; +$navbar-brand-margin-end: 1rem !default; + +$navbar-toggler-padding-y: .25rem !default; +$navbar-toggler-padding-x: .75rem !default; +$navbar-toggler-font-size: $font-size-lg !default; +$navbar-toggler-border-radius: $btn-border-radius !default; +$navbar-toggler-focus-width: $btn-focus-width !default; +$navbar-toggler-transition: box-shadow .15s ease-in-out !default; + +$navbar-light-color: rgba(var(--#{$prefix}emphasis-color-rgb), .65) !default; +$navbar-light-hover-color: rgba(var(--#{$prefix}emphasis-color-rgb), .8) !default; +$navbar-light-active-color: rgba(var(--#{$prefix}emphasis-color-rgb), 1) !default; +$navbar-light-disabled-color: rgba(var(--#{$prefix}emphasis-color-rgb), .3) !default; +$navbar-light-icon-color: rgba($body-color, .75) !default; +$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default; +$navbar-light-toggler-border-color: rgba(var(--#{$prefix}emphasis-color-rgb), .15) !default; +$navbar-light-brand-color: $navbar-light-active-color !default; +$navbar-light-brand-hover-color: $navbar-light-active-color !default; +// scss-docs-end navbar-variables + +// scss-docs-start navbar-dark-variables +$navbar-dark-color: rgba($white, .55) !default; +$navbar-dark-hover-color: rgba($white, .75) !default; +$navbar-dark-active-color: $white !default; +$navbar-dark-disabled-color: rgba($white, .25) !default; +$navbar-dark-icon-color: $navbar-dark-color !default; +$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-dark-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default; +$navbar-dark-toggler-border-color: rgba($white, .1) !default; +$navbar-dark-brand-color: $navbar-dark-active-color !default; +$navbar-dark-brand-hover-color: $navbar-dark-active-color !default; +// scss-docs-end navbar-dark-variables + + +// Dropdowns +// +// Dropdown menu container and contents. + +// scss-docs-start dropdown-variables +$dropdown-min-width: 10rem !default; +$dropdown-padding-x: 0 !default; +$dropdown-padding-y: .5rem !default; +$dropdown-spacer: .125rem !default; +$dropdown-font-size: $font-size-base !default; +$dropdown-color: var(--#{$prefix}body-color) !default; +$dropdown-bg: var(--#{$prefix}body-bg) !default; +$dropdown-border-color: var(--#{$prefix}border-color-translucent) !default; +$dropdown-border-radius: var(--#{$prefix}border-radius) !default; +$dropdown-border-width: var(--#{$prefix}border-width) !default; +$dropdown-inner-border-radius: calc(#{$dropdown-border-radius} - #{$dropdown-border-width}) !default; // stylelint-disable-line function-disallowed-list +$dropdown-divider-bg: $dropdown-border-color !default; +$dropdown-divider-margin-y: $spacer * .5 !default; +$dropdown-box-shadow: var(--#{$prefix}box-shadow) !default; + +$dropdown-link-color: var(--#{$prefix}body-color) !default; +$dropdown-link-hover-color: $dropdown-link-color !default; +$dropdown-link-hover-bg: var(--#{$prefix}tertiary-bg) !default; + +$dropdown-link-active-color: $component-active-color !default; +$dropdown-link-active-bg: $component-active-bg !default; + +$dropdown-link-disabled-color: var(--#{$prefix}tertiary-color) !default; + +$dropdown-item-padding-y: $spacer * .25 !default; +$dropdown-item-padding-x: $spacer !default; + +$dropdown-header-color: $gray-600 !default; +$dropdown-header-padding-x: $dropdown-item-padding-x !default; +$dropdown-header-padding-y: $dropdown-padding-y !default; +// fusv-disable +$dropdown-header-padding: $dropdown-header-padding-y $dropdown-header-padding-x !default; // Deprecated in v5.2.0 +// fusv-enable +// scss-docs-end dropdown-variables + +// scss-docs-start dropdown-dark-variables +$dropdown-dark-color: $gray-300 !default; +$dropdown-dark-bg: $gray-800 !default; +$dropdown-dark-border-color: $dropdown-border-color !default; +$dropdown-dark-divider-bg: $dropdown-divider-bg !default; +$dropdown-dark-box-shadow: null !default; +$dropdown-dark-link-color: $dropdown-dark-color !default; +$dropdown-dark-link-hover-color: $white !default; +$dropdown-dark-link-hover-bg: rgba($white, .15) !default; +$dropdown-dark-link-active-color: $dropdown-link-active-color !default; +$dropdown-dark-link-active-bg: $dropdown-link-active-bg !default; +$dropdown-dark-link-disabled-color: $gray-500 !default; +$dropdown-dark-header-color: $gray-500 !default; +// scss-docs-end dropdown-dark-variables + + +// Pagination + +// scss-docs-start pagination-variables +$pagination-padding-y: .375rem !default; +$pagination-padding-x: .75rem !default; +$pagination-padding-y-sm: .25rem !default; +$pagination-padding-x-sm: .5rem !default; +$pagination-padding-y-lg: .75rem !default; +$pagination-padding-x-lg: 1.5rem !default; + +$pagination-font-size: $font-size-base !default; + +$pagination-color: var(--#{$prefix}link-color) !default; +$pagination-bg: var(--#{$prefix}body-bg) !default; +$pagination-border-radius: var(--#{$prefix}border-radius) !default; +$pagination-border-width: var(--#{$prefix}border-width) !default; +$pagination-margin-start: calc(#{$pagination-border-width} * -1) !default; // stylelint-disable-line function-disallowed-list +$pagination-border-color: var(--#{$prefix}border-color) !default; + +$pagination-focus-color: var(--#{$prefix}link-hover-color) !default; +$pagination-focus-bg: var(--#{$prefix}secondary-bg) !default; +$pagination-focus-box-shadow: $focus-ring-box-shadow !default; +$pagination-focus-outline: 0 !default; + +$pagination-hover-color: var(--#{$prefix}link-hover-color) !default; +$pagination-hover-bg: var(--#{$prefix}tertiary-bg) !default; +$pagination-hover-border-color: var(--#{$prefix}border-color) !default; // Todo in v6: remove this? + +$pagination-active-color: $component-active-color !default; +$pagination-active-bg: $component-active-bg !default; +$pagination-active-border-color: $component-active-bg !default; + +$pagination-disabled-color: var(--#{$prefix}secondary-color) !default; +$pagination-disabled-bg: var(--#{$prefix}secondary-bg) !default; +$pagination-disabled-border-color: var(--#{$prefix}border-color) !default; + +$pagination-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; + +$pagination-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; +$pagination-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; +// scss-docs-end pagination-variables + + +// Placeholders + +// scss-docs-start placeholders +$placeholder-opacity-max: .5 !default; +$placeholder-opacity-min: .2 !default; +// scss-docs-end placeholders + +// Cards + +// scss-docs-start card-variables +$card-spacer-y: $spacer !default; +$card-spacer-x: $spacer !default; +$card-title-spacer-y: $spacer * .5 !default; +$card-title-color: null !default; +$card-subtitle-color: null !default; +$card-border-width: var(--#{$prefix}border-width) !default; +$card-border-color: var(--#{$prefix}border-color-translucent) !default; +$card-border-radius: var(--#{$prefix}border-radius) !default; +$card-box-shadow: null !default; +$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default; +$card-cap-padding-y: $card-spacer-y * .5 !default; +$card-cap-padding-x: $card-spacer-x !default; +$card-cap-bg: rgba(var(--#{$prefix}body-color-rgb), .03) !default; +$card-cap-color: null !default; +$card-height: null !default; +$card-color: null !default; +$card-bg: var(--#{$prefix}body-bg) !default; +$card-img-overlay-padding: $spacer !default; +$card-group-margin: $grid-gutter-width * .5 !default; +// scss-docs-end card-variables + +// Accordion + +// scss-docs-start accordion-variables +$accordion-padding-y: 1rem !default; +$accordion-padding-x: 1.25rem !default; +$accordion-color: var(--#{$prefix}body-color) !default; +$accordion-bg: var(--#{$prefix}body-bg) !default; +$accordion-border-width: var(--#{$prefix}border-width) !default; +$accordion-border-color: var(--#{$prefix}border-color) !default; +$accordion-border-radius: var(--#{$prefix}border-radius) !default; +$accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default; + +$accordion-body-padding-y: $accordion-padding-y !default; +$accordion-body-padding-x: $accordion-padding-x !default; + +$accordion-button-padding-y: $accordion-padding-y !default; +$accordion-button-padding-x: $accordion-padding-x !default; +$accordion-button-color: var(--#{$prefix}body-color) !default; +$accordion-button-bg: var(--#{$prefix}accordion-bg) !default; +$accordion-transition: $btn-transition, border-radius .15s ease !default; +$accordion-button-active-bg: var(--#{$prefix}primary-bg-subtle) !default; +$accordion-button-active-color: var(--#{$prefix}primary-text-emphasis) !default; + +// fusv-disable +$accordion-button-focus-border-color: $input-focus-border-color !default; // Deprecated in v5.3.3 +// fusv-enable +$accordion-button-focus-box-shadow: $btn-focus-box-shadow !default; + +$accordion-icon-width: 1.25rem !default; +$accordion-icon-color: $body-color !default; +$accordion-icon-active-color: $primary-text-emphasis !default; +$accordion-icon-transition: transform .2s ease-in-out !default; +$accordion-icon-transform: rotate(-180deg) !default; + +$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-color}' stroke-linecap='round' stroke-linejoin='round'><path d='M2 5L8 11L14 5'/></svg>") !default; +$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-active-color}' stroke-linecap='round' stroke-linejoin='round'><path d='M2 5L8 11L14 5'/></svg>") !default; +// scss-docs-end accordion-variables + +// Tooltips + +// scss-docs-start tooltip-variables +$tooltip-font-size: $font-size-sm !default; +$tooltip-max-width: 200px !default; +$tooltip-color: var(--#{$prefix}body-bg) !default; +$tooltip-bg: var(--#{$prefix}emphasis-color) !default; +$tooltip-border-radius: var(--#{$prefix}border-radius) !default; +$tooltip-opacity: .9 !default; +$tooltip-padding-y: $spacer * .25 !default; +$tooltip-padding-x: $spacer * .5 !default; +$tooltip-margin: null !default; // TODO: remove this in v6 + +$tooltip-arrow-width: .8rem !default; +$tooltip-arrow-height: .4rem !default; +// fusv-disable +$tooltip-arrow-color: null !default; // Deprecated in Bootstrap 5.2.0 for CSS variables +// fusv-enable +// scss-docs-end tooltip-variables + +// Form tooltips must come after regular tooltips +// scss-docs-start tooltip-feedback-variables +$form-feedback-tooltip-padding-y: $tooltip-padding-y !default; +$form-feedback-tooltip-padding-x: $tooltip-padding-x !default; +$form-feedback-tooltip-font-size: $tooltip-font-size !default; +$form-feedback-tooltip-line-height: null !default; +$form-feedback-tooltip-opacity: $tooltip-opacity !default; +$form-feedback-tooltip-border-radius: $tooltip-border-radius !default; +// scss-docs-end tooltip-feedback-variables + + +// Popovers + +// scss-docs-start popover-variables +$popover-font-size: $font-size-sm !default; +$popover-bg: var(--#{$prefix}body-bg) !default; +$popover-max-width: 276px !default; +$popover-border-width: var(--#{$prefix}border-width) !default; +$popover-border-color: var(--#{$prefix}border-color-translucent) !default; +$popover-border-radius: var(--#{$prefix}border-radius-lg) !default; +$popover-inner-border-radius: calc(#{$popover-border-radius} - #{$popover-border-width}) !default; // stylelint-disable-line function-disallowed-list +$popover-box-shadow: var(--#{$prefix}box-shadow) !default; + +$popover-header-font-size: $font-size-base !default; +$popover-header-bg: var(--#{$prefix}secondary-bg) !default; +$popover-header-color: $headings-color !default; +$popover-header-padding-y: .5rem !default; +$popover-header-padding-x: $spacer !default; + +$popover-body-color: var(--#{$prefix}body-color) !default; +$popover-body-padding-y: $spacer !default; +$popover-body-padding-x: $spacer !default; + +$popover-arrow-width: 1rem !default; +$popover-arrow-height: .5rem !default; +// scss-docs-end popover-variables + +// fusv-disable +// Deprecated in Bootstrap 5.2.0 for CSS variables +$popover-arrow-color: $popover-bg !default; +$popover-arrow-outer-color: var(--#{$prefix}border-color-translucent) !default; +// fusv-enable + + +// Toasts + +// scss-docs-start toast-variables +$toast-max-width: 350px !default; +$toast-padding-x: .75rem !default; +$toast-padding-y: .5rem !default; +$toast-font-size: .875rem !default; +$toast-color: null !default; +$toast-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default; +$toast-border-width: var(--#{$prefix}border-width) !default; +$toast-border-color: var(--#{$prefix}border-color-translucent) !default; +$toast-border-radius: var(--#{$prefix}border-radius) !default; +$toast-box-shadow: var(--#{$prefix}box-shadow) !default; +$toast-spacing: $container-padding-x !default; + +$toast-header-color: var(--#{$prefix}secondary-color) !default; +$toast-header-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default; +$toast-header-border-color: $toast-border-color !default; +// scss-docs-end toast-variables + + +// Badges + +// scss-docs-start badge-variables +$badge-font-size: .75em !default; +$badge-font-weight: $font-weight-bold !default; +$badge-color: $white !default; +$badge-padding-y: .35em !default; +$badge-padding-x: .65em !default; +$badge-border-radius: var(--#{$prefix}border-radius) !default; +// scss-docs-end badge-variables + + +// Modals + +// scss-docs-start modal-variables +$modal-inner-padding: $spacer !default; + +$modal-footer-margin-between: .5rem !default; + +$modal-dialog-margin: .5rem !default; +$modal-dialog-margin-y-sm-up: 1.75rem !default; + +$modal-title-line-height: $line-height-base !default; + +$modal-content-color: null !default; +$modal-content-bg: var(--#{$prefix}body-bg) !default; +$modal-content-border-color: var(--#{$prefix}border-color-translucent) !default; +$modal-content-border-width: var(--#{$prefix}border-width) !default; +$modal-content-border-radius: var(--#{$prefix}border-radius-lg) !default; +$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default; +$modal-content-box-shadow-xs: var(--#{$prefix}box-shadow-sm) !default; +$modal-content-box-shadow-sm-up: var(--#{$prefix}box-shadow) !default; + +$modal-backdrop-bg: $black !default; +$modal-backdrop-opacity: .5 !default; + +$modal-header-border-color: var(--#{$prefix}border-color) !default; +$modal-header-border-width: $modal-content-border-width !default; +$modal-header-padding-y: $modal-inner-padding !default; +$modal-header-padding-x: $modal-inner-padding !default; +$modal-header-padding: $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility + +$modal-footer-bg: null !default; +$modal-footer-border-color: $modal-header-border-color !default; +$modal-footer-border-width: $modal-header-border-width !default; + +$modal-sm: 300px !default; +$modal-md: 500px !default; +$modal-lg: 800px !default; +$modal-xl: 1140px !default; + +$modal-fade-transform: translate(0, -50px) !default; +$modal-show-transform: none !default; +$modal-transition: transform .3s ease-out !default; +$modal-scale-transform: scale(1.02) !default; +// scss-docs-end modal-variables + + +// Alerts +// +// Define alert colors, border radius, and padding. + +// scss-docs-start alert-variables +$alert-padding-y: $spacer !default; +$alert-padding-x: $spacer !default; +$alert-margin-bottom: 1rem !default; +$alert-border-radius: var(--#{$prefix}border-radius) !default; +$alert-link-font-weight: $font-weight-bold !default; +$alert-border-width: var(--#{$prefix}border-width) !default; +$alert-dismissible-padding-r: $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side +// scss-docs-end alert-variables + +// fusv-disable +$alert-bg-scale: -80% !default; // Deprecated in v5.2.0, to be removed in v6 +$alert-border-scale: -70% !default; // Deprecated in v5.2.0, to be removed in v6 +$alert-color-scale: 40% !default; // Deprecated in v5.2.0, to be removed in v6 +// fusv-enable + +// Progress bars + +// scss-docs-start progress-variables +$progress-height: 1rem !default; +$progress-font-size: $font-size-base * .75 !default; +$progress-bg: var(--#{$prefix}secondary-bg) !default; +$progress-border-radius: var(--#{$prefix}border-radius) !default; +$progress-box-shadow: var(--#{$prefix}box-shadow-inset) !default; +$progress-bar-color: $white !default; +$progress-bar-bg: $primary !default; +$progress-bar-animation-timing: 1s linear infinite !default; +$progress-bar-transition: width .6s ease !default; +// scss-docs-end progress-variables + + +// List group + +// scss-docs-start list-group-variables +$list-group-color: var(--#{$prefix}body-color) !default; +$list-group-bg: var(--#{$prefix}body-bg) !default; +$list-group-border-color: var(--#{$prefix}border-color) !default; +$list-group-border-width: var(--#{$prefix}border-width) !default; +$list-group-border-radius: var(--#{$prefix}border-radius) !default; + +$list-group-item-padding-y: $spacer * .5 !default; +$list-group-item-padding-x: $spacer !default; +// fusv-disable +$list-group-item-bg-scale: -80% !default; // Deprecated in v5.3.0 +$list-group-item-color-scale: 40% !default; // Deprecated in v5.3.0 +// fusv-enable + +$list-group-hover-bg: var(--#{$prefix}tertiary-bg) !default; +$list-group-active-color: $component-active-color !default; +$list-group-active-bg: $component-active-bg !default; +$list-group-active-border-color: $list-group-active-bg !default; + +$list-group-disabled-color: var(--#{$prefix}secondary-color) !default; +$list-group-disabled-bg: $list-group-bg !default; + +$list-group-action-color: var(--#{$prefix}secondary-color) !default; +$list-group-action-hover-color: var(--#{$prefix}emphasis-color) !default; + +$list-group-action-active-color: var(--#{$prefix}body-color) !default; +$list-group-action-active-bg: var(--#{$prefix}secondary-bg) !default; +// scss-docs-end list-group-variables + + +// Image thumbnails + +// scss-docs-start thumbnail-variables +$thumbnail-padding: .25rem !default; +$thumbnail-bg: var(--#{$prefix}body-bg) !default; +$thumbnail-border-width: var(--#{$prefix}border-width) !default; +$thumbnail-border-color: var(--#{$prefix}border-color) !default; +$thumbnail-border-radius: var(--#{$prefix}border-radius) !default; +$thumbnail-box-shadow: var(--#{$prefix}box-shadow-sm) !default; +// scss-docs-end thumbnail-variables + + +// Figures + +// scss-docs-start figure-variables +$figure-caption-font-size: $small-font-size !default; +$figure-caption-color: var(--#{$prefix}secondary-color) !default; +// scss-docs-end figure-variables + + +// Breadcrumbs + +// scss-docs-start breadcrumb-variables +$breadcrumb-font-size: null !default; +$breadcrumb-padding-y: 0 !default; +$breadcrumb-padding-x: 0 !default; +$breadcrumb-item-padding-x: .5rem !default; +$breadcrumb-margin-bottom: 1rem !default; +$breadcrumb-bg: null !default; +$breadcrumb-divider-color: var(--#{$prefix}secondary-color) !default; +$breadcrumb-active-color: var(--#{$prefix}secondary-color) !default; +$breadcrumb-divider: quote("/") !default; +$breadcrumb-divider-flipped: $breadcrumb-divider !default; +$breadcrumb-border-radius: null !default; +// scss-docs-end breadcrumb-variables + +// Carousel + +// scss-docs-start carousel-variables +$carousel-control-color: $white !default; +$carousel-control-width: 15% !default; +$carousel-control-opacity: .5 !default; +$carousel-control-hover-opacity: .9 !default; +$carousel-control-transition: opacity .15s ease !default; + +$carousel-indicator-width: 30px !default; +$carousel-indicator-height: 3px !default; +$carousel-indicator-hit-area-height: 10px !default; +$carousel-indicator-spacer: 3px !default; +$carousel-indicator-opacity: .5 !default; +$carousel-indicator-active-bg: $white !default; +$carousel-indicator-active-opacity: 1 !default; +$carousel-indicator-transition: opacity .6s ease !default; + +$carousel-caption-width: 70% !default; +$carousel-caption-color: $white !default; +$carousel-caption-padding-y: 1.25rem !default; +$carousel-caption-spacer: 1.25rem !default; + +$carousel-control-icon-width: 2rem !default; + +$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/></svg>") !default; +$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/></svg>") !default; + +$carousel-transition-duration: .6s !default; +$carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`) +// scss-docs-end carousel-variables + +// scss-docs-start carousel-dark-variables +$carousel-dark-indicator-active-bg: $black !default; +$carousel-dark-caption-color: $black !default; +$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; +// scss-docs-end carousel-dark-variables + + +// Spinners + +// scss-docs-start spinner-variables +$spinner-width: 2rem !default; +$spinner-height: $spinner-width !default; +$spinner-vertical-align: -.125em !default; +$spinner-border-width: .25em !default; +$spinner-animation-speed: .75s !default; + +$spinner-width-sm: 1rem !default; +$spinner-height-sm: $spinner-width-sm !default; +$spinner-border-width-sm: .2em !default; +// scss-docs-end spinner-variables + + +// Close + +// scss-docs-start close-variables +$btn-close-width: 1em !default; +$btn-close-height: $btn-close-width !default; +$btn-close-padding-x: .25em !default; +$btn-close-padding-y: $btn-close-padding-x !default; +$btn-close-color: $black !default; +$btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>") !default; +$btn-close-focus-shadow: $focus-ring-box-shadow !default; +$btn-close-opacity: .5 !default; +$btn-close-hover-opacity: .75 !default; +$btn-close-focus-opacity: 1 !default; +$btn-close-disabled-opacity: .25 !default; +$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; +// scss-docs-end close-variables + + +// Offcanvas + +// scss-docs-start offcanvas-variables +$offcanvas-padding-y: $modal-inner-padding !default; +$offcanvas-padding-x: $modal-inner-padding !default; +$offcanvas-horizontal-width: 400px !default; +$offcanvas-vertical-height: 30vh !default; +$offcanvas-transition-duration: .3s !default; +$offcanvas-border-color: $modal-content-border-color !default; +$offcanvas-border-width: $modal-content-border-width !default; +$offcanvas-title-line-height: $modal-title-line-height !default; +$offcanvas-bg-color: var(--#{$prefix}body-bg) !default; +$offcanvas-color: var(--#{$prefix}body-color) !default; +$offcanvas-box-shadow: $modal-content-box-shadow-xs !default; +$offcanvas-backdrop-bg: $modal-backdrop-bg !default; +$offcanvas-backdrop-opacity: $modal-backdrop-opacity !default; +// scss-docs-end offcanvas-variables + +// Code + +$code-font-size: $small-font-size !default; +$code-color: $pink !default; + +$kbd-padding-y: .1875rem !default; +$kbd-padding-x: .375rem !default; +$kbd-font-size: $code-font-size !default; +$kbd-color: var(--#{$prefix}body-bg) !default; +$kbd-bg: var(--#{$prefix}body-color) !default; +$nested-kbd-font-weight: null !default; // Deprecated in v5.2.0, removing in v6 + +$pre-color: null !default; + +@import "variables-dark"; // TODO: can be removed safely in v6, only here to avoid breaking changes in v5.3 diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss new file mode 100644 index 00000000..52bd577e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss @@ -0,0 +1,62 @@ +@import "mixins/banner"; +@include bsBanner(Grid); + +$include-column-box-sizing: true !default; + +@import "functions"; +@import "variables"; +@import "variables-dark"; +@import "maps"; + +@import "mixins/breakpoints"; +@import "mixins/container"; +@import "mixins/grid"; +@import "mixins/utilities"; + +@import "vendor/rfs"; + +@import "containers"; +@import "grid"; + +@import "utilities"; +// Only use the utilities we need +// stylelint-disable-next-line scss/dollar-variable-default +$utilities: map-get-multiple( + $utilities, + ( + "display", + "order", + "flex", + "flex-direction", + "flex-grow", + "flex-shrink", + "flex-wrap", + "justify-content", + "align-items", + "align-content", + "align-self", + "margin", + "margin-x", + "margin-y", + "margin-top", + "margin-end", + "margin-bottom", + "margin-start", + "negative-margin", + "negative-margin-x", + "negative-margin-y", + "negative-margin-top", + "negative-margin-end", + "negative-margin-bottom", + "negative-margin-start", + "padding", + "padding-x", + "padding-y", + "padding-top", + "padding-end", + "padding-bottom", + "padding-start", + ) +); + +@import "utilities/api"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss new file mode 100644 index 00000000..5b69b955 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss @@ -0,0 +1,10 @@ +@import "mixins/banner"; +@include bsBanner(Reboot); + +@import "functions"; +@import "variables"; +@import "variables-dark"; +@import "maps"; +@import "mixins"; +@import "root"; +@import "reboot"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss new file mode 100644 index 00000000..99c4a359 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss @@ -0,0 +1,19 @@ +@import "mixins/banner"; +@include bsBanner(Utilities); + +// Configuration +@import "functions"; +@import "variables"; +@import "variables-dark"; +@import "maps"; +@import "mixins"; +@import "utilities"; + +// Layout & components +@import "root"; + +// Helpers +@import "helpers"; + +// Utilities +@import "utilities/api"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss new file mode 100644 index 00000000..449d7048 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss @@ -0,0 +1,52 @@ +@import "mixins/banner"; +@include bsBanner(""); + + +// scss-docs-start import-stack +// Configuration +@import "functions"; +@import "variables"; +@import "variables-dark"; +@import "maps"; +@import "mixins"; +@import "utilities"; + +// Layout & components +@import "root"; +@import "reboot"; +@import "type"; +@import "images"; +@import "containers"; +@import "grid"; +@import "tables"; +@import "forms"; +@import "buttons"; +@import "transitions"; +@import "dropdown"; +@import "button-group"; +@import "nav"; +@import "navbar"; +@import "card"; +@import "accordion"; +@import "breadcrumb"; +@import "pagination"; +@import "badge"; +@import "alert"; +@import "progress"; +@import "list-group"; +@import "close"; +@import "toasts"; +@import "modal"; +@import "tooltip"; +@import "popover"; +@import "carousel"; +@import "spinners"; +@import "offcanvas"; +@import "placeholders"; + +// Helpers +@import "helpers"; + +// Utilities +@import "utilities/api"; +// scss-docs-end import-stack diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss new file mode 100644 index 00000000..2cf04704 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss @@ -0,0 +1,95 @@ +.form-floating { + position: relative; + + > .form-control, + > .form-control-plaintext, + > .form-select { + height: $form-floating-height; + min-height: $form-floating-height; + line-height: $form-floating-line-height; + } + + > label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + height: 100%; // allow textareas + padding: $form-floating-padding-y $form-floating-padding-x; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: $input-border-width solid transparent; // Required for aligning label's text with the input as it affects inner box model + transform-origin: 0 0; + @include transition($form-floating-transition); + } + + > .form-control, + > .form-control-plaintext { + padding: $form-floating-padding-y $form-floating-padding-x; + + &::placeholder { + color: transparent; + } + + &:focus, + &:not(:placeholder-shown) { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped + &:-webkit-autofill { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + } + + > .form-select { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + + > .form-control:focus, + > .form-control:not(:placeholder-shown), + > .form-control-plaintext, + > .form-select { + ~ label { + color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); + transform: $form-floating-label-transform; + + &::after { + position: absolute; + inset: $form-floating-padding-y ($form-floating-padding-x * .5); + z-index: -1; + height: $form-floating-label-height; + content: ""; + background-color: $input-bg; + @include border-radius($input-border-radius); + } + } + } + // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped + > .form-control:-webkit-autofill { + ~ label { + color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); + transform: $form-floating-label-transform; + } + } + + > .form-control-plaintext { + ~ label { + border-width: $input-border-width 0; // Required to properly position label text - as explained above + } + } + + > :disabled ~ label, + > .form-control:disabled ~ label { // Required for `.form-control`s because of specificity + color: $form-floating-label-disabled-color; + + &::after { + background-color: $input-disabled-bg; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss new file mode 100644 index 00000000..8a1b639d --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss @@ -0,0 +1,189 @@ +// +// Check/radio +// + +.form-check { + display: block; + min-height: $form-check-min-height; + padding-left: $form-check-padding-start; + margin-bottom: $form-check-margin-bottom; + + .form-check-input { + float: left; + margin-left: $form-check-padding-start * -1; + } +} + +.form-check-reverse { + padding-right: $form-check-padding-start; + padding-left: 0; + text-align: right; + + .form-check-input { + float: right; + margin-right: $form-check-padding-start * -1; + margin-left: 0; + } +} + +.form-check-input { + --#{$prefix}form-check-bg: #{$form-check-input-bg}; + + flex-shrink: 0; + width: $form-check-input-width; + height: $form-check-input-width; + margin-top: ($line-height-base - $form-check-input-width) * .5; // line-height minus check height + vertical-align: top; + appearance: none; + background-color: var(--#{$prefix}form-check-bg); + background-image: var(--#{$prefix}form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: $form-check-input-border; + print-color-adjust: exact; // Keep themed appearance for print + @include transition($form-check-transition); + + &[type="checkbox"] { + @include border-radius($form-check-input-border-radius); + } + + &[type="radio"] { + // stylelint-disable-next-line property-disallowed-list + border-radius: $form-check-radio-border-radius; + } + + &:active { + filter: $form-check-input-active-filter; + } + + &:focus { + border-color: $form-check-input-focus-border; + outline: 0; + box-shadow: $form-check-input-focus-box-shadow; + } + + &:checked { + background-color: $form-check-input-checked-bg-color; + border-color: $form-check-input-checked-border-color; + + &[type="checkbox"] { + @if $enable-gradients { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}, var(--#{$prefix}gradient); + } @else { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}; + } + } + + &[type="radio"] { + @if $enable-gradients { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}, var(--#{$prefix}gradient); + } @else { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}; + } + } + } + + &[type="checkbox"]:indeterminate { + background-color: $form-check-input-indeterminate-bg-color; + border-color: $form-check-input-indeterminate-border-color; + + @if $enable-gradients { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}, var(--#{$prefix}gradient); + } @else { + --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}; + } + } + + &:disabled { + pointer-events: none; + filter: none; + opacity: $form-check-input-disabled-opacity; + } + + // Use disabled attribute in addition of :disabled pseudo-class + // See: https://github.com/twbs/bootstrap/issues/28247 + &[disabled], + &:disabled { + ~ .form-check-label { + cursor: default; + opacity: $form-check-label-disabled-opacity; + } + } +} + +.form-check-label { + color: $form-check-label-color; + cursor: $form-check-label-cursor; +} + +// +// Switch +// + +.form-switch { + padding-left: $form-switch-padding-start; + + .form-check-input { + --#{$prefix}form-switch-bg: #{escape-svg($form-switch-bg-image)}; + + width: $form-switch-width; + margin-left: $form-switch-padding-start * -1; + background-image: var(--#{$prefix}form-switch-bg); + background-position: left center; + @include border-radius($form-switch-border-radius, 0); + @include transition($form-switch-transition); + + &:focus { + --#{$prefix}form-switch-bg: #{escape-svg($form-switch-focus-bg-image)}; + } + + &:checked { + background-position: $form-switch-checked-bg-position; + + @if $enable-gradients { + --#{$prefix}form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}, var(--#{$prefix}gradient); + } @else { + --#{$prefix}form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}; + } + } + } + + &.form-check-reverse { + padding-right: $form-switch-padding-start; + padding-left: 0; + + .form-check-input { + margin-right: $form-switch-padding-start * -1; + margin-left: 0; + } + } +} + +.form-check-inline { + display: inline-block; + margin-right: $form-check-inline-margin-end; +} + +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; + + &[disabled], + &:disabled { + + .btn { + pointer-events: none; + filter: none; + opacity: $form-check-btn-check-disabled-opacity; + } + } +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .form-switch .form-check-input:not(:checked):not(:focus) { + --#{$prefix}form-switch-bg: #{escape-svg($form-switch-bg-image-dark)}; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss new file mode 100644 index 00000000..67ae5f4f --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss @@ -0,0 +1,214 @@ +// +// General form controls (plus a few specific high-level interventions) +// + +.form-control { + display: block; + width: 100%; + padding: $input-padding-y $input-padding-x; + font-family: $input-font-family; + @include font-size($input-font-size); + font-weight: $input-font-weight; + line-height: $input-line-height; + color: $input-color; + appearance: none; // Fix appearance for date inputs in Safari + background-color: $input-bg; + background-clip: padding-box; + border: $input-border-width solid $input-border-color; + + // Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS. + @include border-radius($input-border-radius, 0); + + @include box-shadow($input-box-shadow); + @include transition($input-transition); + + &[type="file"] { + overflow: hidden; // prevent pseudo element button overlap + + &:not(:disabled):not([readonly]) { + cursor: pointer; + } + } + + // Customize the `:focus` state to imitate native WebKit styles. + &:focus { + color: $input-focus-color; + background-color: $input-focus-bg; + border-color: $input-focus-border-color; + outline: 0; + @if $enable-shadows { + @include box-shadow($input-box-shadow, $input-focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $input-focus-box-shadow; + } + } + + &::-webkit-date-and-time-value { + // On Android Chrome, form-control's "width: 100%" makes the input width too small + // Tested under Android 11 / Chrome 89, Android 12 / Chrome 100, Android 13 / Chrome 109 + // + // On iOS Safari, form-control's "appearance: none" + "width: 100%" makes the input width too small + // Tested under iOS 16.2 / Safari 16.2 + min-width: 85px; // Seems to be a good minimum safe width + + // Add some height to date inputs on iOS + // https://github.com/twbs/bootstrap/issues/23307 + // TODO: we can remove this workaround once https://bugs.webkit.org/show_bug.cgi?id=198959 is resolved + // Multiply line-height by 1em if it has no unit + height: if(unit($input-line-height) == "", $input-line-height * 1em, $input-line-height); + + // Android Chrome type="date" is taller than the other inputs + // because of "margin: 1px 24px 1px 4px" inside the shadow DOM + // Tested under Android 11 / Chrome 89, Android 12 / Chrome 100, Android 13 / Chrome 109 + margin: 0; + } + + // Prevent excessive date input height in Webkit + // https://github.com/twbs/bootstrap/issues/34433 + &::-webkit-datetime-edit { + display: block; + padding: 0; + } + + // Placeholder + &::placeholder { + color: $input-placeholder-color; + // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526. + opacity: 1; + } + + // Disabled inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &:disabled { + color: $input-disabled-color; + background-color: $input-disabled-bg; + border-color: $input-disabled-border-color; + // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. + opacity: 1; + } + + // File input buttons theming + &::file-selector-button { + padding: $input-padding-y $input-padding-x; + margin: (-$input-padding-y) (-$input-padding-x); + margin-inline-end: $input-padding-x; + color: $form-file-button-color; + @include gradient-bg($form-file-button-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: $input-border-width; + border-radius: 0; // stylelint-disable-line property-disallowed-list + @include transition($btn-transition); + } + + &:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: $form-file-button-hover-bg; + } +} + +// Readonly controls as plain text +// +// Apply class to a readonly input to make it appear like regular plain +// text (without any border, background color, focus indicator) + +.form-control-plaintext { + display: block; + width: 100%; + padding: $input-padding-y 0; + margin-bottom: 0; // match inputs if this class comes on inputs with default margins + line-height: $input-line-height; + color: $input-plaintext-color; + background-color: transparent; + border: solid transparent; + border-width: $input-border-width 0; + + &:focus { + outline: 0; + } + + &.form-control-sm, + &.form-control-lg { + padding-right: 0; + padding-left: 0; + } +} + +// Form control sizing +// +// Build on `.form-control` with modifier classes to decrease or increase the +// height and font-size of form controls. +// +// Repeated in `_input_group.scss` to avoid Sass extend issues. + +.form-control-sm { + min-height: $input-height-sm; + padding: $input-padding-y-sm $input-padding-x-sm; + @include font-size($input-font-size-sm); + @include border-radius($input-border-radius-sm); + + &::file-selector-button { + padding: $input-padding-y-sm $input-padding-x-sm; + margin: (-$input-padding-y-sm) (-$input-padding-x-sm); + margin-inline-end: $input-padding-x-sm; + } +} + +.form-control-lg { + min-height: $input-height-lg; + padding: $input-padding-y-lg $input-padding-x-lg; + @include font-size($input-font-size-lg); + @include border-radius($input-border-radius-lg); + + &::file-selector-button { + padding: $input-padding-y-lg $input-padding-x-lg; + margin: (-$input-padding-y-lg) (-$input-padding-x-lg); + margin-inline-end: $input-padding-x-lg; + } +} + +// Make sure textareas don't shrink too much when resized +// https://github.com/twbs/bootstrap/pull/29124 +// stylelint-disable selector-no-qualifying-type +textarea { + &.form-control { + min-height: $input-height; + } + + &.form-control-sm { + min-height: $input-height-sm; + } + + &.form-control-lg { + min-height: $input-height-lg; + } +} +// stylelint-enable selector-no-qualifying-type + +.form-control-color { + width: $form-color-width; + height: $input-height; + padding: $input-padding-y; + + &:not(:disabled):not([readonly]) { + cursor: pointer; + } + + &::-moz-color-swatch { + border: 0 !important; // stylelint-disable-line declaration-no-important + @include border-radius($input-border-radius); + } + + &::-webkit-color-swatch { + border: 0 !important; // stylelint-disable-line declaration-no-important + @include border-radius($input-border-radius); + } + + &.form-control-sm { height: $input-height-sm; } + &.form-control-lg { height: $input-height-lg; } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss new file mode 100644 index 00000000..4732213e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss @@ -0,0 +1,91 @@ +// Range +// +// Style range inputs the same across browsers. Vendor-specific rules for pseudo +// elements cannot be mixed. As such, there are no shared styles for focus or +// active states on prefixed selectors. + +.form-range { + width: 100%; + height: add($form-range-thumb-height, $form-range-thumb-focus-box-shadow-width * 2); + padding: 0; // Need to reset padding + appearance: none; + background-color: transparent; + + &:focus { + outline: 0; + + // Pseudo-elements must be split across multiple rulesets to have an effect. + // No box-shadow() mixin for focus accessibility. + &::-webkit-slider-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } + &::-moz-range-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } + } + + &::-moz-focus-outer { + border: 0; + } + + &::-webkit-slider-thumb { + width: $form-range-thumb-width; + height: $form-range-thumb-height; + margin-top: ($form-range-track-height - $form-range-thumb-height) * .5; // Webkit specific + appearance: none; + @include gradient-bg($form-range-thumb-bg); + border: $form-range-thumb-border; + @include border-radius($form-range-thumb-border-radius); + @include box-shadow($form-range-thumb-box-shadow); + @include transition($form-range-thumb-transition); + + &:active { + @include gradient-bg($form-range-thumb-active-bg); + } + } + + &::-webkit-slider-runnable-track { + width: $form-range-track-width; + height: $form-range-track-height; + color: transparent; // Why? + cursor: $form-range-track-cursor; + background-color: $form-range-track-bg; + border-color: transparent; + @include border-radius($form-range-track-border-radius); + @include box-shadow($form-range-track-box-shadow); + } + + &::-moz-range-thumb { + width: $form-range-thumb-width; + height: $form-range-thumb-height; + appearance: none; + @include gradient-bg($form-range-thumb-bg); + border: $form-range-thumb-border; + @include border-radius($form-range-thumb-border-radius); + @include box-shadow($form-range-thumb-box-shadow); + @include transition($form-range-thumb-transition); + + &:active { + @include gradient-bg($form-range-thumb-active-bg); + } + } + + &::-moz-range-track { + width: $form-range-track-width; + height: $form-range-track-height; + color: transparent; + cursor: $form-range-track-cursor; + background-color: $form-range-track-bg; + border-color: transparent; // Firefox specific? + @include border-radius($form-range-track-border-radius); + @include box-shadow($form-range-track-box-shadow); + } + + &:disabled { + pointer-events: none; + + &::-webkit-slider-thumb { + background-color: $form-range-thumb-disabled-bg; + } + + &::-moz-range-thumb { + background-color: $form-range-thumb-disabled-bg; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss new file mode 100644 index 00000000..69ace529 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss @@ -0,0 +1,80 @@ +// Select +// +// Replaces the browser default select with a custom one, mostly pulled from +// https://primer.github.io/. + +.form-select { + --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator)}; + + display: block; + width: 100%; + padding: $form-select-padding-y $form-select-indicator-padding $form-select-padding-y $form-select-padding-x; + font-family: $form-select-font-family; + @include font-size($form-select-font-size); + font-weight: $form-select-font-weight; + line-height: $form-select-line-height; + color: $form-select-color; + appearance: none; + background-color: $form-select-bg; + background-image: var(--#{$prefix}form-select-bg-img), var(--#{$prefix}form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: $form-select-bg-position; + background-size: $form-select-bg-size; + border: $form-select-border-width solid $form-select-border-color; + @include border-radius($form-select-border-radius, 0); + @include box-shadow($form-select-box-shadow); + @include transition($form-select-transition); + + &:focus { + border-color: $form-select-focus-border-color; + outline: 0; + @if $enable-shadows { + @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $form-select-focus-box-shadow; + } + } + + &[multiple], + &[size]:not([size="1"]) { + padding-right: $form-select-padding-x; + background-image: none; + } + + &:disabled { + color: $form-select-disabled-color; + background-color: $form-select-disabled-bg; + border-color: $form-select-disabled-border-color; + } + + // Remove outline from select box in FF + &:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 $form-select-color; + } +} + +.form-select-sm { + padding-top: $form-select-padding-y-sm; + padding-bottom: $form-select-padding-y-sm; + padding-left: $form-select-padding-x-sm; + @include font-size($form-select-font-size-sm); + @include border-radius($form-select-border-radius-sm); +} + +.form-select-lg { + padding-top: $form-select-padding-y-lg; + padding-bottom: $form-select-padding-y-lg; + padding-left: $form-select-padding-x-lg; + @include font-size($form-select-font-size-lg); + @include border-radius($form-select-border-radius-lg); +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .form-select { + --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator-dark)}; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss new file mode 100644 index 00000000..f080d1a2 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss @@ -0,0 +1,11 @@ +// +// Form text +// + +.form-text { + margin-top: $form-text-margin-top; + @include font-size($form-text-font-size); + font-style: $form-text-font-style; + font-weight: $form-text-font-weight; + color: $form-text-color; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss new file mode 100644 index 00000000..58e4d409 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss @@ -0,0 +1,132 @@ +// +// Base styles +// + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; // For form validation feedback + align-items: stretch; + width: 100%; + + > .form-control, + > .form-select, + > .form-floating { + position: relative; // For focus state's z-index + flex: 1 1 auto; + width: 1%; + min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size + } + + // Bring the "active" form control to the top of surrounding elements + > .form-control:focus, + > .form-select:focus, + > .form-floating:focus-within { + z-index: 5; + } + + // Ensure buttons are always above inputs for more visually pleasing borders. + // This isn't needed for `.input-group-text` since it shares the same border-color + // as our inputs. + .btn { + position: relative; + z-index: 2; + + &:focus { + z-index: 5; + } + } +} + + +// Textual addons +// +// Serves as a catch-all element for any text or radio/checkbox input you wish +// to prepend or append to an input. + +.input-group-text { + display: flex; + align-items: center; + padding: $input-group-addon-padding-y $input-group-addon-padding-x; + @include font-size($input-font-size); // Match inputs + font-weight: $input-group-addon-font-weight; + line-height: $input-line-height; + color: $input-group-addon-color; + text-align: center; + white-space: nowrap; + background-color: $input-group-addon-bg; + border: $input-border-width solid $input-group-addon-border-color; + @include border-radius($input-border-radius); +} + + +// Sizing +// +// Remix the default form control sizing classes into new ones for easier +// manipulation. + +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text, +.input-group-lg > .btn { + padding: $input-padding-y-lg $input-padding-x-lg; + @include font-size($input-font-size-lg); + @include border-radius($input-border-radius-lg); +} + +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text, +.input-group-sm > .btn { + padding: $input-padding-y-sm $input-padding-x-sm; + @include font-size($input-font-size-sm); + @include border-radius($input-border-radius-sm); +} + +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: $form-select-padding-x + $form-select-indicator-padding; +} + + +// Rounded corners +// +// These rulesets must come after the sizing ones to properly override sm and lg +// border-radius values when extending. They're more specific than we'd like +// with the `.input-group >` part, but without it, we cannot override the sizing. + +// stylelint-disable-next-line no-duplicate-selectors +.input-group { + &:not(.has-validation) { + > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), + > .dropdown-toggle:nth-last-child(n + 3), + > .form-floating:not(:last-child) > .form-control, + > .form-floating:not(:last-child) > .form-select { + @include border-end-radius(0); + } + } + + &.has-validation { + > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), + > .dropdown-toggle:nth-last-child(n + 4), + > .form-floating:nth-last-child(n + 3) > .form-control, + > .form-floating:nth-last-child(n + 3) > .form-select { + @include border-end-radius(0); + } + } + + $validation-messages: ""; + @each $state in map-keys($form-validation-states) { + $validation-messages: $validation-messages + ":not(." + unquote($state) + "-tooltip)" + ":not(." + unquote($state) + "-feedback)"; + } + + > :not(:first-child):not(.dropdown-menu)#{$validation-messages} { + margin-left: calc(#{$input-border-width} * -1); // stylelint-disable-line function-disallowed-list + @include border-start-radius(0); + } + + > .form-floating:not(:first-child) > .form-control, + > .form-floating:not(:first-child) > .form-select { + @include border-start-radius(0); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss new file mode 100644 index 00000000..39ecafcd --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss @@ -0,0 +1,36 @@ +// +// Labels +// + +.form-label { + margin-bottom: $form-label-margin-bottom; + @include font-size($form-label-font-size); + font-style: $form-label-font-style; + font-weight: $form-label-font-weight; + color: $form-label-color; +} + +// For use with horizontal and inline forms, when you need the label (or legend) +// text to align with the form controls. +.col-form-label { + padding-top: add($input-padding-y, $input-border-width); + padding-bottom: add($input-padding-y, $input-border-width); + margin-bottom: 0; // Override the `<legend>` default + @include font-size(inherit); // Override the `<legend>` default + font-style: $form-label-font-style; + font-weight: $form-label-font-weight; + line-height: $input-line-height; + color: $form-label-color; +} + +.col-form-label-lg { + padding-top: add($input-padding-y-lg, $input-border-width); + padding-bottom: add($input-padding-y-lg, $input-border-width); + @include font-size($input-font-size-lg); +} + +.col-form-label-sm { + padding-top: add($input-padding-y-sm, $input-border-width); + padding-bottom: add($input-padding-y-sm, $input-border-width); + @include font-size($input-font-size-sm); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss new file mode 100644 index 00000000..c48123a7 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss @@ -0,0 +1,12 @@ +// Form validation +// +// Provide feedback to users when form field values are valid or invalid. Works +// primarily for client-side validation via scoped `:invalid` and `:valid` +// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for +// server-side validation. + +// scss-docs-start form-validation-states-loop +@each $state, $data in $form-validation-states { + @include form-validation-state($state, $data...); +} +// scss-docs-end form-validation-states-loop diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss new file mode 100644 index 00000000..e92522a9 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss @@ -0,0 +1,3 @@ +.clearfix { + @include clearfix(); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss new file mode 100644 index 00000000..1a3a4cff --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss @@ -0,0 +1,7 @@ +// All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251 +@each $color, $value in $theme-colors { + .text-bg-#{$color} { + color: color-contrast($value) if($enable-important-utilities, !important, null); + background-color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss new file mode 100644 index 00000000..5f868578 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss @@ -0,0 +1,30 @@ +// All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251 +@each $color, $value in $theme-colors { + .link-#{$color} { + color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); + text-decoration-color: RGBA(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); + + @if $link-shade-percentage != 0 { + &:hover, + &:focus { + $hover-color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)); + color: RGBA(#{to-rgb($hover-color)}, var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); + text-decoration-color: RGBA(to-rgb($hover-color), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); + } + } + } +} + +// One-off special link helper as a bridge until v6 +.link-body-emphasis { + color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null); + text-decoration-color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null); + + @if $link-shade-percentage != 0 { + &:hover, + &:focus { + color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, .75)) if($enable-important-utilities, !important, null); + text-decoration-color: RGBA(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, .75)) if($enable-important-utilities, !important, null); + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss new file mode 100644 index 00000000..26508a8d --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss @@ -0,0 +1,5 @@ +.focus-ring:focus { + outline: 0; + // By default, there is no `--bs-focus-ring-x`, `--bs-focus-ring-y`, or `--bs-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values + box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss new file mode 100644 index 00000000..3f8bcb33 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss @@ -0,0 +1,25 @@ +.icon-link { + display: inline-flex; + gap: $icon-link-gap; + align-items: center; + text-decoration-color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, .5)); + text-underline-offset: $icon-link-underline-offset; + backface-visibility: hidden; + + > .bi { + flex-shrink: 0; + width: $icon-link-icon-size; + height: $icon-link-icon-size; + fill: currentcolor; + @include transition($icon-link-icon-transition); + } +} + +.icon-link-hover { + &:hover, + &:focus-visible { + > .bi { + transform: var(--#{$prefix}icon-link-transform, $icon-link-icon-transform); + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss new file mode 100644 index 00000000..59103d94 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss @@ -0,0 +1,36 @@ +// Shorthand + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: $zindex-fixed; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: $zindex-fixed; +} + +// Responsive sticky top and bottom +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .sticky#{$infix}-top { + position: sticky; + top: 0; + z-index: $zindex-sticky; + } + + .sticky#{$infix}-bottom { + position: sticky; + bottom: 0; + z-index: $zindex-sticky; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss new file mode 100644 index 00000000..b6a7654c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss @@ -0,0 +1,26 @@ +// Credit: Nicolas Gallagher and SUIT CSS. + +.ratio { + position: relative; + width: 100%; + + &::before { + display: block; + padding-top: var(--#{$prefix}aspect-ratio); + content: ""; + } + + > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} + +@each $key, $ratio in $aspect-ratios { + .ratio-#{$key} { + --#{$prefix}aspect-ratio: #{$ratio}; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss new file mode 100644 index 00000000..6cd237ae --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss @@ -0,0 +1,15 @@ +// scss-docs-start stacks +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; +} + +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch; +} +// scss-docs-end stacks diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss new file mode 100644 index 00000000..71a1c755 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss @@ -0,0 +1,15 @@ +// +// Stretched link +// + +.stretched-link { + &::#{$stretched-link-pseudo-element} { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $stretched-link-z-index; + content: ""; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss new file mode 100644 index 00000000..6421dac9 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss @@ -0,0 +1,7 @@ +// +// Text truncation +// + +.text-truncate { + @include text-truncate(); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss new file mode 100644 index 00000000..4760ff03 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss @@ -0,0 +1,8 @@ +// +// Visually hidden +// + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + @include visually-hidden(); +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss new file mode 100644 index 00000000..b6f9d42c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss @@ -0,0 +1,8 @@ +.vr { + display: inline-block; + align-self: stretch; + width: $vr-border-width; + min-height: 1em; + background-color: currentcolor; + opacity: $hr-opacity; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss new file mode 100644 index 00000000..fb524af1 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss @@ -0,0 +1,18 @@ +@include deprecate("`alert-variant()`", "v5.3.0", "v6.0.0"); + +// scss-docs-start alert-variant-mixin +@mixin alert-variant($background, $border, $color) { + --#{$prefix}alert-color: #{$color}; + --#{$prefix}alert-bg: #{$background}; + --#{$prefix}alert-border-color: #{$border}; + --#{$prefix}alert-link-color: #{shade-color($color, 20%)}; + + @if $enable-gradients { + background-image: var(--#{$prefix}gradient); + } + + .alert-link { + color: var(--#{$prefix}alert-link-color); + } +} +// scss-docs-end alert-variant-mixin diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss new file mode 100644 index 00000000..9705ae9e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss @@ -0,0 +1,14 @@ +// Shared between modals and offcanvases +@mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) { + position: fixed; + top: 0; + left: 0; + z-index: $zindex; + width: 100vw; + height: 100vh; + background-color: $backdrop-bg; + + // Fade for backdrop + &.fade { opacity: 0; } + &.show { opacity: $backdrop-opacity; } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss new file mode 100644 index 00000000..20c2fd12 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss @@ -0,0 +1,7 @@ +@mixin bsBanner($file) { + /*! + * Bootstrap #{$file} v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss new file mode 100644 index 00000000..616decbc --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss @@ -0,0 +1,78 @@ +// stylelint-disable property-disallowed-list +// Single side border-radius + +// Helper function to replace negative values with 0 +@function valid-radius($radius) { + $return: (); + @each $value in $radius { + @if type-of($value) == number { + $return: append($return, max($value, 0)); + } @else { + $return: append($return, $value); + } + } + @return $return; +} + +// scss-docs-start border-radius-mixins +@mixin border-radius($radius: $border-radius, $fallback-border-radius: false) { + @if $enable-rounded { + border-radius: valid-radius($radius); + } + @else if $fallback-border-radius != false { + border-radius: $fallback-border-radius; + } +} + +@mixin border-top-radius($radius: $border-radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + border-top-right-radius: valid-radius($radius); + } +} + +@mixin border-end-radius($radius: $border-radius) { + @if $enable-rounded { + border-top-right-radius: valid-radius($radius); + border-bottom-right-radius: valid-radius($radius); + } +} + +@mixin border-bottom-radius($radius: $border-radius) { + @if $enable-rounded { + border-bottom-right-radius: valid-radius($radius); + border-bottom-left-radius: valid-radius($radius); + } +} + +@mixin border-start-radius($radius: $border-radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + border-bottom-left-radius: valid-radius($radius); + } +} + +@mixin border-top-start-radius($radius: $border-radius) { + @if $enable-rounded { + border-top-left-radius: valid-radius($radius); + } +} + +@mixin border-top-end-radius($radius: $border-radius) { + @if $enable-rounded { + border-top-right-radius: valid-radius($radius); + } +} + +@mixin border-bottom-end-radius($radius: $border-radius) { + @if $enable-rounded { + border-bottom-right-radius: valid-radius($radius); + } +} + +@mixin border-bottom-start-radius($radius: $border-radius) { + @if $enable-rounded { + border-bottom-left-radius: valid-radius($radius); + } +} +// scss-docs-end border-radius-mixins diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss new file mode 100644 index 00000000..4172541f --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss @@ -0,0 +1,18 @@ +@mixin box-shadow($shadow...) { + @if $enable-shadows { + $result: (); + + @each $value in $shadow { + @if $value != null { + $result: append($result, $value, "comma"); + } + @if $value == none and length($shadow) > 1 { + @warn "The keyword 'none' must be used as a single argument."; + } + } + + @if (length($result) > 0) { + box-shadow: $result; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss new file mode 100644 index 00000000..286be893 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss @@ -0,0 +1,127 @@ +// Breakpoint viewport sizes and media queries. +// +// Breakpoints are defined as a map of (name: minimum width), order from small to large: +// +// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px) +// +// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. + +// Name of the next breakpoint, or null for the last breakpoint. +// +// >> breakpoint-next(sm) +// md +// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) +// md +// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl xxl)) +// md +@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { + $n: index($breakpoint-names, $name); + @if not $n { + @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; + } + @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); +} + +// Minimum breakpoint width. Null for the smallest (first) breakpoint. +// +// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) +// 576px +@function breakpoint-min($name, $breakpoints: $grid-breakpoints) { + $min: map-get($breakpoints, $name); + @return if($min != 0, $min, null); +} + +// Maximum breakpoint width. +// The maximum value is reduced by 0.02px to work around the limitations of +// `min-` and `max-` prefixes and viewports with fractional widths. +// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max +// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. +// See https://bugs.webkit.org/show_bug.cgi?id=178261 +// +// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) +// 767.98px +@function breakpoint-max($name, $breakpoints: $grid-breakpoints) { + $max: map-get($breakpoints, $name); + @return if($max and $max > 0, $max - .02, null); +} + +// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front. +// Useful for making responsive utilities. +// +// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) +// "" (Returns a blank string) +// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)) +// "-sm" +@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { + @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}"); +} + +// Media of at least the minimum breakpoint width. No query for the smallest breakpoint. +// Makes the @content apply to the given breakpoint and wider. +@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + @if $min { + @media (min-width: $min) { + @content; + } + } @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. No query for the largest breakpoint. +// Makes the @content apply to the given breakpoint and narrower. +@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { + $max: breakpoint-max($name, $breakpoints); + @if $max { + @media (max-width: $max) { + @content; + } + } @else { + @content; + } +} + +// Media that spans multiple breakpoint widths. +// Makes the @content apply between the min and max breakpoints +@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($lower, $breakpoints); + $max: breakpoint-max($upper, $breakpoints); + + @if $min != null and $max != null { + @media (min-width: $min) and (max-width: $max) { + @content; + } + } @else if $max == null { + @include media-breakpoint-up($lower, $breakpoints) { + @content; + } + } @else if $min == null { + @include media-breakpoint-down($upper, $breakpoints) { + @content; + } + } +} + +// Media between the breakpoint's minimum and maximum widths. +// No minimum for the smallest breakpoint, and no maximum for the largest one. +// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. +@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + $next: breakpoint-next($name, $breakpoints); + $max: breakpoint-max($next, $breakpoints); + + @if $min != null and $max != null { + @media (min-width: $min) and (max-width: $max) { + @content; + } + } @else if $max == null { + @include media-breakpoint-up($name, $breakpoints) { + @content; + } + } @else if $min == null { + @include media-breakpoint-down($next, $breakpoints) { + @content; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss new file mode 100644 index 00000000..cf087fda --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss @@ -0,0 +1,70 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +// scss-docs-start btn-variant-mixin +@mixin button-variant( + $background, + $border, + $color: color-contrast($background), + $hover-background: if($color == $color-contrast-light, shade-color($background, $btn-hover-bg-shade-amount), tint-color($background, $btn-hover-bg-tint-amount)), + $hover-border: if($color == $color-contrast-light, shade-color($border, $btn-hover-border-shade-amount), tint-color($border, $btn-hover-border-tint-amount)), + $hover-color: color-contrast($hover-background), + $active-background: if($color == $color-contrast-light, shade-color($background, $btn-active-bg-shade-amount), tint-color($background, $btn-active-bg-tint-amount)), + $active-border: if($color == $color-contrast-light, shade-color($border, $btn-active-border-shade-amount), tint-color($border, $btn-active-border-tint-amount)), + $active-color: color-contrast($active-background), + $disabled-background: $background, + $disabled-border: $border, + $disabled-color: color-contrast($disabled-background) +) { + --#{$prefix}btn-color: #{$color}; + --#{$prefix}btn-bg: #{$background}; + --#{$prefix}btn-border-color: #{$border}; + --#{$prefix}btn-hover-color: #{$hover-color}; + --#{$prefix}btn-hover-bg: #{$hover-background}; + --#{$prefix}btn-hover-border-color: #{$hover-border}; + --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix($color, $border, 15%))}; + --#{$prefix}btn-active-color: #{$active-color}; + --#{$prefix}btn-active-bg: #{$active-background}; + --#{$prefix}btn-active-border-color: #{$active-border}; + --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow}; + --#{$prefix}btn-disabled-color: #{$disabled-color}; + --#{$prefix}btn-disabled-bg: #{$disabled-background}; + --#{$prefix}btn-disabled-border-color: #{$disabled-border}; +} +// scss-docs-end btn-variant-mixin + +// scss-docs-start btn-outline-variant-mixin +@mixin button-outline-variant( + $color, + $color-hover: color-contrast($color), + $active-background: $color, + $active-border: $color, + $active-color: color-contrast($active-background) +) { + --#{$prefix}btn-color: #{$color}; + --#{$prefix}btn-border-color: #{$color}; + --#{$prefix}btn-hover-color: #{$color-hover}; + --#{$prefix}btn-hover-bg: #{$active-background}; + --#{$prefix}btn-hover-border-color: #{$active-border}; + --#{$prefix}btn-focus-shadow-rgb: #{to-rgb($color)}; + --#{$prefix}btn-active-color: #{$active-color}; + --#{$prefix}btn-active-bg: #{$active-background}; + --#{$prefix}btn-active-border-color: #{$active-border}; + --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow}; + --#{$prefix}btn-disabled-color: #{$color}; + --#{$prefix}btn-disabled-bg: transparent; + --#{$prefix}btn-disabled-border-color: #{$color}; + --#{$prefix}gradient: none; +} +// scss-docs-end btn-outline-variant-mixin + +// scss-docs-start btn-size-mixin +@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) { + --#{$prefix}btn-padding-y: #{$padding-y}; + --#{$prefix}btn-padding-x: #{$padding-x}; + @include rfs($font-size, --#{$prefix}btn-font-size); + --#{$prefix}btn-border-radius: #{$border-radius}; +} +// scss-docs-end btn-size-mixin diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss new file mode 100644 index 00000000..be731165 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss @@ -0,0 +1,69 @@ +// scss-docs-start caret-mixins +@mixin caret-down($width: $caret-width) { + border-top: $width solid; + border-right: $width solid transparent; + border-bottom: 0; + border-left: $width solid transparent; +} + +@mixin caret-up($width: $caret-width) { + border-top: 0; + border-right: $width solid transparent; + border-bottom: $width solid; + border-left: $width solid transparent; +} + +@mixin caret-end($width: $caret-width) { + border-top: $width solid transparent; + border-right: 0; + border-bottom: $width solid transparent; + border-left: $width solid; +} + +@mixin caret-start($width: $caret-width) { + border-top: $width solid transparent; + border-right: $width solid; + border-bottom: $width solid transparent; +} + +@mixin caret( + $direction: down, + $width: $caret-width, + $spacing: $caret-spacing, + $vertical-align: $caret-vertical-align +) { + @if $enable-caret { + &::after { + display: inline-block; + margin-left: $spacing; + vertical-align: $vertical-align; + content: ""; + @if $direction == down { + @include caret-down($width); + } @else if $direction == up { + @include caret-up($width); + } @else if $direction == end { + @include caret-end($width); + } + } + + @if $direction == start { + &::after { + display: none; + } + + &::before { + display: inline-block; + margin-right: $spacing; + vertical-align: $vertical-align; + content: ""; + @include caret-start($width); + } + } + + &:empty::after { + margin-left: 0; + } + } +} +// scss-docs-end caret-mixins diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss new file mode 100644 index 00000000..ffc62bb2 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss @@ -0,0 +1,9 @@ +// scss-docs-start clearfix +@mixin clearfix() { + &::after { + display: block; + clear: both; + content: ""; + } +} +// scss-docs-end clearfix diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss new file mode 100644 index 00000000..03338b02 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss @@ -0,0 +1,21 @@ +// scss-docs-start color-mode-mixin +@mixin color-mode($mode: light, $root: false) { + @if $color-mode-type == "media-query" { + @if $root == true { + @media (prefers-color-scheme: $mode) { + :root { + @content; + } + } + } @else { + @media (prefers-color-scheme: $mode) { + @content; + } + } + } @else { + [data-bs-theme="#{$mode}"] { + @content; + } + } +} +// scss-docs-end color-mode-mixin diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss new file mode 100644 index 00000000..90497aa0 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss @@ -0,0 +1,7 @@ +// scss-docs-start mixin-color-scheme +@mixin color-scheme($name) { + @media (prefers-color-scheme: #{$name}) { + @content; + } +} +// scss-docs-end mixin-color-scheme diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss new file mode 100644 index 00000000..b9f33519 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss @@ -0,0 +1,11 @@ +// Container mixins + +@mixin make-container($gutter: $container-padding-x) { + --#{$prefix}gutter-x: #{$gutter}; + --#{$prefix}gutter-y: 0; + width: 100%; + padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list + padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list + margin-right: auto; + margin-left: auto; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss new file mode 100644 index 00000000..df070bc5 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss @@ -0,0 +1,10 @@ +// Deprecate mixin +// +// This mixin can be used to deprecate mixins or functions. +// `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to +// some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap) +@mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) { + @if ($enable-deprecation-messages != false and $ignore-warning != true) { + @warn "#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}."; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss new file mode 100644 index 00000000..00b47641 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss @@ -0,0 +1,163 @@ +// This mixin uses an `if()` technique to be compatible with Dart Sass +// See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details + +// scss-docs-start form-validation-mixins +@mixin form-validation-state-selector($state) { + @if ($state == "valid" or $state == "invalid") { + .was-validated #{if(&, "&", "")}:#{$state}, + #{if(&, "&", "")}.is-#{$state} { + @content; + } + } @else { + #{if(&, "&", "")}.is-#{$state} { + @content; + } + } +} + +@mixin form-validation-state( + $state, + $color, + $icon, + $tooltip-color: color-contrast($color), + $tooltip-bg-color: rgba($color, $form-feedback-tooltip-opacity), + $focus-box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity), + $border-color: $color +) { + .#{$state}-feedback { + display: none; + width: 100%; + margin-top: $form-feedback-margin-top; + @include font-size($form-feedback-font-size); + font-style: $form-feedback-font-style; + color: $color; + } + + .#{$state}-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; // Contain to parent when possible + padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x; + margin-top: .1rem; + @include font-size($form-feedback-tooltip-font-size); + line-height: $form-feedback-tooltip-line-height; + color: $tooltip-color; + background-color: $tooltip-bg-color; + @include border-radius($form-feedback-tooltip-border-radius); + } + + @include form-validation-state-selector($state) { + ~ .#{$state}-feedback, + ~ .#{$state}-tooltip { + display: block; + } + } + + .form-control { + @include form-validation-state-selector($state) { + border-color: $border-color; + + @if $enable-validation-icons { + padding-right: $input-height-inner; + background-image: escape-svg($icon); + background-repeat: no-repeat; + background-position: right $input-height-inner-quarter center; + background-size: $input-height-inner-half $input-height-inner-half; + } + + &:focus { + border-color: $border-color; + @if $enable-shadows { + @include box-shadow($input-box-shadow, $focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $focus-box-shadow; + } + } + } + } + + // stylelint-disable-next-line selector-no-qualifying-type + textarea.form-control { + @include form-validation-state-selector($state) { + @if $enable-validation-icons { + padding-right: $input-height-inner; + background-position: top $input-height-inner-quarter right $input-height-inner-quarter; + } + } + } + + .form-select { + @include form-validation-state-selector($state) { + border-color: $border-color; + + @if $enable-validation-icons { + &:not([multiple]):not([size]), + &:not([multiple])[size="1"] { + --#{$prefix}form-select-bg-icon: #{escape-svg($icon)}; + padding-right: $form-select-feedback-icon-padding-end; + background-position: $form-select-bg-position, $form-select-feedback-icon-position; + background-size: $form-select-bg-size, $form-select-feedback-icon-size; + } + } + + &:focus { + border-color: $border-color; + @if $enable-shadows { + @include box-shadow($form-select-box-shadow, $focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $focus-box-shadow; + } + } + } + } + + .form-control-color { + @include form-validation-state-selector($state) { + @if $enable-validation-icons { + width: add($form-color-width, $input-height-inner); + } + } + } + + .form-check-input { + @include form-validation-state-selector($state) { + border-color: $border-color; + + &:checked { + background-color: $color; + } + + &:focus { + box-shadow: $focus-box-shadow; + } + + ~ .form-check-label { + color: $color; + } + } + } + .form-check-inline .form-check-input { + ~ .#{$state}-feedback { + margin-left: .5em; + } + } + + .input-group { + > .form-control:not(:focus), + > .form-select:not(:focus), + > .form-floating:not(:focus-within) { + @include form-validation-state-selector($state) { + @if $state == "valid" { + z-index: 3; + } @else if $state == "invalid" { + z-index: 4; + } + } + } + } +} +// scss-docs-end form-validation-mixins diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss new file mode 100644 index 00000000..608e18df --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss @@ -0,0 +1,47 @@ +// Gradients + +// scss-docs-start gradient-bg-mixin +@mixin gradient-bg($color: null) { + background-color: $color; + + @if $enable-gradients { + background-image: var(--#{$prefix}gradient); + } +} +// scss-docs-end gradient-bg-mixin + +// scss-docs-start gradient-mixins +// Horizontal gradient, from left to right +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) { + background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); +} + +// Vertical gradient, from top to bottom +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +@mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: null, $end-percent: null) { + background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); +} + +@mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) { + background-image: linear-gradient($deg, $start-color, $end-color); +} + +@mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { + background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); +} + +@mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) { + background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); +} + +@mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) { + background-image: radial-gradient(circle, $inner-color, $outer-color); +} + +@mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) { + background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); +} +// scss-docs-end gradient-mixins diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss new file mode 100644 index 00000000..ea307399 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss @@ -0,0 +1,151 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +@mixin make-row($gutter: $grid-gutter-width) { + --#{$prefix}gutter-x: #{$gutter}; + --#{$prefix}gutter-y: 0; + display: flex; + flex-wrap: wrap; + // TODO: Revisit calc order after https://github.com/react-bootstrap/react-bootstrap/issues/6039 is fixed + margin-top: calc(-1 * var(--#{$prefix}gutter-y)); // stylelint-disable-line function-disallowed-list + margin-right: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list + margin-left: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list +} + +@mixin make-col-ready() { + // Add box sizing if only the grid is loaded + box-sizing: if(variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null); + // Prevent columns from becoming too narrow when at smaller grid tiers by + // always setting `width: 100%;`. This works because we set the width + // later on to override this initial width. + flex-shrink: 0; + width: 100%; + max-width: 100%; // Prevent `.col-auto`, `.col` (& responsive variants) from breaking out the grid + padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list + padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list + margin-top: var(--#{$prefix}gutter-y); +} + +@mixin make-col($size: false, $columns: $grid-columns) { + @if $size { + flex: 0 0 auto; + width: percentage(divide($size, $columns)); + + } @else { + flex: 1 1 0; + max-width: 100%; + } +} + +@mixin make-col-auto() { + flex: 0 0 auto; + width: auto; +} + +@mixin make-col-offset($size, $columns: $grid-columns) { + $num: divide($size, $columns); + margin-left: if($num == 0, 0, percentage($num)); +} + +// Row columns +// +// Specify on a parent element(e.g., .row) to force immediate children into NN +// number of columns. Supports wrapping to new lines, but does not do a Masonry +// style grid. +@mixin row-cols($count) { + > * { + flex: 0 0 auto; + width: percentage(divide(1, $count)); + } +} + +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `$grid-columns`. + +@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) { + @each $breakpoint in map-keys($breakpoints) { + $infix: breakpoint-infix($breakpoint, $breakpoints); + + @include media-breakpoint-up($breakpoint, $breakpoints) { + // Provide basic `.col-{bp}` classes for equal-width flexbox columns + .col#{$infix} { + flex: 1 0 0%; // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + } + + .row-cols#{$infix}-auto > * { + @include make-col-auto(); + } + + @if $grid-row-columns > 0 { + @for $i from 1 through $grid-row-columns { + .row-cols#{$infix}-#{$i} { + @include row-cols($i); + } + } + } + + .col#{$infix}-auto { + @include make-col-auto(); + } + + @if $columns > 0 { + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + @include make-col($i, $columns); + } + } + + // `$columns - 1` because offsetting by the width of an entire row isn't possible + @for $i from 0 through ($columns - 1) { + @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0 + .offset#{$infix}-#{$i} { + @include make-col-offset($i, $columns); + } + } + } + } + + // Gutters + // + // Make use of `.g-*`, `.gx-*` or `.gy-*` utilities to change spacing between the columns. + @each $key, $value in $gutters { + .g#{$infix}-#{$key}, + .gx#{$infix}-#{$key} { + --#{$prefix}gutter-x: #{$value}; + } + + .g#{$infix}-#{$key}, + .gy#{$infix}-#{$key} { + --#{$prefix}gutter-y: #{$value}; + } + } + } + } +} + +@mixin make-cssgrid($columns: $grid-columns, $breakpoints: $grid-breakpoints) { + @each $breakpoint in map-keys($breakpoints) { + $infix: breakpoint-infix($breakpoint, $breakpoints); + + @include media-breakpoint-up($breakpoint, $breakpoints) { + @if $columns > 0 { + @for $i from 1 through $columns { + .g-col#{$infix}-#{$i} { + grid-column: auto / span $i; + } + } + + // Start with `1` because `0` is an invalid value. + // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible. + @for $i from 1 through ($columns - 1) { + .g-start#{$infix}-#{$i} { + grid-column-start: $i; + } + } + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss new file mode 100644 index 00000000..e1df779f --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss @@ -0,0 +1,16 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +@mixin img-fluid { + // Part 1: Set a maximum relative to the parent + max-width: 100%; + // Part 2: Override the height to auto, otherwise images will be stretched + // when setting a width and height attribute on the img element. + height: auto; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss new file mode 100644 index 00000000..6274f343 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss @@ -0,0 +1,26 @@ +@include deprecate("`list-group-item-variant()`", "v5.3.0", "v6.0.0"); + +// List Groups + +// scss-docs-start list-group-mixin +@mixin list-group-item-variant($state, $background, $color) { + .list-group-item-#{$state} { + color: $color; + background-color: $background; + + &.list-group-item-action { + &:hover, + &:focus { + color: $color; + background-color: shade-color($background, 10%); + } + + &.active { + color: $white; + background-color: $color; + border-color: $color; + } + } + } +} +// scss-docs-end list-group-mixin diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss new file mode 100644 index 00000000..25185626 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss @@ -0,0 +1,7 @@ +// Lists + +// Unstyled keeps list items block level, just removes default browser padding and list-style +@mixin list-unstyled { + padding-left: 0; + list-style: none; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss new file mode 100644 index 00000000..0d657964 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss @@ -0,0 +1,10 @@ +// Pagination + +// scss-docs-start pagination-mixin +@mixin pagination-size($padding-y, $padding-x, $font-size, $border-radius) { + --#{$prefix}pagination-padding-x: #{$padding-x}; + --#{$prefix}pagination-padding-y: #{$padding-y}; + @include rfs($font-size, --#{$prefix}pagination-font-size); + --#{$prefix}pagination-border-radius: #{$border-radius}; +} +// scss-docs-end pagination-mixin diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss new file mode 100644 index 00000000..f5bd1afe --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss @@ -0,0 +1,17 @@ +@mixin reset-text { + font-family: $font-family-base; + // We deliberately do NOT reset font-size or overflow-wrap / word-wrap. + font-style: normal; + font-weight: $font-weight-normal; + line-height: $line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss new file mode 100644 index 00000000..66f233a6 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss @@ -0,0 +1,6 @@ +// Resize anything + +@mixin resizable($direction) { + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` + resize: $direction; // Options: horizontal, vertical, both +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss new file mode 100644 index 00000000..5fe1b9b2 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss @@ -0,0 +1,24 @@ +// scss-docs-start table-variant +@mixin table-variant($state, $background) { + .table-#{$state} { + $color: color-contrast(opaque($body-bg, $background)); + $hover-bg: mix($color, $background, percentage($table-hover-bg-factor)); + $striped-bg: mix($color, $background, percentage($table-striped-bg-factor)); + $active-bg: mix($color, $background, percentage($table-active-bg-factor)); + $table-border-color: mix($color, $background, percentage($table-border-factor)); + + --#{$prefix}table-color: #{$color}; + --#{$prefix}table-bg: #{$background}; + --#{$prefix}table-border-color: #{$table-border-color}; + --#{$prefix}table-striped-bg: #{$striped-bg}; + --#{$prefix}table-striped-color: #{color-contrast($striped-bg)}; + --#{$prefix}table-active-bg: #{$active-bg}; + --#{$prefix}table-active-color: #{color-contrast($active-bg)}; + --#{$prefix}table-hover-bg: #{$hover-bg}; + --#{$prefix}table-hover-color: #{color-contrast($hover-bg)}; + + color: var(--#{$prefix}table-color); + border-color: var(--#{$prefix}table-border-color); + } +} +// scss-docs-end table-variant diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss new file mode 100644 index 00000000..3504bb1a --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss @@ -0,0 +1,8 @@ +// Text truncate +// Requires inline-block or block for proper styling + +@mixin text-truncate() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss new file mode 100644 index 00000000..d437f6d8 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss @@ -0,0 +1,26 @@ +// stylelint-disable property-disallowed-list +@mixin transition($transition...) { + @if length($transition) == 0 { + $transition: $transition-base; + } + + @if length($transition) > 1 { + @each $value in $transition { + @if $value == null or $value == none { + @warn "The keyword 'none' or 'null' must be used as a single argument."; + } + } + } + + @if $enable-transitions { + @if nth($transition, 1) != null { + transition: $transition; + } + + @if $enable-reduced-motion and nth($transition, 1) != null and nth($transition, 1) != none { + @media (prefers-reduced-motion: reduce) { + transition: none; + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss new file mode 100644 index 00000000..4795e894 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss @@ -0,0 +1,97 @@ +// Utility generator +// Used to generate utilities & print utilities +@mixin generate-utility($utility, $infix: "", $is-rfs-media-query: false) { + $values: map-get($utility, values); + + // If the values are a list or string, convert it into a map + @if type-of($values) == "string" or type-of(nth($values, 1)) != "list" { + $values: zip($values, $values); + } + + @each $key, $value in $values { + $properties: map-get($utility, property); + + // Multiple properties are possible, for example with vertical or horizontal margins or paddings + @if type-of($properties) == "string" { + $properties: append((), $properties); + } + + // Use custom class if present + $property-class: if(map-has-key($utility, class), map-get($utility, class), nth($properties, 1)); + $property-class: if($property-class == null, "", $property-class); + + // Use custom CSS variable name if present, otherwise default to `class` + $css-variable-name: if(map-has-key($utility, css-variable-name), map-get($utility, css-variable-name), map-get($utility, class)); + + // State params to generate pseudo-classes + $state: if(map-has-key($utility, state), map-get($utility, state), ()); + + $infix: if($property-class == "" and str-slice($infix, 1, 1) == "-", str-slice($infix, 2), $infix); + + // Don't prefix if value key is null (e.g. with shadow class) + $property-class-modifier: if($key, if($property-class == "" and $infix == "", "", "-") + $key, ""); + + @if map-get($utility, rfs) { + // Inside the media query + @if $is-rfs-media-query { + $val: rfs-value($value); + + // Do not render anything if fluid and non fluid values are the same + $value: if($val == rfs-fluid-value($value), null, $val); + } + @else { + $value: rfs-fluid-value($value); + } + } + + $is-css-var: map-get($utility, css-var); + $is-local-vars: map-get($utility, local-vars); + $is-rtl: map-get($utility, rtl); + + @if $value != null { + @if $is-rtl == false { + /* rtl:begin:remove */ + } + + @if $is-css-var { + .#{$property-class + $infix + $property-class-modifier} { + --#{$prefix}#{$css-variable-name}: #{$value}; + } + + @each $pseudo in $state { + .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { + --#{$prefix}#{$css-variable-name}: #{$value}; + } + } + } @else { + .#{$property-class + $infix + $property-class-modifier} { + @each $property in $properties { + @if $is-local-vars { + @each $local-var, $variable in $is-local-vars { + --#{$prefix}#{$local-var}: #{$variable}; + } + } + #{$property}: $value if($enable-important-utilities, !important, null); + } + } + + @each $pseudo in $state { + .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} { + @each $property in $properties { + @if $is-local-vars { + @each $local-var, $variable in $is-local-vars { + --#{$prefix}#{$local-var}: #{$variable}; + } + } + #{$property}: $value if($enable-important-utilities, !important, null); + } + } + } + } + + @if $is-rtl == false { + /* rtl:end:remove */ + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss new file mode 100644 index 00000000..082aeec9 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss @@ -0,0 +1,33 @@ +// stylelint-disable declaration-no-important + +// Hide content visually while keeping it accessible to assistive technologies +// +// See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ +// See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/ + +@mixin visually-hidden() { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686 + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; + + // Fix for positioned table caption that could become anonymous cells + &:not(caption) { + position: absolute !important; + } +} + +// Use to only display content when it's focused, or one of its child elements is focused +// (i.e. when focus is within the element/container that the class was applied to) +// +// Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 + +@mixin visually-hidden-focusable() { + &:not(:focus):not(:focus-within) { + @include visually-hidden(); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js new file mode 100644 index 00000000..25d838c9 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js @@ -0,0 +1,16 @@ +/* eslint-disable camelcase */ + +'use strict' + +const path = require('node:path') + +module.exports = { + spec_dir: 'scss', + // Make Jasmine look for `.test.scss` files + spec_files: ['**/*.{test,spec}.scss'], + // Compile them into JS scripts running `sass-true` + requires: [path.join(__dirname, 'sass-true/register')], + // Ensure we use `require` so that the require.extensions works + // as `import` completely bypasses it + jsLoader: 'require' +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss new file mode 100644 index 00000000..f08ae587 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss @@ -0,0 +1,7 @@ +// TODO: this file can be removed safely in v6 when `@import "variables-dark"` will be removed at the end of _variables.scss + +@import "../../functions"; +@import "../../variables"; +// Voluntarily not importing _variables-dark.scss +@import "../../maps"; +@import "../../mixins"; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss new file mode 100644 index 00000000..9ecc628d --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss @@ -0,0 +1,69 @@ +// stylelint-disable selector-attribute-quotes + +@import "../../functions"; +@import "../../variables"; +@import "../../variables-dark"; +@import "../../maps"; +@import "../../mixins"; + +@include describe("global $color-mode-type: data") { + @include it("generates data attribute selectors for dark mode") { + @include assert() { + @include output() { + @include color-mode(dark) { + .element { + color: var(--bs-primary-text-emphasis); + background-color: var(--bs-primary-bg-subtle); + } + } + @include color-mode(dark, true) { + --custom-color: #{mix($indigo, $blue, 50%)}; + } + } + @include expect() { + [data-bs-theme=dark] .element { + color: var(--bs-primary-text-emphasis); + background-color: var(--bs-primary-bg-subtle); + } + [data-bs-theme=dark] { + --custom-color: #3a3ff8; + } + } + } + } +} + +@include describe("global $color-mode-type: media-query") { + @include it("generates media queries for dark mode") { + $color-mode-type: media-query !global; + + @include assert() { + @include output() { + @include color-mode(dark) { + .element { + color: var(--bs-primary-text-emphasis); + background-color: var(--bs-primary-bg-subtle); + } + } + @include color-mode(dark, true) { + --custom-color: #{mix($indigo, $blue, 50%)}; + } + } + @include expect() { + @media (prefers-color-scheme: dark) { + .element { + color: var(--bs-primary-text-emphasis); + background-color: var(--bs-primary-bg-subtle); + } + } + @media (prefers-color-scheme: dark) { + :root { + --custom-color: #3a3ff8; + } + } + } + } + + $color-mode-type: data !global; + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss new file mode 100644 index 00000000..00ed82d6 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss @@ -0,0 +1,8 @@ +$color-mode-type: media-query; + +@import "../../bootstrap"; + +@include describe("global $color-mode-type: media-query") { + @include it("compiles entirely Bootstrap CSS with media-query color mode") { // stylelint-disable-line block-no-empty + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss new file mode 100644 index 00000000..8140ac47 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss @@ -0,0 +1,393 @@ +$prefix: bs-; +$enable-important-utilities: false; + +// Important: Do not import rfs to check that the mixin just calls the appropriate functions from it +@import "../../mixins/utilities"; + +@mixin test-generate-utility($params...) { + @include assert() { + @include output() { + @include generate-utility($params...); + } + + @include expect() { + @content; + } + } +} + +@include describe(generate-utility) { + @include it("generates a utility class for each value") { + @include test-generate-utility( + ( + property: "padding", + values: (small: .5rem, large: 2rem) + ) + ) { + .padding-small { + padding: .5rem; + } + + .padding-large { + padding: 2rem; + } + } + } + + @include describe("global $enable-important-utilities: true") { + @include it("sets !important") { + $enable-important-utilities: true !global; + + @include test-generate-utility( + ( + property: "padding", + values: (small: .5rem, large: 2rem) + ) + ) { + .padding-small { + padding: .5rem !important; + } + + .padding-large { + padding: 2rem !important; + } + } + + $enable-important-utilities: false !global; + } + } + + @include describe("$utility") { + @include describe("values") { + @include it("supports null keys") { + @include test-generate-utility( + ( + property: "padding", + values: (null: 1rem, small: .5rem, large: 2rem) + ), + ) { + .padding { + padding: 1rem; + } + + .padding-small { + padding: .5rem; + } + + .padding-large { + padding: 2rem; + } + } + } + + @include it("ignores null values") { + @include test-generate-utility( + ( + property: "padding", + values: (small: null, large: 2rem) + ) + ) { + .padding-large { + padding: 2rem; + } + } + } + + @include it("supports lists") { + @include test-generate-utility( + ( + property: "padding", + values: 1rem 2rem + ) + ) { + .padding-1rem { + padding: 1rem; + } + + .padding-2rem { + padding: 2rem; + } + } + } + + @include it("supports single values") { + @include test-generate-utility( + ( + property: padding, + values: 1rem + ) + ) { + .padding-1rem { + padding: 1rem; + } + } + } + } + + @include describe("class & property") { + @include it("adds each property to the declaration") { + @include test-generate-utility( + ( + class: padding-x, + property: padding-inline-start padding-inline-end, + values: 1rem + ) + ) { + .padding-x-1rem { + padding-inline-start: 1rem; + padding-inline-end: 1rem; + } + } + } + + @include it("uses the first property as class name") { + @include test-generate-utility( + ( + property: padding-inline-start padding-inline-end, + values: 1rem + ) + ) { + .padding-inline-start-1rem { + padding-inline-start: 1rem; + padding-inline-end: 1rem; + } + } + } + + @include it("supports a null class to create classes straight from the values") { + @include test-generate-utility( + ( + property: visibility, + class: null, + values: ( + visible: visible, + invisible: hidden, + ) + ) + ) { + .visible { + visibility: visible; + } + + .invisible { + visibility: hidden; + } + } + } + } + + @include describe("state") { + @include it("Generates selectors for each states") { + @include test-generate-utility( + ( + property: padding, + values: 1rem, + state: hover focus, + ) + ) { + .padding-1rem { + padding: 1rem; + } + + .padding-1rem-hover:hover { + padding: 1rem; + } + + .padding-1rem-focus:focus { + padding: 1rem; + } + } + } + } + + @include describe("css-var"){ + @include it("sets a CSS variable instead of the property") { + @include test-generate-utility( + ( + property: padding, + css-variable-name: padding, + css-var: true, + values: 1rem 2rem + ) + ) { + .padding-1rem { + --bs-padding: 1rem; + } + + .padding-2rem { + --bs-padding: 2rem; + } + } + } + + @include it("defaults to class") { + @include test-generate-utility( + ( + property: padding, + class: padding, + css-var: true, + values: 1rem 2rem + ) + ) { + .padding-1rem { + --bs-padding: 1rem; + } + + .padding-2rem { + --bs-padding: 2rem; + } + } + } + } + + @include describe("local-vars") { + @include it("generates the listed variables") { + @include test-generate-utility( + ( + property: color, + class: desaturated-color, + local-vars: ( + color-opacity: 1, + color-saturation: .25 + ), + values: ( + blue: hsla(192deg, var(--bs-color-saturation), 0, var(--bs-color-opacity)) + ) + ) + ) { + .desaturated-color-blue { + --bs-color-opacity: 1; + // Sass compilation will put a leading zero so we want to keep that one + // stylelint-disable-next-line @stylistic/number-leading-zero + --bs-color-saturation: 0.25; + color: hsla(192deg, var(--bs-color-saturation), 0, var(--bs-color-opacity)); + } + } + } + } + + @include describe("css-var & state") { + @include it("Generates a rule with for each state with a CSS variable") { + @include test-generate-utility( + ( + property: padding, + css-var: true, + css-variable-name: padding, + values: 1rem, + state: hover focus, + ) + ) { + .padding-1rem { + --bs-padding: 1rem; + } + + .padding-1rem-hover:hover { + --bs-padding: 1rem; + } + + .padding-1rem-focus:focus { + --bs-padding: 1rem; + } + } + } + } + + @include describe("rtl") { + @include it("sets up RTLCSS for removal when false") { + @include test-generate-utility( + ( + property: padding, + values: 1rem, + rtl: false + ) + ) { + /* rtl:begin:remove */ + + .padding-1rem { + padding: 1rem; + } + + /* rtl:end:remove */ + + } + } + } + + @include describe("rfs") { + @include it("sets the fluid value when not inside media query") { + @include test-generate-utility( + ( + property: padding, + values: 1rem, + rfs: true + ) + ) { + .padding-1rem { + padding: rfs-fluid-value(1rem); + } + } + } + + @include it("sets the value when inside the media query") { + @include test-generate-utility( + ( + property: padding, + values: 1rem, + rfs: true + ), + $is-rfs-media-query: true + ) { + .padding-1rem { + padding: rfs-value(1rem); + } + } + } + } + } + + @include describe("$infix") { + @include it("inserts the given infix") { + @include test-generate-utility( + ( + property: "padding", + values: (null: 1rem, small: .5rem, large: 2rem) + ), + $infix: -sm + ) { + .padding-sm { + padding: 1rem; + } + + .padding-sm-small { + padding: .5rem; + } + + .padding-sm-large { + padding: 2rem; + } + } + } + + @include it("strips leading - if class is null") { + @include test-generate-utility( + ( + property: visibility, + class: null, + values: ( + visible: visible, + invisible: hidden, + ) + ), + -sm + ) { + .sm-visible { + visibility: visible; + } + + .sm-invisible { + visibility: hidden; + } + } + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js new file mode 100644 index 00000000..d93e414c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js @@ -0,0 +1,14 @@ +'use strict' + +const path = require('node:path') + +const runnerPath = path.join(__dirname, 'runner').replace(/\\/g, '/') + +require.extensions['.scss'] = (module, filename) => { + const normalizedFilename = filename.replace(/\\/g, '/') + + return module._compile(` + const runner = require('${runnerPath}') + runner('${normalizedFilename}', { describe, it }) + `, filename) +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js new file mode 100644 index 00000000..bef870ac --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js @@ -0,0 +1,17 @@ +'use strict' + +const fs = require('node:fs') +const path = require('node:path') +const { runSass } = require('sass-true') + +module.exports = (filename, { describe, it }) => { + const data = fs.readFileSync(filename, 'utf8') + const TRUE_SETUP = '$true-terminal-output: false; @import "true";' + const sassString = TRUE_SETUP + data + + runSass( + { describe, it, sourceType: 'string' }, + sassString, + { loadPaths: [path.dirname(filename)] } + ) +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss new file mode 100644 index 00000000..304d8d1c --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss @@ -0,0 +1,75 @@ +@import "../../functions"; +@import "../../variables"; +@import "../../variables-dark"; +@import "../../maps"; +@import "../../mixins"; + +$utilities: (); + +@include describe("utilities/api") { + @include it("generates utilities for each breakpoints") { + $utilities: ( + margin: ( + property: margin, + values: auto + ), + padding: ( + property: padding, + responsive: true, + values: 1rem + ), + font-size: ( + property: font-size, + values: (large: 1.25rem), + print: true + ) + ) !global; + + $grid-breakpoints: ( + xs: 0, + sm: 333px, + md: 666px + ) !global; + + @include assert() { + @include output() { + @import "../../utilities/api"; + } + + @include expect() { + // margin is not set to responsive + .margin-auto { + margin: auto !important; + } + + // padding is, though + .padding-1rem { + padding: 1rem !important; + } + + .font-size-large { + font-size: 1.25rem !important; + } + + @media (min-width: 333px) { + .padding-sm-1rem { + padding: 1rem !important; + } + } + + @media (min-width: 666px) { + .padding-md-1rem { + padding: 1rem !important; + } + } + + @media print { + .font-size-print-large { + font-size: 1.25rem !important; + } + } + } + + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss new file mode 100644 index 00000000..62e1d398 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss @@ -0,0 +1,47 @@ +// Loop over each breakpoint +@each $breakpoint in map-keys($grid-breakpoints) { + + // Generate media query if needed + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + // Loop over each utility property + @each $key, $utility in $utilities { + // The utility can be disabled with `false`, thus check if the utility is a map first + // Only proceed if responsive media queries are enabled or if it's the base media query + @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "") { + @include generate-utility($utility, $infix); + } + } + } +} + +// RFS rescaling +@media (min-width: $rfs-mq-value) { + @each $breakpoint in map-keys($grid-breakpoints) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) { + // Loop over each utility property + @each $key, $utility in $utilities { + // The utility can be disabled with `false`, thus check if the utility is a map first + // Only proceed if responsive media queries are enabled or if it's the base media query + @if type-of($utility) == "map" and map-get($utility, rfs) and (map-get($utility, responsive) or $infix == "") { + @include generate-utility($utility, $infix, true); + } + } + } + } +} + + +// Print utilities +@media print { + @each $key, $utility in $utilities { + // The utility can be disabled with `false`, thus check if the utility is a map first + // Then check if the utility needs print styles + @if type-of($utility) == "map" and map-get($utility, print) == true { + @include generate-utility($utility, "-print"); + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss new file mode 100644 index 00000000..aa1f82b9 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss @@ -0,0 +1,348 @@ +// stylelint-disable scss/dimension-no-non-numeric-values + +// SCSS RFS mixin +// +// Automated responsive values for font sizes, paddings, margins and much more +// +// Licensed under MIT (https://github.com/twbs/rfs/blob/main/LICENSE) + +// Configuration + +// Base value +$rfs-base-value: 1.25rem !default; +$rfs-unit: rem !default; + +@if $rfs-unit != rem and $rfs-unit != px { + @error "`#{$rfs-unit}` is not a valid unit for $rfs-unit. Use `px` or `rem`."; +} + +// Breakpoint at where values start decreasing if screen width is smaller +$rfs-breakpoint: 1200px !default; +$rfs-breakpoint-unit: px !default; + +@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem { + @error "`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`."; +} + +// Resize values based on screen height and width +$rfs-two-dimensional: false !default; + +// Factor of decrease +$rfs-factor: 10 !default; + +@if type-of($rfs-factor) != number or $rfs-factor <= 1 { + @error "`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1."; +} + +// Mode. Possibilities: "min-media-query", "max-media-query" +$rfs-mode: min-media-query !default; + +// Generate enable or disable classes. Possibilities: false, "enable" or "disable" +$rfs-class: false !default; + +// 1 rem = $rfs-rem-value px +$rfs-rem-value: 16 !default; + +// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14 +$rfs-safari-iframe-resize-bug-fix: false !default; + +// Disable RFS by setting $enable-rfs to false +$enable-rfs: true !default; + +// Cache $rfs-base-value unit +$rfs-base-value-unit: unit($rfs-base-value); + +@function divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + @if $dividend == 0 { + @return 0; + } + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + $remainder: $dividend; + $result: 0; + $factor: 10; + @while ($remainder > 0 and $precision >= 0) { + $quotient: 0; + @while ($remainder >= $divisor) { + $remainder: $remainder - $divisor; + $quotient: $quotient + 1; + } + $result: $result * 10 + $quotient; + $factor: $factor * .1; + $remainder: $remainder * 10; + $precision: $precision - 1; + @if ($precision < 0 and $remainder >= $divisor * 5) { + $result: $result + 1; + } + } + $result: $result * $factor * $sign; + $dividend-unit: unit($dividend); + $divisor-unit: unit($divisor); + $unit-map: ( + "px": 1px, + "rem": 1rem, + "em": 1em, + "%": 1% + ); + @if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) { + $result: $result * map-get($unit-map, $dividend-unit); + } + @return $result; +} + +// Remove px-unit from $rfs-base-value for calculations +@if $rfs-base-value-unit == px { + $rfs-base-value: divide($rfs-base-value, $rfs-base-value * 0 + 1); +} +@else if $rfs-base-value-unit == rem { + $rfs-base-value: divide($rfs-base-value, divide($rfs-base-value * 0 + 1, $rfs-rem-value)); +} + +// Cache $rfs-breakpoint unit to prevent multiple calls +$rfs-breakpoint-unit-cache: unit($rfs-breakpoint); + +// Remove unit from $rfs-breakpoint for calculations +@if $rfs-breakpoint-unit-cache == px { + $rfs-breakpoint: divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1); +} +@else if $rfs-breakpoint-unit-cache == rem or $rfs-breakpoint-unit-cache == "em" { + $rfs-breakpoint: divide($rfs-breakpoint, divide($rfs-breakpoint * 0 + 1, $rfs-rem-value)); +} + +// Calculate the media query value +$rfs-mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit}); +$rfs-mq-property-width: if($rfs-mode == max-media-query, max-width, min-width); +$rfs-mq-property-height: if($rfs-mode == max-media-query, max-height, min-height); + +// Internal mixin used to determine which media query needs to be used +@mixin _rfs-media-query { + @if $rfs-two-dimensional { + @if $rfs-mode == max-media-query { + @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}), (#{$rfs-mq-property-height}: #{$rfs-mq-value}) { + @content; + } + } + @else { + @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) and (#{$rfs-mq-property-height}: #{$rfs-mq-value}) { + @content; + } + } + } + @else { + @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) { + @content; + } + } +} + +// Internal mixin that adds disable classes to the selector if needed. +@mixin _rfs-rule { + @if $rfs-class == disable and $rfs-mode == max-media-query { + // Adding an extra class increases specificity, which prevents the media query to override the property + &, + .disable-rfs &, + &.disable-rfs { + @content; + } + } + @else if $rfs-class == enable and $rfs-mode == min-media-query { + .enable-rfs &, + &.enable-rfs { + @content; + } + } @else { + @content; + } +} + +// Internal mixin that adds enable classes to the selector if needed. +@mixin _rfs-media-query-rule { + + @if $rfs-class == enable { + @if $rfs-mode == min-media-query { + @content; + } + + @include _rfs-media-query () { + .enable-rfs &, + &.enable-rfs { + @content; + } + } + } + @else { + @if $rfs-class == disable and $rfs-mode == min-media-query { + .disable-rfs &, + &.disable-rfs { + @content; + } + } + @include _rfs-media-query () { + @content; + } + } +} + +// Helper function to get the formatted non-responsive value +@function rfs-value($values) { + // Convert to list + $values: if(type-of($values) != list, ($values,), $values); + + $val: ""; + + // Loop over each value and calculate value + @each $value in $values { + @if $value == 0 { + $val: $val + " 0"; + } + @else { + // Cache $value unit + $unit: if(type-of($value) == "number", unit($value), false); + + @if $unit == px { + // Convert to rem if needed + $val: $val + " " + if($rfs-unit == rem, #{divide($value, $value * 0 + $rfs-rem-value)}rem, $value); + } + @else if $unit == rem { + // Convert to px if needed + $val: $val + " " + if($rfs-unit == px, #{divide($value, $value * 0 + 1) * $rfs-rem-value}px, $value); + } @else { + // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value + $val: $val + " " + $value; + } + } + } + + // Remove first space + @return unquote(str-slice($val, 2)); +} + +// Helper function to get the responsive value calculated by RFS +@function rfs-fluid-value($values) { + // Convert to list + $values: if(type-of($values) != list, ($values,), $values); + + $val: ""; + + // Loop over each value and calculate value + @each $value in $values { + @if $value == 0 { + $val: $val + " 0"; + } @else { + // Cache $value unit + $unit: if(type-of($value) == "number", unit($value), false); + + // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value + @if not $unit or $unit != px and $unit != rem { + $val: $val + " " + $value; + } @else { + // Remove unit from $value for calculations + $value: divide($value, $value * 0 + if($unit == px, 1, divide(1, $rfs-rem-value))); + + // Only add the media query if the value is greater than the minimum value + @if abs($value) <= $rfs-base-value or not $enable-rfs { + $val: $val + " " + if($rfs-unit == rem, #{divide($value, $rfs-rem-value)}rem, #{$value}px); + } + @else { + // Calculate the minimum value + $value-min: $rfs-base-value + divide(abs($value) - $rfs-base-value, $rfs-factor); + + // Calculate difference between $value and the minimum value + $value-diff: abs($value) - $value-min; + + // Base value formatting + $min-width: if($rfs-unit == rem, #{divide($value-min, $rfs-rem-value)}rem, #{$value-min}px); + + // Use negative value if needed + $min-width: if($value < 0, -$min-width, $min-width); + + // Use `vmin` if two-dimensional is enabled + $variable-unit: if($rfs-two-dimensional, vmin, vw); + + // Calculate the variable width between 0 and $rfs-breakpoint + $variable-width: #{divide($value-diff * 100, $rfs-breakpoint)}#{$variable-unit}; + + // Return the calculated value + $val: $val + " calc(" + $min-width + if($value < 0, " - ", " + ") + $variable-width + ")"; + } + } + } + } + + // Remove first space + @return unquote(str-slice($val, 2)); +} + +// RFS mixin +@mixin rfs($values, $property: font-size) { + @if $values != null { + $val: rfs-value($values); + $fluid-val: rfs-fluid-value($values); + + // Do not print the media query if responsive & non-responsive values are the same + @if $val == $fluid-val { + #{$property}: $val; + } + @else { + @include _rfs-rule () { + #{$property}: if($rfs-mode == max-media-query, $val, $fluid-val); + + // Include safari iframe resize fix if needed + min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null); + } + + @include _rfs-media-query-rule () { + #{$property}: if($rfs-mode == max-media-query, $fluid-val, $val); + } + } + } +} + +// Shorthand helper mixins +@mixin font-size($value) { + @include rfs($value); +} + +@mixin padding($value) { + @include rfs($value, padding); +} + +@mixin padding-top($value) { + @include rfs($value, padding-top); +} + +@mixin padding-right($value) { + @include rfs($value, padding-right); +} + +@mixin padding-bottom($value) { + @include rfs($value, padding-bottom); +} + +@mixin padding-left($value) { + @include rfs($value, padding-left); +} + +@mixin margin($value) { + @include rfs($value, margin); +} + +@mixin margin-top($value) { + @include rfs($value, margin-top); +} + +@mixin margin-right($value) { + @include rfs($value, margin-right); +} + +@mixin margin-bottom($value) { + @include rfs($value, margin-bottom); +} + +@mixin margin-left($value) { + @include rfs($value, margin-left); +} diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.js b/extensions/pagetop-bootsier/static/js/bootstrap.js new file mode 100644 index 00000000..e3a59e3a --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.js @@ -0,0 +1,4494 @@ +/*! + * Bootstrap v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core')) : + typeof define === 'function' && define.amd ? define(['@popperjs/core'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory(global.Popper)); +})(this, (function (Popper) { 'use strict'; + + function _interopNamespaceDefault(e) { + const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }); + if (e) { + for (const k in e) { + if (k !== 'default') { + const d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: () => e[k] + }); + } + } + } + n.default = e; + return Object.freeze(n); + } + + const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper); + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/data.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + /** + * Constants + */ + + const elementMap = new Map(); + const Data = { + set(element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()); + } + const instanceMap = elementMap.get(element); + + // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + if (!instanceMap.has(key) && instanceMap.size !== 0) { + // eslint-disable-next-line no-console + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); + return; + } + instanceMap.set(key, instance); + }, + get(element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null; + } + return null; + }, + remove(element, key) { + if (!elementMap.has(element)) { + return; + } + const instanceMap = elementMap.get(element); + instanceMap.delete(key); + + // free up element references if there are no instances left for an element + if (instanceMap.size === 0) { + elementMap.delete(element); + } + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/index.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const MAX_UID = 1000000; + const MILLISECONDS_MULTIPLIER = 1000; + const TRANSITION_END = 'transitionend'; + + /** + * Properly escape IDs selectors to handle weird IDs + * @param {string} selector + * @returns {string} + */ + const parseSelector = selector => { + if (selector && window.CSS && window.CSS.escape) { + // document.querySelector needs escaping to handle IDs (html5+) containing for instance / + selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`); + } + return selector; + }; + + // Shout-out Angus Croll (https://goo.gl/pxwQGp) + const toType = object => { + if (object === null || object === undefined) { + return `${object}`; + } + return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase(); + }; + + /** + * Public Util API + */ + + const getUID = prefix => { + do { + prefix += Math.floor(Math.random() * MAX_UID); + } while (document.getElementById(prefix)); + return prefix; + }; + const getTransitionDurationFromElement = element => { + if (!element) { + return 0; + } + + // Get transition-duration of the element + let { + transitionDuration, + transitionDelay + } = window.getComputedStyle(element); + const floatTransitionDuration = Number.parseFloat(transitionDuration); + const floatTransitionDelay = Number.parseFloat(transitionDelay); + + // Return 0 if element or transition duration is not found + if (!floatTransitionDuration && !floatTransitionDelay) { + return 0; + } + + // If multiple durations are defined, take the first + transitionDuration = transitionDuration.split(',')[0]; + transitionDelay = transitionDelay.split(',')[0]; + return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; + }; + const triggerTransitionEnd = element => { + element.dispatchEvent(new Event(TRANSITION_END)); + }; + const isElement = object => { + if (!object || typeof object !== 'object') { + return false; + } + if (typeof object.jquery !== 'undefined') { + object = object[0]; + } + return typeof object.nodeType !== 'undefined'; + }; + const getElement = object => { + // it's a jQuery object or a node element + if (isElement(object)) { + return object.jquery ? object[0] : object; + } + if (typeof object === 'string' && object.length > 0) { + return document.querySelector(parseSelector(object)); + } + return null; + }; + const isVisible = element => { + if (!isElement(element) || element.getClientRects().length === 0) { + return false; + } + const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; + // Handle `details` element as its content may falsie appear visible when it is closed + const closedDetails = element.closest('details:not([open])'); + if (!closedDetails) { + return elementIsVisible; + } + if (closedDetails !== element) { + const summary = element.closest('summary'); + if (summary && summary.parentNode !== closedDetails) { + return false; + } + if (summary === null) { + return false; + } + } + return elementIsVisible; + }; + const isDisabled = element => { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return true; + } + if (element.classList.contains('disabled')) { + return true; + } + if (typeof element.disabled !== 'undefined') { + return element.disabled; + } + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; + }; + const findShadowRoot = element => { + if (!document.documentElement.attachShadow) { + return null; + } + + // Can find the shadow root otherwise it'll return the document + if (typeof element.getRootNode === 'function') { + const root = element.getRootNode(); + return root instanceof ShadowRoot ? root : null; + } + if (element instanceof ShadowRoot) { + return element; + } + + // when we don't find a shadow root + if (!element.parentNode) { + return null; + } + return findShadowRoot(element.parentNode); + }; + const noop = () => {}; + + /** + * Trick to restart an element's animation + * + * @param {HTMLElement} element + * @return void + * + * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + */ + const reflow = element => { + element.offsetHeight; // eslint-disable-line no-unused-expressions + }; + const getjQuery = () => { + if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return window.jQuery; + } + return null; + }; + const DOMContentLoadedCallbacks = []; + const onDOMContentLoaded = callback => { + if (document.readyState === 'loading') { + // add listener on the first call when the document is in loading state + if (!DOMContentLoadedCallbacks.length) { + document.addEventListener('DOMContentLoaded', () => { + for (const callback of DOMContentLoadedCallbacks) { + callback(); + } + }); + } + DOMContentLoadedCallbacks.push(callback); + } else { + callback(); + } + }; + const isRTL = () => document.documentElement.dir === 'rtl'; + const defineJQueryPlugin = plugin => { + onDOMContentLoaded(() => { + const $ = getjQuery(); + /* istanbul ignore if */ + if ($) { + const name = plugin.NAME; + const JQUERY_NO_CONFLICT = $.fn[name]; + $.fn[name] = plugin.jQueryInterface; + $.fn[name].Constructor = plugin; + $.fn[name].noConflict = () => { + $.fn[name] = JQUERY_NO_CONFLICT; + return plugin.jQueryInterface; + }; + } + }); + }; + const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { + return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue; + }; + const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback); + return; + } + const durationPadding = 5; + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; + let called = false; + const handler = ({ + target + }) => { + if (target !== transitionElement) { + return; + } + called = true; + transitionElement.removeEventListener(TRANSITION_END, handler); + execute(callback); + }; + transitionElement.addEventListener(TRANSITION_END, handler); + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement); + } + }, emulatedDuration); + }; + + /** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ + const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + const listLength = list.length; + let index = list.indexOf(activeElement); + + // if the element does not exist in the list return an element + // depending on the direction and if cycle is allowed + if (index === -1) { + return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]; + } + index += shouldGetNext ? 1 : -1; + if (isCycleAllowed) { + index = (index + listLength) % listLength; + } + return list[Math.max(0, Math.min(index, listLength - 1))]; + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/event-handler.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const namespaceRegex = /[^.]*(?=\..*)\.|.*/; + const stripNameRegex = /\..*/; + const stripUidRegex = /::\d+$/; + const eventRegistry = {}; // Events storage + let uidEvent = 1; + const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + }; + const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); + + /** + * Private methods + */ + + function makeEventUid(element, uid) { + return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; + } + function getElementEvents(element) { + const uid = makeEventUid(element); + element.uidEvent = uid; + eventRegistry[uid] = eventRegistry[uid] || {}; + return eventRegistry[uid]; + } + function bootstrapHandler(element, fn) { + return function handler(event) { + hydrateObj(event, { + delegateTarget: element + }); + if (handler.oneOff) { + EventHandler.off(element, event.type, fn); + } + return fn.apply(element, [event]); + }; + } + function bootstrapDelegationHandler(element, selector, fn) { + return function handler(event) { + const domElements = element.querySelectorAll(selector); + for (let { + target + } = event; target && target !== this; target = target.parentNode) { + for (const domElement of domElements) { + if (domElement !== target) { + continue; + } + hydrateObj(event, { + delegateTarget: target + }); + if (handler.oneOff) { + EventHandler.off(element, event.type, selector, fn); + } + return fn.apply(target, [event]); + } + } + }; + } + function findHandler(events, callable, delegationSelector = null) { + return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector); + } + function normalizeParameters(originalTypeEvent, handler, delegationFunction) { + const isDelegated = typeof handler === 'string'; + // TODO: tooltip passes `false` instead of selector, so we need to check + const callable = isDelegated ? delegationFunction : handler || delegationFunction; + let typeEvent = getTypeEvent(originalTypeEvent); + if (!nativeEvents.has(typeEvent)) { + typeEvent = originalTypeEvent; + } + return [isDelegated, callable, typeEvent]; + } + function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { + if (typeof originalTypeEvent !== 'string' || !element) { + return; + } + let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); + + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (originalTypeEvent in customEvents) { + const wrapFunction = fn => { + return function (event) { + if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) { + return fn.call(this, event); + } + }; + }; + callable = wrapFunction(callable); + } + const events = getElementEvents(element); + const handlers = events[typeEvent] || (events[typeEvent] = {}); + const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null); + if (previousFunction) { + previousFunction.oneOff = previousFunction.oneOff && oneOff; + return; + } + const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, '')); + const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable); + fn.delegationSelector = isDelegated ? handler : null; + fn.callable = callable; + fn.oneOff = oneOff; + fn.uidEvent = uid; + handlers[uid] = fn; + element.addEventListener(typeEvent, fn, isDelegated); + } + function removeHandler(element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector); + if (!fn) { + return; + } + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); + delete events[typeEvent][fn.uidEvent]; + } + function removeNamespacedHandlers(element, events, typeEvent, namespace) { + const storeElementEvent = events[typeEvent] || {}; + for (const [handlerKey, event] of Object.entries(storeElementEvent)) { + if (handlerKey.includes(namespace)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); + } + } + } + function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, ''); + return customEvents[event] || event; + } + const EventHandler = { + on(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, false); + }, + one(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, true); + }, + off(element, originalTypeEvent, handler, delegationFunction) { + if (typeof originalTypeEvent !== 'string' || !element) { + return; + } + const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); + const inNamespace = typeEvent !== originalTypeEvent; + const events = getElementEvents(element); + const storeElementEvent = events[typeEvent] || {}; + const isNamespace = originalTypeEvent.startsWith('.'); + if (typeof callable !== 'undefined') { + // Simplest case: handler is passed, remove that listener ONLY. + if (!Object.keys(storeElementEvent).length) { + return; + } + removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null); + return; + } + if (isNamespace) { + for (const elementEvent of Object.keys(events)) { + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); + } + } + for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { + const handlerKey = keyHandlers.replace(stripUidRegex, ''); + if (!inNamespace || originalTypeEvent.includes(handlerKey)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); + } + } + }, + trigger(element, event, args) { + if (typeof event !== 'string' || !element) { + return null; + } + const $ = getjQuery(); + const typeEvent = getTypeEvent(event); + const inNamespace = event !== typeEvent; + let jQueryEvent = null; + let bubbles = true; + let nativeDispatch = true; + let defaultPrevented = false; + if (inNamespace && $) { + jQueryEvent = $.Event(event, args); + $(element).trigger(jQueryEvent); + bubbles = !jQueryEvent.isPropagationStopped(); + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); + defaultPrevented = jQueryEvent.isDefaultPrevented(); + } + const evt = hydrateObj(new Event(event, { + bubbles, + cancelable: true + }), args); + if (defaultPrevented) { + evt.preventDefault(); + } + if (nativeDispatch) { + element.dispatchEvent(evt); + } + if (evt.defaultPrevented && jQueryEvent) { + jQueryEvent.preventDefault(); + } + return evt; + } + }; + function hydrateObj(obj, meta = {}) { + for (const [key, value] of Object.entries(meta)) { + try { + obj[key] = value; + } catch (_unused) { + Object.defineProperty(obj, key, { + configurable: true, + get() { + return value; + } + }); + } + } + return obj; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + function normalizeData(value) { + if (value === 'true') { + return true; + } + if (value === 'false') { + return false; + } + if (value === Number(value).toString()) { + return Number(value); + } + if (value === '' || value === 'null') { + return null; + } + if (typeof value !== 'string') { + return value; + } + try { + return JSON.parse(decodeURIComponent(value)); + } catch (_unused) { + return value; + } + } + function normalizeDataKey(key) { + return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); + } + const Manipulator = { + setDataAttribute(element, key, value) { + element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); + }, + removeDataAttribute(element, key) { + element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); + }, + getDataAttributes(element) { + if (!element) { + return {}; + } + const attributes = {}; + const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); + for (const key of bsKeys) { + let pureKey = key.replace(/^bs/, ''); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); + attributes[pureKey] = normalizeData(element.dataset[key]); + } + return attributes; + }, + getDataAttribute(element, key) { + return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/config.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Class definition + */ + + class Config { + // Getters + static get Default() { + return {}; + } + static get DefaultType() { + return {}; + } + static get NAME() { + throw new Error('You have to implement the static method "NAME", for each component!'); + } + _getConfig(config) { + config = this._mergeConfigObj(config); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + _configAfterMerge(config) { + return config; + } + _mergeConfigObj(config, element) { + const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse + + return { + ...this.constructor.Default, + ...(typeof jsonConfig === 'object' ? jsonConfig : {}), + ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}), + ...(typeof config === 'object' ? config : {}) + }; + } + _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { + for (const [property, expectedTypes] of Object.entries(configTypes)) { + const value = config[property]; + const valueType = isElement(value) ? 'element' : toType(value); + if (!new RegExp(expectedTypes).test(valueType)) { + throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); + } + } + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const VERSION = '5.3.3'; + + /** + * Class definition + */ + + class BaseComponent extends Config { + constructor(element, config) { + super(); + element = getElement(element); + if (!element) { + return; + } + this._element = element; + this._config = this._getConfig(config); + Data.set(this._element, this.constructor.DATA_KEY, this); + } + + // Public + dispose() { + Data.remove(this._element, this.constructor.DATA_KEY); + EventHandler.off(this._element, this.constructor.EVENT_KEY); + for (const propertyName of Object.getOwnPropertyNames(this)) { + this[propertyName] = null; + } + } + _queueCallback(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated); + } + _getConfig(config) { + config = this._mergeConfigObj(config, this._element); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + + // Static + static getInstance(element) { + return Data.get(getElement(element), this.DATA_KEY); + } + static getOrCreateInstance(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); + } + static get VERSION() { + return VERSION; + } + static get DATA_KEY() { + return `bs.${this.NAME}`; + } + static get EVENT_KEY() { + return `.${this.DATA_KEY}`; + } + static eventName(name) { + return `${name}${this.EVENT_KEY}`; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap dom/selector-engine.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const getSelector = element => { + let selector = element.getAttribute('data-bs-target'); + if (!selector || selector === '#') { + let hrefAttribute = element.getAttribute('href'); + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { + return null; + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}`; + } + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; + } + return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null; + }; + const SelectorEngine = { + find(selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); + }, + findOne(selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector); + }, + children(element, selector) { + return [].concat(...element.children).filter(child => child.matches(selector)); + }, + parents(element, selector) { + const parents = []; + let ancestor = element.parentNode.closest(selector); + while (ancestor) { + parents.push(ancestor); + ancestor = ancestor.parentNode.closest(selector); + } + return parents; + }, + prev(element, selector) { + let previous = element.previousElementSibling; + while (previous) { + if (previous.matches(selector)) { + return [previous]; + } + previous = previous.previousElementSibling; + } + return []; + }, + // TODO: this is now unused; remove later along with prev() + next(element, selector) { + let next = element.nextElementSibling; + while (next) { + if (next.matches(selector)) { + return [next]; + } + next = next.nextElementSibling; + } + return []; + }, + focusableChildren(element) { + const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); + }, + getSelectorFromElement(element) { + const selector = getSelector(element); + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null; + } + return null; + }, + getElementFromSelector(element) { + const selector = getSelector(element); + return selector ? SelectorEngine.findOne(selector) : null; + }, + getMultipleElementsFromSelector(element) { + const selector = getSelector(element); + return selector ? SelectorEngine.find(selector) : []; + } + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/component-functions.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const enableDismissTrigger = (component, method = 'hide') => { + const clickEvent = `click.dismiss${component.EVENT_KEY}`; + const name = component.NAME; + EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + if (isDisabled(this)) { + return; + } + const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); + const instance = component.getOrCreateInstance(target); + + // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + instance[method](); + }); + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$f = 'alert'; + const DATA_KEY$a = 'bs.alert'; + const EVENT_KEY$b = `.${DATA_KEY$a}`; + const EVENT_CLOSE = `close${EVENT_KEY$b}`; + const EVENT_CLOSED = `closed${EVENT_KEY$b}`; + const CLASS_NAME_FADE$5 = 'fade'; + const CLASS_NAME_SHOW$8 = 'show'; + + /** + * Class definition + */ + + class Alert extends BaseComponent { + // Getters + static get NAME() { + return NAME$f; + } + + // Public + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); + if (closeEvent.defaultPrevented) { + return; + } + this._element.classList.remove(CLASS_NAME_SHOW$8); + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); + this._queueCallback(() => this._destroyElement(), this._element, isAnimated); + } + + // Private + _destroyElement() { + this._element.remove(); + EventHandler.trigger(this._element, EVENT_CLOSED); + this.dispose(); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Alert.getOrCreateInstance(this); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](this); + }); + } + } + + /** + * Data API implementation + */ + + enableDismissTrigger(Alert, 'close'); + + /** + * jQuery + */ + + defineJQueryPlugin(Alert); + + /** + * -------------------------------------------------------------------------- + * Bootstrap button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$e = 'button'; + const DATA_KEY$9 = 'bs.button'; + const EVENT_KEY$a = `.${DATA_KEY$9}`; + const DATA_API_KEY$6 = '.data-api'; + const CLASS_NAME_ACTIVE$3 = 'active'; + const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; + const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; + + /** + * Class definition + */ + + class Button extends BaseComponent { + // Getters + static get NAME() { + return NAME$e; + } + + // Public + toggle() { + // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method + this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Button.getOrCreateInstance(this); + if (config === 'toggle') { + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { + event.preventDefault(); + const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); + const data = Button.getOrCreateInstance(button); + data.toggle(); + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Button); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/swipe.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$d = 'swipe'; + const EVENT_KEY$9 = '.bs.swipe'; + const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`; + const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`; + const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`; + const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`; + const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`; + const POINTER_TYPE_TOUCH = 'touch'; + const POINTER_TYPE_PEN = 'pen'; + const CLASS_NAME_POINTER_EVENT = 'pointer-event'; + const SWIPE_THRESHOLD = 40; + const Default$c = { + endCallback: null, + leftCallback: null, + rightCallback: null + }; + const DefaultType$c = { + endCallback: '(function|null)', + leftCallback: '(function|null)', + rightCallback: '(function|null)' + }; + + /** + * Class definition + */ + + class Swipe extends Config { + constructor(element, config) { + super(); + this._element = element; + if (!element || !Swipe.isSupported()) { + return; + } + this._config = this._getConfig(config); + this._deltaX = 0; + this._supportPointerEvents = Boolean(window.PointerEvent); + this._initEvents(); + } + + // Getters + static get Default() { + return Default$c; + } + static get DefaultType() { + return DefaultType$c; + } + static get NAME() { + return NAME$d; + } + + // Public + dispose() { + EventHandler.off(this._element, EVENT_KEY$9); + } + + // Private + _start(event) { + if (!this._supportPointerEvents) { + this._deltaX = event.touches[0].clientX; + return; + } + if (this._eventIsPointerPenTouch(event)) { + this._deltaX = event.clientX; + } + } + _end(event) { + if (this._eventIsPointerPenTouch(event)) { + this._deltaX = event.clientX - this._deltaX; + } + this._handleSwipe(); + execute(this._config.endCallback); + } + _move(event) { + this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; + } + _handleSwipe() { + const absDeltaX = Math.abs(this._deltaX); + if (absDeltaX <= SWIPE_THRESHOLD) { + return; + } + const direction = absDeltaX / this._deltaX; + this._deltaX = 0; + if (!direction) { + return; + } + execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); + } + _initEvents() { + if (this._supportPointerEvents) { + EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); + EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); + this._element.classList.add(CLASS_NAME_POINTER_EVENT); + } else { + EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); + EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event)); + EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); + } + } + _eventIsPointerPenTouch(event) { + return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); + } + + // Static + static isSupported() { + return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$c = 'carousel'; + const DATA_KEY$8 = 'bs.carousel'; + const EVENT_KEY$8 = `.${DATA_KEY$8}`; + const DATA_API_KEY$5 = '.data-api'; + const ARROW_LEFT_KEY$1 = 'ArrowLeft'; + const ARROW_RIGHT_KEY$1 = 'ArrowRight'; + const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch + + const ORDER_NEXT = 'next'; + const ORDER_PREV = 'prev'; + const DIRECTION_LEFT = 'left'; + const DIRECTION_RIGHT = 'right'; + const EVENT_SLIDE = `slide${EVENT_KEY$8}`; + const EVENT_SLID = `slid${EVENT_KEY$8}`; + const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`; + const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`; + const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`; + const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`; + const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`; + const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`; + const CLASS_NAME_CAROUSEL = 'carousel'; + const CLASS_NAME_ACTIVE$2 = 'active'; + const CLASS_NAME_SLIDE = 'slide'; + const CLASS_NAME_END = 'carousel-item-end'; + const CLASS_NAME_START = 'carousel-item-start'; + const CLASS_NAME_NEXT = 'carousel-item-next'; + const CLASS_NAME_PREV = 'carousel-item-prev'; + const SELECTOR_ACTIVE = '.active'; + const SELECTOR_ITEM = '.carousel-item'; + const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM; + const SELECTOR_ITEM_IMG = '.carousel-item img'; + const SELECTOR_INDICATORS = '.carousel-indicators'; + const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; + const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; + const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT + }; + const Default$b = { + interval: 5000, + keyboard: true, + pause: 'hover', + ride: false, + touch: true, + wrap: true + }; + const DefaultType$b = { + interval: '(number|boolean)', + // TODO:v6 remove boolean support + keyboard: 'boolean', + pause: '(string|boolean)', + ride: '(boolean|string)', + touch: 'boolean', + wrap: 'boolean' + }; + + /** + * Class definition + */ + + class Carousel extends BaseComponent { + constructor(element, config) { + super(element, config); + this._interval = null; + this._activeElement = null; + this._isSliding = false; + this.touchTimeout = null; + this._swipeHelper = null; + this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); + this._addEventListeners(); + if (this._config.ride === CLASS_NAME_CAROUSEL) { + this.cycle(); + } + } + + // Getters + static get Default() { + return Default$b; + } + static get DefaultType() { + return DefaultType$b; + } + static get NAME() { + return NAME$c; + } + + // Public + next() { + this._slide(ORDER_NEXT); + } + nextWhenVisible() { + // FIXME TODO use `document.visibilityState` + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && isVisible(this._element)) { + this.next(); + } + } + prev() { + this._slide(ORDER_PREV); + } + pause() { + if (this._isSliding) { + triggerTransitionEnd(this._element); + } + this._clearInterval(); + } + cycle() { + this._clearInterval(); + this._updateInterval(); + this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval); + } + _maybeEnableCycle() { + if (!this._config.ride) { + return; + } + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.cycle()); + return; + } + this.cycle(); + } + to(index) { + const items = this._getItems(); + if (index > items.length - 1 || index < 0) { + return; + } + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); + return; + } + const activeIndex = this._getItemIndex(this._getActive()); + if (activeIndex === index) { + return; + } + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; + this._slide(order, items[index]); + } + dispose() { + if (this._swipeHelper) { + this._swipeHelper.dispose(); + } + super.dispose(); + } + + // Private + _configAfterMerge(config) { + config.defaultInterval = config.interval; + return config; + } + _addEventListeners() { + if (this._config.keyboard) { + EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event)); + } + if (this._config.pause === 'hover') { + EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause()); + EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle()); + } + if (this._config.touch && Swipe.isSupported()) { + this._addTouchEventListeners(); + } + } + _addTouchEventListeners() { + for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { + EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()); + } + const endCallBack = () => { + if (this._config.pause !== 'hover') { + return; + } + + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + + this.pause(); + if (this.touchTimeout) { + clearTimeout(this.touchTimeout); + } + this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval); + }; + const swipeConfig = { + leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), + rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), + endCallback: endCallBack + }; + this._swipeHelper = new Swipe(this._element, swipeConfig); + } + _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return; + } + const direction = KEY_TO_DIRECTION[event.key]; + if (direction) { + event.preventDefault(); + this._slide(this._directionToOrder(direction)); + } + } + _getItemIndex(element) { + return this._getItems().indexOf(element); + } + _setActiveIndicatorElement(index) { + if (!this._indicatorsElement) { + return; + } + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement); + activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); + activeIndicator.removeAttribute('aria-current'); + const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement); + if (newActiveIndicator) { + newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2); + newActiveIndicator.setAttribute('aria-current', 'true'); + } + } + _updateInterval() { + const element = this._activeElement || this._getActive(); + if (!element) { + return; + } + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); + this._config.interval = elementInterval || this._config.defaultInterval; + } + _slide(order, element = null) { + if (this._isSliding) { + return; + } + const activeElement = this._getActive(); + const isNext = order === ORDER_NEXT; + const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap); + if (nextElement === activeElement) { + return; + } + const nextElementIndex = this._getItemIndex(nextElement); + const triggerEvent = eventName => { + return EventHandler.trigger(this._element, eventName, { + relatedTarget: nextElement, + direction: this._orderToDirection(order), + from: this._getItemIndex(activeElement), + to: nextElementIndex + }); + }; + const slideEvent = triggerEvent(EVENT_SLIDE); + if (slideEvent.defaultPrevented) { + return; + } + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + // TODO: change tests that use empty divs to avoid this check + return; + } + const isCycling = Boolean(this._interval); + this.pause(); + this._isSliding = true; + this._setActiveIndicatorElement(nextElementIndex); + this._activeElement = nextElement; + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; + nextElement.classList.add(orderClassName); + reflow(nextElement); + activeElement.classList.add(directionalClassName); + nextElement.classList.add(directionalClassName); + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName); + nextElement.classList.add(CLASS_NAME_ACTIVE$2); + activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); + this._isSliding = false; + triggerEvent(EVENT_SLID); + }; + this._queueCallback(completeCallBack, activeElement, this._isAnimated()); + if (isCycling) { + this.cycle(); + } + } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_SLIDE); + } + _getActive() { + return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); + } + _getItems() { + return SelectorEngine.find(SELECTOR_ITEM, this._element); + } + _clearInterval() { + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + } + _directionToOrder(direction) { + if (isRTL()) { + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; + } + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; + } + _orderToDirection(order) { + if (isRTL()) { + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Carousel.getOrCreateInstance(this, config); + if (typeof config === 'number') { + data.to(config); + return; + } + if (typeof config === 'string') { + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { + return; + } + event.preventDefault(); + const carousel = Carousel.getOrCreateInstance(target); + const slideIndex = this.getAttribute('data-bs-slide-to'); + if (slideIndex) { + carousel.to(slideIndex); + carousel._maybeEnableCycle(); + return; + } + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { + carousel.next(); + carousel._maybeEnableCycle(); + return; + } + carousel.prev(); + carousel._maybeEnableCycle(); + }); + EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => { + const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); + for (const carousel of carousels) { + Carousel.getOrCreateInstance(carousel); + } + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Carousel); + + /** + * -------------------------------------------------------------------------- + * Bootstrap collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$b = 'collapse'; + const DATA_KEY$7 = 'bs.collapse'; + const EVENT_KEY$7 = `.${DATA_KEY$7}`; + const DATA_API_KEY$4 = '.data-api'; + const EVENT_SHOW$6 = `show${EVENT_KEY$7}`; + const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`; + const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`; + const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`; + const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`; + const CLASS_NAME_SHOW$7 = 'show'; + const CLASS_NAME_COLLAPSE = 'collapse'; + const CLASS_NAME_COLLAPSING = 'collapsing'; + const CLASS_NAME_COLLAPSED = 'collapsed'; + const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; + const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; + const WIDTH = 'width'; + const HEIGHT = 'height'; + const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; + const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; + const Default$a = { + parent: null, + toggle: true + }; + const DefaultType$a = { + parent: '(null|element)', + toggle: 'boolean' + }; + + /** + * Class definition + */ + + class Collapse extends BaseComponent { + constructor(element, config) { + super(element, config); + this._isTransitioning = false; + this._triggerArray = []; + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); + for (const elem of toggleList) { + const selector = SelectorEngine.getSelectorFromElement(elem); + const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element); + if (selector !== null && filterElement.length) { + this._triggerArray.push(elem); + } + } + this._initializeChildren(); + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); + } + if (this._config.toggle) { + this.toggle(); + } + } + + // Getters + static get Default() { + return Default$a; + } + static get DefaultType() { + return DefaultType$a; + } + static get NAME() { + return NAME$b; + } + + // Public + toggle() { + if (this._isShown()) { + this.hide(); + } else { + this.show(); + } + } + show() { + if (this._isTransitioning || this._isShown()) { + return; + } + let activeChildren = []; + + // find active children + if (this._config.parent) { + activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, { + toggle: false + })); + } + if (activeChildren.length && activeChildren[0]._isTransitioning) { + return; + } + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6); + if (startEvent.defaultPrevented) { + return; + } + for (const activeInstance of activeChildren) { + activeInstance.hide(); + } + const dimension = this._getDimension(); + this._element.classList.remove(CLASS_NAME_COLLAPSE); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.style[dimension] = 0; + this._addAriaAndCollapsedClass(this._triggerArray, true); + this._isTransitioning = true; + const complete = () => { + this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + this._element.style[dimension] = ''; + EventHandler.trigger(this._element, EVENT_SHOWN$6); + }; + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); + const scrollSize = `scroll${capitalizedDimension}`; + this._queueCallback(complete, this._element, true); + this._element.style[dimension] = `${this._element[scrollSize]}px`; + } + hide() { + if (this._isTransitioning || !this._isShown()) { + return; + } + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6); + if (startEvent.defaultPrevented) { + return; + } + const dimension = this._getDimension(); + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; + reflow(this._element); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + for (const trigger of this._triggerArray) { + const element = SelectorEngine.getElementFromSelector(trigger); + if (element && !this._isShown(element)) { + this._addAriaAndCollapsedClass([trigger], false); + } + } + this._isTransitioning = true; + const complete = () => { + this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE); + EventHandler.trigger(this._element, EVENT_HIDDEN$6); + }; + this._element.style[dimension] = ''; + this._queueCallback(complete, this._element, true); + } + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW$7); + } + + // Private + _configAfterMerge(config) { + config.toggle = Boolean(config.toggle); // Coerce string values + config.parent = getElement(config.parent); + return config; + } + _getDimension() { + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; + } + _initializeChildren() { + if (!this._config.parent) { + return; + } + const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4); + for (const element of children) { + const selected = SelectorEngine.getElementFromSelector(element); + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)); + } + } + } + _getFirstLevelChildren(selector) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); + // remove children if greater depth + return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)); + } + _addAriaAndCollapsedClass(triggerArray, isOpen) { + if (!triggerArray.length) { + return; + } + for (const element of triggerArray) { + element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen); + element.setAttribute('aria-expanded', isOpen); + } + } + + // Static + static jQueryInterface(config) { + const _config = {}; + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false; + } + return this.each(function () { + const data = Collapse.getOrCreateInstance(this, _config); + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + } + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { + // preventDefault only for <a> elements (which change the URL) not inside the collapsible element + if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { + event.preventDefault(); + } + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { + Collapse.getOrCreateInstance(element, { + toggle: false + }).toggle(); + } + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Collapse); + + /** + * -------------------------------------------------------------------------- + * Bootstrap dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$a = 'dropdown'; + const DATA_KEY$6 = 'bs.dropdown'; + const EVENT_KEY$6 = `.${DATA_KEY$6}`; + const DATA_API_KEY$3 = '.data-api'; + const ESCAPE_KEY$2 = 'Escape'; + const TAB_KEY$1 = 'Tab'; + const ARROW_UP_KEY$1 = 'ArrowUp'; + const ARROW_DOWN_KEY$1 = 'ArrowDown'; + const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button + + const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`; + const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`; + const EVENT_SHOW$5 = `show${EVENT_KEY$6}`; + const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`; + const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; + const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`; + const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`; + const CLASS_NAME_SHOW$6 = 'show'; + const CLASS_NAME_DROPUP = 'dropup'; + const CLASS_NAME_DROPEND = 'dropend'; + const CLASS_NAME_DROPSTART = 'dropstart'; + const CLASS_NAME_DROPUP_CENTER = 'dropup-center'; + const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'; + const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'; + const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`; + const SELECTOR_MENU = '.dropdown-menu'; + const SELECTOR_NAVBAR = '.navbar'; + const SELECTOR_NAVBAR_NAV = '.navbar-nav'; + const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; + const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; + const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; + const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; + const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; + const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; + const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; + const PLACEMENT_TOPCENTER = 'top'; + const PLACEMENT_BOTTOMCENTER = 'bottom'; + const Default$9 = { + autoClose: true, + boundary: 'clippingParents', + display: 'dynamic', + offset: [0, 2], + popperConfig: null, + reference: 'toggle' + }; + const DefaultType$9 = { + autoClose: '(boolean|string)', + boundary: '(string|element)', + display: 'string', + offset: '(array|string|function)', + popperConfig: '(null|object|function)', + reference: '(string|element|object)' + }; + + /** + * Class definition + */ + + class Dropdown extends BaseComponent { + constructor(element, config) { + super(element, config); + this._popper = null; + this._parent = this._element.parentNode; // dropdown wrapper + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent); + this._inNavbar = this._detectNavbar(); + } + + // Getters + static get Default() { + return Default$9; + } + static get DefaultType() { + return DefaultType$9; + } + static get NAME() { + return NAME$a; + } + + // Public + toggle() { + return this._isShown() ? this.hide() : this.show(); + } + show() { + if (isDisabled(this._element) || this._isShown()) { + return; + } + const relatedTarget = { + relatedTarget: this._element + }; + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget); + if (showEvent.defaultPrevented) { + return; + } + this._createPopper(); + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop); + } + } + this._element.focus(); + this._element.setAttribute('aria-expanded', true); + this._menu.classList.add(CLASS_NAME_SHOW$6); + this._element.classList.add(CLASS_NAME_SHOW$6); + EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget); + } + hide() { + if (isDisabled(this._element) || !this._isShown()) { + return; + } + const relatedTarget = { + relatedTarget: this._element + }; + this._completeHide(relatedTarget); + } + dispose() { + if (this._popper) { + this._popper.destroy(); + } + super.dispose(); + } + update() { + this._inNavbar = this._detectNavbar(); + if (this._popper) { + this._popper.update(); + } + } + + // Private + _completeHide(relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget); + if (hideEvent.defaultPrevented) { + return; + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop); + } + } + if (this._popper) { + this._popper.destroy(); + } + this._menu.classList.remove(CLASS_NAME_SHOW$6); + this._element.classList.remove(CLASS_NAME_SHOW$6); + this._element.setAttribute('aria-expanded', 'false'); + Manipulator.removeDataAttribute(this._menu, 'popper'); + EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); + } + _getConfig(config) { + config = super._getConfig(config); + if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { + // Popper virtual elements require a getBoundingClientRect method + throw new TypeError(`${NAME$a.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); + } + return config; + } + _createPopper() { + if (typeof Popper__namespace === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); + } + let referenceElement = this._element; + if (this._config.reference === 'parent') { + referenceElement = this._parent; + } else if (isElement(this._config.reference)) { + referenceElement = getElement(this._config.reference); + } else if (typeof this._config.reference === 'object') { + referenceElement = this._config.reference; + } + const popperConfig = this._getPopperConfig(); + this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig); + } + _isShown() { + return this._menu.classList.contains(CLASS_NAME_SHOW$6); + } + _getPlacement() { + const parentDropdown = this._parent; + if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { + return PLACEMENT_RIGHT; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { + return PLACEMENT_LEFT; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { + return PLACEMENT_TOPCENTER; + } + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { + return PLACEMENT_BOTTOMCENTER; + } + + // We need to trim the value because custom properties can also include spaces + const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { + return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; + } + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; + } + _detectNavbar() { + return this._element.closest(SELECTOR_NAVBAR) !== null; + } + _getOffset() { + const { + offset + } = this._config; + if (typeof offset === 'string') { + return offset.split(',').map(value => Number.parseInt(value, 10)); + } + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element); + } + return offset; + } + _getPopperConfig() { + const defaultBsPopperConfig = { + placement: this._getPlacement(), + modifiers: [{ + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, { + name: 'offset', + options: { + offset: this._getOffset() + } + }] + }; + + // Disable Popper if we have a static display or Dropdown is in Navbar + if (this._inNavbar || this._config.display === 'static') { + Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove + defaultBsPopperConfig.modifiers = [{ + name: 'applyStyles', + enabled: false + }]; + } + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [defaultBsPopperConfig]) + }; + } + _selectMenuItem({ + key, + target + }) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)); + if (!items.length) { + return; + } + + // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY + getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Dropdown.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + static clearMenus(event) { + if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) { + return; + } + const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN); + for (const toggle of openToggles) { + const context = Dropdown.getInstance(toggle); + if (!context || context._config.autoClose === false) { + continue; + } + const composedPath = event.composedPath(); + const isMenuTarget = composedPath.includes(context._menu); + if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { + continue; + } + + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { + continue; + } + const relatedTarget = { + relatedTarget: context._element + }; + if (event.type === 'click') { + relatedTarget.clickEvent = event; + } + context._completeHide(relatedTarget); + } + } + static dataApiKeydownHandler(event) { + // If not an UP | DOWN | ESCAPE key => not a dropdown command + // If input/textarea && if key is other than ESCAPE => not a dropdown command + + const isInput = /input|textarea/i.test(event.target.tagName); + const isEscapeEvent = event.key === ESCAPE_KEY$2; + const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key); + if (!isUpOrDownEvent && !isEscapeEvent) { + return; + } + if (isInput && !isEscapeEvent) { + return; + } + event.preventDefault(); + + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode); + const instance = Dropdown.getOrCreateInstance(getToggleButton); + if (isUpOrDownEvent) { + event.stopPropagation(); + instance.show(); + instance._selectMenuItem(event); + return; + } + if (instance._isShown()) { + // else is escape and we check if it is shown + event.stopPropagation(); + instance.hide(); + getToggleButton.focus(); + } + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); + EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { + event.preventDefault(); + Dropdown.getOrCreateInstance(this).toggle(); + }); + + /** + * jQuery + */ + + defineJQueryPlugin(Dropdown); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/backdrop.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$9 = 'backdrop'; + const CLASS_NAME_FADE$4 = 'fade'; + const CLASS_NAME_SHOW$5 = 'show'; + const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`; + const Default$8 = { + className: 'modal-backdrop', + clickCallback: null, + isAnimated: false, + isVisible: true, + // if false, we use the backdrop helper without adding any element to the dom + rootElement: 'body' // give the choice to place backdrop under different elements + }; + const DefaultType$8 = { + className: 'string', + clickCallback: '(function|null)', + isAnimated: 'boolean', + isVisible: 'boolean', + rootElement: '(element|string)' + }; + + /** + * Class definition + */ + + class Backdrop extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + this._isAppended = false; + this._element = null; + } + + // Getters + static get Default() { + return Default$8; + } + static get DefaultType() { + return DefaultType$8; + } + static get NAME() { + return NAME$9; + } + + // Public + show(callback) { + if (!this._config.isVisible) { + execute(callback); + return; + } + this._append(); + const element = this._getElement(); + if (this._config.isAnimated) { + reflow(element); + } + element.classList.add(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + execute(callback); + }); + } + hide(callback) { + if (!this._config.isVisible) { + execute(callback); + return; + } + this._getElement().classList.remove(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + this.dispose(); + execute(callback); + }); + } + dispose() { + if (!this._isAppended) { + return; + } + EventHandler.off(this._element, EVENT_MOUSEDOWN); + this._element.remove(); + this._isAppended = false; + } + + // Private + _getElement() { + if (!this._element) { + const backdrop = document.createElement('div'); + backdrop.className = this._config.className; + if (this._config.isAnimated) { + backdrop.classList.add(CLASS_NAME_FADE$4); + } + this._element = backdrop; + } + return this._element; + } + _configAfterMerge(config) { + // use getElement() with the default "body" to get a fresh Element on each instantiation + config.rootElement = getElement(config.rootElement); + return config; + } + _append() { + if (this._isAppended) { + return; + } + const element = this._getElement(); + this._config.rootElement.append(element); + EventHandler.on(element, EVENT_MOUSEDOWN, () => { + execute(this._config.clickCallback); + }); + this._isAppended = true; + } + _emulateAnimation(callback) { + executeAfterTransition(callback, this._getElement(), this._config.isAnimated); + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/focustrap.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$8 = 'focustrap'; + const DATA_KEY$5 = 'bs.focustrap'; + const EVENT_KEY$5 = `.${DATA_KEY$5}`; + const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`; + const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`; + const TAB_KEY = 'Tab'; + const TAB_NAV_FORWARD = 'forward'; + const TAB_NAV_BACKWARD = 'backward'; + const Default$7 = { + autofocus: true, + trapElement: null // The element to trap focus inside of + }; + const DefaultType$7 = { + autofocus: 'boolean', + trapElement: 'element' + }; + + /** + * Class definition + */ + + class FocusTrap extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + this._isActive = false; + this._lastTabNavDirection = null; + } + + // Getters + static get Default() { + return Default$7; + } + static get DefaultType() { + return DefaultType$7; + } + static get NAME() { + return NAME$8; + } + + // Public + activate() { + if (this._isActive) { + return; + } + if (this._config.autofocus) { + this._config.trapElement.focus(); + } + EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event)); + EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); + this._isActive = true; + } + deactivate() { + if (!this._isActive) { + return; + } + this._isActive = false; + EventHandler.off(document, EVENT_KEY$5); + } + + // Private + _handleFocusin(event) { + const { + trapElement + } = this._config; + if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { + return; + } + const elements = SelectorEngine.focusableChildren(trapElement); + if (elements.length === 0) { + trapElement.focus(); + } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { + elements[elements.length - 1].focus(); + } else { + elements[0].focus(); + } + } + _handleKeydown(event) { + if (event.key !== TAB_KEY) { + return; + } + this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/scrollBar.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; + const SELECTOR_STICKY_CONTENT = '.sticky-top'; + const PROPERTY_PADDING = 'padding-right'; + const PROPERTY_MARGIN = 'margin-right'; + + /** + * Class definition + */ + + class ScrollBarHelper { + constructor() { + this._element = document.body; + } + + // Public + getWidth() { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth; + return Math.abs(window.innerWidth - documentWidth); + } + hide() { + const width = this.getWidth(); + this._disableOverFlow(); + // give padding to element to balance the hidden scrollbar width + this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); + this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); + } + reset() { + this._resetElementAttributes(this._element, 'overflow'); + this._resetElementAttributes(this._element, PROPERTY_PADDING); + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); + } + isOverflowing() { + return this.getWidth() > 0; + } + + // Private + _disableOverFlow() { + this._saveInitialAttribute(this._element, 'overflow'); + this._element.style.overflow = 'hidden'; + } + _setElementAttributes(selector, styleProperty, callback) { + const scrollbarWidth = this.getWidth(); + const manipulationCallBack = element => { + if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { + return; + } + this._saveInitialAttribute(element, styleProperty); + const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); + element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); + }; + this._applyManipulationCallback(selector, manipulationCallBack); + } + _saveInitialAttribute(element, styleProperty) { + const actualValue = element.style.getPropertyValue(styleProperty); + if (actualValue) { + Manipulator.setDataAttribute(element, styleProperty, actualValue); + } + } + _resetElementAttributes(selector, styleProperty) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProperty); + // We only want to remove the property if the value is `null`; the value can also be zero + if (value === null) { + element.style.removeProperty(styleProperty); + return; + } + Manipulator.removeDataAttribute(element, styleProperty); + element.style.setProperty(styleProperty, value); + }; + this._applyManipulationCallback(selector, manipulationCallBack); + } + _applyManipulationCallback(selector, callBack) { + if (isElement(selector)) { + callBack(selector); + return; + } + for (const sel of SelectorEngine.find(selector, this._element)) { + callBack(sel); + } + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$7 = 'modal'; + const DATA_KEY$4 = 'bs.modal'; + const EVENT_KEY$4 = `.${DATA_KEY$4}`; + const DATA_API_KEY$2 = '.data-api'; + const ESCAPE_KEY$1 = 'Escape'; + const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`; + const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`; + const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`; + const EVENT_SHOW$4 = `show${EVENT_KEY$4}`; + const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`; + const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`; + const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`; + const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`; + const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`; + const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`; + const CLASS_NAME_OPEN = 'modal-open'; + const CLASS_NAME_FADE$3 = 'fade'; + const CLASS_NAME_SHOW$4 = 'show'; + const CLASS_NAME_STATIC = 'modal-static'; + const OPEN_SELECTOR$1 = '.modal.show'; + const SELECTOR_DIALOG = '.modal-dialog'; + const SELECTOR_MODAL_BODY = '.modal-body'; + const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; + const Default$6 = { + backdrop: true, + focus: true, + keyboard: true + }; + const DefaultType$6 = { + backdrop: '(boolean|string)', + focus: 'boolean', + keyboard: 'boolean' + }; + + /** + * Class definition + */ + + class Modal extends BaseComponent { + constructor(element, config) { + super(element, config); + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); + this._isShown = false; + this._isTransitioning = false; + this._scrollBar = new ScrollBarHelper(); + this._addEventListeners(); + } + + // Getters + static get Default() { + return Default$6; + } + static get DefaultType() { + return DefaultType$6; + } + static get NAME() { + return NAME$7; + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } + show(relatedTarget) { + if (this._isShown || this._isTransitioning) { + return; + } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, { + relatedTarget + }); + if (showEvent.defaultPrevented) { + return; + } + this._isShown = true; + this._isTransitioning = true; + this._scrollBar.hide(); + document.body.classList.add(CLASS_NAME_OPEN); + this._adjustDialog(); + this._backdrop.show(() => this._showElement(relatedTarget)); + } + hide() { + if (!this._isShown || this._isTransitioning) { + return; + } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4); + if (hideEvent.defaultPrevented) { + return; + } + this._isShown = false; + this._isTransitioning = true; + this._focustrap.deactivate(); + this._element.classList.remove(CLASS_NAME_SHOW$4); + this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); + } + dispose() { + EventHandler.off(window, EVENT_KEY$4); + EventHandler.off(this._dialog, EVENT_KEY$4); + this._backdrop.dispose(); + this._focustrap.deactivate(); + super.dispose(); + } + handleUpdate() { + this._adjustDialog(); + } + + // Private + _initializeBackDrop() { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), + // 'static' option will be translated to true, and booleans will keep their value, + isAnimated: this._isAnimated() + }); + } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }); + } + _showElement(relatedTarget) { + // try to append dynamic modal + if (!document.body.contains(this._element)) { + document.body.append(this._element); + } + this._element.style.display = 'block'; + this._element.removeAttribute('aria-hidden'); + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.scrollTop = 0; + const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); + if (modalBody) { + modalBody.scrollTop = 0; + } + reflow(this._element); + this._element.classList.add(CLASS_NAME_SHOW$4); + const transitionComplete = () => { + if (this._config.focus) { + this._focustrap.activate(); + } + this._isTransitioning = false; + EventHandler.trigger(this._element, EVENT_SHOWN$4, { + relatedTarget + }); + }; + this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); + } + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { + if (event.key !== ESCAPE_KEY$1) { + return; + } + if (this._config.keyboard) { + this.hide(); + return; + } + this._triggerBackdropTransition(); + }); + EventHandler.on(window, EVENT_RESIZE$1, () => { + if (this._isShown && !this._isTransitioning) { + this._adjustDialog(); + } + }); + EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { + // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks + EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { + if (this._element !== event.target || this._element !== event2.target) { + return; + } + if (this._config.backdrop === 'static') { + this._triggerBackdropTransition(); + return; + } + if (this._config.backdrop) { + this.hide(); + } + }); + }); + } + _hideModal() { + this._element.style.display = 'none'; + this._element.setAttribute('aria-hidden', true); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + this._isTransitioning = false; + this._backdrop.hide(() => { + document.body.classList.remove(CLASS_NAME_OPEN); + this._resetAdjustments(); + this._scrollBar.reset(); + EventHandler.trigger(this._element, EVENT_HIDDEN$4); + }); + } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_FADE$3); + } + _triggerBackdropTransition() { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1); + if (hideEvent.defaultPrevented) { + return; + } + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + const initialOverflowY = this._element.style.overflowY; + // return if the following background transition hasn't yet completed + if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { + return; + } + if (!isModalOverflowing) { + this._element.style.overflowY = 'hidden'; + } + this._element.classList.add(CLASS_NAME_STATIC); + this._queueCallback(() => { + this._element.classList.remove(CLASS_NAME_STATIC); + this._queueCallback(() => { + this._element.style.overflowY = initialOverflowY; + }, this._dialog); + }, this._dialog); + this._element.focus(); + } + + /** + * The following methods are used to handle overflowing modals + */ + + _adjustDialog() { + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + const scrollbarWidth = this._scrollBar.getWidth(); + const isBodyOverflowing = scrollbarWidth > 0; + if (isBodyOverflowing && !isModalOverflowing) { + const property = isRTL() ? 'paddingLeft' : 'paddingRight'; + this._element.style[property] = `${scrollbarWidth}px`; + } + if (!isBodyOverflowing && isModalOverflowing) { + const property = isRTL() ? 'paddingRight' : 'paddingLeft'; + this._element.style[property] = `${scrollbarWidth}px`; + } + } + _resetAdjustments() { + this._element.style.paddingLeft = ''; + this._element.style.paddingRight = ''; + } + + // Static + static jQueryInterface(config, relatedTarget) { + return this.each(function () { + const data = Modal.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](relatedTarget); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + EventHandler.one(target, EVENT_SHOW$4, showEvent => { + if (showEvent.defaultPrevented) { + // only register focus restorer if modal will actually get shown + return; + } + EventHandler.one(target, EVENT_HIDDEN$4, () => { + if (isVisible(this)) { + this.focus(); + } + }); + }); + + // avoid conflict when clicking modal toggler while another one is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); + if (alreadyOpen) { + Modal.getInstance(alreadyOpen).hide(); + } + const data = Modal.getOrCreateInstance(target); + data.toggle(this); + }); + enableDismissTrigger(Modal); + + /** + * jQuery + */ + + defineJQueryPlugin(Modal); + + /** + * -------------------------------------------------------------------------- + * Bootstrap offcanvas.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$6 = 'offcanvas'; + const DATA_KEY$3 = 'bs.offcanvas'; + const EVENT_KEY$3 = `.${DATA_KEY$3}`; + const DATA_API_KEY$1 = '.data-api'; + const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`; + const ESCAPE_KEY = 'Escape'; + const CLASS_NAME_SHOW$3 = 'show'; + const CLASS_NAME_SHOWING$1 = 'showing'; + const CLASS_NAME_HIDING = 'hiding'; + const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; + const OPEN_SELECTOR = '.offcanvas.show'; + const EVENT_SHOW$3 = `show${EVENT_KEY$3}`; + const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`; + const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`; + const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`; + const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`; + const EVENT_RESIZE = `resize${EVENT_KEY$3}`; + const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`; + const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`; + const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; + const Default$5 = { + backdrop: true, + keyboard: true, + scroll: false + }; + const DefaultType$5 = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + scroll: 'boolean' + }; + + /** + * Class definition + */ + + class Offcanvas extends BaseComponent { + constructor(element, config) { + super(element, config); + this._isShown = false; + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); + this._addEventListeners(); + } + + // Getters + static get Default() { + return Default$5; + } + static get DefaultType() { + return DefaultType$5; + } + static get NAME() { + return NAME$6; + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } + show(relatedTarget) { + if (this._isShown) { + return; + } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { + relatedTarget + }); + if (showEvent.defaultPrevented) { + return; + } + this._isShown = true; + this._backdrop.show(); + if (!this._config.scroll) { + new ScrollBarHelper().hide(); + } + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.classList.add(CLASS_NAME_SHOWING$1); + const completeCallBack = () => { + if (!this._config.scroll || this._config.backdrop) { + this._focustrap.activate(); + } + this._element.classList.add(CLASS_NAME_SHOW$3); + this._element.classList.remove(CLASS_NAME_SHOWING$1); + EventHandler.trigger(this._element, EVENT_SHOWN$3, { + relatedTarget + }); + }; + this._queueCallback(completeCallBack, this._element, true); + } + hide() { + if (!this._isShown) { + return; + } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); + if (hideEvent.defaultPrevented) { + return; + } + this._focustrap.deactivate(); + this._element.blur(); + this._isShown = false; + this._element.classList.add(CLASS_NAME_HIDING); + this._backdrop.hide(); + const completeCallback = () => { + this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + if (!this._config.scroll) { + new ScrollBarHelper().reset(); + } + EventHandler.trigger(this._element, EVENT_HIDDEN$3); + }; + this._queueCallback(completeCallback, this._element, true); + } + dispose() { + this._backdrop.dispose(); + this._focustrap.deactivate(); + super.dispose(); + } + + // Private + _initializeBackDrop() { + const clickCallback = () => { + if (this._config.backdrop === 'static') { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + return; + } + this.hide(); + }; + + // 'static' option will be translated to true, and booleans will keep their value + const isVisible = Boolean(this._config.backdrop); + return new Backdrop({ + className: CLASS_NAME_BACKDROP, + isVisible, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: isVisible ? clickCallback : null + }); + } + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }); + } + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (event.key !== ESCAPE_KEY) { + return; + } + if (this._config.keyboard) { + this.hide(); + return; + } + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + }); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Offcanvas.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](this); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { + const target = SelectorEngine.getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + if (isDisabled(this)) { + return; + } + EventHandler.one(target, EVENT_HIDDEN$3, () => { + // focus on trigger when it is closed + if (isVisible(this)) { + this.focus(); + } + }); + + // avoid conflict when clicking a toggler of an offcanvas, while another is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); + if (alreadyOpen && alreadyOpen !== target) { + Offcanvas.getInstance(alreadyOpen).hide(); + } + const data = Offcanvas.getOrCreateInstance(target); + data.toggle(this); + }); + EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { + for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { + Offcanvas.getOrCreateInstance(selector).show(); + } + }); + EventHandler.on(window, EVENT_RESIZE, () => { + for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) { + if (getComputedStyle(element).position !== 'fixed') { + Offcanvas.getOrCreateInstance(element).hide(); + } + } + }); + enableDismissTrigger(Offcanvas); + + /** + * jQuery + */ + + defineJQueryPlugin(Offcanvas); + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/sanitizer.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + // js-docs-start allow-list + const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + const DefaultAllowlist = { + // Global attributes allowed on any supplied element below. + '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + dd: [], + div: [], + dl: [], + dt: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [] + }; + // js-docs-end allow-list + + const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); + + /** + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. + * + * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 + */ + // eslint-disable-next-line unicorn/better-regex + const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; + const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase(); + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); + } + return true; + } + + // Check if a regular expression validates the attribute. + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); + }; + function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { + if (!unsafeHtml.length) { + return unsafeHtml; + } + if (sanitizeFunction && typeof sanitizeFunction === 'function') { + return sanitizeFunction(unsafeHtml); + } + const domParser = new window.DOMParser(); + const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); + const elements = [].concat(...createdDocument.body.querySelectorAll('*')); + for (const element of elements) { + const elementName = element.nodeName.toLowerCase(); + if (!Object.keys(allowList).includes(elementName)) { + element.remove(); + continue; + } + const attributeList = [].concat(...element.attributes); + const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); + for (const attribute of attributeList) { + if (!allowedAttribute(attribute, allowedAttributes)) { + element.removeAttribute(attribute.nodeName); + } + } + } + return createdDocument.body.innerHTML; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap util/template-factory.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$5 = 'TemplateFactory'; + const Default$4 = { + allowList: DefaultAllowlist, + content: {}, + // { selector : text , selector2 : text2 , } + extraClass: '', + html: false, + sanitize: true, + sanitizeFn: null, + template: '<div></div>' + }; + const DefaultType$4 = { + allowList: 'object', + content: 'object', + extraClass: '(string|function)', + html: 'boolean', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + template: 'string' + }; + const DefaultContentType = { + entry: '(string|element|function|null)', + selector: '(string|element)' + }; + + /** + * Class definition + */ + + class TemplateFactory extends Config { + constructor(config) { + super(); + this._config = this._getConfig(config); + } + + // Getters + static get Default() { + return Default$4; + } + static get DefaultType() { + return DefaultType$4; + } + static get NAME() { + return NAME$5; + } + + // Public + getContent() { + return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); + } + hasContent() { + return this.getContent().length > 0; + } + changeContent(content) { + this._checkContent(content); + this._config.content = { + ...this._config.content, + ...content + }; + return this; + } + toHtml() { + const templateWrapper = document.createElement('div'); + templateWrapper.innerHTML = this._maybeSanitize(this._config.template); + for (const [selector, text] of Object.entries(this._config.content)) { + this._setContent(templateWrapper, text, selector); + } + const template = templateWrapper.children[0]; + const extraClass = this._resolvePossibleFunction(this._config.extraClass); + if (extraClass) { + template.classList.add(...extraClass.split(' ')); + } + return template; + } + + // Private + _typeCheckConfig(config) { + super._typeCheckConfig(config); + this._checkContent(config.content); + } + _checkContent(arg) { + for (const [selector, content] of Object.entries(arg)) { + super._typeCheckConfig({ + selector, + entry: content + }, DefaultContentType); + } + } + _setContent(template, content, selector) { + const templateElement = SelectorEngine.findOne(selector, template); + if (!templateElement) { + return; + } + content = this._resolvePossibleFunction(content); + if (!content) { + templateElement.remove(); + return; + } + if (isElement(content)) { + this._putElementInTemplate(getElement(content), templateElement); + return; + } + if (this._config.html) { + templateElement.innerHTML = this._maybeSanitize(content); + return; + } + templateElement.textContent = content; + } + _maybeSanitize(arg) { + return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; + } + _resolvePossibleFunction(arg) { + return execute(arg, [this]); + } + _putElementInTemplate(element, templateElement) { + if (this._config.html) { + templateElement.innerHTML = ''; + templateElement.append(element); + return; + } + templateElement.textContent = element.textContent; + } + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap tooltip.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$4 = 'tooltip'; + const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); + const CLASS_NAME_FADE$2 = 'fade'; + const CLASS_NAME_MODAL = 'modal'; + const CLASS_NAME_SHOW$2 = 'show'; + const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; + const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; + const EVENT_MODAL_HIDE = 'hide.bs.modal'; + const TRIGGER_HOVER = 'hover'; + const TRIGGER_FOCUS = 'focus'; + const TRIGGER_CLICK = 'click'; + const TRIGGER_MANUAL = 'manual'; + const EVENT_HIDE$2 = 'hide'; + const EVENT_HIDDEN$2 = 'hidden'; + const EVENT_SHOW$2 = 'show'; + const EVENT_SHOWN$2 = 'shown'; + const EVENT_INSERTED = 'inserted'; + const EVENT_CLICK$1 = 'click'; + const EVENT_FOCUSIN$1 = 'focusin'; + const EVENT_FOCUSOUT$1 = 'focusout'; + const EVENT_MOUSEENTER = 'mouseenter'; + const EVENT_MOUSELEAVE = 'mouseleave'; + const AttachmentMap = { + AUTO: 'auto', + TOP: 'top', + RIGHT: isRTL() ? 'left' : 'right', + BOTTOM: 'bottom', + LEFT: isRTL() ? 'right' : 'left' + }; + const Default$3 = { + allowList: DefaultAllowlist, + animation: true, + boundary: 'clippingParents', + container: false, + customClass: '', + delay: 0, + fallbackPlacements: ['top', 'right', 'bottom', 'left'], + html: false, + offset: [0, 6], + placement: 'top', + popperConfig: null, + sanitize: true, + sanitizeFn: null, + selector: false, + template: '<div class="tooltip" role="tooltip">' + '<div class="tooltip-arrow"></div>' + '<div class="tooltip-inner"></div>' + '</div>', + title: '', + trigger: 'hover focus' + }; + const DefaultType$3 = { + allowList: 'object', + animation: 'boolean', + boundary: '(string|element)', + container: '(string|element|boolean)', + customClass: '(string|function)', + delay: '(number|object)', + fallbackPlacements: 'array', + html: 'boolean', + offset: '(array|string|function)', + placement: '(string|function)', + popperConfig: '(null|object|function)', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + selector: '(string|boolean)', + template: 'string', + title: '(string|element|function)', + trigger: 'string' + }; + + /** + * Class definition + */ + + class Tooltip extends BaseComponent { + constructor(element, config) { + if (typeof Popper__namespace === 'undefined') { + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); + } + super(element, config); + + // Private + this._isEnabled = true; + this._timeout = 0; + this._isHovered = null; + this._activeTrigger = {}; + this._popper = null; + this._templateFactory = null; + this._newContent = null; + + // Protected + this.tip = null; + this._setListeners(); + if (!this._config.selector) { + this._fixTitle(); + } + } + + // Getters + static get Default() { + return Default$3; + } + static get DefaultType() { + return DefaultType$3; + } + static get NAME() { + return NAME$4; + } + + // Public + enable() { + this._isEnabled = true; + } + disable() { + this._isEnabled = false; + } + toggleEnabled() { + this._isEnabled = !this._isEnabled; + } + toggle() { + if (!this._isEnabled) { + return; + } + this._activeTrigger.click = !this._activeTrigger.click; + if (this._isShown()) { + this._leave(); + return; + } + this._enter(); + } + dispose() { + clearTimeout(this._timeout); + EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + if (this._element.getAttribute('data-bs-original-title')) { + this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title')); + } + this._disposePopper(); + super.dispose(); + } + show() { + if (this._element.style.display === 'none') { + throw new Error('Please use show on visible elements'); + } + if (!(this._isWithContent() && this._isEnabled)) { + return; + } + const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2)); + const shadowRoot = findShadowRoot(this._element); + const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element); + if (showEvent.defaultPrevented || !isInTheDom) { + return; + } + + // TODO: v6 remove this or make it optional + this._disposePopper(); + const tip = this._getTipElement(); + this._element.setAttribute('aria-describedby', tip.getAttribute('id')); + const { + container + } = this._config; + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { + container.append(tip); + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)); + } + this._popper = this._createPopper(tip); + tip.classList.add(CLASS_NAME_SHOW$2); + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop); + } + } + const complete = () => { + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2)); + if (this._isHovered === false) { + this._leave(); + } + this._isHovered = false; + }; + this._queueCallback(complete, this.tip, this._isAnimated()); + } + hide() { + if (!this._isShown()) { + return; + } + const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2)); + if (hideEvent.defaultPrevented) { + return; + } + const tip = this._getTipElement(); + tip.classList.remove(CLASS_NAME_SHOW$2); + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop); + } + } + this._activeTrigger[TRIGGER_CLICK] = false; + this._activeTrigger[TRIGGER_FOCUS] = false; + this._activeTrigger[TRIGGER_HOVER] = false; + this._isHovered = null; // it is a trick to support manual triggering + + const complete = () => { + if (this._isWithActiveTrigger()) { + return; + } + if (!this._isHovered) { + this._disposePopper(); + } + this._element.removeAttribute('aria-describedby'); + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2)); + }; + this._queueCallback(complete, this.tip, this._isAnimated()); + } + update() { + if (this._popper) { + this._popper.update(); + } + } + + // Protected + _isWithContent() { + return Boolean(this._getTitle()); + } + _getTipElement() { + if (!this.tip) { + this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()); + } + return this.tip; + } + _createTipElement(content) { + const tip = this._getTemplateFactory(content).toHtml(); + + // TODO: remove this check in v6 + if (!tip) { + return null; + } + tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); + // TODO: v6 the following can be achieved with CSS only + tip.classList.add(`bs-${this.constructor.NAME}-auto`); + const tipId = getUID(this.constructor.NAME).toString(); + tip.setAttribute('id', tipId); + if (this._isAnimated()) { + tip.classList.add(CLASS_NAME_FADE$2); + } + return tip; + } + setContent(content) { + this._newContent = content; + if (this._isShown()) { + this._disposePopper(); + this.show(); + } + } + _getTemplateFactory(content) { + if (this._templateFactory) { + this._templateFactory.changeContent(content); + } else { + this._templateFactory = new TemplateFactory({ + ...this._config, + // the `content` var has to be after `this._config` + // to override config.content in case of popover + content, + extraClass: this._resolvePossibleFunction(this._config.customClass) + }); + } + return this._templateFactory; + } + _getContentForTemplate() { + return { + [SELECTOR_TOOLTIP_INNER]: this._getTitle() + }; + } + _getTitle() { + return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title'); + } + + // Private + _initializeOnDelegatedTarget(event) { + return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); + } + _isAnimated() { + return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2); + } + _isShown() { + return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2); + } + _createPopper(tip) { + const placement = execute(this._config.placement, [this, tip, this._element]); + const attachment = AttachmentMap[placement.toUpperCase()]; + return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment)); + } + _getOffset() { + const { + offset + } = this._config; + if (typeof offset === 'string') { + return offset.split(',').map(value => Number.parseInt(value, 10)); + } + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element); + } + return offset; + } + _resolvePossibleFunction(arg) { + return execute(arg, [this._element]); + } + _getPopperConfig(attachment) { + const defaultBsPopperConfig = { + placement: attachment, + modifiers: [{ + name: 'flip', + options: { + fallbackPlacements: this._config.fallbackPlacements + } + }, { + name: 'offset', + options: { + offset: this._getOffset() + } + }, { + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, { + name: 'arrow', + options: { + element: `.${this.constructor.NAME}-arrow` + } + }, { + name: 'preSetPlacement', + enabled: true, + phase: 'beforeMain', + fn: data => { + // Pre-set Popper's placement attribute in order to read the arrow sizes properly. + // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement + this._getTipElement().setAttribute('data-popper-placement', data.state.placement); + } + }] + }; + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [defaultBsPopperConfig]) + }; + } + _setListeners() { + const triggers = this._config.trigger.split(' '); + for (const trigger of triggers) { + if (trigger === 'click') { + EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context.toggle(); + }); + } else if (trigger !== TRIGGER_MANUAL) { + const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1); + const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1); + EventHandler.on(this._element, eventIn, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; + context._enter(); + }); + EventHandler.on(this._element, eventOut, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); + context._leave(); + }); + } + } + this._hideModalHandler = () => { + if (this._element) { + this.hide(); + } + }; + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + } + _fixTitle() { + const title = this._element.getAttribute('title'); + if (!title) { + return; + } + if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) { + this._element.setAttribute('aria-label', title); + } + this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility + this._element.removeAttribute('title'); + } + _enter() { + if (this._isShown() || this._isHovered) { + this._isHovered = true; + return; + } + this._isHovered = true; + this._setTimeout(() => { + if (this._isHovered) { + this.show(); + } + }, this._config.delay.show); + } + _leave() { + if (this._isWithActiveTrigger()) { + return; + } + this._isHovered = false; + this._setTimeout(() => { + if (!this._isHovered) { + this.hide(); + } + }, this._config.delay.hide); + } + _setTimeout(handler, timeout) { + clearTimeout(this._timeout); + this._timeout = setTimeout(handler, timeout); + } + _isWithActiveTrigger() { + return Object.values(this._activeTrigger).includes(true); + } + _getConfig(config) { + const dataAttributes = Manipulator.getDataAttributes(this._element); + for (const dataAttribute of Object.keys(dataAttributes)) { + if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { + delete dataAttributes[dataAttribute]; + } + } + config = { + ...dataAttributes, + ...(typeof config === 'object' && config ? config : {}) + }; + config = this._mergeConfigObj(config); + config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; + } + _configAfterMerge(config) { + config.container = config.container === false ? document.body : getElement(config.container); + if (typeof config.delay === 'number') { + config.delay = { + show: config.delay, + hide: config.delay + }; + } + if (typeof config.title === 'number') { + config.title = config.title.toString(); + } + if (typeof config.content === 'number') { + config.content = config.content.toString(); + } + return config; + } + _getDelegateConfig() { + const config = {}; + for (const [key, value] of Object.entries(this._config)) { + if (this.constructor.Default[key] !== value) { + config[key] = value; + } + } + config.selector = false; + config.trigger = 'manual'; + + // In the future can be replaced with: + // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) + // `Object.fromEntries(keysWithDifferentValues)` + return config; + } + _disposePopper() { + if (this._popper) { + this._popper.destroy(); + this._popper = null; + } + if (this.tip) { + this.tip.remove(); + this.tip = null; + } + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Tooltip.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * jQuery + */ + + defineJQueryPlugin(Tooltip); + + /** + * -------------------------------------------------------------------------- + * Bootstrap popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$3 = 'popover'; + const SELECTOR_TITLE = '.popover-header'; + const SELECTOR_CONTENT = '.popover-body'; + const Default$2 = { + ...Tooltip.Default, + content: '', + offset: [0, 8], + placement: 'right', + template: '<div class="popover" role="tooltip">' + '<div class="popover-arrow"></div>' + '<h3 class="popover-header"></h3>' + '<div class="popover-body"></div>' + '</div>', + trigger: 'click' + }; + const DefaultType$2 = { + ...Tooltip.DefaultType, + content: '(null|string|element|function)' + }; + + /** + * Class definition + */ + + class Popover extends Tooltip { + // Getters + static get Default() { + return Default$2; + } + static get DefaultType() { + return DefaultType$2; + } + static get NAME() { + return NAME$3; + } + + // Overrides + _isWithContent() { + return this._getTitle() || this._getContent(); + } + + // Private + _getContentForTemplate() { + return { + [SELECTOR_TITLE]: this._getTitle(), + [SELECTOR_CONTENT]: this._getContent() + }; + } + _getContent() { + return this._resolvePossibleFunction(this._config.content); + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Popover.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * jQuery + */ + + defineJQueryPlugin(Popover); + + /** + * -------------------------------------------------------------------------- + * Bootstrap scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$2 = 'scrollspy'; + const DATA_KEY$2 = 'bs.scrollspy'; + const EVENT_KEY$2 = `.${DATA_KEY$2}`; + const DATA_API_KEY = '.data-api'; + const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; + const EVENT_CLICK = `click${EVENT_KEY$2}`; + const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`; + const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; + const CLASS_NAME_ACTIVE$1 = 'active'; + const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; + const SELECTOR_TARGET_LINKS = '[href]'; + const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'; + const SELECTOR_NAV_LINKS = '.nav-link'; + const SELECTOR_NAV_ITEMS = '.nav-item'; + const SELECTOR_LIST_ITEMS = '.list-group-item'; + const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`; + const SELECTOR_DROPDOWN = '.dropdown'; + const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; + const Default$1 = { + offset: null, + // TODO: v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: '0px 0px -25%', + smoothScroll: false, + target: null, + threshold: [0.1, 0.5, 1] + }; + const DefaultType$1 = { + offset: '(number|null)', + // TODO v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: 'string', + smoothScroll: 'boolean', + target: 'element', + threshold: 'array' + }; + + /** + * Class definition + */ + + class ScrollSpy extends BaseComponent { + constructor(element, config) { + super(element, config); + + // this._element is the observablesContainer and config.target the menu links wrapper + this._targetLinks = new Map(); + this._observableSections = new Map(); + this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; + this._activeTarget = null; + this._observer = null; + this._previousScrollData = { + visibleEntryTop: 0, + parentScrollTop: 0 + }; + this.refresh(); // initialize + } + + // Getters + static get Default() { + return Default$1; + } + static get DefaultType() { + return DefaultType$1; + } + static get NAME() { + return NAME$2; + } + + // Public + refresh() { + this._initializeTargetsAndObservables(); + this._maybeEnableSmoothScroll(); + if (this._observer) { + this._observer.disconnect(); + } else { + this._observer = this._getNewObserver(); + } + for (const section of this._observableSections.values()) { + this._observer.observe(section); + } + } + dispose() { + this._observer.disconnect(); + super.dispose(); + } + + // Private + _configAfterMerge(config) { + // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case + config.target = getElement(config.target) || document.body; + + // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only + config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; + if (typeof config.threshold === 'string') { + config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)); + } + return config; + } + _maybeEnableSmoothScroll() { + if (!this._config.smoothScroll) { + return; + } + + // unregister any previous listeners + EventHandler.off(this._config.target, EVENT_CLICK); + EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { + const observableSection = this._observableSections.get(event.target.hash); + if (observableSection) { + event.preventDefault(); + const root = this._rootElement || window; + const height = observableSection.offsetTop - this._element.offsetTop; + if (root.scrollTo) { + root.scrollTo({ + top: height, + behavior: 'smooth' + }); + return; + } + + // Chrome 60 doesn't support `scrollTo` + root.scrollTop = height; + } + }); + } + _getNewObserver() { + const options = { + root: this._rootElement, + threshold: this._config.threshold, + rootMargin: this._config.rootMargin + }; + return new IntersectionObserver(entries => this._observerCallback(entries), options); + } + + // The logic of selection + _observerCallback(entries) { + const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`); + const activate = entry => { + this._previousScrollData.visibleEntryTop = entry.target.offsetTop; + this._process(targetElement(entry)); + }; + const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; + const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; + this._previousScrollData.parentScrollTop = parentScrollTop; + for (const entry of entries) { + if (!entry.isIntersecting) { + this._activeTarget = null; + this._clearActiveClass(targetElement(entry)); + continue; + } + const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; + // if we are scrolling down, pick the bigger offsetTop + if (userScrollsDown && entryIsLowerThanPrevious) { + activate(entry); + // if parent isn't scrolled, let's keep the first visible item, breaking the iteration + if (!parentScrollTop) { + return; + } + continue; + } + + // if we are scrolling up, pick the smallest offsetTop + if (!userScrollsDown && !entryIsLowerThanPrevious) { + activate(entry); + } + } + } + _initializeTargetsAndObservables() { + this._targetLinks = new Map(); + this._observableSections = new Map(); + const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target); + for (const anchor of targetLinks) { + // ensure that the anchor has an id and is not disabled + if (!anchor.hash || isDisabled(anchor)) { + continue; + } + const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element); + + // ensure that the observableSection exists & is visible + if (isVisible(observableSection)) { + this._targetLinks.set(decodeURI(anchor.hash), anchor); + this._observableSections.set(anchor.hash, observableSection); + } + } + } + _process(target) { + if (this._activeTarget === target) { + return; + } + this._clearActiveClass(this._config.target); + this._activeTarget = target; + target.classList.add(CLASS_NAME_ACTIVE$1); + this._activateParents(target); + EventHandler.trigger(this._element, EVENT_ACTIVATE, { + relatedTarget: target + }); + } + _activateParents(target) { + // Activate dropdown parents + if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1); + return; + } + for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { + // Set triggered links parents as active + // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor + for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) { + item.classList.add(CLASS_NAME_ACTIVE$1); + } + } + } + _clearActiveClass(parent) { + parent.classList.remove(CLASS_NAME_ACTIVE$1); + const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE$1}`, parent); + for (const node of activeNodes) { + node.classList.remove(CLASS_NAME_ACTIVE$1); + } + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = ScrollSpy.getOrCreateInstance(this, config); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(window, EVENT_LOAD_DATA_API$1, () => { + for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) { + ScrollSpy.getOrCreateInstance(spy); + } + }); + + /** + * jQuery + */ + + defineJQueryPlugin(ScrollSpy); + + /** + * -------------------------------------------------------------------------- + * Bootstrap tab.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME$1 = 'tab'; + const DATA_KEY$1 = 'bs.tab'; + const EVENT_KEY$1 = `.${DATA_KEY$1}`; + const EVENT_HIDE$1 = `hide${EVENT_KEY$1}`; + const EVENT_HIDDEN$1 = `hidden${EVENT_KEY$1}`; + const EVENT_SHOW$1 = `show${EVENT_KEY$1}`; + const EVENT_SHOWN$1 = `shown${EVENT_KEY$1}`; + const EVENT_CLICK_DATA_API = `click${EVENT_KEY$1}`; + const EVENT_KEYDOWN = `keydown${EVENT_KEY$1}`; + const EVENT_LOAD_DATA_API = `load${EVENT_KEY$1}`; + const ARROW_LEFT_KEY = 'ArrowLeft'; + const ARROW_RIGHT_KEY = 'ArrowRight'; + const ARROW_UP_KEY = 'ArrowUp'; + const ARROW_DOWN_KEY = 'ArrowDown'; + const HOME_KEY = 'Home'; + const END_KEY = 'End'; + const CLASS_NAME_ACTIVE = 'active'; + const CLASS_NAME_FADE$1 = 'fade'; + const CLASS_NAME_SHOW$1 = 'show'; + const CLASS_DROPDOWN = 'dropdown'; + const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'; + const SELECTOR_DROPDOWN_MENU = '.dropdown-menu'; + const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`; + const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]'; + const SELECTOR_OUTER = '.nav-item, .list-group-item'; + const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`; + const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]'; // TODO: could only be `tab` in v6 + const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`; + const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]`; + + /** + * Class definition + */ + + class Tab extends BaseComponent { + constructor(element) { + super(element); + this._parent = this._element.closest(SELECTOR_TAB_PANEL); + if (!this._parent) { + return; + // TODO: should throw exception in v6 + // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`) + } + + // Set up initial aria attributes + this._setInitialAttributes(this._parent, this._getChildren()); + EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)); + } + + // Getters + static get NAME() { + return NAME$1; + } + + // Public + show() { + // Shows this elem and deactivate the active sibling if exists + const innerElem = this._element; + if (this._elemIsActive(innerElem)) { + return; + } + + // Search for active tab on same parent to deactivate it + const active = this._getActiveElem(); + const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE$1, { + relatedTarget: innerElem + }) : null; + const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW$1, { + relatedTarget: active + }); + if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) { + return; + } + this._deactivate(active, innerElem); + this._activate(innerElem, active); + } + + // Private + _activate(element, relatedElem) { + if (!element) { + return; + } + element.classList.add(CLASS_NAME_ACTIVE); + this._activate(SelectorEngine.getElementFromSelector(element)); // Search and activate/show the proper section + + const complete = () => { + if (element.getAttribute('role') !== 'tab') { + element.classList.add(CLASS_NAME_SHOW$1); + return; + } + element.removeAttribute('tabindex'); + element.setAttribute('aria-selected', true); + this._toggleDropDown(element, true); + EventHandler.trigger(element, EVENT_SHOWN$1, { + relatedTarget: relatedElem + }); + }; + this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1)); + } + _deactivate(element, relatedElem) { + if (!element) { + return; + } + element.classList.remove(CLASS_NAME_ACTIVE); + element.blur(); + this._deactivate(SelectorEngine.getElementFromSelector(element)); // Search and deactivate the shown section too + + const complete = () => { + if (element.getAttribute('role') !== 'tab') { + element.classList.remove(CLASS_NAME_SHOW$1); + return; + } + element.setAttribute('aria-selected', false); + element.setAttribute('tabindex', '-1'); + this._toggleDropDown(element, false); + EventHandler.trigger(element, EVENT_HIDDEN$1, { + relatedTarget: relatedElem + }); + }; + this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE$1)); + } + _keydown(event) { + if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key)) { + return; + } + event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page + event.preventDefault(); + const children = this._getChildren().filter(element => !isDisabled(element)); + let nextActiveElement; + if ([HOME_KEY, END_KEY].includes(event.key)) { + nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]; + } else { + const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key); + nextActiveElement = getNextActiveElement(children, event.target, isNext, true); + } + if (nextActiveElement) { + nextActiveElement.focus({ + preventScroll: true + }); + Tab.getOrCreateInstance(nextActiveElement).show(); + } + } + _getChildren() { + // collection of inner elements + return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent); + } + _getActiveElem() { + return this._getChildren().find(child => this._elemIsActive(child)) || null; + } + _setInitialAttributes(parent, children) { + this._setAttributeIfNotExists(parent, 'role', 'tablist'); + for (const child of children) { + this._setInitialAttributesOnChild(child); + } + } + _setInitialAttributesOnChild(child) { + child = this._getInnerElement(child); + const isActive = this._elemIsActive(child); + const outerElem = this._getOuterElement(child); + child.setAttribute('aria-selected', isActive); + if (outerElem !== child) { + this._setAttributeIfNotExists(outerElem, 'role', 'presentation'); + } + if (!isActive) { + child.setAttribute('tabindex', '-1'); + } + this._setAttributeIfNotExists(child, 'role', 'tab'); + + // set attributes to the related panel too + this._setInitialAttributesOnTargetPanel(child); + } + _setInitialAttributesOnTargetPanel(child) { + const target = SelectorEngine.getElementFromSelector(child); + if (!target) { + return; + } + this._setAttributeIfNotExists(target, 'role', 'tabpanel'); + if (child.id) { + this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`); + } + } + _toggleDropDown(element, open) { + const outerElem = this._getOuterElement(element); + if (!outerElem.classList.contains(CLASS_DROPDOWN)) { + return; + } + const toggle = (selector, className) => { + const element = SelectorEngine.findOne(selector, outerElem); + if (element) { + element.classList.toggle(className, open); + } + }; + toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE); + toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW$1); + outerElem.setAttribute('aria-expanded', open); + } + _setAttributeIfNotExists(element, attribute, value) { + if (!element.hasAttribute(attribute)) { + element.setAttribute(attribute, value); + } + } + _elemIsActive(elem) { + return elem.classList.contains(CLASS_NAME_ACTIVE); + } + + // Try to get the inner element (usually the .nav-link) + _getInnerElement(elem) { + return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem); + } + + // Try to get the outer element (usually the .nav-item) + _getOuterElement(elem) { + return elem.closest(SELECTOR_OUTER) || elem; + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Tab.getOrCreateInstance(this); + if (typeof config !== 'string') { + return; + } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`); + } + data[config](); + }); + } + } + + /** + * Data API implementation + */ + + EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault(); + } + if (isDisabled(this)) { + return; + } + Tab.getOrCreateInstance(this).show(); + }); + + /** + * Initialize on focus + */ + EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) { + Tab.getOrCreateInstance(element); + } + }); + /** + * jQuery + */ + + defineJQueryPlugin(Tab); + + /** + * -------------------------------------------------------------------------- + * Bootstrap toast.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + + /** + * Constants + */ + + const NAME = 'toast'; + const DATA_KEY = 'bs.toast'; + const EVENT_KEY = `.${DATA_KEY}`; + const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`; + const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`; + const EVENT_FOCUSIN = `focusin${EVENT_KEY}`; + const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`; + const EVENT_HIDE = `hide${EVENT_KEY}`; + const EVENT_HIDDEN = `hidden${EVENT_KEY}`; + const EVENT_SHOW = `show${EVENT_KEY}`; + const EVENT_SHOWN = `shown${EVENT_KEY}`; + const CLASS_NAME_FADE = 'fade'; + const CLASS_NAME_HIDE = 'hide'; // @deprecated - kept here only for backwards compatibility + const CLASS_NAME_SHOW = 'show'; + const CLASS_NAME_SHOWING = 'showing'; + const DefaultType = { + animation: 'boolean', + autohide: 'boolean', + delay: 'number' + }; + const Default = { + animation: true, + autohide: true, + delay: 5000 + }; + + /** + * Class definition + */ + + class Toast extends BaseComponent { + constructor(element, config) { + super(element, config); + this._timeout = null; + this._hasMouseInteraction = false; + this._hasKeyboardInteraction = false; + this._setListeners(); + } + + // Getters + static get Default() { + return Default; + } + static get DefaultType() { + return DefaultType; + } + static get NAME() { + return NAME; + } + + // Public + show() { + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW); + if (showEvent.defaultPrevented) { + return; + } + this._clearTimeout(); + if (this._config.animation) { + this._element.classList.add(CLASS_NAME_FADE); + } + const complete = () => { + this._element.classList.remove(CLASS_NAME_SHOWING); + EventHandler.trigger(this._element, EVENT_SHOWN); + this._maybeScheduleHide(); + }; + this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated + reflow(this._element); + this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING); + this._queueCallback(complete, this._element, this._config.animation); + } + hide() { + if (!this.isShown()) { + return; + } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE); + if (hideEvent.defaultPrevented) { + return; + } + const complete = () => { + this._element.classList.add(CLASS_NAME_HIDE); // @deprecated + this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW); + EventHandler.trigger(this._element, EVENT_HIDDEN); + }; + this._element.classList.add(CLASS_NAME_SHOWING); + this._queueCallback(complete, this._element, this._config.animation); + } + dispose() { + this._clearTimeout(); + if (this.isShown()) { + this._element.classList.remove(CLASS_NAME_SHOW); + } + super.dispose(); + } + isShown() { + return this._element.classList.contains(CLASS_NAME_SHOW); + } + + // Private + + _maybeScheduleHide() { + if (!this._config.autohide) { + return; + } + if (this._hasMouseInteraction || this._hasKeyboardInteraction) { + return; + } + this._timeout = setTimeout(() => { + this.hide(); + }, this._config.delay); + } + _onInteraction(event, isInteracting) { + switch (event.type) { + case 'mouseover': + case 'mouseout': + { + this._hasMouseInteraction = isInteracting; + break; + } + case 'focusin': + case 'focusout': + { + this._hasKeyboardInteraction = isInteracting; + break; + } + } + if (isInteracting) { + this._clearTimeout(); + return; + } + const nextElement = event.relatedTarget; + if (this._element === nextElement || this._element.contains(nextElement)) { + return; + } + this._maybeScheduleHide(); + } + _setListeners() { + EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true)); + EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false)); + EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true)); + EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false)); + } + _clearTimeout() { + clearTimeout(this._timeout); + this._timeout = null; + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Toast.getOrCreateInstance(this, config); + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`); + } + data[config](this); + } + }); + } + } + + /** + * Data API implementation + */ + + enableDismissTrigger(Toast); + + /** + * jQuery + */ + + defineJQueryPlugin(Toast); + + /** + * -------------------------------------------------------------------------- + * Bootstrap index.umd.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + const index_umd = { + Alert, + Button, + Carousel, + Collapse, + Dropdown, + Modal, + Offcanvas, + Popover, + ScrollSpy, + Tab, + Toast, + Tooltip + }; + + return index_umd; + +})); +//# sourceMappingURL=bootstrap.js.map diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.js.map new file mode 100644 index 00000000..98a87052 --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bootstrap.js","sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"names":["elementMap","Map","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","MAX_UID","MILLISECONDS_MULTIPLIER","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","undefined","Object","prototype","toString","call","toLowerCase","getUID","prefix","Math","floor","random","document","getElementById","getTransitionDurationFromElement","transitionDuration","transitionDelay","getComputedStyle","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","querySelector","isVisible","getClientRects","elementIsVisible","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","onDOMContentLoaded","callback","readyState","addEventListener","push","isRTL","dir","defineJQueryPlugin","plugin","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","durationPadding","emulatedDuration","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","bootstrapHandler","event","hydrateObj","delegateTarget","oneOff","EventHandler","off","type","apply","bootstrapDelegationHandler","domElements","querySelectorAll","domElement","findHandler","events","callable","delegationSelector","values","find","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","wrapFunction","relatedTarget","handlers","previousFunction","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","VERSION","BaseComponent","_element","_config","Data","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","close","closeEvent","_destroyElement","each","data","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","POINTER_TYPE_TOUCH","POINTER_TYPE_PEN","CLASS_NAME_POINTER_EVENT","SWIPE_THRESHOLD","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","touches","clientX","_eventIsPointerPenTouch","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","TOUCHEVENT_COMPAT_WAIT","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_SLIDE","CLASS_NAME_END","CLASS_NAME_START","CLASS_NAME_NEXT","CLASS_NAME_PREV","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","SELECTOR_ITEM_IMG","SELECTOR_INDICATORS","SELECTOR_DATA_SLIDE","SELECTOR_DATA_RIDE","KEY_TO_DIRECTION","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","endCallBack","clearTimeout","swipeConfig","_directionToOrder","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","slideEvent","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_COLLAPSED","CLASS_NAME_DEEPER_CHILDREN","CLASS_NAME_HORIZONTAL","WIDTH","HEIGHT","SELECTOR_ACTIVES","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","startEvent","activeInstance","dimension","_getDimension","style","complete","capitalizedDimension","scrollSize","getBoundingClientRect","selected","triggerArray","isOpen","ESCAPE_KEY","TAB_KEY","ARROW_UP_KEY","ARROW_DOWN_KEY","RIGHT_MOUSE_BUTTON","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","CLASS_NAME_DROPUP","CLASS_NAME_DROPEND","CLASS_NAME_DROPSTART","CLASS_NAME_DROPUP_CENTER","CLASS_NAME_DROPDOWN_CENTER","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","SELECTOR_NAVBAR","SELECTOR_NAVBAR_NAV","SELECTOR_VISIBLE_ITEMS","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","PLACEMENT_TOPCENTER","PLACEMENT_BOTTOMCENTER","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","showEvent","_createPopper","focus","_completeHide","destroy","update","hideEvent","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_FORWARD","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","manipulationCallBack","setProperty","_applyManipulationCallback","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","OPEN_SELECTOR","SELECTOR_DIALOG","SELECTOR_MODAL_BODY","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","CLASS_NAME_BACKDROP","scroll","Offcanvas","blur","completeCallback","position","ARIA_ATTRIBUTE_PATTERN","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","sanitizeHtml","unsafeHtml","allowList","sanitizeFunction","domParser","DOMParser","createdDocument","parseFromString","elementName","attributeList","allowedAttributes","innerHTML","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","DISALLOWED_ATTRIBUTES","CLASS_NAME_MODAL","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","TRIGGER_MANUAL","EVENT_INSERTED","EVENT_CLICK","EVENT_FOCUSOUT","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","shadowRoot","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","CLASS_NAME_DROPDOWN_ITEM","SELECTOR_DATA_SPY","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LIST_GROUP","SELECTOR_NAV_LINKS","SELECTOR_NAV_ITEMS","SELECTOR_LIST_ITEMS","SELECTOR_LINK_ITEMS","SELECTOR_DROPDOWN","SELECTOR_DROPDOWN_TOGGLE","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","HOME_KEY","END_KEY","CLASS_DROPDOWN","SELECTOR_DROPDOWN_MENU","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_TAB_PANEL","SELECTOR_OUTER","SELECTOR_INNER","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"mappings":"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"} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js b/extensions/pagetop-bootsier/static/js/bootstrap.min.js new file mode 100644 index 00000000..d5dc5ea1 --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t)for(const i in t)if("default"!==i){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}return e.default=t,Object.freeze(e)}const i=e(t),s=new Map,n={set(t,e,i){s.has(t)||s.set(t,new Map);const n=s.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>s.has(t)&&s.get(t).get(e)||null,remove(t,e){if(!s.has(t))return;const i=s.get(t);i.delete(e),0===i.size&&s.delete(t)}},o="transitionend",r=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),a=t=>{t.dispatchEvent(new Event(o))},l=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),c=t=>l(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(r(t)):null,h=t=>{if(!l(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},d=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),u=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?u(t.parentNode):null},_=()=>{},g=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,m=[],p=()=>"rtl"===document.documentElement.dir,b=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,s=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=s,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of m)t()})),m.push(e)):e()},v=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,y=(t,e,i=!0)=>{if(!i)return void v(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let n=!1;const r=({target:i})=>{i===e&&(n=!0,e.removeEventListener(o,r),v(t))};e.addEventListener(o,r),setTimeout((()=>{n||a(e)}),s)},w=(t,e,i,s)=>{const n=t.length;let o=t.indexOf(e);return-1===o?!i&&s?t[n-1]:t[0]:(o+=i?1:-1,s&&(o=(o+n)%n),t[Math.max(0,Math.min(o,n-1))])},A=/[^.]*(?=\..*)\.|.*/,E=/\..*/,C=/::\d+$/,T={};let k=1;const $={mouseenter:"mouseover",mouseleave:"mouseout"},S=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${k++}`||t.uidEvent||k++}function O(t){const e=L(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function I(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function D(t,e,i){const s="string"==typeof e,n=s?i:e||i;let o=M(t);return S.has(o)||(o=t),[s,n,o]}function N(t,e,i,s,n){if("string"!=typeof e||!t)return;let[o,r,a]=D(e,i,s);if(e in $){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=O(t),c=l[a]||(l[a]={}),h=I(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=L(r,e.replace(A,"")),u=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return F(n,{delegateTarget:r}),s.oneOff&&j.off(t,n.type,e,i),i.apply(r,[n])}}(t,i,r):function(t,e){return function i(s){return F(s,{delegateTarget:t}),i.oneOff&&j.off(t,s.type,e),e.apply(t,[s])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function P(t,e,i,s,n){const o=I(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}function x(t,e,i,s){const n=e[i]||{};for(const[o,r]of Object.entries(n))o.includes(s)&&P(t,e,i,r.callable,r.delegationSelector)}function M(t){return t=t.replace(E,""),$[t]||t}const j={on(t,e,i,s){N(t,e,i,s,!1)},one(t,e,i,s){N(t,e,i,s,!0)},off(t,e,i,s){if("string"!=typeof e||!t)return;const[n,o,r]=D(e,i,s),a=r!==e,l=O(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))x(t,l,i,e.slice(1));for(const[i,s]of Object.entries(c)){const n=i.replace(C,"");a&&!e.includes(n)||P(t,l,r,s.callable,s.delegationSelector)}}else{if(!Object.keys(c).length)return;P(t,l,r,o,n?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const s=f();let n=null,o=!0,r=!0,a=!1;e!==M(e)&&s&&(n=s.Event(e,i),s(t).trigger(n),o=!n.isPropagationStopped(),r=!n.isImmediatePropagationStopped(),a=n.isDefaultPrevented());const l=F(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&n&&n.preventDefault(),l}};function F(t,e={}){for(const[i,s]of Object.entries(e))try{t[i]=s}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>s})}return t}function z(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function H(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const B={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${H(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${H(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const s of i){let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=z(t.dataset[s])}return e},getDataAttribute:(t,e)=>z(t.getAttribute(`data-bs-${H(e)}`))};class q{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=l(e)?B.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...l(e)?B.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[s,n]of Object.entries(e)){const e=t[s],o=l(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(n).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${s}" provided type "${o}" but expected type "${n}".`)}var i}}class W extends q{constructor(t,e){super(),(t=c(t))&&(this._element=t,this._config=this._getConfig(e),n.set(this._element,this.constructor.DATA_KEY,this))}dispose(){n.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){y(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return n.get(c(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const R=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>r(t))).join(","):null},K={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let s=t.parentNode.closest(e);for(;s;)i.push(s),s=s.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!d(t)&&h(t)))},getSelectorFromElement(t){const e=R(t);return e&&K.findOne(e)?e:null},getElementFromSelector(t){const e=R(t);return e?K.findOne(e):null},getMultipleElementsFromSelector(t){const e=R(t);return e?K.find(e):[]}},V=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),d(this))return;const n=K.getElementFromSelector(this)||this.closest(`.${s}`);t.getOrCreateInstance(n)[e]()}))},Q=".bs.alert",X=`close${Q}`,Y=`closed${Q}`;class U extends W{static get NAME(){return"alert"}close(){if(j.trigger(this._element,X).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,Y),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=U.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}V(U,"close"),b(U);const G='[data-bs-toggle="button"]';class J extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=J.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}j.on(document,"click.bs.button.data-api",G,(t=>{t.preventDefault();const e=t.target.closest(G);J.getOrCreateInstance(e).toggle()})),b(J);const Z=".bs.swipe",tt=`touchstart${Z}`,et=`touchmove${Z}`,it=`touchend${Z}`,st=`pointerdown${Z}`,nt=`pointerup${Z}`,ot={endCallback:null,leftCallback:null,rightCallback:null},rt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class at extends q{constructor(t,e){super(),this._element=t,t&&at.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return ot}static get DefaultType(){return rt}static get NAME(){return"swipe"}dispose(){j.off(this._element,Z)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),v(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&v(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(j.on(this._element,st,(t=>this._start(t))),j.on(this._element,nt,(t=>this._end(t))),this._element.classList.add("pointer-event")):(j.on(this._element,tt,(t=>this._start(t))),j.on(this._element,et,(t=>this._move(t))),j.on(this._element,it,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const lt=".bs.carousel",ct=".data-api",ht="next",dt="prev",ut="left",_t="right",gt=`slide${lt}`,ft=`slid${lt}`,mt=`keydown${lt}`,pt=`mouseenter${lt}`,bt=`mouseleave${lt}`,vt=`dragstart${lt}`,yt=`load${lt}${ct}`,wt=`click${lt}${ct}`,At="carousel",Et="active",Ct=".active",Tt=".carousel-item",kt=Ct+Tt,$t={ArrowLeft:_t,ArrowRight:ut},St={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Lt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class Ot extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=K.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===At&&this.cycle()}static get Default(){return St}static get DefaultType(){return Lt}static get NAME(){return"carousel"}next(){this._slide(ht)}nextWhenVisible(){!document.hidden&&h(this._element)&&this.next()}prev(){this._slide(dt)}pause(){this._isSliding&&a(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?j.one(this._element,ft,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,ft,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const s=t>i?ht:dt;this._slide(s,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&j.on(this._element,mt,(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,pt,(()=>this.pause())),j.on(this._element,bt,(()=>this._maybeEnableCycle()))),this._config.touch&&at.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of K.find(".carousel-item img",this._element))j.on(t,vt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ut)),rightCallback:()=>this._slide(this._directionToOrder(_t)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new at(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=$t[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=K.findOne(Ct,this._indicatorsElement);e.classList.remove(Et),e.removeAttribute("aria-current");const i=K.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(Et),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),s=t===ht,n=e||w(this._getItems(),i,s,this._config.wrap);if(n===i)return;const o=this._getItemIndex(n),r=e=>j.trigger(this._element,e,{relatedTarget:n,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(gt).defaultPrevented)return;if(!i||!n)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=n;const l=s?"carousel-item-start":"carousel-item-end",c=s?"carousel-item-next":"carousel-item-prev";n.classList.add(c),g(n),i.classList.add(l),n.classList.add(l),this._queueCallback((()=>{n.classList.remove(l,c),n.classList.add(Et),i.classList.remove(Et,c,l),this._isSliding=!1,r(ft)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return K.findOne(kt,this._element)}_getItems(){return K.find(Tt,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ut?dt:ht:t===ut?ht:dt}_orderToDirection(t){return p()?t===dt?ut:_t:t===dt?_t:ut}static jQueryInterface(t){return this.each((function(){const e=Ot.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}j.on(document,wt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=K.getElementFromSelector(this);if(!e||!e.classList.contains(At))return;t.preventDefault();const i=Ot.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===B.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),j.on(window,yt,(()=>{const t=K.find('[data-bs-ride="carousel"]');for(const e of t)Ot.getOrCreateInstance(e)})),b(Ot);const It=".bs.collapse",Dt=`show${It}`,Nt=`shown${It}`,Pt=`hide${It}`,xt=`hidden${It}`,Mt=`click${It}.data-api`,jt="show",Ft="collapse",zt="collapsing",Ht=`:scope .${Ft} .${Ft}`,Bt='[data-bs-toggle="collapse"]',qt={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Rt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=K.find(Bt);for(const t of i){const e=K.getSelectorFromElement(t),i=K.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return qt}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Rt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(j.trigger(this._element,Dt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Ft),this._element.classList.add(zt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(zt),this._element.classList.add(Ft,jt),this._element.style[e]="",j.trigger(this._element,Nt)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,Pt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,g(this._element),this._element.classList.add(zt),this._element.classList.remove(Ft,jt);for(const t of this._triggerArray){const e=K.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(zt),this._element.classList.add(Ft),j.trigger(this._element,xt)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(jt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=c(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Bt);for(const e of t){const t=K.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=K.find(Ht,this._config.parent);return K.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Rt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,Mt,Bt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of K.getMultipleElementsFromSelector(this))Rt.getOrCreateInstance(t,{toggle:!1}).toggle()})),b(Rt);const Kt="dropdown",Vt=".bs.dropdown",Qt=".data-api",Xt="ArrowUp",Yt="ArrowDown",Ut=`hide${Vt}`,Gt=`hidden${Vt}`,Jt=`show${Vt}`,Zt=`shown${Vt}`,te=`click${Vt}${Qt}`,ee=`keydown${Vt}${Qt}`,ie=`keyup${Vt}${Qt}`,se="show",ne='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',oe=`${ne}.${se}`,re=".dropdown-menu",ae=p()?"top-end":"top-start",le=p()?"top-start":"top-end",ce=p()?"bottom-end":"bottom-start",he=p()?"bottom-start":"bottom-end",de=p()?"left-start":"right-start",ue=p()?"right-start":"left-start",_e={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},ge={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class fe extends W{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=K.next(this._element,re)[0]||K.prev(this._element,re)[0]||K.findOne(re,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return _e}static get DefaultType(){return ge}static get NAME(){return Kt}toggle(){return this._isShown()?this.hide():this.show()}show(){if(d(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!j.trigger(this._element,Jt,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))j.on(t,"mouseover",_);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(se),this._element.classList.add(se),j.trigger(this._element,Zt,t)}}hide(){if(d(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!j.trigger(this._element,Ut,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.off(t,"mouseover",_);this._popper&&this._popper.destroy(),this._menu.classList.remove(se),this._element.classList.remove(se),this._element.setAttribute("aria-expanded","false"),B.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,Gt,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!l(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Kt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===i)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:l(this._config.reference)?t=c(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=i.createPopper(t,this._menu,e)}_isShown(){return this._menu.classList.contains(se)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return de;if(t.classList.contains("dropstart"))return ue;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?le:ae:e?he:ce}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(B.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...v(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=K.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>h(t)));i.length&&w(i,e,t===Yt,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=fe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=K.find(oe);for(const i of e){const e=fe.getInstance(i);if(!e||!1===e._config.autoClose)continue;const s=t.composedPath(),n=s.includes(e._menu);if(s.includes(e._element)||"inside"===e._config.autoClose&&!n||"outside"===e._config.autoClose&&n)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,s=[Xt,Yt].includes(t.key);if(!s&&!i)return;if(e&&!i)return;t.preventDefault();const n=this.matches(ne)?this:K.prev(this,ne)[0]||K.next(this,ne)[0]||K.findOne(ne,t.delegateTarget.parentNode),o=fe.getOrCreateInstance(n);if(s)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),n.focus())}}j.on(document,ee,ne,fe.dataApiKeydownHandler),j.on(document,ee,re,fe.dataApiKeydownHandler),j.on(document,te,fe.clearMenus),j.on(document,ie,fe.clearMenus),j.on(document,te,ne,(function(t){t.preventDefault(),fe.getOrCreateInstance(this).toggle()})),b(fe);const me="backdrop",pe="show",be=`mousedown.bs.${me}`,ve={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},ye={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class we extends q{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return ve}static get DefaultType(){return ye}static get NAME(){return me}show(t){if(!this._config.isVisible)return void v(t);this._append();const e=this._getElement();this._config.isAnimated&&g(e),e.classList.add(pe),this._emulateAnimation((()=>{v(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(pe),this._emulateAnimation((()=>{this.dispose(),v(t)}))):v(t)}dispose(){this._isAppended&&(j.off(this._element,be),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=c(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),j.on(t,be,(()=>{v(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){y(t,this._getElement(),this._config.isAnimated)}}const Ae=".bs.focustrap",Ee=`focusin${Ae}`,Ce=`keydown.tab${Ae}`,Te="backward",ke={autofocus:!0,trapElement:null},$e={autofocus:"boolean",trapElement:"element"};class Se extends q{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return ke}static get DefaultType(){return $e}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),j.off(document,Ae),j.on(document,Ee,(t=>this._handleFocusin(t))),j.on(document,Ce,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,Ae))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=K.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Te?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Te:"forward")}}const Le=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Oe=".sticky-top",Ie="padding-right",De="margin-right";class Ne{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Ie,(e=>e+t)),this._setElementAttributes(Le,Ie,(e=>e+t)),this._setElementAttributes(Oe,De,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Ie),this._resetElementAttributes(Le,Ie),this._resetElementAttributes(Oe,De)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const s=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+s)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(n))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&B.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=B.getDataAttribute(t,e);null!==i?(B.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(l(t))e(t);else for(const i of K.find(t,this._element))e(i)}}const Pe=".bs.modal",xe=`hide${Pe}`,Me=`hidePrevented${Pe}`,je=`hidden${Pe}`,Fe=`show${Pe}`,ze=`shown${Pe}`,He=`resize${Pe}`,Be=`click.dismiss${Pe}`,qe=`mousedown.dismiss${Pe}`,We=`keydown.dismiss${Pe}`,Re=`click${Pe}.data-api`,Ke="modal-open",Ve="show",Qe="modal-static",Xe={backdrop:!0,focus:!0,keyboard:!0},Ye={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ue extends W{constructor(t,e){super(t,e),this._dialog=K.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Ne,this._addEventListeners()}static get Default(){return Xe}static get DefaultType(){return Ye}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,Fe,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Ke),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(j.trigger(this._element,xe).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Ve),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){j.off(window,Pe),j.off(this._dialog,Pe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new we({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Se({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=K.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),g(this._element),this._element.classList.add(Ve),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,ze,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){j.on(this._element,We,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),j.on(window,He,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),j.on(this._element,qe,(t=>{j.one(this._element,Be,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Ke),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,je)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,Me).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Qe)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Qe),this._queueCallback((()=>{this._element.classList.remove(Qe),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ue.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,Re,'[data-bs-toggle="modal"]',(function(t){const e=K.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,Fe,(t=>{t.defaultPrevented||j.one(e,je,(()=>{h(this)&&this.focus()}))}));const i=K.findOne(".modal.show");i&&Ue.getInstance(i).hide(),Ue.getOrCreateInstance(e).toggle(this)})),V(Ue),b(Ue);const Ge=".bs.offcanvas",Je=".data-api",Ze=`load${Ge}${Je}`,ti="show",ei="showing",ii="hiding",si=".offcanvas.show",ni=`show${Ge}`,oi=`shown${Ge}`,ri=`hide${Ge}`,ai=`hidePrevented${Ge}`,li=`hidden${Ge}`,ci=`resize${Ge}`,hi=`click${Ge}${Je}`,di=`keydown.dismiss${Ge}`,ui={backdrop:!0,keyboard:!0,scroll:!1},_i={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class gi extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return ui}static get DefaultType(){return _i}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,ni,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Ne).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ei),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(ti),this._element.classList.remove(ei),j.trigger(this._element,oi,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,ri).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(ii),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(ti,ii),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Ne).reset(),j.trigger(this._element,li)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new we({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():j.trigger(this._element,ai)}:null})}_initializeFocusTrap(){return new Se({trapElement:this._element})}_addEventListeners(){j.on(this._element,di,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():j.trigger(this._element,ai))}))}static jQueryInterface(t){return this.each((function(){const e=gi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,hi,'[data-bs-toggle="offcanvas"]',(function(t){const e=K.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this))return;j.one(e,li,(()=>{h(this)&&this.focus()}));const i=K.findOne(si);i&&i!==e&&gi.getInstance(i).hide(),gi.getOrCreateInstance(e).toggle(this)})),j.on(window,Ze,(()=>{for(const t of K.find(si))gi.getOrCreateInstance(t).show()})),j.on(window,ci,(()=>{for(const t of K.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&gi.getOrCreateInstance(t).hide()})),V(gi),b(gi);const fi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},mi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),pi=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,bi=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!mi.has(i)||Boolean(pi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},vi={allowList:fi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},yi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},wi={entry:"(string|element|function|null)",selector:"(string|element)"};class Ai extends q{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return vi}static get DefaultType(){return yi}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},wi)}_setContent(t,e,i){const s=K.findOne(i,t);s&&((e=this._resolvePossibleFunction(e))?l(e)?this._putElementInTemplate(c(e),s):this._config.html?s.innerHTML=this._maybeSanitize(e):s.textContent=e:s.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const s=(new window.DOMParser).parseFromString(t,"text/html"),n=[].concat(...s.body.querySelectorAll("*"));for(const t of n){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const s=[].concat(...t.attributes),n=[].concat(e["*"]||[],e[i]||[]);for(const e of s)bi(e,n)||t.removeAttribute(e.nodeName)}return s.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return v(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Ei=new Set(["sanitize","allowList","sanitizeFn"]),Ci="fade",Ti="show",ki=".modal",$i="hide.bs.modal",Si="hover",Li="focus",Oi={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},Ii={allowList:fi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},Di={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class Ni extends W{constructor(t,e){if(void 0===i)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Ii}static get DefaultType(){return Di}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ki),$i,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.eventName("show")),e=(u(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:s}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(s.append(i),j.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(Ti),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.on(t,"mouseover",_);this._queueCallback((()=>{j.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!j.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(Ti),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.off(t,"mouseover",_);this._activeTrigger.click=!1,this._activeTrigger[Li]=!1,this._activeTrigger[Si]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(Ci,Ti),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(Ci),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Ai({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Ci)}_isShown(){return this.tip&&this.tip.classList.contains(Ti)}_createPopper(t){const e=v(this._config.placement,[this,t,this._element]),s=Oi[e.toUpperCase()];return i.createPopper(this._element,t,this._getPopperConfig(s))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return v(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...v(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)j.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===Si?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===Si?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");j.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?Li:Si]=!0,e._enter()})),j.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?Li:Si]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ki),$i,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=B.getDataAttributes(this._element);for(const t of Object.keys(e))Ei.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:c(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}b(Ni);const Pi={...Ni.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},xi={...Ni.DefaultType,content:"(null|string|element|function)"};class Mi extends Ni{static get Default(){return Pi}static get DefaultType(){return xi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=Mi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}b(Mi);const ji=".bs.scrollspy",Fi=`activate${ji}`,zi=`click${ji}`,Hi=`load${ji}.data-api`,Bi="active",qi="[href]",Wi=".nav-link",Ri=`${Wi}, .nav-item > ${Wi}, .list-group-item`,Ki={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Vi={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Qi extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ki}static get DefaultType(){return Vi}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=c(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(j.off(this._config.target,zi),j.on(this._config.target,zi,qi,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,s=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:s,behavior:"smooth"});i.scrollTop=s}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},s=(this._rootElement||document.documentElement).scrollTop,n=s>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=s;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(n&&t){if(i(o),!s)return}else n||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=K.find(qi,this._config.target);for(const e of t){if(!e.hash||d(e))continue;const t=K.findOne(decodeURI(e.hash),this._element);h(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Bi),this._activateParents(t),j.trigger(this._element,Fi,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))K.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Bi);else for(const e of K.parents(t,".nav, .list-group"))for(const t of K.prev(e,Ri))t.classList.add(Bi)}_clearActiveClass(t){t.classList.remove(Bi);const e=K.find(`${qi}.${Bi}`,t);for(const t of e)t.classList.remove(Bi)}static jQueryInterface(t){return this.each((function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,Hi,(()=>{for(const t of K.find('[data-bs-spy="scroll"]'))Qi.getOrCreateInstance(t)})),b(Qi);const Xi=".bs.tab",Yi=`hide${Xi}`,Ui=`hidden${Xi}`,Gi=`show${Xi}`,Ji=`shown${Xi}`,Zi=`click${Xi}`,ts=`keydown${Xi}`,es=`load${Xi}`,is="ArrowLeft",ss="ArrowRight",ns="ArrowUp",os="ArrowDown",rs="Home",as="End",ls="active",cs="fade",hs="show",ds=".dropdown-toggle",us=`:not(${ds})`,_s='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',gs=`.nav-link${us}, .list-group-item${us}, [role="tab"]${us}, ${_s}`,fs=`.${ls}[data-bs-toggle="tab"], .${ls}[data-bs-toggle="pill"], .${ls}[data-bs-toggle="list"]`;class ms extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),j.on(this._element,ts,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?j.trigger(e,Yi,{relatedTarget:t}):null;j.trigger(t,Gi,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(ls),this._activate(K.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),j.trigger(t,Ji,{relatedTarget:e})):t.classList.add(hs)}),t,t.classList.contains(cs)))}_deactivate(t,e){t&&(t.classList.remove(ls),t.blur(),this._deactivate(K.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),j.trigger(t,Ui,{relatedTarget:e})):t.classList.remove(hs)}),t,t.classList.contains(cs)))}_keydown(t){if(![is,ss,ns,os,rs,as].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!d(t)));let i;if([rs,as].includes(t.key))i=e[t.key===rs?0:e.length-1];else{const s=[ss,os].includes(t.key);i=w(e,t.target,s,!0)}i&&(i.focus({preventScroll:!0}),ms.getOrCreateInstance(i).show())}_getChildren(){return K.find(gs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=K.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const s=(t,s)=>{const n=K.findOne(t,i);n&&n.classList.toggle(s,e)};s(ds,ls),s(".dropdown-menu",hs),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(ls)}_getInnerElement(t){return t.matches(gs)?t:K.findOne(gs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=ms.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,Zi,_s,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this)||ms.getOrCreateInstance(this).show()})),j.on(window,es,(()=>{for(const t of K.find(fs))ms.getOrCreateInstance(t)})),b(ms);const ps=".bs.toast",bs=`mouseover${ps}`,vs=`mouseout${ps}`,ys=`focusin${ps}`,ws=`focusout${ps}`,As=`hide${ps}`,Es=`hidden${ps}`,Cs=`show${ps}`,Ts=`shown${ps}`,ks="hide",$s="show",Ss="showing",Ls={animation:"boolean",autohide:"boolean",delay:"number"},Os={animation:!0,autohide:!0,delay:5e3};class Is extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Os}static get DefaultType(){return Ls}static get NAME(){return"toast"}show(){j.trigger(this._element,Cs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(ks),g(this._element),this._element.classList.add($s,Ss),this._queueCallback((()=>{this._element.classList.remove(Ss),j.trigger(this._element,Ts),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(j.trigger(this._element,As).defaultPrevented||(this._element.classList.add(Ss),this._queueCallback((()=>{this._element.classList.add(ks),this._element.classList.remove(Ss,$s),j.trigger(this._element,Es)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove($s),super.dispose()}isShown(){return this._element.classList.contains($s)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,bs,(t=>this._onInteraction(t,!0))),j.on(this._element,vs,(t=>this._onInteraction(t,!1))),j.on(this._element,ys,(t=>this._onInteraction(t,!0))),j.on(this._element,ws,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Is.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return V(Is),b(Is),{Alert:U,Button:J,Carousel:Ot,Collapse:Rt,Dropdown:fe,Modal:Ue,Offcanvas:gi,Popover:Mi,ScrollSpy:Qi,Tab:ms,Toast:Is,Tooltip:Ni}})); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map new file mode 100644 index 00000000..4d437152 --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map @@ -0,0 +1 @@ +{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","destroy","update","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","scroll","Offcanvas","blur","completeCallback","position","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","allowList","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","HOME_KEY","END_KEY","SELECTOR_DROPDOWN_TOGGLE","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index 865807a1..d94afad7 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -25,7 +25,6 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { let app = &global::SETTINGS.app.name; Page::new(request) - .with_theme("Basic") .with_title(L10n::l("welcome_title")) .add_component( Intro::new() diff --git a/tools/changelog.sh b/tools/changelog.sh index 253f22bc..bd5f20bb 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -50,15 +50,23 @@ case "$CRATE" in pagetop) CHANGELOG_FILE="CHANGELOG.md" PATH_FLAGS=( + # Helpers --exclude-path "helpers/pagetop-statics/**/*" --exclude-path "helpers/pagetop-build/**/*" --exclude-path "helpers/pagetop-macros/**/*" + # Extensions + --exclude-path "extensions/pagetop-aliner/**/*" + --exclude-path "extensions/pagetop-bootsier/**/*" ) ;; pagetop-aliner) CHANGELOG_FILE="extensions/pagetop-aliner/CHANGELOG.md" PATH_FLAGS=(--include-path "extensions/pagetop-aliner/**/*") ;; + pagetop-bootsier) + CHANGELOG_FILE="extensions/pagetop-bootsier/CHANGELOG.md" + PATH_FLAGS=(--include-path "extensions/pagetop-bootsier/**/*") + ;; *) echo "Error: unsupported crate '$CRATE'" >&2 exit 1 From 345bac9d2548168539933c93b6418399f084f21e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 12:29:16 +0200 Subject: [PATCH 153/224] =?UTF-8?q?=F0=9F=93=9D=20Repasa=20doc=20y=20cambi?= =?UTF-8?q?a=20caracteres=20Unicode=20ambiguos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ src/core.rs | 2 +- src/core/theme/definition.rs | 4 ++-- src/html/assets/javascript.rs | 12 ++++++------ src/html/assets/stylesheet.rs | 4 ++-- src/service.rs | 8 ++++---- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0e635189..2a05be13 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,10 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui * **[pagetop-aliner](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner)**, es un tema para demos y pruebas que muestra esquemáticamente la composición de las páginas HTML. + * **[pagetop-bootsier](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier)**, + tema basado en [Bootstrap](https://getbootstrap.com) para ofrecer su catálogo de estilos y + componentes flexibles. + # 🧪 Pruebas diff --git a/src/core.rs b/src/core.rs index 9ecbd2e9..ab4d693b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -7,7 +7,7 @@ use std::any::Any; pub enum TypeInfo { /// Ruta completa tal y como la devuelve [`core::any::type_name`]. FullName, - /// Último segmento de la ruta – por ejemplo `Vec<i32>` en lugar de `alloc::vec::Vec<i32>`. + /// Último segmento de la ruta, por ejemplo `Vec<i32>` en lugar de `alloc::vec::Vec<i32>`. ShortName, /// Conserva todo **desde** `start` inclusive hasta el final. NameFrom(isize), diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 1eb7b226..2a20c078 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -283,14 +283,14 @@ pub trait Theme: Extension + ThemePage + Send + Sync { <Self as ThemePage>::render_head(self, page) } - /// Contenido predeterminado para la página de error "*403 – Forbidden*". + /// Contenido predeterminado para la página de error "*403 - Forbidden*". /// /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. fn error403(&self, page: &mut Page) -> Markup { html! { div { h1 { (L10n::l("error403_notice").using(page)) } } } } - /// Contenido predeterminado para la página de error "*404 – Not Found*". + /// Contenido predeterminado para la página de error "*404 - Not Found*". /// /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. fn error404(&self, page: &mut Page) -> Markup { diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 6dc9b852..0e86f0d4 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -8,16 +8,16 @@ use crate::{join, join_pair, AutoDefault, Weight}; // Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del // script en relación con el análisis del documento HTML y la ejecución del resto de scripts. // -// - [`From`] – Carga estándar con la etiqueta `<script src="...">`. -// - [`Defer`] – Igual que [`From`], pero con el atributo `defer`, descarga en paralelo y se +// - [`From`] - Carga estándar con la etiqueta `<script src="...">`. +// - [`Defer`] - Igual que [`From`], pero con el atributo `defer`, descarga en paralelo y se // ejecuta tras el análisis del documento HTML, respetando el orden de // aparición. -// - [`Async`] – Igual que [`From`], pero con el atributo `async`, descarga en paralelo y se +// - [`Async`] - Igual que [`From`], pero con el atributo `async`, descarga en paralelo y se // ejecuta en cuanto esté listo, **sin garantizar** el orden relativo respecto a // otros scripts. -// - [`Inline`] – Inserta el código directamente en la etiqueta `<script>`. -// - [`OnLoad`] – Inserta el código JavaScript y lo ejecuta tras el evento `DOMContentLoaded`. -// - [`OnLoadAsync`] – Igual que [`OnLoad`], pero con manejador asíncrono (`async`), útil si dentro +// - [`Inline`] - Inserta el código directamente en la etiqueta `<script>`. +// - [`OnLoad`] - Inserta el código JavaScript y lo ejecuta tras el evento `DOMContentLoaded`. +// - [`OnLoadAsync`] - Igual que [`OnLoad`], pero con manejador asíncrono (`async`), útil si dentro // del código JavaScript se utiliza `await`. #[derive(AutoDefault)] enum Source { diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 8d1bf29f..5f0eaaa0 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -8,9 +8,9 @@ use crate::{join_pair, AutoDefault, Weight}; // Los estilos pueden cargarse desde un archivo externo o estar embebidos directamente en una // etiqueta `<style>`. // -// - [`From`] – Carga la hoja de estilos desde un archivo externo, insertándola mediante una +// - [`From`] - Carga la hoja de estilos desde un archivo externo, insertándola mediante una // etiqueta `<link>` con `rel="stylesheet"`. -// - [`Inline`] – Inserta directamente el contenido CSS dentro de una etiqueta `<style>`. +// - [`Inline`] - Inserta directamente el contenido CSS dentro de una etiqueta `<style>`. #[derive(AutoDefault)] enum Source { #[default] diff --git a/src/service.rs b/src/service.rs index 9a936fc3..1b2f766c 100644 --- a/src/service.rs +++ b/src/service.rs @@ -93,11 +93,11 @@ macro_rules! include_files_service { /// /// # Argumentos /// -/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la +/// * `$scfg` - Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la /// configuración. -/// * `$path` – Ruta al directorio local con los archivos estáticos. -/// * `$bundle` – Nombre del conjunto de recursos que esta macro integra en el binario. -/// * `$route` – Ruta URL base desde la que se servirán los archivos. +/// * `$path` - Ruta al directorio local con los archivos estáticos. +/// * `$bundle` - Nombre del conjunto de recursos que esta macro integra en el binario. +/// * `$route` - Ruta URL base desde la que se servirán los archivos. /// /// # Ejemplos /// From 6052b87c9c50378cf2f324e66b038411d2779364 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 13:08:33 +0200 Subject: [PATCH 154/224] =?UTF-8?q?=F0=9F=93=84=20Actualiza=20licencias=20?= =?UTF-8?q?y=20revisa=20*badges*=20de=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- extensions/pagetop-aliner/LICENSE-APACHE | 201 +++++++++++++++++++++ extensions/pagetop-aliner/LICENSE-MIT | 21 +++ extensions/pagetop-aliner/README.md | 2 +- extensions/pagetop-aliner/src/lib.rs | 2 +- extensions/pagetop-bootsier/LICENSE-APACHE | 201 +++++++++++++++++++++ extensions/pagetop-bootsier/LICENSE-MIT | 21 +++ extensions/pagetop-bootsier/README.md | 2 +- extensions/pagetop-bootsier/src/lib.rs | 2 +- helpers/pagetop-build/README.md | 2 +- helpers/pagetop-build/src/lib.rs | 2 +- helpers/pagetop-macros/README.md | 2 +- helpers/pagetop-macros/src/lib.rs | 2 +- helpers/pagetop-statics/README.md | 5 +- helpers/pagetop-statics/src/lib.rs | 5 +- src/lib.rs | 2 +- 16 files changed, 462 insertions(+), 12 deletions(-) create mode 100644 extensions/pagetop-aliner/LICENSE-APACHE create mode 100644 extensions/pagetop-aliner/LICENSE-MIT create mode 100644 extensions/pagetop-bootsier/LICENSE-APACHE create mode 100644 extensions/pagetop-bootsier/LICENSE-MIT diff --git a/README.md b/README.md index 2a05be13..d9eeb402 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ <p>Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> diff --git a/extensions/pagetop-aliner/LICENSE-APACHE b/extensions/pagetop-aliner/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/extensions/pagetop-aliner/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/extensions/pagetop-aliner/LICENSE-MIT b/extensions/pagetop-aliner/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/extensions/pagetop-aliner/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index ac6da910..798c6596 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -4,10 +4,10 @@ <p>Tema de <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) [![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 92f9aa41..54898b5d 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -5,10 +5,10 @@ <p>Tema para <strong>PageTop</strong> que muestra esquemáticamente la composición de las páginas HTML.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) [![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> diff --git a/extensions/pagetop-bootsier/LICENSE-APACHE b/extensions/pagetop-bootsier/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/extensions/pagetop-bootsier/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/extensions/pagetop-bootsier/LICENSE-MIT b/extensions/pagetop-bootsier/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/extensions/pagetop-bootsier/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 87bd3c7b..120b3680 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -4,10 +4,10 @@ <p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 8157ec2d..fbb97c63 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -5,10 +5,10 @@ <p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 57273e83..948429cc 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -4,10 +4,10 @@ <p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index c6e72367..30fcfe32 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -5,10 +5,10 @@ <p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 7c9c2e86..fc2f976c 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -4,10 +4,10 @@ <p>Una colección de macros que mejoran la experiencia de desarrollo con <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 709ce572..61780cce 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -5,10 +5,10 @@ <p>Una colección de macros que mejoran la experiencia de desarrollo con <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index 4168cd4a..ba7ae266 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -4,7 +4,10 @@ <p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-statics?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-statics) +[![Crates.io](https://img.shields.io/crates/v/pagetop-statics.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-statics) +[![Descargas](https://img.shields.io/crates/d/pagetop-statics.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-statics) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index 201d90ef..05570933 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -5,7 +5,10 @@ <p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-statics?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-statics) +[![Crates.io](https://img.shields.io/crates/v/pagetop-statics.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-statics) +[![Descargas](https://img.shields.io/crates/d/pagetop-statics.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-statics) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) </div> diff --git a/src/lib.rs b/src/lib.rs index 6f5c5cfb..daef77a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,10 @@ <p>Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.</p> -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) + ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) <br> </div> From f68f33a7a2fca80975b9bb922f0bb0535dfe3ee6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 13:27:05 +0200 Subject: [PATCH 155/224] =?UTF-8?q?=F0=9F=93=9D=20depura=20enlaces=20de=20?= =?UTF-8?q?informaci=C3=B3n=20de=20licencias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- extensions/pagetop-aliner/README.md | 2 +- extensions/pagetop-aliner/src/lib.rs | 2 +- extensions/pagetop-bootsier/README.md | 2 +- extensions/pagetop-bootsier/src/lib.rs | 2 +- helpers/pagetop-build/README.md | 2 +- helpers/pagetop-build/src/lib.rs | 2 +- helpers/pagetop-macros/README.md | 2 +- helpers/pagetop-macros/src/lib.rs | 2 +- helpers/pagetop-statics/README.md | 2 +- helpers/pagetop-statics/src/lib.rs | 2 +- src/lib.rs | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d9eeb402..be720910 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop#licencia) <br> </div> diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 798c6596..0560a067 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -7,7 +7,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) [![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia) <br> </div> diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 54898b5d..b51800c2 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -8,7 +8,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) [![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia) <br> </div> diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 120b3680..1a33b4ea 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -7,7 +7,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia) <br> </div> diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index fbb97c63..c413e8e5 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -8,7 +8,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia) <br> </div> diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 948429cc..875acbd9 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -7,7 +7,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build#licencia) </div> diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 30fcfe32..9088ec99 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -8,7 +8,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build#licencia) </div> diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index fc2f976c..66fdc1fa 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -7,7 +7,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros#licencia) </div> diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 61780cce..2781aef8 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -8,7 +8,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros#licencia) </div> diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index ba7ae266..cd1da6ac 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -7,7 +7,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-statics?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-statics) [![Crates.io](https://img.shields.io/crates/v/pagetop-statics.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-statics) [![Descargas](https://img.shields.io/crates/d/pagetop-statics.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-statics) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics#licencia) </div> diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index 05570933..6f04bd2a 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -8,7 +8,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop-statics?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-statics) [![Crates.io](https://img.shields.io/crates/v/pagetop-statics.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-statics) [![Descargas](https://img.shields.io/crates/d/pagetop-statics.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-statics) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics#licencia) </div> diff --git a/src/lib.rs b/src/lib.rs index daef77a8..de6d9e68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) - ![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop#licencia) <br> </div> From e4cd1e56e09b825a22bc9509ab6f9f32b31a5743 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 20:04:35 +0200 Subject: [PATCH 156/224] =?UTF-8?q?=F0=9F=8D=B1=20(bootsier):=20Actualiza?= =?UTF-8?q?=20bootstrap=20v5.3.3=20a=20v5.3.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/build.rs | 9 +- extensions/pagetop-bootsier/src/lib.rs | 2 +- .../bootstrap-5.3.3/mixins/_box-shadow.scss | 18 -- .../_accordion.scss | 13 +- .../_alert.scss | 0 .../_badge.scss | 0 .../_breadcrumb.scss | 0 .../_button-group.scss | 11 +- .../_buttons.scss | 0 .../{bootstrap-5.3.3 => bootstrap}/_card.scss | 19 +- .../_carousel.scss | 40 ++-- .../_close.scss | 15 +- .../_containers.scss | 0 .../_dropdown.scss | 0 .../_forms.scss | 0 .../_functions.scss | 4 +- .../{bootstrap-5.3.3 => bootstrap}/_grid.scss | 0 .../_helpers.scss | 0 .../_images.scss | 0 .../_list-group.scss | 52 ++--- .../{bootstrap-5.3.3 => bootstrap}/_maps.scss | 0 .../_mixins.scss | 0 .../_modal.scss | 8 +- .../{bootstrap-5.3.3 => bootstrap}/_nav.scss | 2 +- .../_navbar.scss | 2 +- .../_offcanvas.scss | 6 +- .../_pagination.scss | 2 +- .../_placeholders.scss | 0 .../_popover.scss | 0 .../_progress.scss | 2 +- .../_reboot.scss | 8 +- .../{bootstrap-5.3.3 => bootstrap}/_root.scss | 0 .../_spinners.scss | 1 + .../_tables.scss | 0 .../_toasts.scss | 0 .../_tooltip.scss | 0 .../_transitions.scss | 0 .../{bootstrap-5.3.3 => bootstrap}/_type.scss | 2 +- .../_utilities.scss | 0 .../_variables-dark.scss | 19 +- .../_variables.scss | 30 +-- .../bootstrap-grid.scss | 0 .../bootstrap-reboot.scss | 0 .../bootstrap-utilities.scss | 0 .../bootstrap.scss | 0 .../forms/_floating-labels.scss | 34 ++-- .../forms/_form-check.scss | 0 .../forms/_form-control.scss | 0 .../forms/_form-range.scss | 0 .../forms/_form-select.scss | 0 .../forms/_form-text.scss | 0 .../forms/_input-group.scss | 2 +- .../forms/_labels.scss | 0 .../forms/_validation.scss | 0 .../helpers/_clearfix.scss | 0 .../helpers/_color-bg.scss | 0 .../helpers/_colored-links.scss | 0 .../helpers/_focus-ring.scss | 0 .../helpers/_icon-link.scss | 0 .../helpers/_position.scss | 0 .../helpers/_ratio.scss | 0 .../helpers/_stacks.scss | 0 .../helpers/_stretched-link.scss | 0 .../helpers/_text-truncation.scss | 0 .../helpers/_visually-hidden.scss | 0 .../helpers/_vr.scss | 0 .../mixins/_alert.scss | 0 .../mixins/_backdrop.scss | 0 .../mixins/_banner.scss | 4 +- .../mixins/_border-radius.scss | 0 .../static/bootstrap/mixins/_box-shadow.scss | 24 +++ .../mixins/_breakpoints.scss | 0 .../mixins/_buttons.scss | 0 .../mixins/_caret.scss | 0 .../mixins/_clearfix.scss | 0 .../mixins/_color-mode.scss | 0 .../mixins/_color-scheme.scss | 0 .../mixins/_container.scss | 0 .../mixins/_deprecate.scss | 0 .../mixins/_forms.scss | 0 .../mixins/_gradients.scss | 0 .../mixins/_grid.scss | 2 +- .../mixins/_image.scss | 0 .../mixins/_list-group.scss | 0 .../mixins/_lists.scss | 0 .../mixins/_pagination.scss | 0 .../mixins/_reset-text.scss | 0 .../mixins/_resize.scss | 0 .../mixins/_table-variants.scss | 0 .../mixins/_text-truncate.scss | 0 .../mixins/_transition.scss | 0 .../mixins/_utilities.scss | 0 .../mixins/_visually-hidden.scss | 7 +- .../tests/jasmine.js | 0 .../_auto-import-of-variables-dark.test.scss | 0 .../tests/mixins/_box-shadow.test.scss | 188 ++++++++++++++++++ .../tests/mixins/_color-contrast.test.scss | 139 +++++++++++++ .../tests/mixins/_color-modes.test.scss | 0 .../_media-query-color-mode-full.test.scss | 0 .../tests/mixins/_utilities.test.scss | 0 .../tests/sass-true/register.js | 0 .../tests/sass-true/runner.js | 0 .../tests/utilities/_api.test.scss | 0 .../utilities/_api.scss | 0 .../vendor/_rfs.scss | 0 .../pagetop-bootsier/static/js/bootstrap.js | 34 ++-- .../static/js/bootstrap.js.map | 2 +- .../static/js/bootstrap.min.js | 6 +- .../static/js/bootstrap.min.js.map | 2 +- 109 files changed, 536 insertions(+), 173 deletions(-) delete mode 100644 extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_accordion.scss (97%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_alert.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_badge.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_breadcrumb.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_button-group.scss (87%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_buttons.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_card.scss (95%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_carousel.scss (87%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_close.scss (87%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_containers.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_dropdown.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_forms.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_functions.scss (99%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_grid.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_helpers.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_images.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_list-group.scss (93%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_maps.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_mixins.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_modal.scss (96%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_nav.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_navbar.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_offcanvas.scss (93%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_pagination.scss (98%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_placeholders.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_popover.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_progress.scss (96%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_reboot.scss (98%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_root.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_spinners.scss (99%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_tables.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_toasts.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_tooltip.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_transitions.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_type.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_variables-dark.scss (90%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/_variables.scss (98%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/bootstrap-grid.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/bootstrap-reboot.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/bootstrap-utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/bootstrap.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_floating-labels.scss (78%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_form-check.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_form-control.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_form-range.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_form-select.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_form-text.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_input-group.scss (98%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_labels.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/forms/_validation.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_clearfix.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_color-bg.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_colored-links.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_focus-ring.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_icon-link.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_position.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_ratio.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_stacks.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_stretched-link.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_text-truncation.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_visually-hidden.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/helpers/_vr.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_alert.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_backdrop.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_banner.scss (52%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_border-radius.scss (100%) create mode 100644 extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_breakpoints.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_buttons.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_caret.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_clearfix.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_color-mode.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_color-scheme.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_container.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_deprecate.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_forms.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_gradients.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_grid.scss (98%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_image.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_list-group.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_lists.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_pagination.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_reset-text.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_resize.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_table-variants.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_text-truncate.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_transition.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/mixins/_visually-hidden.scss (87%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/jasmine.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/mixins/_auto-import-of-variables-dark.test.scss (100%) create mode 100644 extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss create mode 100644 extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/mixins/_color-modes.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/mixins/_media-query-color-mode-full.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/mixins/_utilities.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/sass-true/register.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/sass-true/runner.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/tests/utilities/_api.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/utilities/_api.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap-5.3.3 => bootstrap}/vendor/_rfs.scss (100%) diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs index 47cb4740..f2d0f794 100644 --- a/extensions/pagetop-bootsier/build.rs +++ b/extensions/pagetop-bootsier/build.rs @@ -4,12 +4,9 @@ use std::env; use std::path::Path; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_scss( - "./static/bootstrap-5.3.3/bootstrap.scss", - "bootstrap.min.css", - ) - .with_name("bootsier") - .build()?; + StaticFilesBundle::from_scss("./static/bootstrap/bootstrap.scss", "bootstrap.min.css") + .with_name("bootsier") + .build()?; StaticFilesBundle::from_dir("./static/js", Some(bootstrap_js_files)) .with_name("bootsier_js") .build() diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index c413e8e5..fecf1273 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -86,7 +86,7 @@ use pagetop::prelude::*; pub type BootsierRegion = ThemeRegion; // Versión de la librería Bootstrap. -const BOOTSTRAP_VERSION: &str = "5.3.3"; +const BOOTSTRAP_VERSION: &str = "5.3.8"; /// Tema basado en [Bootstrap](https://getbootstrap.com/) para los componentes base de PageTop. /// diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss b/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss deleted file mode 100644 index 4172541f..00000000 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_box-shadow.scss +++ /dev/null @@ -1,18 +0,0 @@ -@mixin box-shadow($shadow...) { - @if $enable-shadows { - $result: (); - - @each $value in $shadow { - @if $value != null { - $result: append($result, $value, "comma"); - } - @if $value == none and length($shadow) > 1 { - @warn "The keyword 'none' must be used as a single argument."; - } - } - - @if (length($result) > 0) { - box-shadow: $result; - } - } -} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss b/extensions/pagetop-bootsier/static/bootstrap/_accordion.scss similarity index 97% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss rename to extensions/pagetop-bootsier/static/bootstrap/_accordion.scss index 17e5436e..e9f267fb 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_accordion.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_accordion.scss @@ -134,17 +134,12 @@ &:last-child { border-bottom: 0; } // stylelint-disable selector-max-class - > .accordion-header .accordion-button { - &, - &.collapsed { - @include border-radius(0); - } - } - // stylelint-enable selector-max-class - - > .accordion-collapse { + > .accordion-collapse, + > .accordion-header .accordion-button, + > .accordion-header .accordion-button.collapsed { @include border-radius(0); } + // stylelint-enable selector-max-class } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss b/extensions/pagetop-bootsier/static/bootstrap/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_alert.scss rename to extensions/pagetop-bootsier/static/bootstrap/_alert.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss b/extensions/pagetop-bootsier/static/bootstrap/_badge.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_badge.scss rename to extensions/pagetop-bootsier/static/bootstrap/_badge.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss b/extensions/pagetop-bootsier/static/bootstrap/_breadcrumb.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_breadcrumb.scss rename to extensions/pagetop-bootsier/static/bootstrap/_breadcrumb.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss b/extensions/pagetop-bootsier/static/bootstrap/_button-group.scss similarity index 87% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss rename to extensions/pagetop-bootsier/static/bootstrap/_button-group.scss index 55ae3f65..78e12522 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_button-group.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_button-group.scss @@ -39,7 +39,7 @@ // Prevent double borders when buttons are next to each other > :not(.btn-check:first-child) + .btn, > .btn-group:not(:first-child) { - margin-left: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list + margin-left: calc(-1 * #{$btn-border-width}); // stylelint-disable-line function-disallowed-list } // Reset rounded corners @@ -126,7 +126,7 @@ > .btn:not(:first-child), > .btn-group:not(:first-child) { - margin-top: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list + margin-top: calc(-1 * #{$btn-border-width}); // stylelint-disable-line function-disallowed-list } // Reset rounded corners @@ -135,7 +135,12 @@ @include border-bottom-radius(0); } - > .btn ~ .btn, + // The top radius should be 0 if the button is: + // - the "third or more" child + // - the second child and the previous element isn't `.btn-check` (making it the first child visually) + // - part of a btn-group which isn't the first child + > .btn:nth-child(n + 3), + > :not(.btn-check) + .btn, > .btn-group:not(:first-child) > .btn { @include border-top-radius(0); } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss b/extensions/pagetop-bootsier/static/bootstrap/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_buttons.scss rename to extensions/pagetop-bootsier/static/bootstrap/_buttons.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss b/extensions/pagetop-bootsier/static/bootstrap/_card.scss similarity index 95% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss rename to extensions/pagetop-bootsier/static/bootstrap/_card.scss index d3535a98..dcebe6ac 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_card.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_card.scss @@ -193,8 +193,7 @@ // The child selector allows nested `.card` within `.card-group` // to display properly. > .card { - // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 - flex: 1 0 0%; + flex: 1 0 0; margin-bottom: 0; + .card { @@ -207,13 +206,13 @@ &:not(:last-child) { @include border-end-radius(0); - .card-img-top, - .card-header { + > .card-img-top, + > .card-header { // stylelint-disable-next-line property-disallowed-list border-top-right-radius: 0; } - .card-img-bottom, - .card-footer { + > .card-img-bottom, + > .card-footer { // stylelint-disable-next-line property-disallowed-list border-bottom-right-radius: 0; } @@ -222,13 +221,13 @@ &:not(:first-child) { @include border-start-radius(0); - .card-img-top, - .card-header { + > .card-img-top, + > .card-header { // stylelint-disable-next-line property-disallowed-list border-top-left-radius: 0; } - .card-img-bottom, - .card-footer { + > .card-img-bottom, + > .card-footer { // stylelint-disable-next-line property-disallowed-list border-bottom-left-radius: 0; } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss b/extensions/pagetop-bootsier/static/bootstrap/_carousel.scss similarity index 87% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss rename to extensions/pagetop-bootsier/static/bootstrap/_carousel.scss index 3a135220..5ebf6b15 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_carousel.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_carousel.scss @@ -99,6 +99,7 @@ color: $carousel-control-color; text-align: center; background: none; + filter: var(--#{$prefix}carousel-control-icon-filter); border: 0; opacity: $carousel-control-opacity; @include transition($carousel-control-transition); @@ -168,7 +169,7 @@ margin-left: $carousel-indicator-spacer; text-indent: -999px; cursor: pointer; - background-color: $carousel-indicator-active-bg; + background-color: var(--#{$prefix}carousel-indicator-active-bg); background-clip: padding-box; border: 0; // Use transparent borders to increase the hit area by 10px on top and bottom. @@ -195,42 +196,31 @@ left: (100% - $carousel-caption-width) * .5; padding-top: $carousel-caption-padding-y; padding-bottom: $carousel-caption-padding-y; - color: $carousel-caption-color; + color: var(--#{$prefix}carousel-caption-color); text-align: center; } // Dark mode carousel @mixin carousel-dark() { - .carousel-control-prev-icon, - .carousel-control-next-icon { - filter: $carousel-dark-control-icon-filter; - } - - .carousel-indicators [data-bs-target] { - background-color: $carousel-dark-indicator-active-bg; - } - - .carousel-caption { - color: $carousel-dark-caption-color; - } + --#{$prefix}carousel-indicator-active-bg: #{$carousel-indicator-active-bg-dark}; + --#{$prefix}carousel-caption-color: #{$carousel-caption-color-dark}; + --#{$prefix}carousel-control-icon-filter: #{$carousel-control-icon-filter-dark}; } .carousel-dark { @include carousel-dark(); } +:root, +[data-bs-theme="light"] { + --#{$prefix}carousel-indicator-active-bg: #{$carousel-indicator-active-bg}; + --#{$prefix}carousel-caption-color: #{$carousel-caption-color}; + --#{$prefix}carousel-control-icon-filter: #{$carousel-control-icon-filter}; +} + @if $enable-dark-mode { - @include color-mode(dark) { - @if $color-mode-type == "media-query" { - .carousel { - @include carousel-dark(); - } - } @else { - .carousel, - &.carousel { - @include carousel-dark(); - } - } + @include color-mode(dark, true) { + @include carousel-dark(); } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss b/extensions/pagetop-bootsier/static/bootstrap/_close.scss similarity index 87% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss rename to extensions/pagetop-bootsier/static/bootstrap/_close.scss index 4d6e73c1..d53c96fb 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_close.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_close.scss @@ -12,7 +12,6 @@ --#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow}; --#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity}; --#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity}; - --#{$prefix}btn-close-white-filter: #{$btn-close-white-filter}; // scss-docs-end close-css-vars box-sizing: content-box; @@ -21,6 +20,7 @@ padding: $btn-close-padding-y $btn-close-padding-x; color: var(--#{$prefix}btn-close-color); background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements + filter: var(--#{$prefix}btn-close-filter); border: 0; // for button elements @include border-radius(); opacity: var(--#{$prefix}btn-close-opacity); @@ -47,17 +47,20 @@ } @mixin btn-close-white() { - filter: var(--#{$prefix}btn-close-white-filter); + --#{$prefix}btn-close-filter: #{$btn-close-filter-dark}; } .btn-close-white { @include btn-close-white(); } +:root, +[data-bs-theme="light"] { + --#{$prefix}btn-close-filter: #{$btn-close-filter}; +} + @if $enable-dark-mode { - @include color-mode(dark) { - .btn-close { - @include btn-close-white(); - } + @include color-mode(dark, true) { + @include btn-close-white(); } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss b/extensions/pagetop-bootsier/static/bootstrap/_containers.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_containers.scss rename to extensions/pagetop-bootsier/static/bootstrap/_containers.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss b/extensions/pagetop-bootsier/static/bootstrap/_dropdown.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_dropdown.scss rename to extensions/pagetop-bootsier/static/bootstrap/_dropdown.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss b/extensions/pagetop-bootsier/static/bootstrap/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_forms.scss rename to extensions/pagetop-bootsier/static/bootstrap/_forms.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss b/extensions/pagetop-bootsier/static/bootstrap/_functions.scss similarity index 99% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss rename to extensions/pagetop-bootsier/static/bootstrap/_functions.scss index 90296586..59d431a1 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_functions.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_functions.scss @@ -157,7 +157,7 @@ $_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003 @each $color in $foregrounds { $contrast-ratio: contrast-ratio($background, $color); - @if $contrast-ratio > $min-contrast-ratio { + @if $contrast-ratio >= $min-contrast-ratio { @return $color; } @else if $contrast-ratio > $max-ratio { $max-ratio: $contrast-ratio; @@ -177,7 +177,7 @@ $_luminance-list: .0008 .001 .0011 .0013 .0015 .0017 .002 .0022 .0025 .0027 .003 @return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05)); } -// Return WCAG2.1 relative luminance +// Return WCAG2.2 relative luminance // See https://www.w3.org/TR/WCAG/#dfn-relative-luminance // See https://www.w3.org/TR/WCAG/#dfn-contrast-ratio @function luminance($color) { diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss b/extensions/pagetop-bootsier/static/bootstrap/_grid.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_grid.scss rename to extensions/pagetop-bootsier/static/bootstrap/_grid.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss b/extensions/pagetop-bootsier/static/bootstrap/_helpers.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_helpers.scss rename to extensions/pagetop-bootsier/static/bootstrap/_helpers.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss b/extensions/pagetop-bootsier/static/bootstrap/_images.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_images.scss rename to extensions/pagetop-bootsier/static/bootstrap/_images.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss b/extensions/pagetop-bootsier/static/bootstrap/_list-group.scss similarity index 93% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss rename to extensions/pagetop-bootsier/static/bootstrap/_list-group.scss index 455531ee..3bdff679 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_list-group.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_list-group.scss @@ -43,31 +43,6 @@ } } -// Interactive list items -// -// Use anchor or button elements instead of `li`s or `div`s to create interactive -// list items. Includes an extra `.active` modifier class for selected items. - -.list-group-item-action { - width: 100%; // For `<button>`s (anchors become 100% by default though) - color: var(--#{$prefix}list-group-action-color); - text-align: inherit; // For `<button>`s (anchors inherit) - - // Hover state - &:hover, - &:focus { - z-index: 1; // Place hover/focus items above their siblings for proper border styling - color: var(--#{$prefix}list-group-action-hover-color); - text-decoration: none; - background-color: var(--#{$prefix}list-group-action-hover-bg); - } - - &:active { - color: var(--#{$prefix}list-group-action-active-color); - background-color: var(--#{$prefix}list-group-action-active-bg); - } -} - // Individual list items // // Use on `li`s or `div`s within the `.list-group` parent. @@ -115,6 +90,33 @@ } } +// Interactive list items +// +// Use anchor or button elements instead of `li`s or `div`s to create interactive +// list items. Includes an extra `.active` modifier class for selected items. + +.list-group-item-action { + width: 100%; // For `<button>`s (anchors become 100% by default though) + color: var(--#{$prefix}list-group-action-color); + text-align: inherit; // For `<button>`s (anchors inherit) + + &:not(.active) { + // Hover state + &:hover, + &:focus { + z-index: 1; // Place hover/focus items above their siblings for proper border styling + color: var(--#{$prefix}list-group-action-hover-color); + text-decoration: none; + background-color: var(--#{$prefix}list-group-action-hover-bg); + } + + &:active { + color: var(--#{$prefix}list-group-action-active-color); + background-color: var(--#{$prefix}list-group-action-active-bg); + } + } +} + // Horizontal // // Change the layout of list group items from vertical (default) to horizontal. diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss b/extensions/pagetop-bootsier/static/bootstrap/_maps.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_maps.scss rename to extensions/pagetop-bootsier/static/bootstrap/_maps.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss b/extensions/pagetop-bootsier/static/bootstrap/_mixins.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_mixins.scss rename to extensions/pagetop-bootsier/static/bootstrap/_mixins.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss b/extensions/pagetop-bootsier/static/bootstrap/_modal.scss similarity index 96% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss rename to extensions/pagetop-bootsier/static/bootstrap/_modal.scss index 494db94e..a3492c17 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_modal.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_modal.scss @@ -59,8 +59,8 @@ // When fading in the modal, animate it to slide down .modal.fade & { - @include transition($modal-transition); transform: $modal-fade-transform; + @include transition($modal-transition); } .modal.show & { transform: $modal-show-transform; @@ -132,7 +132,11 @@ .btn-close { padding: calc(var(--#{$prefix}modal-header-padding-y) * .5) calc(var(--#{$prefix}modal-header-padding-x) * .5); - margin: calc(-.5 * var(--#{$prefix}modal-header-padding-y)) calc(-.5 * var(--#{$prefix}modal-header-padding-x)) calc(-.5 * var(--#{$prefix}modal-header-padding-y)) auto; + // Split properties to avoid invalid calc() function if value is 0 + margin-top: calc(-.5 * var(--#{$prefix}modal-header-padding-y)); + margin-right: calc(-.5 * var(--#{$prefix}modal-header-padding-x)); + margin-bottom: calc(-.5 * var(--#{$prefix}modal-header-padding-y)); + margin-left: auto; } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss b/extensions/pagetop-bootsier/static/bootstrap/_nav.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss rename to extensions/pagetop-bootsier/static/bootstrap/_nav.scss index ff073d36..96fa5289 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_nav.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_nav.scss @@ -169,8 +169,8 @@ .nav-justified { > .nav-link, .nav-item { - flex-basis: 0; flex-grow: 1; + flex-basis: 0; text-align: center; } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss b/extensions/pagetop-bootsier/static/bootstrap/_navbar.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss rename to extensions/pagetop-bootsier/static/bootstrap/_navbar.scss index 71619382..86aa441e 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_navbar.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_navbar.scss @@ -139,8 +139,8 @@ // the default flexbox row orientation. Requires the use of `flex-wrap: wrap` // on the `.navbar` parent. .navbar-collapse { - flex-basis: 100%; flex-grow: 1; + flex-basis: 100%; // For always expanded or extra full navbars, ensure content aligns itself // properly vertically. Can be easily overridden with flex utilities. align-items: center; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss b/extensions/pagetop-bootsier/static/bootstrap/_offcanvas.scss similarity index 93% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss rename to extensions/pagetop-bootsier/static/bootstrap/_offcanvas.scss index eb2c97ab..b40b2cd9 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_offcanvas.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_offcanvas.scss @@ -127,7 +127,11 @@ .btn-close { padding: calc(var(--#{$prefix}offcanvas-padding-y) * .5) calc(var(--#{$prefix}offcanvas-padding-x) * .5); - margin: calc(-.5 * var(--#{$prefix}offcanvas-padding-y)) calc(-.5 * var(--#{$prefix}offcanvas-padding-x)) calc(-.5 * var(--#{$prefix}offcanvas-padding-y)) auto; + // Split properties to avoid invalid calc() function if value is 0 + margin-top: calc(-.5 * var(--#{$prefix}offcanvas-padding-y)); + margin-right: calc(-.5 * var(--#{$prefix}offcanvas-padding-x)); + margin-bottom: calc(-.5 * var(--#{$prefix}offcanvas-padding-y)); + margin-left: auto; } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss b/extensions/pagetop-bootsier/static/bootstrap/_pagination.scss similarity index 98% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss rename to extensions/pagetop-bootsier/static/bootstrap/_pagination.scss index f275a62e..9f09694c 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_pagination.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_pagination.scss @@ -75,7 +75,7 @@ margin-left: $pagination-margin-start; } - @if $pagination-margin-start == calc(#{$pagination-border-width} * -1) { + @if $pagination-margin-start == calc(-1 * #{$pagination-border-width}) { &:first-child { .page-link { @include border-start-radius(var(--#{$prefix}pagination-border-radius)); diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss b/extensions/pagetop-bootsier/static/bootstrap/_placeholders.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_placeholders.scss rename to extensions/pagetop-bootsier/static/bootstrap/_placeholders.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss b/extensions/pagetop-bootsier/static/bootstrap/_popover.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_popover.scss rename to extensions/pagetop-bootsier/static/bootstrap/_popover.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss b/extensions/pagetop-bootsier/static/bootstrap/_progress.scss similarity index 96% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss rename to extensions/pagetop-bootsier/static/bootstrap/_progress.scss index 148c3815..732365c5 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_progress.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_progress.scss @@ -3,7 +3,7 @@ // scss-docs-start progress-keyframes @if $enable-transitions { @keyframes progress-bar-stripes { - 0% { background-position-x: $progress-height; } + 0% { background-position-x: var(--#{$prefix}progress-height); } } } // scss-docs-end progress-keyframes diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss b/extensions/pagetop-bootsier/static/bootstrap/_reboot.scss similarity index 98% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss rename to extensions/pagetop-bootsier/static/bootstrap/_reboot.scss index 18791753..524645fb 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_reboot.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_reboot.scss @@ -499,9 +499,9 @@ legend { width: 100%; padding: 0; margin-bottom: $legend-margin-bottom; - @include font-size($legend-font-size); font-weight: $legend-font-weight; line-height: inherit; + @include font-size($legend-font-size); + * { clear: left; // 2 @@ -534,6 +534,12 @@ legend { [type="search"] { -webkit-appearance: textfield; // 1 outline-offset: -2px; // 2 + + // 3. Better affordance and consistent appearance for search cancel button + &::-webkit-search-cancel-button { + cursor: pointer; + filter: grayscale(1); + } } // 1. A few input types should stay LTR diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss b/extensions/pagetop-bootsier/static/bootstrap/_root.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_root.scss rename to extensions/pagetop-bootsier/static/bootstrap/_root.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss b/extensions/pagetop-bootsier/static/bootstrap/_spinners.scss similarity index 99% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss rename to extensions/pagetop-bootsier/static/bootstrap/_spinners.scss index ec847320..9dff2892 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_spinners.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_spinners.scss @@ -5,6 +5,7 @@ .spinner-grow, .spinner-border { display: inline-block; + flex-shrink: 0; width: var(--#{$prefix}spinner-width); height: var(--#{$prefix}spinner-height); vertical-align: var(--#{$prefix}spinner-vertical-align); diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss b/extensions/pagetop-bootsier/static/bootstrap/_tables.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tables.scss rename to extensions/pagetop-bootsier/static/bootstrap/_tables.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss b/extensions/pagetop-bootsier/static/bootstrap/_toasts.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_toasts.scss rename to extensions/pagetop-bootsier/static/bootstrap/_toasts.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss b/extensions/pagetop-bootsier/static/bootstrap/_tooltip.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_tooltip.scss rename to extensions/pagetop-bootsier/static/bootstrap/_tooltip.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss b/extensions/pagetop-bootsier/static/bootstrap/_transitions.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_transitions.scss rename to extensions/pagetop-bootsier/static/bootstrap/_transitions.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss b/extensions/pagetop-bootsier/static/bootstrap/_type.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss rename to extensions/pagetop-bootsier/static/bootstrap/_type.scss index 37d64bf8..6961390f 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_type.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_type.scss @@ -34,11 +34,11 @@ // Type display classes @each $display, $font-size in $display-font-sizes { .display-#{$display} { - @include font-size($font-size); font-family: $display-font-family; font-style: $display-font-style; font-weight: $display-font-weight; line-height: $display-line-height; + @include font-size($font-size); } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss b/extensions/pagetop-bootsier/static/bootstrap/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_utilities.scss rename to extensions/pagetop-bootsier/static/bootstrap/_utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss b/extensions/pagetop-bootsier/static/bootstrap/_variables-dark.scss similarity index 90% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss rename to extensions/pagetop-bootsier/static/bootstrap/_variables-dark.scss index 6422b387..260f6dcc 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables-dark.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_variables-dark.scss @@ -82,6 +82,21 @@ $form-invalid-border-color-dark: $red-300 !default; $accordion-icon-color-dark: $primary-text-emphasis-dark !default; $accordion-icon-active-color-dark: $primary-text-emphasis-dark !default; -$accordion-button-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>") !default; -$accordion-button-active-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>") !default; +$accordion-button-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/></svg>") !default; +$accordion-button-active-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/></svg>") !default; // scss-docs-end sass-dark-mode-vars + + +// +// Carousel +// + +$carousel-indicator-active-bg-dark: $carousel-dark-indicator-active-bg !default; +$carousel-caption-color-dark: $carousel-dark-caption-color !default; +$carousel-control-icon-filter-dark: $carousel-dark-control-icon-filter !default; + +// +// Close button +// + +$btn-close-filter-dark: $btn-close-white-filter !default; diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss b/extensions/pagetop-bootsier/static/bootstrap/_variables.scss similarity index 98% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss rename to extensions/pagetop-bootsier/static/bootstrap/_variables.scss index 06531395..1ffa7e74 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/_variables.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/_variables.scss @@ -67,8 +67,8 @@ $colors: ( ) !default; // scss-docs-end colors-map -// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.0 are 3, 4.5 and 7. -// See https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast +// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.2 are 3, 4.5 and 7. +// See https://www.w3.org/TR/WCAG/#contrast-minimum $min-contrast-ratio: 4.5 !default; // Customize the light and dark text colors for use in our color contrast function. @@ -1091,7 +1091,7 @@ $form-feedback-valid-color: $success !default; $form-feedback-invalid-color: $danger !default; $form-feedback-icon-valid-color: $form-feedback-valid-color !default; -$form-feedback-icon-valid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#{$form-feedback-icon-valid-color}' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>") !default; +$form-feedback-icon-valid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#{$form-feedback-icon-valid-color}' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/></svg>") !default; $form-feedback-icon-invalid-color: $form-feedback-invalid-color !default; $form-feedback-icon-invalid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='#{$form-feedback-icon-invalid-color}'><circle cx='6' cy='6' r='4.5'/><path stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/><circle cx='6' cy='8.2' r='.6' fill='#{$form-feedback-icon-invalid-color}' stroke='none'/></svg>") !default; // scss-docs-end form-feedback-variables @@ -1302,7 +1302,7 @@ $pagination-color: var(--#{$prefix}link-color) !default; $pagination-bg: var(--#{$prefix}body-bg) !default; $pagination-border-radius: var(--#{$prefix}border-radius) !default; $pagination-border-width: var(--#{$prefix}border-width) !default; -$pagination-margin-start: calc(#{$pagination-border-width} * -1) !default; // stylelint-disable-line function-disallowed-list +$pagination-margin-start: calc(-1 * #{$pagination-border-width}) !default; // stylelint-disable-line function-disallowed-list $pagination-border-color: var(--#{$prefix}border-color) !default; $pagination-focus-color: var(--#{$prefix}link-hover-color) !default; @@ -1394,8 +1394,8 @@ $accordion-icon-active-color: $primary-text-emphasis !default; $accordion-icon-transition: transform .2s ease-in-out !default; $accordion-icon-transform: rotate(-180deg) !default; -$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-color}' stroke-linecap='round' stroke-linejoin='round'><path d='M2 5L8 11L14 5'/></svg>") !default; -$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-active-color}' stroke-linecap='round' stroke-linejoin='round'><path d='M2 5L8 11L14 5'/></svg>") !default; +$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-color}' stroke-linecap='round' stroke-linejoin='round'><path d='m2 5 6 6 6-6'/></svg>") !default; +$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-active-color}' stroke-linecap='round' stroke-linejoin='round'><path d='m2 5 6 6 6-6'/></svg>") !default; // scss-docs-end accordion-variables // Tooltips @@ -1507,7 +1507,7 @@ $modal-dialog-margin-y-sm-up: 1.75rem !default; $modal-title-line-height: $line-height-base !default; -$modal-content-color: null !default; +$modal-content-color: var(--#{$prefix}body-color) !default; $modal-content-bg: var(--#{$prefix}body-bg) !default; $modal-content-border-color: var(--#{$prefix}border-color-translucent) !default; $modal-content-border-width: var(--#{$prefix}border-width) !default; @@ -1652,6 +1652,7 @@ $carousel-control-width: 15% !default; $carousel-control-opacity: .5 !default; $carousel-control-hover-opacity: .9 !default; $carousel-control-transition: opacity .15s ease !default; +$carousel-control-icon-filter: null !default; $carousel-indicator-width: 30px !default; $carousel-indicator-height: 3px !default; @@ -1669,17 +1670,17 @@ $carousel-caption-spacer: 1.25rem !default; $carousel-control-icon-width: 2rem !default; -$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/></svg>") !default; -$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/></svg>") !default; +$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/></svg>") !default; +$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$carousel-control-color}'><path d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/></svg>") !default; $carousel-transition-duration: .6s !default; $carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`) // scss-docs-end carousel-variables // scss-docs-start carousel-dark-variables -$carousel-dark-indicator-active-bg: $black !default; -$carousel-dark-caption-color: $black !default; -$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; +$carousel-dark-indicator-active-bg: $black !default; // Deprecated in v5.3.4 +$carousel-dark-caption-color: $black !default; // Deprecated in v5.3.4 +$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; // Deprecated in v5.3.4 // scss-docs-end carousel-dark-variables @@ -1706,13 +1707,14 @@ $btn-close-height: $btn-close-width !default; $btn-close-padding-x: .25em !default; $btn-close-padding-y: $btn-close-padding-x !default; $btn-close-color: $black !default; -$btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>") !default; +$btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$btn-close-color}'><path d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/></svg>") !default; $btn-close-focus-shadow: $focus-ring-box-shadow !default; $btn-close-opacity: .5 !default; $btn-close-hover-opacity: .75 !default; $btn-close-focus-opacity: 1 !default; $btn-close-disabled-opacity: .25 !default; -$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; +$btn-close-filter: null !default; +$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; // Deprecated in v5.3.4 // scss-docs-end close-variables diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss b/extensions/pagetop-bootsier/static/bootstrap/bootstrap-grid.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-grid.scss rename to extensions/pagetop-bootsier/static/bootstrap/bootstrap-grid.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss b/extensions/pagetop-bootsier/static/bootstrap/bootstrap-reboot.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-reboot.scss rename to extensions/pagetop-bootsier/static/bootstrap/bootstrap-reboot.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss b/extensions/pagetop-bootsier/static/bootstrap/bootstrap-utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap-utilities.scss rename to extensions/pagetop-bootsier/static/bootstrap/bootstrap-utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss b/extensions/pagetop-bootsier/static/bootstrap/bootstrap.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/bootstrap.scss rename to extensions/pagetop-bootsier/static/bootstrap/bootstrap.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_floating-labels.scss similarity index 78% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_floating-labels.scss index 2cf04704..38df1155 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_floating-labels.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/forms/_floating-labels.scss @@ -14,9 +14,11 @@ top: 0; left: 0; z-index: 2; + max-width: 100%; height: 100%; // allow textareas padding: $form-floating-padding-y $form-floating-padding-x; overflow: hidden; + color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); text-align: start; text-overflow: ellipsis; white-space: nowrap; @@ -49,6 +51,7 @@ > .form-select { padding-top: $form-floating-input-padding-t; padding-bottom: $form-floating-input-padding-b; + padding-left: $form-floating-padding-x; } > .form-control:focus, @@ -56,27 +59,30 @@ > .form-control-plaintext, > .form-select { ~ label { - color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); transform: $form-floating-label-transform; - - &::after { - position: absolute; - inset: $form-floating-padding-y ($form-floating-padding-x * .5); - z-index: -1; - height: $form-floating-label-height; - content: ""; - background-color: $input-bg; - @include border-radius($input-border-radius); - } } } // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped > .form-control:-webkit-autofill { ~ label { - color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity}); transform: $form-floating-label-transform; } } + > textarea:focus, + > textarea:not(:placeholder-shown) { + ~ label::after { + position: absolute; + inset: $form-floating-padding-y ($form-floating-padding-x * .5); + z-index: -1; + height: $form-floating-label-height; + content: ""; + background-color: $input-bg; + @include border-radius($input-border-radius); + } + } + > textarea:disabled ~ label::after { + background-color: $input-disabled-bg; + } > .form-control-plaintext { ~ label { @@ -87,9 +93,5 @@ > :disabled ~ label, > .form-control:disabled ~ label { // Required for `.form-control`s because of specificity color: $form-floating-label-disabled-color; - - &::after { - background-color: $input-disabled-bg; - } } } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_form-check.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-check.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_form-check.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_form-control.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-control.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_form-control.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_form-range.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-range.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_form-range.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_form-select.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-select.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_form-select.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_form-text.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_form-text.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_form-text.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_input-group.scss similarity index 98% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_input-group.scss index 58e4d409..8078ebb1 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_input-group.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/forms/_input-group.scss @@ -121,7 +121,7 @@ } > :not(:first-child):not(.dropdown-menu)#{$validation-messages} { - margin-left: calc(#{$input-border-width} * -1); // stylelint-disable-line function-disallowed-list + margin-left: calc(-1 * #{$input-border-width}); // stylelint-disable-line function-disallowed-list @include border-start-radius(0); } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_labels.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_labels.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_labels.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss b/extensions/pagetop-bootsier/static/bootstrap/forms/_validation.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/forms/_validation.scss rename to extensions/pagetop-bootsier/static/bootstrap/forms/_validation.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_clearfix.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_clearfix.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_color-bg.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_color-bg.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_color-bg.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_colored-links.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_colored-links.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_colored-links.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_focus-ring.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_focus-ring.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_focus-ring.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_icon-link.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_icon-link.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_icon-link.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_position.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_position.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_position.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_ratio.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_ratio.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_ratio.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_stacks.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stacks.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_stacks.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_stretched-link.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_stretched-link.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_stretched-link.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_text-truncation.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_text-truncation.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_text-truncation.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_visually-hidden.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_visually-hidden.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss b/extensions/pagetop-bootsier/static/bootstrap/helpers/_vr.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/helpers/_vr.scss rename to extensions/pagetop-bootsier/static/bootstrap/helpers/_vr.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_alert.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_alert.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_backdrop.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_backdrop.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_backdrop.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_banner.scss similarity index 52% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_banner.scss index 20c2fd12..dd8a5103 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_banner.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/mixins/_banner.scss @@ -1,7 +1,7 @@ @mixin bsBanner($file) { /*! - * Bootstrap #{$file} v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors + * Bootstrap #{$file} v5.3.8 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ } diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_border-radius.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_border-radius.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_border-radius.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss new file mode 100644 index 00000000..0bb6bf7e --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss @@ -0,0 +1,24 @@ +@mixin box-shadow($shadow...) { + @if $enable-shadows { + $result: (); + $has-single-value: false; + $single-value: null; + + @each $value in $shadow { + @if $value != null { + @if $value == none or $value == initial or $value == inherit or $value == unset { + $has-single-value: true; + $single-value: $value; + } @else { + $result: append($result, $value, "comma"); + } + } + } + + @if $has-single-value { + box-shadow: $single-value; + } @else if (length($result) > 0) { + box-shadow: $result; + } + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_breakpoints.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_breakpoints.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_breakpoints.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_buttons.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_buttons.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_caret.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_caret.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_caret.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_clearfix.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_clearfix.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_color-mode.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-mode.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_color-mode.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_color-scheme.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_color-scheme.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_color-scheme.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_container.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_container.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_container.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_deprecate.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_deprecate.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_deprecate.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_forms.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_forms.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_gradients.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_gradients.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_gradients.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_grid.scss similarity index 98% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_grid.scss index ea307399..db77e07f 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_grid.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/mixins/_grid.scss @@ -72,7 +72,7 @@ @include media-breakpoint-up($breakpoint, $breakpoints) { // Provide basic `.col-{bp}` classes for equal-width flexbox columns .col#{$infix} { - flex: 1 0 0%; // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0; } .row-cols#{$infix}-auto > * { diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_image.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_image.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_image.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_list-group.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_list-group.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_list-group.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_lists.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_lists.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_lists.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_pagination.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_pagination.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_pagination.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_reset-text.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_reset-text.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_reset-text.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_resize.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_resize.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_resize.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_table-variants.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_table-variants.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_table-variants.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_text-truncate.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_text-truncate.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_text-truncate.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_transition.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_transition.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_transition.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_utilities.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss b/extensions/pagetop-bootsier/static/bootstrap/mixins/_visually-hidden.scss similarity index 87% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/bootstrap/mixins/_visually-hidden.scss index 082aeec9..9dd0ad33 100644 --- a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/mixins/_visually-hidden.scss +++ b/extensions/pagetop-bootsier/static/bootstrap/mixins/_visually-hidden.scss @@ -19,12 +19,17 @@ &:not(caption) { position: absolute !important; } + + // Fix to prevent overflowing children to become focusable + * { + overflow: hidden !important; + } } // Use to only display content when it's focused, or one of its child elements is focused // (i.e. when focus is within the element/container that the class was applied to) // -// Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// Useful for "Skip to main content" links; see https://www.w3.org/WAI/WCAG22/Techniques/general/G1.html @mixin visually-hidden-focusable() { &:not(:focus):not(:focus-within) { diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js b/extensions/pagetop-bootsier/static/bootstrap/tests/jasmine.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/jasmine.js rename to extensions/pagetop-bootsier/static/bootstrap/tests/jasmine.js diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_auto-import-of-variables-dark.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_auto-import-of-variables-dark.test.scss rename to extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_auto-import-of-variables-dark.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss new file mode 100644 index 00000000..f5a07484 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss @@ -0,0 +1,188 @@ +@import "../../functions"; +@import "../../variables"; +@import "../../mixins"; + +// Store original value +$original-enable-shadows: $enable-shadows; + +// Enable shadows for all tests +$enable-shadows: true !global; + +@include describe("box-shadow mixin") { + @include it("handles single none value") { + @include assert() { + @include output() { + .test { + @include box-shadow(none); + } + } + + @include expect() { + .test { + box-shadow: none; + } + } + } + } + + @include it("handles multiple none values by consolidating them") { + @include assert() { + @include output() { + .test { + @include box-shadow(none, none, none); + } + } + + @include expect() { + .test { + box-shadow: none; + } + } + } + } + + @include it("handles other single-value keywords (initial, inherit, unset)") { + @include assert() { + @include output() { + .test-initial { + @include box-shadow(initial); + } + .test-inherit { + @include box-shadow(inherit); + } + .test-unset { + @include box-shadow(unset); + } + } + + @include expect() { + .test-initial { + box-shadow: initial; + } + .test-inherit { + box-shadow: inherit; + } + .test-unset { + box-shadow: unset; + } + } + } + } + + @include it("handles multiple single-value keywords by using the last one") { + @include assert() { + @include output() { + .test { + @include box-shadow(initial, inherit, unset); + } + } + + @include expect() { + .test { + box-shadow: unset; + } + } + } + } + + @include it("handles regular box-shadow values") { + @include assert() { + @include output() { + .test { + @include box-shadow(0 0 10px rgba(0, 0, 0, .5)); + } + } + + @include expect() { + .test { + box-shadow: 0 0 10px rgba(0, 0, 0, .5); + } + } + } + } + + @include it("handles multiple regular box-shadow values") { + @include assert() { + @include output() { + .test { + @include box-shadow(0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3)); + } + } + + @include expect() { + .test { + box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3); + } + } + } + } + + @include it("handles null values by ignoring them") { + @include assert() { + @include output() { + .test { + @include box-shadow(null, 0 0 10px rgba(0, 0, 0, .5), null); + } + } + + @include expect() { + .test { + box-shadow: 0 0 10px rgba(0, 0, 0, .5); + } + } + } + } + + @include it("handles mixed values with keywords and regular shadows") { + @include assert() { + @include output() { + .test { + @include box-shadow(none, 0 0 10px rgba(0, 0, 0, .5)); + } + } + + @include expect() { + .test { + box-shadow: none; + } + } + } + } + + @include it("handles empty input") { + @include assert() { + @include output() { + .test { + @include box-shadow(); + } + } + + @include expect() { + .test { // stylelint-disable-line block-no-empty + } + } + } + } + + @include it("respects $enable-shadows variable") { + $enable-shadows: false !global; + + @include assert() { + @include output() { + .test { + @include box-shadow(0 0 10px rgba(0, 0, 0, .5)); + } + } + + @include expect() { + .test { // stylelint-disable-line block-no-empty + } + } + } + + $enable-shadows: true !global; + } +} + +// Restore original value +$enable-shadows: $original-enable-shadows !global; diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss new file mode 100644 index 00000000..5b94ac57 --- /dev/null +++ b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss @@ -0,0 +1,139 @@ +@import "../../functions"; +@import "../../variables"; +@import "../../variables-dark"; +@import "../../maps"; +@import "../../mixins"; + +@include describe("color-contrast function") { + @include it("should return a color when contrast ratio equals minimum requirement (WCAG 2.1 compliance)") { + // Test case: Background color that produces contrast ratio close to 4.5:1 + // This tests the WCAG 2.1 requirement that contrast should be "at least 4.5:1" (>= 4.5) + // rather than "strictly greater than 4.5:1" (> 4.5) + + // #777777 produces 4.4776:1 contrast ratio with white text + // Since this is below the 4.5:1 threshold, it should return the highest available contrast color + $test-background: #777; + $result: color-contrast($test-background); + + @include assert-equal($result, $black, "Should return black (highest available contrast) for background with 4.4776:1 contrast ratio (below threshold)"); + } + + @include it("should return a color when contrast ratio is above minimum requirement") { + // Test case: Background color that produces contrast ratio above 4.5:1 + // #767676 produces 4.5415:1 contrast ratio with white text + $test-background: #767676; + $result: color-contrast($test-background); + + @include assert-equal($result, $white, "Should return white for background with 4.5415:1 contrast ratio (above threshold)"); + } + + @include it("should return a color when contrast ratio is below minimum requirement") { + // Test case: Background color that produces contrast ratio below 4.5:1 + // #787878 produces 4.4155:1 contrast ratio with white text + $test-background: #787878; + $result: color-contrast($test-background); + + // Should return the color with the highest available contrast ratio + @include assert-equal($result, $black, "Should return black (highest available contrast) for background with 4.4155:1 contrast ratio (below threshold)"); + } + + @include it("should handle edge case with very light background") { + // Test case: Very light background that should return dark text + $test-background: #f8f9fa; // Very light gray + $result: color-contrast($test-background); + + @include assert-equal($result, $color-contrast-dark, "Should return dark text for very light background"); + } + + @include it("should handle edge case with very dark background") { + // Test case: Very dark background that should return light text + $test-background: #212529; // Very dark gray + $result: color-contrast($test-background); + + @include assert-equal($result, $color-contrast-light, "Should return light text for very dark background"); + } + + @include it("should work with custom minimum contrast ratio") { + // Test case: Using a custom minimum contrast ratio + $test-background: #666; + $result: color-contrast($test-background, $color-contrast-dark, $color-contrast-light, 3); + + @include assert-equal($result, $white, "Should return white when using custom minimum contrast ratio of 3.0"); + } + + @include it("should test contrast ratio calculation accuracy") { + // Test case: Verify that contrast-ratio function works correctly + $background: #767676; + $foreground: $white; + $ratio: contrast-ratio($background, $foreground); + // Bootstrap's implementation calculates this as ~4.5415, not exactly 4.5, due to its luminance math. + // We use 4.54 as the threshold for this test to match the actual implementation. + @include assert-true($ratio >= 4.54 and $ratio <= 4.55, "Contrast ratio should be approximately 4.54:1 (Bootstrap's math)"); + } + + @include it("should test luminance calculation") { + // Test case: Verify luminance function works correctly + $white-luminance: luminance($white); + $black-luminance: luminance($black); + + @include assert-equal($white-luminance, 1, "White should have luminance of 1"); + @include assert-equal($black-luminance, 0, "Black should have luminance of 0"); + } + + @include it("should handle rgba colors correctly") { + // Test case: Test with rgba colors + $test-background: rgba(118, 118, 118, 1); // Same as #767676 + $result: color-contrast($test-background); + + @include assert-equal($result, $white, "Should handle rgba colors correctly"); + } + + @include it("should test the WCAG 2.1 boundary condition with color below threshold") { + // Test case: Background color that produces contrast ratio below 4.5:1 + // #787878 produces 4.4155:1 contrast ratio with white + $test-background: #787878; // Produces 4.4155:1 contrast ratio + $contrast-ratio: contrast-ratio($test-background, $white); + + // Verify the contrast ratio is below 4.5:1 + @include assert-true($contrast-ratio < 4.5, "Contrast ratio should be below 4.5:1 threshold"); + + // The color-contrast function should return the color with highest available contrast + $result: color-contrast($test-background); + @include assert-equal($result, $black, "color-contrast should return black (highest available contrast) for below-threshold ratio"); + } + + @include it("should test the WCAG 2.1 boundary condition with color at threshold") { + // Test case: Background color that produces contrast ratio close to 4.5:1 + // #777777 produces 4.4776:1 contrast ratio with white + $test-background: #777; // Produces 4.4776:1 contrast ratio + $contrast-ratio: contrast-ratio($test-background, $white); + + // Verify the contrast ratio is below 4.5:1 threshold + @include assert-true($contrast-ratio < 4.5, "Contrast ratio is below threshold, function should handle gracefully"); + } + + @include it("should demonstrate the difference between > and >= operators") { + // Test case: Demonstrates the difference between > and >= operators + // Uses #767676 with a custom minimum contrast ratio that matches its actual ratio (4.5415) + // With > 4.5415: should return black (fallback to highest available) + // With >= 4.5415: should return white (meets threshold) + + $test-background: #767676; // Produces 4.5415:1 contrast ratio + $actual-ratio: contrast-ratio($test-background, $white); + + // Test with a custom minimum that matches the actual ratio + $result: color-contrast($test-background, $color-contrast-dark, $color-contrast-light, $actual-ratio); + + // Should return white when using >= implementation + @include assert-equal($result, $white, "color-contrast should return white when using exact ratio as threshold (>= implementation)"); + } + + @include it("should test additional working colors above threshold") { + // Test case: Background color that produces contrast ratio well above 4.5:1 + // #757575 produces 4.6047:1 contrast ratio with white text + $test-background: #757575; // Produces 4.6047:1 contrast ratio + $result: color-contrast($test-background); + + @include assert-equal($result, $white, "Should return white for background with 4.6047:1 contrast ratio (well above threshold)"); + } +} diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-modes.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_color-modes.test.scss rename to extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-modes.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_media-query-color-mode-full.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_media-query-color-mode-full.test.scss rename to extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_media-query-color-mode-full.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_utilities.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/mixins/_utilities.test.scss rename to extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_utilities.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js b/extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/register.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/register.js rename to extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/register.js diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js b/extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/runner.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/sass-true/runner.js rename to extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/runner.js diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss b/extensions/pagetop-bootsier/static/bootstrap/tests/utilities/_api.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/tests/utilities/_api.test.scss rename to extensions/pagetop-bootsier/static/bootstrap/tests/utilities/_api.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss b/extensions/pagetop-bootsier/static/bootstrap/utilities/_api.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/utilities/_api.scss rename to extensions/pagetop-bootsier/static/bootstrap/utilities/_api.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss b/extensions/pagetop-bootsier/static/bootstrap/vendor/_rfs.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap-5.3.3/vendor/_rfs.scss rename to extensions/pagetop-bootsier/static/bootstrap/vendor/_rfs.scss diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.js b/extensions/pagetop-bootsier/static/js/bootstrap.js index e3a59e3a..e1f6e58f 100644 --- a/extensions/pagetop-bootsier/static/js/bootstrap.js +++ b/extensions/pagetop-bootsier/static/js/bootstrap.js @@ -1,6 +1,6 @@ /*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Bootstrap v5.3.8 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ (function (global, factory) { @@ -224,7 +224,7 @@ * @param {HTMLElement} element * @return void * - * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation */ const reflow = element => { element.offsetHeight; // eslint-disable-line no-unused-expressions @@ -269,7 +269,7 @@ }); }; const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { - return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue; + return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue; }; const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { if (!waitForTransition) { @@ -591,7 +591,7 @@ const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); for (const key of bsKeys) { let pureKey = key.replace(/^bs/, ''); - pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1); attributes[pureKey] = normalizeData(element.dataset[key]); } return attributes; @@ -666,7 +666,7 @@ * Constants */ - const VERSION = '5.3.3'; + const VERSION = '5.3.8'; /** * Class definition @@ -692,6 +692,8 @@ this[propertyName] = null; } } + + // Private _queueCallback(callback, element, isAnimated = true) { executeAfterTransition(callback, element, isAnimated); } @@ -1623,11 +1625,11 @@ this._element.style[dimension] = ''; this._queueCallback(complete, this._element, true); } + + // Private _isShown(element = this._element) { return element.classList.contains(CLASS_NAME_SHOW$7); } - - // Private _configAfterMerge(config) { config.toggle = Boolean(config.toggle); // Coerce string values config.parent = getElement(config.parent); @@ -1881,7 +1883,7 @@ } _createPopper() { if (typeof Popper__namespace === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)'); } let referenceElement = this._element; if (this._config.reference === 'parent') { @@ -1960,7 +1962,7 @@ } return { ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [defaultBsPopperConfig]) + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) }; } _selectMenuItem({ @@ -2982,7 +2984,6 @@ * * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 */ - // eslint-disable-next-line unicorn/better-regex const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; const allowedAttribute = (attribute, allowedAttributeList) => { const attributeName = attribute.nodeName.toLowerCase(); @@ -3147,7 +3148,7 @@ return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; } _resolvePossibleFunction(arg) { - return execute(arg, [this]); + return execute(arg, [undefined, this]); } _putElementInTemplate(element, templateElement) { if (this._config.html) { @@ -3246,7 +3247,7 @@ class Tooltip extends BaseComponent { constructor(element, config) { if (typeof Popper__namespace === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)'); } super(element, config); @@ -3292,7 +3293,6 @@ if (!this._isEnabled) { return; } - this._activeTrigger.click = !this._activeTrigger.click; if (this._isShown()) { this._leave(); return; @@ -3480,7 +3480,7 @@ return offset; } _resolvePossibleFunction(arg) { - return execute(arg, [this._element]); + return execute(arg, [this._element, this._element]); } _getPopperConfig(attachment) { const defaultBsPopperConfig = { @@ -3518,7 +3518,7 @@ }; return { ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [defaultBsPopperConfig]) + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) }; } _setListeners() { @@ -3527,6 +3527,7 @@ if (trigger === 'click') { EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK]); context.toggle(); }); } else if (trigger !== TRIGGER_MANUAL) { @@ -4392,7 +4393,6 @@ } // Private - _maybeScheduleHide() { if (!this._config.autohide) { return; diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.js.map index 98a87052..062cdd5e 100644 --- a/extensions/pagetop-bootsier/static/js/bootstrap.js.map +++ b/extensions/pagetop-bootsier/static/js/bootstrap.js.map @@ -1 +1 @@ -{"version":3,"file":"bootstrap.js","sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"names":["elementMap","Map","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","MAX_UID","MILLISECONDS_MULTIPLIER","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","undefined","Object","prototype","toString","call","toLowerCase","getUID","prefix","Math","floor","random","document","getElementById","getTransitionDurationFromElement","transitionDuration","transitionDelay","getComputedStyle","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","querySelector","isVisible","getClientRects","elementIsVisible","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","onDOMContentLoaded","callback","readyState","addEventListener","push","isRTL","dir","defineJQueryPlugin","plugin","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","durationPadding","emulatedDuration","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","bootstrapHandler","event","hydrateObj","delegateTarget","oneOff","EventHandler","off","type","apply","bootstrapDelegationHandler","domElements","querySelectorAll","domElement","findHandler","events","callable","delegationSelector","values","find","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","wrapFunction","relatedTarget","handlers","previousFunction","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","VERSION","BaseComponent","_element","_config","Data","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","close","closeEvent","_destroyElement","each","data","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","POINTER_TYPE_TOUCH","POINTER_TYPE_PEN","CLASS_NAME_POINTER_EVENT","SWIPE_THRESHOLD","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","touches","clientX","_eventIsPointerPenTouch","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","TOUCHEVENT_COMPAT_WAIT","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_SLIDE","CLASS_NAME_END","CLASS_NAME_START","CLASS_NAME_NEXT","CLASS_NAME_PREV","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","SELECTOR_ITEM_IMG","SELECTOR_INDICATORS","SELECTOR_DATA_SLIDE","SELECTOR_DATA_RIDE","KEY_TO_DIRECTION","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","endCallBack","clearTimeout","swipeConfig","_directionToOrder","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","slideEvent","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_COLLAPSED","CLASS_NAME_DEEPER_CHILDREN","CLASS_NAME_HORIZONTAL","WIDTH","HEIGHT","SELECTOR_ACTIVES","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","startEvent","activeInstance","dimension","_getDimension","style","complete","capitalizedDimension","scrollSize","getBoundingClientRect","selected","triggerArray","isOpen","ESCAPE_KEY","TAB_KEY","ARROW_UP_KEY","ARROW_DOWN_KEY","RIGHT_MOUSE_BUTTON","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","CLASS_NAME_DROPUP","CLASS_NAME_DROPEND","CLASS_NAME_DROPSTART","CLASS_NAME_DROPUP_CENTER","CLASS_NAME_DROPDOWN_CENTER","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","SELECTOR_NAVBAR","SELECTOR_NAVBAR_NAV","SELECTOR_VISIBLE_ITEMS","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","PLACEMENT_TOPCENTER","PLACEMENT_BOTTOMCENTER","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","showEvent","_createPopper","focus","_completeHide","destroy","update","hideEvent","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_FORWARD","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","manipulationCallBack","setProperty","_applyManipulationCallback","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","OPEN_SELECTOR","SELECTOR_DIALOG","SELECTOR_MODAL_BODY","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","CLASS_NAME_BACKDROP","scroll","Offcanvas","blur","completeCallback","position","ARIA_ATTRIBUTE_PATTERN","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","sanitizeHtml","unsafeHtml","allowList","sanitizeFunction","domParser","DOMParser","createdDocument","parseFromString","elementName","attributeList","allowedAttributes","innerHTML","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","DISALLOWED_ATTRIBUTES","CLASS_NAME_MODAL","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","TRIGGER_MANUAL","EVENT_INSERTED","EVENT_CLICK","EVENT_FOCUSOUT","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","shadowRoot","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","CLASS_NAME_DROPDOWN_ITEM","SELECTOR_DATA_SPY","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LIST_GROUP","SELECTOR_NAV_LINKS","SELECTOR_NAV_ITEMS","SELECTOR_LIST_ITEMS","SELECTOR_LINK_ITEMS","SELECTOR_DROPDOWN","SELECTOR_DROPDOWN_TOGGLE","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","HOME_KEY","END_KEY","CLASS_DROPDOWN","SELECTOR_DROPDOWN_MENU","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_TAB_PANEL","SELECTOR_OUTER","SELECTOR_INNER","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"bootstrap.js","sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.8'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n // Private\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n // Private\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [undefined, this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org/docs/v2/)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element, this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK])\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"names":["elementMap","Map","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","MAX_UID","MILLISECONDS_MULTIPLIER","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","undefined","Object","prototype","toString","call","toLowerCase","getUID","prefix","Math","floor","random","document","getElementById","getTransitionDurationFromElement","transitionDuration","transitionDelay","getComputedStyle","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","querySelector","isVisible","getClientRects","elementIsVisible","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","onDOMContentLoaded","callback","readyState","addEventListener","push","isRTL","dir","defineJQueryPlugin","plugin","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","durationPadding","emulatedDuration","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","bootstrapHandler","event","hydrateObj","delegateTarget","oneOff","EventHandler","off","type","apply","bootstrapDelegationHandler","domElements","querySelectorAll","domElement","findHandler","events","callable","delegationSelector","values","find","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","wrapFunction","relatedTarget","handlers","previousFunction","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","VERSION","BaseComponent","_element","_config","Data","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","close","closeEvent","_destroyElement","each","data","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","POINTER_TYPE_TOUCH","POINTER_TYPE_PEN","CLASS_NAME_POINTER_EVENT","SWIPE_THRESHOLD","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","touches","clientX","_eventIsPointerPenTouch","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","TOUCHEVENT_COMPAT_WAIT","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_SLIDE","CLASS_NAME_END","CLASS_NAME_START","CLASS_NAME_NEXT","CLASS_NAME_PREV","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","SELECTOR_ITEM_IMG","SELECTOR_INDICATORS","SELECTOR_DATA_SLIDE","SELECTOR_DATA_RIDE","KEY_TO_DIRECTION","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","endCallBack","clearTimeout","swipeConfig","_directionToOrder","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","slideEvent","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_COLLAPSED","CLASS_NAME_DEEPER_CHILDREN","CLASS_NAME_HORIZONTAL","WIDTH","HEIGHT","SELECTOR_ACTIVES","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","startEvent","activeInstance","dimension","_getDimension","style","complete","capitalizedDimension","scrollSize","getBoundingClientRect","selected","triggerArray","isOpen","ESCAPE_KEY","TAB_KEY","ARROW_UP_KEY","ARROW_DOWN_KEY","RIGHT_MOUSE_BUTTON","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","CLASS_NAME_DROPUP","CLASS_NAME_DROPEND","CLASS_NAME_DROPSTART","CLASS_NAME_DROPUP_CENTER","CLASS_NAME_DROPDOWN_CENTER","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","SELECTOR_NAVBAR","SELECTOR_NAVBAR_NAV","SELECTOR_VISIBLE_ITEMS","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","PLACEMENT_TOPCENTER","PLACEMENT_BOTTOMCENTER","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","showEvent","_createPopper","focus","_completeHide","destroy","update","hideEvent","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_FORWARD","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","manipulationCallBack","setProperty","_applyManipulationCallback","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","OPEN_SELECTOR","SELECTOR_DIALOG","SELECTOR_MODAL_BODY","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","CLASS_NAME_BACKDROP","scroll","Offcanvas","blur","completeCallback","position","ARIA_ATTRIBUTE_PATTERN","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","sanitizeHtml","unsafeHtml","allowList","sanitizeFunction","domParser","DOMParser","createdDocument","parseFromString","elementName","attributeList","allowedAttributes","innerHTML","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","DISALLOWED_ATTRIBUTES","CLASS_NAME_MODAL","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","TRIGGER_MANUAL","EVENT_INSERTED","EVENT_CLICK","EVENT_FOCUSOUT","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","shadowRoot","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","CLASS_NAME_DROPDOWN_ITEM","SELECTOR_DATA_SPY","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LIST_GROUP","SELECTOR_NAV_LINKS","SELECTOR_NAV_ITEMS","SELECTOR_LIST_ITEMS","SELECTOR_LINK_ITEMS","SELECTOR_DROPDOWN","SELECTOR_DROPDOWN_TOGGLE","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","HOME_KEY","END_KEY","CLASS_DROPDOWN","SELECTOR_DROPDOWN_MENU","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_TAB_PANEL","SELECTOR_OUTER","SELECTOR_INNER","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"mappings":"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"} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js b/extensions/pagetop-bootsier/static/js/bootstrap.min.js index d5dc5ea1..7f2bc627 100644 --- a/extensions/pagetop-bootsier/static/js/bootstrap.min.js +++ b/extensions/pagetop-bootsier/static/js/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Bootstrap v5.3.8 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t)for(const i in t)if("default"!==i){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}return e.default=t,Object.freeze(e)}const i=e(t),s=new Map,n={set(t,e,i){s.has(t)||s.set(t,new Map);const n=s.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>s.has(t)&&s.get(t).get(e)||null,remove(t,e){if(!s.has(t))return;const i=s.get(t);i.delete(e),0===i.size&&s.delete(t)}},o="transitionend",r=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),a=t=>{t.dispatchEvent(new Event(o))},l=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),c=t=>l(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(r(t)):null,h=t=>{if(!l(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},d=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),u=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?u(t.parentNode):null},_=()=>{},g=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,m=[],p=()=>"rtl"===document.documentElement.dir,b=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,s=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=s,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of m)t()})),m.push(e)):e()},v=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,y=(t,e,i=!0)=>{if(!i)return void v(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let n=!1;const r=({target:i})=>{i===e&&(n=!0,e.removeEventListener(o,r),v(t))};e.addEventListener(o,r),setTimeout((()=>{n||a(e)}),s)},w=(t,e,i,s)=>{const n=t.length;let o=t.indexOf(e);return-1===o?!i&&s?t[n-1]:t[0]:(o+=i?1:-1,s&&(o=(o+n)%n),t[Math.max(0,Math.min(o,n-1))])},A=/[^.]*(?=\..*)\.|.*/,E=/\..*/,C=/::\d+$/,T={};let k=1;const $={mouseenter:"mouseover",mouseleave:"mouseout"},S=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${k++}`||t.uidEvent||k++}function O(t){const e=L(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function I(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function D(t,e,i){const s="string"==typeof e,n=s?i:e||i;let o=M(t);return S.has(o)||(o=t),[s,n,o]}function N(t,e,i,s,n){if("string"!=typeof e||!t)return;let[o,r,a]=D(e,i,s);if(e in $){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=O(t),c=l[a]||(l[a]={}),h=I(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=L(r,e.replace(A,"")),u=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return F(n,{delegateTarget:r}),s.oneOff&&j.off(t,n.type,e,i),i.apply(r,[n])}}(t,i,r):function(t,e){return function i(s){return F(s,{delegateTarget:t}),i.oneOff&&j.off(t,s.type,e),e.apply(t,[s])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function P(t,e,i,s,n){const o=I(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}function x(t,e,i,s){const n=e[i]||{};for(const[o,r]of Object.entries(n))o.includes(s)&&P(t,e,i,r.callable,r.delegationSelector)}function M(t){return t=t.replace(E,""),$[t]||t}const j={on(t,e,i,s){N(t,e,i,s,!1)},one(t,e,i,s){N(t,e,i,s,!0)},off(t,e,i,s){if("string"!=typeof e||!t)return;const[n,o,r]=D(e,i,s),a=r!==e,l=O(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))x(t,l,i,e.slice(1));for(const[i,s]of Object.entries(c)){const n=i.replace(C,"");a&&!e.includes(n)||P(t,l,r,s.callable,s.delegationSelector)}}else{if(!Object.keys(c).length)return;P(t,l,r,o,n?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const s=f();let n=null,o=!0,r=!0,a=!1;e!==M(e)&&s&&(n=s.Event(e,i),s(t).trigger(n),o=!n.isPropagationStopped(),r=!n.isImmediatePropagationStopped(),a=n.isDefaultPrevented());const l=F(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&n&&n.preventDefault(),l}};function F(t,e={}){for(const[i,s]of Object.entries(e))try{t[i]=s}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>s})}return t}function z(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function H(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const B={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${H(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${H(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const s of i){let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=z(t.dataset[s])}return e},getDataAttribute:(t,e)=>z(t.getAttribute(`data-bs-${H(e)}`))};class q{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=l(e)?B.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...l(e)?B.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[s,n]of Object.entries(e)){const e=t[s],o=l(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(n).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${s}" provided type "${o}" but expected type "${n}".`)}var i}}class W extends q{constructor(t,e){super(),(t=c(t))&&(this._element=t,this._config=this._getConfig(e),n.set(this._element,this.constructor.DATA_KEY,this))}dispose(){n.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){y(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return n.get(c(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const R=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>r(t))).join(","):null},K={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let s=t.parentNode.closest(e);for(;s;)i.push(s),s=s.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!d(t)&&h(t)))},getSelectorFromElement(t){const e=R(t);return e&&K.findOne(e)?e:null},getElementFromSelector(t){const e=R(t);return e?K.findOne(e):null},getMultipleElementsFromSelector(t){const e=R(t);return e?K.find(e):[]}},V=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),d(this))return;const n=K.getElementFromSelector(this)||this.closest(`.${s}`);t.getOrCreateInstance(n)[e]()}))},Q=".bs.alert",X=`close${Q}`,Y=`closed${Q}`;class U extends W{static get NAME(){return"alert"}close(){if(j.trigger(this._element,X).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,Y),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=U.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}V(U,"close"),b(U);const G='[data-bs-toggle="button"]';class J extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=J.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}j.on(document,"click.bs.button.data-api",G,(t=>{t.preventDefault();const e=t.target.closest(G);J.getOrCreateInstance(e).toggle()})),b(J);const Z=".bs.swipe",tt=`touchstart${Z}`,et=`touchmove${Z}`,it=`touchend${Z}`,st=`pointerdown${Z}`,nt=`pointerup${Z}`,ot={endCallback:null,leftCallback:null,rightCallback:null},rt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class at extends q{constructor(t,e){super(),this._element=t,t&&at.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return ot}static get DefaultType(){return rt}static get NAME(){return"swipe"}dispose(){j.off(this._element,Z)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),v(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&v(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(j.on(this._element,st,(t=>this._start(t))),j.on(this._element,nt,(t=>this._end(t))),this._element.classList.add("pointer-event")):(j.on(this._element,tt,(t=>this._start(t))),j.on(this._element,et,(t=>this._move(t))),j.on(this._element,it,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const lt=".bs.carousel",ct=".data-api",ht="next",dt="prev",ut="left",_t="right",gt=`slide${lt}`,ft=`slid${lt}`,mt=`keydown${lt}`,pt=`mouseenter${lt}`,bt=`mouseleave${lt}`,vt=`dragstart${lt}`,yt=`load${lt}${ct}`,wt=`click${lt}${ct}`,At="carousel",Et="active",Ct=".active",Tt=".carousel-item",kt=Ct+Tt,$t={ArrowLeft:_t,ArrowRight:ut},St={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Lt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class Ot extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=K.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===At&&this.cycle()}static get Default(){return St}static get DefaultType(){return Lt}static get NAME(){return"carousel"}next(){this._slide(ht)}nextWhenVisible(){!document.hidden&&h(this._element)&&this.next()}prev(){this._slide(dt)}pause(){this._isSliding&&a(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?j.one(this._element,ft,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,ft,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const s=t>i?ht:dt;this._slide(s,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&j.on(this._element,mt,(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,pt,(()=>this.pause())),j.on(this._element,bt,(()=>this._maybeEnableCycle()))),this._config.touch&&at.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of K.find(".carousel-item img",this._element))j.on(t,vt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ut)),rightCallback:()=>this._slide(this._directionToOrder(_t)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new at(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=$t[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=K.findOne(Ct,this._indicatorsElement);e.classList.remove(Et),e.removeAttribute("aria-current");const i=K.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(Et),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),s=t===ht,n=e||w(this._getItems(),i,s,this._config.wrap);if(n===i)return;const o=this._getItemIndex(n),r=e=>j.trigger(this._element,e,{relatedTarget:n,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(gt).defaultPrevented)return;if(!i||!n)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=n;const l=s?"carousel-item-start":"carousel-item-end",c=s?"carousel-item-next":"carousel-item-prev";n.classList.add(c),g(n),i.classList.add(l),n.classList.add(l),this._queueCallback((()=>{n.classList.remove(l,c),n.classList.add(Et),i.classList.remove(Et,c,l),this._isSliding=!1,r(ft)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return K.findOne(kt,this._element)}_getItems(){return K.find(Tt,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ut?dt:ht:t===ut?ht:dt}_orderToDirection(t){return p()?t===dt?ut:_t:t===dt?_t:ut}static jQueryInterface(t){return this.each((function(){const e=Ot.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}j.on(document,wt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=K.getElementFromSelector(this);if(!e||!e.classList.contains(At))return;t.preventDefault();const i=Ot.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===B.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),j.on(window,yt,(()=>{const t=K.find('[data-bs-ride="carousel"]');for(const e of t)Ot.getOrCreateInstance(e)})),b(Ot);const It=".bs.collapse",Dt=`show${It}`,Nt=`shown${It}`,Pt=`hide${It}`,xt=`hidden${It}`,Mt=`click${It}.data-api`,jt="show",Ft="collapse",zt="collapsing",Ht=`:scope .${Ft} .${Ft}`,Bt='[data-bs-toggle="collapse"]',qt={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Rt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=K.find(Bt);for(const t of i){const e=K.getSelectorFromElement(t),i=K.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return qt}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Rt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(j.trigger(this._element,Dt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Ft),this._element.classList.add(zt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(zt),this._element.classList.add(Ft,jt),this._element.style[e]="",j.trigger(this._element,Nt)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,Pt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,g(this._element),this._element.classList.add(zt),this._element.classList.remove(Ft,jt);for(const t of this._triggerArray){const e=K.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(zt),this._element.classList.add(Ft),j.trigger(this._element,xt)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(jt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=c(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Bt);for(const e of t){const t=K.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=K.find(Ht,this._config.parent);return K.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Rt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,Mt,Bt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of K.getMultipleElementsFromSelector(this))Rt.getOrCreateInstance(t,{toggle:!1}).toggle()})),b(Rt);const Kt="dropdown",Vt=".bs.dropdown",Qt=".data-api",Xt="ArrowUp",Yt="ArrowDown",Ut=`hide${Vt}`,Gt=`hidden${Vt}`,Jt=`show${Vt}`,Zt=`shown${Vt}`,te=`click${Vt}${Qt}`,ee=`keydown${Vt}${Qt}`,ie=`keyup${Vt}${Qt}`,se="show",ne='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',oe=`${ne}.${se}`,re=".dropdown-menu",ae=p()?"top-end":"top-start",le=p()?"top-start":"top-end",ce=p()?"bottom-end":"bottom-start",he=p()?"bottom-start":"bottom-end",de=p()?"left-start":"right-start",ue=p()?"right-start":"left-start",_e={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},ge={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class fe extends W{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=K.next(this._element,re)[0]||K.prev(this._element,re)[0]||K.findOne(re,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return _e}static get DefaultType(){return ge}static get NAME(){return Kt}toggle(){return this._isShown()?this.hide():this.show()}show(){if(d(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!j.trigger(this._element,Jt,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))j.on(t,"mouseover",_);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(se),this._element.classList.add(se),j.trigger(this._element,Zt,t)}}hide(){if(d(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!j.trigger(this._element,Ut,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.off(t,"mouseover",_);this._popper&&this._popper.destroy(),this._menu.classList.remove(se),this._element.classList.remove(se),this._element.setAttribute("aria-expanded","false"),B.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,Gt,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!l(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Kt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===i)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let t=this._element;"parent"===this._config.reference?t=this._parent:l(this._config.reference)?t=c(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=i.createPopper(t,this._menu,e)}_isShown(){return this._menu.classList.contains(se)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return de;if(t.classList.contains("dropstart"))return ue;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?le:ae:e?he:ce}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(B.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...v(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=K.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>h(t)));i.length&&w(i,e,t===Yt,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=fe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=K.find(oe);for(const i of e){const e=fe.getInstance(i);if(!e||!1===e._config.autoClose)continue;const s=t.composedPath(),n=s.includes(e._menu);if(s.includes(e._element)||"inside"===e._config.autoClose&&!n||"outside"===e._config.autoClose&&n)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,s=[Xt,Yt].includes(t.key);if(!s&&!i)return;if(e&&!i)return;t.preventDefault();const n=this.matches(ne)?this:K.prev(this,ne)[0]||K.next(this,ne)[0]||K.findOne(ne,t.delegateTarget.parentNode),o=fe.getOrCreateInstance(n);if(s)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),n.focus())}}j.on(document,ee,ne,fe.dataApiKeydownHandler),j.on(document,ee,re,fe.dataApiKeydownHandler),j.on(document,te,fe.clearMenus),j.on(document,ie,fe.clearMenus),j.on(document,te,ne,(function(t){t.preventDefault(),fe.getOrCreateInstance(this).toggle()})),b(fe);const me="backdrop",pe="show",be=`mousedown.bs.${me}`,ve={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},ye={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class we extends q{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return ve}static get DefaultType(){return ye}static get NAME(){return me}show(t){if(!this._config.isVisible)return void v(t);this._append();const e=this._getElement();this._config.isAnimated&&g(e),e.classList.add(pe),this._emulateAnimation((()=>{v(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(pe),this._emulateAnimation((()=>{this.dispose(),v(t)}))):v(t)}dispose(){this._isAppended&&(j.off(this._element,be),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=c(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),j.on(t,be,(()=>{v(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){y(t,this._getElement(),this._config.isAnimated)}}const Ae=".bs.focustrap",Ee=`focusin${Ae}`,Ce=`keydown.tab${Ae}`,Te="backward",ke={autofocus:!0,trapElement:null},$e={autofocus:"boolean",trapElement:"element"};class Se extends q{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return ke}static get DefaultType(){return $e}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),j.off(document,Ae),j.on(document,Ee,(t=>this._handleFocusin(t))),j.on(document,Ce,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,Ae))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=K.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Te?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Te:"forward")}}const Le=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Oe=".sticky-top",Ie="padding-right",De="margin-right";class Ne{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Ie,(e=>e+t)),this._setElementAttributes(Le,Ie,(e=>e+t)),this._setElementAttributes(Oe,De,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Ie),this._resetElementAttributes(Le,Ie),this._resetElementAttributes(Oe,De)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const s=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+s)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(n))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&B.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=B.getDataAttribute(t,e);null!==i?(B.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(l(t))e(t);else for(const i of K.find(t,this._element))e(i)}}const Pe=".bs.modal",xe=`hide${Pe}`,Me=`hidePrevented${Pe}`,je=`hidden${Pe}`,Fe=`show${Pe}`,ze=`shown${Pe}`,He=`resize${Pe}`,Be=`click.dismiss${Pe}`,qe=`mousedown.dismiss${Pe}`,We=`keydown.dismiss${Pe}`,Re=`click${Pe}.data-api`,Ke="modal-open",Ve="show",Qe="modal-static",Xe={backdrop:!0,focus:!0,keyboard:!0},Ye={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ue extends W{constructor(t,e){super(t,e),this._dialog=K.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Ne,this._addEventListeners()}static get Default(){return Xe}static get DefaultType(){return Ye}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,Fe,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Ke),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(j.trigger(this._element,xe).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Ve),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){j.off(window,Pe),j.off(this._dialog,Pe),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new we({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Se({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=K.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),g(this._element),this._element.classList.add(Ve),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,ze,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){j.on(this._element,We,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),j.on(window,He,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),j.on(this._element,qe,(t=>{j.one(this._element,Be,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Ke),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,je)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,Me).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Qe)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Qe),this._queueCallback((()=>{this._element.classList.remove(Qe),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Ue.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,Re,'[data-bs-toggle="modal"]',(function(t){const e=K.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,Fe,(t=>{t.defaultPrevented||j.one(e,je,(()=>{h(this)&&this.focus()}))}));const i=K.findOne(".modal.show");i&&Ue.getInstance(i).hide(),Ue.getOrCreateInstance(e).toggle(this)})),V(Ue),b(Ue);const Ge=".bs.offcanvas",Je=".data-api",Ze=`load${Ge}${Je}`,ti="show",ei="showing",ii="hiding",si=".offcanvas.show",ni=`show${Ge}`,oi=`shown${Ge}`,ri=`hide${Ge}`,ai=`hidePrevented${Ge}`,li=`hidden${Ge}`,ci=`resize${Ge}`,hi=`click${Ge}${Je}`,di=`keydown.dismiss${Ge}`,ui={backdrop:!0,keyboard:!0,scroll:!1},_i={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class gi extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return ui}static get DefaultType(){return _i}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,ni,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Ne).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ei),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(ti),this._element.classList.remove(ei),j.trigger(this._element,oi,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,ri).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(ii),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(ti,ii),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Ne).reset(),j.trigger(this._element,li)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new we({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():j.trigger(this._element,ai)}:null})}_initializeFocusTrap(){return new Se({trapElement:this._element})}_addEventListeners(){j.on(this._element,di,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():j.trigger(this._element,ai))}))}static jQueryInterface(t){return this.each((function(){const e=gi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,hi,'[data-bs-toggle="offcanvas"]',(function(t){const e=K.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this))return;j.one(e,li,(()=>{h(this)&&this.focus()}));const i=K.findOne(si);i&&i!==e&&gi.getInstance(i).hide(),gi.getOrCreateInstance(e).toggle(this)})),j.on(window,Ze,(()=>{for(const t of K.find(si))gi.getOrCreateInstance(t).show()})),j.on(window,ci,(()=>{for(const t of K.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&gi.getOrCreateInstance(t).hide()})),V(gi),b(gi);const fi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},mi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),pi=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,bi=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!mi.has(i)||Boolean(pi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},vi={allowList:fi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},yi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},wi={entry:"(string|element|function|null)",selector:"(string|element)"};class Ai extends q{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return vi}static get DefaultType(){return yi}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},wi)}_setContent(t,e,i){const s=K.findOne(i,t);s&&((e=this._resolvePossibleFunction(e))?l(e)?this._putElementInTemplate(c(e),s):this._config.html?s.innerHTML=this._maybeSanitize(e):s.textContent=e:s.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const s=(new window.DOMParser).parseFromString(t,"text/html"),n=[].concat(...s.body.querySelectorAll("*"));for(const t of n){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const s=[].concat(...t.attributes),n=[].concat(e["*"]||[],e[i]||[]);for(const e of s)bi(e,n)||t.removeAttribute(e.nodeName)}return s.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return v(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Ei=new Set(["sanitize","allowList","sanitizeFn"]),Ci="fade",Ti="show",ki=".modal",$i="hide.bs.modal",Si="hover",Li="focus",Oi={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},Ii={allowList:fi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},Di={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class Ni extends W{constructor(t,e){if(void 0===i)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Ii}static get DefaultType(){return Di}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ki),$i,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.eventName("show")),e=(u(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:s}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(s.append(i),j.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(Ti),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.on(t,"mouseover",_);this._queueCallback((()=>{j.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!j.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(Ti),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))j.off(t,"mouseover",_);this._activeTrigger.click=!1,this._activeTrigger[Li]=!1,this._activeTrigger[Si]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(Ci,Ti),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(Ci),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Ai({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(Ci)}_isShown(){return this.tip&&this.tip.classList.contains(Ti)}_createPopper(t){const e=v(this._config.placement,[this,t,this._element]),s=Oi[e.toUpperCase()];return i.createPopper(this._element,t,this._getPopperConfig(s))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return v(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...v(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)j.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===Si?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===Si?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");j.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?Li:Si]=!0,e._enter()})),j.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?Li:Si]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ki),$i,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=B.getDataAttributes(this._element);for(const t of Object.keys(e))Ei.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:c(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}b(Ni);const Pi={...Ni.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},xi={...Ni.DefaultType,content:"(null|string|element|function)"};class Mi extends Ni{static get Default(){return Pi}static get DefaultType(){return xi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=Mi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}b(Mi);const ji=".bs.scrollspy",Fi=`activate${ji}`,zi=`click${ji}`,Hi=`load${ji}.data-api`,Bi="active",qi="[href]",Wi=".nav-link",Ri=`${Wi}, .nav-item > ${Wi}, .list-group-item`,Ki={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Vi={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Qi extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ki}static get DefaultType(){return Vi}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=c(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(j.off(this._config.target,zi),j.on(this._config.target,zi,qi,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,s=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:s,behavior:"smooth"});i.scrollTop=s}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},s=(this._rootElement||document.documentElement).scrollTop,n=s>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=s;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(n&&t){if(i(o),!s)return}else n||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=K.find(qi,this._config.target);for(const e of t){if(!e.hash||d(e))continue;const t=K.findOne(decodeURI(e.hash),this._element);h(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Bi),this._activateParents(t),j.trigger(this._element,Fi,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))K.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Bi);else for(const e of K.parents(t,".nav, .list-group"))for(const t of K.prev(e,Ri))t.classList.add(Bi)}_clearActiveClass(t){t.classList.remove(Bi);const e=K.find(`${qi}.${Bi}`,t);for(const t of e)t.classList.remove(Bi)}static jQueryInterface(t){return this.each((function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,Hi,(()=>{for(const t of K.find('[data-bs-spy="scroll"]'))Qi.getOrCreateInstance(t)})),b(Qi);const Xi=".bs.tab",Yi=`hide${Xi}`,Ui=`hidden${Xi}`,Gi=`show${Xi}`,Ji=`shown${Xi}`,Zi=`click${Xi}`,ts=`keydown${Xi}`,es=`load${Xi}`,is="ArrowLeft",ss="ArrowRight",ns="ArrowUp",os="ArrowDown",rs="Home",as="End",ls="active",cs="fade",hs="show",ds=".dropdown-toggle",us=`:not(${ds})`,_s='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',gs=`.nav-link${us}, .list-group-item${us}, [role="tab"]${us}, ${_s}`,fs=`.${ls}[data-bs-toggle="tab"], .${ls}[data-bs-toggle="pill"], .${ls}[data-bs-toggle="list"]`;class ms extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),j.on(this._element,ts,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?j.trigger(e,Yi,{relatedTarget:t}):null;j.trigger(t,Gi,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(ls),this._activate(K.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),j.trigger(t,Ji,{relatedTarget:e})):t.classList.add(hs)}),t,t.classList.contains(cs)))}_deactivate(t,e){t&&(t.classList.remove(ls),t.blur(),this._deactivate(K.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),j.trigger(t,Ui,{relatedTarget:e})):t.classList.remove(hs)}),t,t.classList.contains(cs)))}_keydown(t){if(![is,ss,ns,os,rs,as].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!d(t)));let i;if([rs,as].includes(t.key))i=e[t.key===rs?0:e.length-1];else{const s=[ss,os].includes(t.key);i=w(e,t.target,s,!0)}i&&(i.focus({preventScroll:!0}),ms.getOrCreateInstance(i).show())}_getChildren(){return K.find(gs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=K.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const s=(t,s)=>{const n=K.findOne(t,i);n&&n.classList.toggle(s,e)};s(ds,ls),s(".dropdown-menu",hs),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(ls)}_getInnerElement(t){return t.matches(gs)?t:K.findOne(gs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=ms.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,Zi,_s,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this)||ms.getOrCreateInstance(this).show()})),j.on(window,es,(()=>{for(const t of K.find(fs))ms.getOrCreateInstance(t)})),b(ms);const ps=".bs.toast",bs=`mouseover${ps}`,vs=`mouseout${ps}`,ys=`focusin${ps}`,ws=`focusout${ps}`,As=`hide${ps}`,Es=`hidden${ps}`,Cs=`show${ps}`,Ts=`shown${ps}`,ks="hide",$s="show",Ss="showing",Ls={animation:"boolean",autohide:"boolean",delay:"number"},Os={animation:!0,autohide:!0,delay:5e3};class Is extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Os}static get DefaultType(){return Ls}static get NAME(){return"toast"}show(){j.trigger(this._element,Cs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(ks),g(this._element),this._element.classList.add($s,Ss),this._queueCallback((()=>{this._element.classList.remove(Ss),j.trigger(this._element,Ts),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(j.trigger(this._element,As).defaultPrevented||(this._element.classList.add(Ss),this._queueCallback((()=>{this._element.classList.add(ks),this._element.classList.remove(Ss,$s),j.trigger(this._element,Es)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove($s),super.dispose()}isShown(){return this._element.classList.contains($s)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,bs,(t=>this._onInteraction(t,!0))),j.on(this._element,vs,(t=>this._onInteraction(t,!1))),j.on(this._element,ys,(t=>this._onInteraction(t,!0))),j.on(this._element,ws,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Is.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return V(Is),b(Is),{Alert:U,Button:J,Carousel:Ot,Collapse:Rt,Dropdown:fe,Modal:Ue,Offcanvas:gi,Popover:Mi,ScrollSpy:Qi,Tab:ms,Toast:Is,Tooltip:Ni}})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,function(t){"use strict";function e(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t)for(const i in t)if("default"!==i){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}return e.default=t,Object.freeze(e)}const i=e(t),s=new Map,n={set(t,e,i){s.has(t)||s.set(t,new Map);const n=s.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>s.has(t)&&s.get(t).get(e)||null,remove(t,e){if(!s.has(t))return;const i=s.get(t);i.delete(e),0===i.size&&s.delete(t)}},o="transitionend",r=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),a=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),l=t=>{t.dispatchEvent(new Event(o))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(r(t)):null,d=t=>{if(!c(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},u=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),_=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?_(t.parentNode):null},g=()=>{},f=t=>{t.offsetHeight},m=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],b=()=>"rtl"===document.documentElement.dir,v=t=>{var e;e=()=>{const e=m();if(e){const i=t.NAME,s=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=s,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},y=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,w=(t,e,i=!0)=>{if(!i)return void y(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let n=!1;const r=({target:i})=>{i===e&&(n=!0,e.removeEventListener(o,r),y(t))};e.addEventListener(o,r),setTimeout(()=>{n||l(e)},s)},A=(t,e,i,s)=>{const n=t.length;let o=t.indexOf(e);return-1===o?!i&&s?t[n-1]:t[0]:(o+=i?1:-1,s&&(o=(o+n)%n),t[Math.max(0,Math.min(o,n-1))])},E=/[^.]*(?=\..*)\.|.*/,C=/\..*/,T=/::\d+$/,k={};let $=1;const S={mouseenter:"mouseover",mouseleave:"mouseout"},L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${$++}`||t.uidEvent||$++}function I(t){const e=O(t);return t.uidEvent=e,k[e]=k[e]||{},k[e]}function D(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function N(t,e,i){const s="string"==typeof e,n=s?i:e||i;let o=j(t);return L.has(o)||(o=t),[s,n,o]}function P(t,e,i,s,n){if("string"!=typeof e||!t)return;let[o,r,a]=N(e,i,s);if(e in S){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=I(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=O(r,e.replace(E,"")),u=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return z(n,{delegateTarget:r}),s.oneOff&&F.off(t,n.type,e,i),i.apply(r,[n])}}(t,i,r):function(t,e){return function i(s){return z(s,{delegateTarget:t}),i.oneOff&&F.off(t,s.type,e),e.apply(t,[s])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function x(t,e,i,s,n){const o=D(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}function M(t,e,i,s){const n=e[i]||{};for(const[o,r]of Object.entries(n))o.includes(s)&&x(t,e,i,r.callable,r.delegationSelector)}function j(t){return t=t.replace(C,""),S[t]||t}const F={on(t,e,i,s){P(t,e,i,s,!1)},one(t,e,i,s){P(t,e,i,s,!0)},off(t,e,i,s){if("string"!=typeof e||!t)return;const[n,o,r]=N(e,i,s),a=r!==e,l=I(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))M(t,l,i,e.slice(1));for(const[i,s]of Object.entries(c)){const n=i.replace(T,"");a&&!e.includes(n)||x(t,l,r,s.callable,s.delegationSelector)}}else{if(!Object.keys(c).length)return;x(t,l,r,o,n?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const s=m();let n=null,o=!0,r=!0,a=!1;e!==j(e)&&s&&(n=s.Event(e,i),s(t).trigger(n),o=!n.isPropagationStopped(),r=!n.isImmediatePropagationStopped(),a=n.isDefaultPrevented());const l=z(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&n&&n.preventDefault(),l}};function z(t,e={}){for(const[i,s]of Object.entries(e))try{t[i]=s}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>s})}return t}function H(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function B(t){return t.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`)}const q={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${B(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${B(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter(t=>t.startsWith("bs")&&!t.startsWith("bsConfig"));for(const s of i){let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1),e[i]=H(t.dataset[s])}return e},getDataAttribute:(t,e)=>H(t.getAttribute(`data-bs-${B(e)}`))};class W{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=c(e)?q.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...c(e)?q.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[i,s]of Object.entries(e)){const e=t[i],n=c(e)?"element":a(e);if(!new RegExp(s).test(n))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${n}" but expected type "${s}".`)}}}class R extends W{constructor(t,e){super(),(t=h(t))&&(this._element=t,this._config=this._getConfig(e),n.set(this._element,this.constructor.DATA_KEY,this))}dispose(){n.remove(this._element,this.constructor.DATA_KEY),F.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){w(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return n.get(h(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.8"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const K=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map(t=>r(t)).join(","):null},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let s=t.parentNode.closest(e);for(;s;)i.push(s),s=s.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,t).filter(t=>!u(t)&&d(t))},getSelectorFromElement(t){const e=K(t);return e&&V.findOne(e)?e:null},getElementFromSelector(t){const e=K(t);return e?V.findOne(e):null},getMultipleElementsFromSelector(t){const e=K(t);return e?V.find(e):[]}},Q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;F.on(document,i,`[data-bs-dismiss="${s}"]`,function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),u(this))return;const n=V.getElementFromSelector(this)||this.closest(`.${s}`);t.getOrCreateInstance(n)[e]()})},X=".bs.alert",Y=`close${X}`,U=`closed${X}`;class G extends R{static get NAME(){return"alert"}close(){if(F.trigger(this._element,Y).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),F.trigger(this._element,U),this.dispose()}static jQueryInterface(t){return this.each(function(){const e=G.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}Q(G,"close"),v(G);const J='[data-bs-toggle="button"]';class Z extends R{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each(function(){const e=Z.getOrCreateInstance(this);"toggle"===t&&e[t]()})}}F.on(document,"click.bs.button.data-api",J,t=>{t.preventDefault();const e=t.target.closest(J);Z.getOrCreateInstance(e).toggle()}),v(Z);const tt=".bs.swipe",et=`touchstart${tt}`,it=`touchmove${tt}`,st=`touchend${tt}`,nt=`pointerdown${tt}`,ot=`pointerup${tt}`,rt={endCallback:null,leftCallback:null,rightCallback:null},at={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class lt extends W{constructor(t,e){super(),this._element=t,t&&lt.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return rt}static get DefaultType(){return at}static get NAME(){return"swipe"}dispose(){F.off(this._element,tt)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),y(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&y(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(F.on(this._element,nt,t=>this._start(t)),F.on(this._element,ot,t=>this._end(t)),this._element.classList.add("pointer-event")):(F.on(this._element,et,t=>this._start(t)),F.on(this._element,it,t=>this._move(t)),F.on(this._element,st,t=>this._end(t)))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ct=".bs.carousel",ht=".data-api",dt="ArrowLeft",ut="ArrowRight",_t="next",gt="prev",ft="left",mt="right",pt=`slide${ct}`,bt=`slid${ct}`,vt=`keydown${ct}`,yt=`mouseenter${ct}`,wt=`mouseleave${ct}`,At=`dragstart${ct}`,Et=`load${ct}${ht}`,Ct=`click${ct}${ht}`,Tt="carousel",kt="active",$t=".active",St=".carousel-item",Lt=$t+St,Ot={[dt]:mt,[ut]:ft},It={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Dt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class Nt extends R{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===Tt&&this.cycle()}static get Default(){return It}static get DefaultType(){return Dt}static get NAME(){return"carousel"}next(){this._slide(_t)}nextWhenVisible(){!document.hidden&&d(this._element)&&this.next()}prev(){this._slide(gt)}pause(){this._isSliding&&l(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?F.one(this._element,bt,()=>this.cycle()):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void F.one(this._element,bt,()=>this.to(t));const i=this._getItemIndex(this._getActive());if(i===t)return;const s=t>i?_t:gt;this._slide(s,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&F.on(this._element,vt,t=>this._keydown(t)),"hover"===this._config.pause&&(F.on(this._element,yt,()=>this.pause()),F.on(this._element,wt,()=>this._maybeEnableCycle())),this._config.touch&&lt.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of V.find(".carousel-item img",this._element))F.on(t,At,t=>t.preventDefault());const t={leftCallback:()=>this._slide(this._directionToOrder(ft)),rightCallback:()=>this._slide(this._directionToOrder(mt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new lt(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Ot[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=V.findOne($t,this._indicatorsElement);e.classList.remove(kt),e.removeAttribute("aria-current");const i=V.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(kt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),s=t===_t,n=e||A(this._getItems(),i,s,this._config.wrap);if(n===i)return;const o=this._getItemIndex(n),r=e=>F.trigger(this._element,e,{relatedTarget:n,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(pt).defaultPrevented)return;if(!i||!n)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=n;const l=s?"carousel-item-start":"carousel-item-end",c=s?"carousel-item-next":"carousel-item-prev";n.classList.add(c),f(n),i.classList.add(l),n.classList.add(l),this._queueCallback(()=>{n.classList.remove(l,c),n.classList.add(kt),i.classList.remove(kt,c,l),this._isSliding=!1,r(bt)},i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return V.findOne(Lt,this._element)}_getItems(){return V.find(St,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return b()?t===ft?gt:_t:t===ft?_t:gt}_orderToDirection(t){return b()?t===gt?ft:mt:t===gt?mt:ft}static jQueryInterface(t){return this.each(function(){const e=Nt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)})}}F.on(document,Ct,"[data-bs-slide], [data-bs-slide-to]",function(t){const e=V.getElementFromSelector(this);if(!e||!e.classList.contains(Tt))return;t.preventDefault();const i=Nt.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===q.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())}),F.on(window,Et,()=>{const t=V.find('[data-bs-ride="carousel"]');for(const e of t)Nt.getOrCreateInstance(e)}),v(Nt);const Pt=".bs.collapse",xt=`show${Pt}`,Mt=`shown${Pt}`,jt=`hide${Pt}`,Ft=`hidden${Pt}`,zt=`click${Pt}.data-api`,Ht="show",Bt="collapse",qt="collapsing",Wt=`:scope .${Bt} .${Bt}`,Rt='[data-bs-toggle="collapse"]',Kt={parent:null,toggle:!0},Vt={parent:"(null|element)",toggle:"boolean"};class Qt extends R{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=V.find(Rt);for(const t of i){const e=V.getSelectorFromElement(t),i=V.find(e).filter(t=>t===this._element);null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Kt}static get DefaultType(){return Vt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter(t=>t!==this._element).map(t=>Qt.getOrCreateInstance(t,{toggle:!1}))),t.length&&t[0]._isTransitioning)return;if(F.trigger(this._element,xt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Bt),this._element.classList.add(qt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(qt),this._element.classList.add(Bt,Ht),this._element.style[e]="",F.trigger(this._element,Mt)},this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(F.trigger(this._element,jt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,f(this._element),this._element.classList.add(qt),this._element.classList.remove(Bt,Ht);for(const t of this._triggerArray){const e=V.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(qt),this._element.classList.add(Bt),F.trigger(this._element,Ft)},this._element,!0)}_isShown(t=this._element){return t.classList.contains(Ht)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=h(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Rt);for(const e of t){const t=V.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=V.find(Wt,this._config.parent);return V.find(t,this._config.parent).filter(t=>!e.includes(t))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each(function(){const i=Qt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}})}}F.on(document,zt,Rt,function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of V.getMultipleElementsFromSelector(this))Qt.getOrCreateInstance(t,{toggle:!1}).toggle()}),v(Qt);const Xt="dropdown",Yt=".bs.dropdown",Ut=".data-api",Gt="ArrowUp",Jt="ArrowDown",Zt=`hide${Yt}`,te=`hidden${Yt}`,ee=`show${Yt}`,ie=`shown${Yt}`,se=`click${Yt}${Ut}`,ne=`keydown${Yt}${Ut}`,oe=`keyup${Yt}${Ut}`,re="show",ae='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',le=`${ae}.${re}`,ce=".dropdown-menu",he=b()?"top-end":"top-start",de=b()?"top-start":"top-end",ue=b()?"bottom-end":"bottom-start",_e=b()?"bottom-start":"bottom-end",ge=b()?"left-start":"right-start",fe=b()?"right-start":"left-start",me={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},pe={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class be extends R{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=V.next(this._element,ce)[0]||V.prev(this._element,ce)[0]||V.findOne(ce,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return me}static get DefaultType(){return pe}static get NAME(){return Xt}toggle(){return this._isShown()?this.hide():this.show()}show(){if(u(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!F.trigger(this._element,ee,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))F.on(t,"mouseover",g);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(re),this._element.classList.add(re),F.trigger(this._element,ie,t)}}hide(){if(u(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!F.trigger(this._element,Zt,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.off(t,"mouseover",g);this._popper&&this._popper.destroy(),this._menu.classList.remove(re),this._element.classList.remove(re),this._element.setAttribute("aria-expanded","false"),q.removeDataAttribute(this._menu,"popper"),F.trigger(this._element,te,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!c(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Xt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===i)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let t=this._element;"parent"===this._config.reference?t=this._parent:c(this._config.reference)?t=h(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=i.createPopper(t,this._menu,e)}_isShown(){return this._menu.classList.contains(re)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return ge;if(t.classList.contains("dropstart"))return fe;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?de:he:e?_e:ue}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(q.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...y(this._config.popperConfig,[void 0,t])}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(t=>d(t));i.length&&A(i,e,t===Jt,!i.includes(e)).focus()}static jQueryInterface(t){return this.each(function(){const e=be.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=V.find(le);for(const i of e){const e=be.getInstance(i);if(!e||!1===e._config.autoClose)continue;const s=t.composedPath(),n=s.includes(e._menu);if(s.includes(e._element)||"inside"===e._config.autoClose&&!n||"outside"===e._config.autoClose&&n)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,s=[Gt,Jt].includes(t.key);if(!s&&!i)return;if(e&&!i)return;t.preventDefault();const n=this.matches(ae)?this:V.prev(this,ae)[0]||V.next(this,ae)[0]||V.findOne(ae,t.delegateTarget.parentNode),o=be.getOrCreateInstance(n);if(s)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),n.focus())}}F.on(document,ne,ae,be.dataApiKeydownHandler),F.on(document,ne,ce,be.dataApiKeydownHandler),F.on(document,se,be.clearMenus),F.on(document,oe,be.clearMenus),F.on(document,se,ae,function(t){t.preventDefault(),be.getOrCreateInstance(this).toggle()}),v(be);const ve="backdrop",ye="show",we=`mousedown.bs.${ve}`,Ae={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ee={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ce extends W{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Ae}static get DefaultType(){return Ee}static get NAME(){return ve}show(t){if(!this._config.isVisible)return void y(t);this._append();const e=this._getElement();this._config.isAnimated&&f(e),e.classList.add(ye),this._emulateAnimation(()=>{y(t)})}hide(t){this._config.isVisible?(this._getElement().classList.remove(ye),this._emulateAnimation(()=>{this.dispose(),y(t)})):y(t)}dispose(){this._isAppended&&(F.off(this._element,we),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=h(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),F.on(t,we,()=>{y(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(t){w(t,this._getElement(),this._config.isAnimated)}}const Te=".bs.focustrap",ke=`focusin${Te}`,$e=`keydown.tab${Te}`,Se="backward",Le={autofocus:!0,trapElement:null},Oe={autofocus:"boolean",trapElement:"element"};class Ie extends W{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Le}static get DefaultType(){return Oe}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),F.off(document,Te),F.on(document,ke,t=>this._handleFocusin(t)),F.on(document,$e,t=>this._handleKeydown(t)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,F.off(document,Te))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=V.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Se?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Se:"forward")}}const De=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ne=".sticky-top",Pe="padding-right",xe="margin-right";class Me{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Pe,e=>e+t),this._setElementAttributes(De,Pe,e=>e+t),this._setElementAttributes(Ne,xe,e=>e-t)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Pe),this._resetElementAttributes(De,Pe),this._resetElementAttributes(Ne,xe)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const s=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+s)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(n))}px`)})}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&q.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=q.getDataAttribute(t,e);null!==i?(q.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)})}_applyManipulationCallback(t,e){if(c(t))e(t);else for(const i of V.find(t,this._element))e(i)}}const je=".bs.modal",Fe=`hide${je}`,ze=`hidePrevented${je}`,He=`hidden${je}`,Be=`show${je}`,qe=`shown${je}`,We=`resize${je}`,Re=`click.dismiss${je}`,Ke=`mousedown.dismiss${je}`,Ve=`keydown.dismiss${je}`,Qe=`click${je}.data-api`,Xe="modal-open",Ye="show",Ue="modal-static",Ge={backdrop:!0,focus:!0,keyboard:!0},Je={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ze extends R{constructor(t,e){super(t,e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Me,this._addEventListeners()}static get Default(){return Ge}static get DefaultType(){return Je}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||F.trigger(this._element,Be,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Xe),this._adjustDialog(),this._backdrop.show(()=>this._showElement(t)))}hide(){this._isShown&&!this._isTransitioning&&(F.trigger(this._element,Fe).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Ye),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated())))}dispose(){F.off(window,je),F.off(this._dialog,je),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ce({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ie({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=V.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),f(this._element),this._element.classList.add(Ye),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,F.trigger(this._element,qe,{relatedTarget:t})},this._dialog,this._isAnimated())}_addEventListeners(){F.on(this._element,Ve,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())}),F.on(window,We,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),F.on(this._element,Ke,t=>{F.one(this._element,Re,e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Xe),this._resetAdjustments(),this._scrollBar.reset(),F.trigger(this._element,He)})}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(F.trigger(this._element,ze).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Ue)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Ue),this._queueCallback(()=>{this._element.classList.remove(Ue),this._queueCallback(()=>{this._element.style.overflowY=e},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=b()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=b()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each(function(){const i=Ze.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}})}}F.on(document,Qe,'[data-bs-toggle="modal"]',function(t){const e=V.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),F.one(e,Be,t=>{t.defaultPrevented||F.one(e,He,()=>{d(this)&&this.focus()})});const i=V.findOne(".modal.show");i&&Ze.getInstance(i).hide(),Ze.getOrCreateInstance(e).toggle(this)}),Q(Ze),v(Ze);const ti=".bs.offcanvas",ei=".data-api",ii=`load${ti}${ei}`,si="show",ni="showing",oi="hiding",ri=".offcanvas.show",ai=`show${ti}`,li=`shown${ti}`,ci=`hide${ti}`,hi=`hidePrevented${ti}`,di=`hidden${ti}`,ui=`resize${ti}`,_i=`click${ti}${ei}`,gi=`keydown.dismiss${ti}`,fi={backdrop:!0,keyboard:!0,scroll:!1},mi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class pi extends R{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return fi}static get DefaultType(){return mi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||F.trigger(this._element,ai,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Me).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ni),this._queueCallback(()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(si),this._element.classList.remove(ni),F.trigger(this._element,li,{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(F.trigger(this._element,ci).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(oi),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(si,oi),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Me).reset(),F.trigger(this._element,di)},this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ce({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():F.trigger(this._element,hi)}:null})}_initializeFocusTrap(){return new Ie({trapElement:this._element})}_addEventListeners(){F.on(this._element,gi,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():F.trigger(this._element,hi))})}static jQueryInterface(t){return this.each(function(){const e=pi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}F.on(document,_i,'[data-bs-toggle="offcanvas"]',function(t){const e=V.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),u(this))return;F.one(e,di,()=>{d(this)&&this.focus()});const i=V.findOne(ri);i&&i!==e&&pi.getInstance(i).hide(),pi.getOrCreateInstance(e).toggle(this)}),F.on(window,ii,()=>{for(const t of V.find(ri))pi.getOrCreateInstance(t).show()}),F.on(window,ui,()=>{for(const t of V.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&pi.getOrCreateInstance(t).hide()}),Q(pi),v(pi);const bi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},vi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),yi=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,wi=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!vi.has(i)||Boolean(yi.test(t.nodeValue)):e.filter(t=>t instanceof RegExp).some(t=>t.test(i))},Ai={allowList:bi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},Ei={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ci={entry:"(string|element|function|null)",selector:"(string|element)"};class Ti extends W{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Ai}static get DefaultType(){return Ei}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map(t=>this._resolvePossibleFunction(t)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Ci)}_setContent(t,e,i){const s=V.findOne(i,t);s&&((e=this._resolvePossibleFunction(e))?c(e)?this._putElementInTemplate(h(e),s):this._config.html?s.innerHTML=this._maybeSanitize(e):s.textContent=e:s.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const s=(new window.DOMParser).parseFromString(t,"text/html"),n=[].concat(...s.body.querySelectorAll("*"));for(const t of n){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const s=[].concat(...t.attributes),n=[].concat(e["*"]||[],e[i]||[]);for(const e of s)wi(e,n)||t.removeAttribute(e.nodeName)}return s.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return y(t,[void 0,this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const ki=new Set(["sanitize","allowList","sanitizeFn"]),$i="fade",Si="show",Li=".tooltip-inner",Oi=".modal",Ii="hide.bs.modal",Di="hover",Ni="focus",Pi="click",xi={AUTO:"auto",TOP:"top",RIGHT:b()?"left":"right",BOTTOM:"bottom",LEFT:b()?"right":"left"},Mi={allowList:bi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},ji={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class Fi extends R{constructor(t,e){if(void 0===i)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Mi}static get DefaultType(){return ji}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),F.off(this._element.closest(Oi),Ii,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=F.trigger(this._element,this.constructor.eventName("show")),e=(_(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:s}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(s.append(i),F.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(Si),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.on(t,"mouseover",g);this._queueCallback(()=>{F.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(this._isShown()&&!F.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(Si),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.off(t,"mouseover",g);this._activeTrigger[Pi]=!1,this._activeTrigger[Ni]=!1,this._activeTrigger[Di]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),F.trigger(this._element,this.constructor.eventName("hidden")))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove($i,Si),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add($i),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Ti({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[Li]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains($i)}_isShown(){return this.tip&&this.tip.classList.contains(Si)}_createPopper(t){const e=y(this._config.placement,[this,t,this._element]),s=xi[e.toUpperCase()];return i.createPopper(this._element,t,this._getPopperConfig(s))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return y(t,[this._element,this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...y(this._config.popperConfig,[void 0,e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)F.on(this._element,this.constructor.eventName("click"),this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger[Pi]=!(e._isShown()&&e._activeTrigger[Pi]),e.toggle()});else if("manual"!==e){const t=e===Di?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===Di?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");F.on(this._element,t,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?Ni:Di]=!0,e._enter()}),F.on(this._element,i,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?Ni:Di]=e._element.contains(t.relatedTarget),e._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},F.on(this._element.closest(Oi),Ii,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=q.getDataAttributes(this._element);for(const t of Object.keys(e))ki.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:h(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each(function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}v(Fi);const zi=".popover-header",Hi=".popover-body",Bi={...Fi.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},qi={...Fi.DefaultType,content:"(null|string|element|function)"};class Wi extends Fi{static get Default(){return Bi}static get DefaultType(){return qi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[zi]:this._getTitle(),[Hi]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each(function(){const e=Wi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}v(Wi);const Ri=".bs.scrollspy",Ki=`activate${Ri}`,Vi=`click${Ri}`,Qi=`load${Ri}.data-api`,Xi="active",Yi="[href]",Ui=".nav-link",Gi=`${Ui}, .nav-item > ${Ui}, .list-group-item`,Ji={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Zi={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class ts extends R{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ji}static get DefaultType(){return Zi}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=h(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map(t=>Number.parseFloat(t))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(F.off(this._config.target,Vi),F.on(this._config.target,Vi,Yi,t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,s=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:s,behavior:"smooth"});i.scrollTop=s}}))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},s=(this._rootElement||document.documentElement).scrollTop,n=s>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=s;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(n&&t){if(i(o),!s)return}else n||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=V.find(Yi,this._config.target);for(const e of t){if(!e.hash||u(e))continue;const t=V.findOne(decodeURI(e.hash),this._element);d(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Xi),this._activateParents(t),F.trigger(this._element,Ki,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))V.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Xi);else for(const e of V.parents(t,".nav, .list-group"))for(const t of V.prev(e,Gi))t.classList.add(Xi)}_clearActiveClass(t){t.classList.remove(Xi);const e=V.find(`${Yi}.${Xi}`,t);for(const t of e)t.classList.remove(Xi)}static jQueryInterface(t){return this.each(function(){const e=ts.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}F.on(window,Qi,()=>{for(const t of V.find('[data-bs-spy="scroll"]'))ts.getOrCreateInstance(t)}),v(ts);const es=".bs.tab",is=`hide${es}`,ss=`hidden${es}`,ns=`show${es}`,os=`shown${es}`,rs=`click${es}`,as=`keydown${es}`,ls=`load${es}`,cs="ArrowLeft",hs="ArrowRight",ds="ArrowUp",us="ArrowDown",_s="Home",gs="End",fs="active",ms="fade",ps="show",bs=".dropdown-toggle",vs=`:not(${bs})`,ys='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',ws=`.nav-link${vs}, .list-group-item${vs}, [role="tab"]${vs}, ${ys}`,As=`.${fs}[data-bs-toggle="tab"], .${fs}[data-bs-toggle="pill"], .${fs}[data-bs-toggle="list"]`;class Es extends R{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),F.on(this._element,as,t=>this._keydown(t)))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?F.trigger(e,is,{relatedTarget:t}):null;F.trigger(t,ns,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(fs),this._activate(V.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),F.trigger(t,os,{relatedTarget:e})):t.classList.add(ps)},t,t.classList.contains(ms)))}_deactivate(t,e){t&&(t.classList.remove(fs),t.blur(),this._deactivate(V.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),F.trigger(t,ss,{relatedTarget:e})):t.classList.remove(ps)},t,t.classList.contains(ms)))}_keydown(t){if(![cs,hs,ds,us,_s,gs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter(t=>!u(t));let i;if([_s,gs].includes(t.key))i=e[t.key===_s?0:e.length-1];else{const s=[hs,us].includes(t.key);i=A(e,t.target,s,!0)}i&&(i.focus({preventScroll:!0}),Es.getOrCreateInstance(i).show())}_getChildren(){return V.find(ws,this._parent)}_getActiveElem(){return this._getChildren().find(t=>this._elemIsActive(t))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=V.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const s=(t,s)=>{const n=V.findOne(t,i);n&&n.classList.toggle(s,e)};s(bs,fs),s(".dropdown-menu",ps),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(fs)}_getInnerElement(t){return t.matches(ws)?t:V.findOne(ws,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each(function(){const e=Es.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}F.on(document,rs,ys,function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),u(this)||Es.getOrCreateInstance(this).show()}),F.on(window,ls,()=>{for(const t of V.find(As))Es.getOrCreateInstance(t)}),v(Es);const Cs=".bs.toast",Ts=`mouseover${Cs}`,ks=`mouseout${Cs}`,$s=`focusin${Cs}`,Ss=`focusout${Cs}`,Ls=`hide${Cs}`,Os=`hidden${Cs}`,Is=`show${Cs}`,Ds=`shown${Cs}`,Ns="hide",Ps="show",xs="showing",Ms={animation:"boolean",autohide:"boolean",delay:"number"},js={animation:!0,autohide:!0,delay:5e3};class Fs extends R{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return js}static get DefaultType(){return Ms}static get NAME(){return"toast"}show(){F.trigger(this._element,Is).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Ns),f(this._element),this._element.classList.add(Ps,xs),this._queueCallback(()=>{this._element.classList.remove(xs),F.trigger(this._element,Ds),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this.isShown()&&(F.trigger(this._element,Ls).defaultPrevented||(this._element.classList.add(xs),this._queueCallback(()=>{this._element.classList.add(Ns),this._element.classList.remove(xs,Ps),F.trigger(this._element,Os)},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Ps),super.dispose()}isShown(){return this._element.classList.contains(Ps)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){F.on(this._element,Ts,t=>this._onInteraction(t,!0)),F.on(this._element,ks,t=>this._onInteraction(t,!1)),F.on(this._element,$s,t=>this._onInteraction(t,!0)),F.on(this._element,Ss,t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each(function(){const e=Fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}})}}return Q(Fs),v(Fs),{Alert:G,Button:Z,Carousel:Nt,Collapse:Qt,Dropdown:be,Modal:Ze,Offcanvas:pi,Popover:Wi,ScrollSpy:ts,Tab:Es,Toast:Fs,Tooltip:Fi}}); //# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map index 4d437152..df4b8491 100644 --- a/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map +++ b/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map @@ -1 +1 @@ -{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","destroy","update","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","scroll","Offcanvas","blur","completeCallback","position","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","allowList","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","HOME_KEY","END_KEY","SELECTOR_DROPDOWN_TOGGLE","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","Object","prototype","toString","call","toLowerCase","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ARROW_LEFT_KEY$1","ARROW_RIGHT_KEY$1","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","destroy","update","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","scroll","Offcanvas","blur","completeCallback","position","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","allowList","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","HOME_KEY","END_KEY","SELECTOR_DROPDOWN_TOGGLE","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.8'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n // Private\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n // Private\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [undefined, this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org/docs/v2/)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element, this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK])\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file From bcde17cb9e789ad75ed255fc8ad381cefe2b200d Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 12 Oct 2025 20:37:38 +0200 Subject: [PATCH 157/224] =?UTF-8?q?=F0=9F=8E=A8=20(bootsier):=20Ajusta=20e?= =?UTF-8?q?stilos=20para=20personalizar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/.gitattributes | 1 + extensions/pagetop-bootsier/Cargo.toml | 2 +- extensions/pagetop-bootsier/README.md | 2 +- extensions/pagetop-bootsier/build.rs | 6 +- extensions/pagetop-bootsier/src/lib.rs | 4 +- .../static/scss/_customs.scss | 108 ++++++++++++++++++ .../static/scss/bootsier.scss | 55 +++++++++ .../bootstrap-5.3.8}/_accordion.scss | 0 .../bootstrap-5.3.8}/_alert.scss | 0 .../bootstrap-5.3.8}/_badge.scss | 0 .../bootstrap-5.3.8}/_breadcrumb.scss | 0 .../bootstrap-5.3.8}/_button-group.scss | 0 .../bootstrap-5.3.8}/_buttons.scss | 0 .../bootstrap-5.3.8}/_card.scss | 0 .../bootstrap-5.3.8}/_carousel.scss | 0 .../bootstrap-5.3.8}/_close.scss | 0 .../bootstrap-5.3.8}/_containers.scss | 0 .../bootstrap-5.3.8}/_dropdown.scss | 0 .../bootstrap-5.3.8}/_forms.scss | 0 .../bootstrap-5.3.8}/_functions.scss | 0 .../bootstrap-5.3.8}/_grid.scss | 0 .../bootstrap-5.3.8}/_helpers.scss | 0 .../bootstrap-5.3.8}/_images.scss | 0 .../bootstrap-5.3.8}/_list-group.scss | 0 .../bootstrap-5.3.8}/_maps.scss | 0 .../bootstrap-5.3.8}/_mixins.scss | 0 .../bootstrap-5.3.8}/_modal.scss | 0 .../bootstrap-5.3.8}/_nav.scss | 0 .../bootstrap-5.3.8}/_navbar.scss | 0 .../bootstrap-5.3.8}/_offcanvas.scss | 0 .../bootstrap-5.3.8}/_pagination.scss | 0 .../bootstrap-5.3.8}/_placeholders.scss | 0 .../bootstrap-5.3.8}/_popover.scss | 0 .../bootstrap-5.3.8}/_progress.scss | 0 .../bootstrap-5.3.8}/_reboot.scss | 0 .../bootstrap-5.3.8}/_root.scss | 0 .../bootstrap-5.3.8}/_spinners.scss | 0 .../bootstrap-5.3.8}/_tables.scss | 0 .../bootstrap-5.3.8}/_toasts.scss | 0 .../bootstrap-5.3.8}/_tooltip.scss | 0 .../bootstrap-5.3.8}/_transitions.scss | 0 .../bootstrap-5.3.8}/_type.scss | 0 .../bootstrap-5.3.8}/_utilities.scss | 0 .../bootstrap-5.3.8}/_variables-dark.scss | 0 .../bootstrap-5.3.8}/_variables.scss | 0 .../bootstrap-5.3.8}/bootstrap-grid.scss | 0 .../bootstrap-5.3.8}/bootstrap-reboot.scss | 0 .../bootstrap-5.3.8}/bootstrap-utilities.scss | 0 .../bootstrap-5.3.8}/bootstrap.scss | 0 .../forms/_floating-labels.scss | 0 .../bootstrap-5.3.8}/forms/_form-check.scss | 0 .../bootstrap-5.3.8}/forms/_form-control.scss | 0 .../bootstrap-5.3.8}/forms/_form-range.scss | 0 .../bootstrap-5.3.8}/forms/_form-select.scss | 0 .../bootstrap-5.3.8}/forms/_form-text.scss | 0 .../bootstrap-5.3.8}/forms/_input-group.scss | 0 .../bootstrap-5.3.8}/forms/_labels.scss | 0 .../bootstrap-5.3.8}/forms/_validation.scss | 0 .../bootstrap-5.3.8}/helpers/_clearfix.scss | 0 .../bootstrap-5.3.8}/helpers/_color-bg.scss | 0 .../helpers/_colored-links.scss | 0 .../bootstrap-5.3.8}/helpers/_focus-ring.scss | 0 .../bootstrap-5.3.8}/helpers/_icon-link.scss | 0 .../bootstrap-5.3.8}/helpers/_position.scss | 0 .../bootstrap-5.3.8}/helpers/_ratio.scss | 0 .../bootstrap-5.3.8}/helpers/_stacks.scss | 0 .../helpers/_stretched-link.scss | 0 .../helpers/_text-truncation.scss | 0 .../helpers/_visually-hidden.scss | 0 .../bootstrap-5.3.8}/helpers/_vr.scss | 0 .../bootstrap-5.3.8}/mixins/_alert.scss | 0 .../bootstrap-5.3.8}/mixins/_backdrop.scss | 0 .../bootstrap-5.3.8}/mixins/_banner.scss | 0 .../mixins/_border-radius.scss | 0 .../bootstrap-5.3.8}/mixins/_box-shadow.scss | 0 .../bootstrap-5.3.8}/mixins/_breakpoints.scss | 0 .../bootstrap-5.3.8}/mixins/_buttons.scss | 0 .../bootstrap-5.3.8}/mixins/_caret.scss | 0 .../bootstrap-5.3.8}/mixins/_clearfix.scss | 0 .../bootstrap-5.3.8}/mixins/_color-mode.scss | 0 .../mixins/_color-scheme.scss | 0 .../bootstrap-5.3.8}/mixins/_container.scss | 0 .../bootstrap-5.3.8}/mixins/_deprecate.scss | 0 .../bootstrap-5.3.8}/mixins/_forms.scss | 0 .../bootstrap-5.3.8}/mixins/_gradients.scss | 0 .../bootstrap-5.3.8}/mixins/_grid.scss | 0 .../bootstrap-5.3.8}/mixins/_image.scss | 0 .../bootstrap-5.3.8}/mixins/_list-group.scss | 0 .../bootstrap-5.3.8}/mixins/_lists.scss | 0 .../bootstrap-5.3.8}/mixins/_pagination.scss | 0 .../bootstrap-5.3.8}/mixins/_reset-text.scss | 0 .../bootstrap-5.3.8}/mixins/_resize.scss | 0 .../mixins/_table-variants.scss | 0 .../mixins/_text-truncate.scss | 0 .../bootstrap-5.3.8}/mixins/_transition.scss | 0 .../bootstrap-5.3.8}/mixins/_utilities.scss | 0 .../mixins/_visually-hidden.scss | 0 .../bootstrap-5.3.8}/tests/jasmine.js | 0 .../_auto-import-of-variables-dark.test.scss | 0 .../tests/mixins/_box-shadow.test.scss | 0 .../tests/mixins/_color-contrast.test.scss | 0 .../tests/mixins/_color-modes.test.scss | 0 .../_media-query-color-mode-full.test.scss | 0 .../tests/mixins/_utilities.test.scss | 0 .../tests/sass-true/register.js | 0 .../tests/sass-true/runner.js | 0 .../tests/utilities/_api.test.scss | 0 .../bootstrap-5.3.8}/utilities/_api.scss | 0 .../bootstrap-5.3.8}/vendor/_rfs.scss | 0 109 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 extensions/pagetop-bootsier/.gitattributes create mode 100644 extensions/pagetop-bootsier/static/scss/_customs.scss create mode 100644 extensions/pagetop-bootsier/static/scss/bootsier.scss rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_accordion.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_alert.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_badge.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_breadcrumb.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_button-group.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_buttons.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_card.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_carousel.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_close.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_containers.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_dropdown.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_forms.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_functions.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_grid.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_helpers.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_images.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_list-group.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_maps.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_mixins.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_modal.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_nav.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_navbar.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_offcanvas.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_pagination.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_placeholders.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_popover.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_progress.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_reboot.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_root.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_spinners.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_tables.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_toasts.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_tooltip.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_transitions.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_type.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_variables-dark.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/_variables.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/bootstrap-grid.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/bootstrap-reboot.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/bootstrap-utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/bootstrap.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_floating-labels.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_form-check.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_form-control.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_form-range.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_form-select.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_form-text.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_input-group.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_labels.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/forms/_validation.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_clearfix.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_color-bg.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_colored-links.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_focus-ring.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_icon-link.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_position.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_ratio.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_stacks.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_stretched-link.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_text-truncation.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_visually-hidden.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/helpers/_vr.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_alert.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_backdrop.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_banner.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_border-radius.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_box-shadow.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_breakpoints.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_buttons.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_caret.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_clearfix.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_color-mode.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_color-scheme.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_container.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_deprecate.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_forms.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_gradients.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_grid.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_image.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_list-group.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_lists.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_pagination.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_reset-text.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_resize.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_table-variants.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_text-truncate.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_transition.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_utilities.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/mixins/_visually-hidden.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/jasmine.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_auto-import-of-variables-dark.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_box-shadow.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_color-contrast.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_color-modes.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_media-query-color-mode-full.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/mixins/_utilities.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/sass-true/register.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/sass-true/runner.js (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/tests/utilities/_api.test.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/utilities/_api.scss (100%) rename extensions/pagetop-bootsier/static/{bootstrap => scss/bootstrap-5.3.8}/vendor/_rfs.scss (100%) diff --git a/extensions/pagetop-bootsier/.gitattributes b/extensions/pagetop-bootsier/.gitattributes new file mode 100644 index 00000000..940d6a84 --- /dev/null +++ b/extensions/pagetop-bootsier/.gitattributes @@ -0,0 +1 @@ +static/** linguist-vendored diff --git a/extensions/pagetop-bootsier/Cargo.toml b/extensions/pagetop-bootsier/Cargo.toml index 8945e5a8..11306b63 100644 --- a/extensions/pagetop-bootsier/Cargo.toml +++ b/extensions/pagetop-bootsier/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.18" edition = "2021" description = """ - Tema de PageTop basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles. + Tema de PageTop basado en Bootstrap para dar vida a tus diseños web. """ categories = ["web-programming", "gui"] keywords = ["pagetop", "theme", "bootstrap", "css", "js"] diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 1a33b4ea..7495bc22 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -2,7 +2,7 @@ <h1>PageTop Bootsier</h1> -<p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> +<p>Tema de <strong>PageTop</strong> basado en Bootstrap para dar vida a tus diseños web.</p> [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs index f2d0f794..5945660a 100644 --- a/extensions/pagetop-bootsier/build.rs +++ b/extensions/pagetop-bootsier/build.rs @@ -4,8 +4,8 @@ use std::env; use std::path::Path; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_scss("./static/bootstrap/bootstrap.scss", "bootstrap.min.css") - .with_name("bootsier") + StaticFilesBundle::from_scss("./static/scss/bootsier.scss", "bootstrap.min.css") + .with_name("bootsier_bs") .build()?; StaticFilesBundle::from_dir("./static/js", Some(bootstrap_js_files)) .with_name("bootsier_js") @@ -13,7 +13,7 @@ fn main() -> std::io::Result<()> { } fn bootstrap_js_files(path: &Path) -> bool { - // No filtering during development, only on "release" compilation. + // No filtra durante el desarrollo, solo en la compilación "release". env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release" || path.file_name().map_or(false, |n| n == "bootstrap.min.js") } diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index fecf1273..a6583333 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -100,7 +100,7 @@ impl Extension for Bootsier { } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - static_files_service!(scfg, [bootsier] => "/bootsier/css"); + static_files_service!(scfg, [bootsier_bs] => "/bootsier/bs"); static_files_service!(scfg, [bootsier_js] => "/bootsier/js"); } } @@ -108,7 +108,7 @@ impl Extension for Bootsier { impl Theme for Bootsier { fn after_render_page_body(&self, page: &mut Page) { page.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/bootsier/css/bootstrap.min.css") + StyleSheet::from("/bootsier/bs/bootstrap.min.css") .with_version(BOOTSTRAP_VERSION) .with_weight(-90), )) diff --git a/extensions/pagetop-bootsier/static/scss/_customs.scss b/extensions/pagetop-bootsier/static/scss/_customs.scss new file mode 100644 index 00000000..988d7055 --- /dev/null +++ b/extensions/pagetop-bootsier/static/scss/_customs.scss @@ -0,0 +1,108 @@ +// Enable CSS Grid +$enable-grid-classes: false; +$enable-cssgrid: true; + +// Opacity +.bg-opacity-0 { + --bs-bg-opacity: 0; +} + +.border-opacity-0 { + --bs-border-opacity: 0; +} + +.text-opacity-0 { + --bs-text-opacity: 0; +} +.text-opacity-10 { + --bs-text-opacity: 0.1; +} + +// Extending utilities +$utilities: map-merge( + $utilities, + ( + // Individual border widths + "border-top": ( + property: border-top-width, + class: border-top, + values: $border-widths + ), + "border-end": ( + property: border-right-width, + class: border-end, + values: $border-widths + ), + "border-bottom": ( + property: border-bottom-width, + class: border-bottom, + values: $border-widths + ), + "border-start": ( + property: border-left-width, + class: border-start, + values: $border-widths + ), + // Individual rounded values + "rounded-top-start": ( + property: border-top-left-radius, + class: rounded-top-start, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-top-end": ( + property: border-top-right-radius, + class: rounded-top-end, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-bottom-start": ( + property: border-bottom-left-radius, + class: rounded-bottom-start, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + "rounded-bottom-end": ( + property: border-bottom-right-radius, + class: rounded-bottom-end, + values: ( + null: var(--#{$prefix}border-radius), + 0: 0, + 1: var(--#{$prefix}border-radius-sm), + 2: var(--#{$prefix}border-radius), + 3: var(--#{$prefix}border-radius-lg), + 4: var(--#{$prefix}border-radius-xl), + 5: var(--#{$prefix}border-radius-xxl), + circle: 50%, + pill: var(--#{$prefix}border-radius-pill) + ) + ), + ) +); diff --git a/extensions/pagetop-bootsier/static/scss/bootsier.scss b/extensions/pagetop-bootsier/static/scss/bootsier.scss new file mode 100644 index 00000000..0d52046a --- /dev/null +++ b/extensions/pagetop-bootsier/static/scss/bootsier.scss @@ -0,0 +1,55 @@ +@import "bootstrap-5.3.8/mixins/banner"; +@include bsBanner(""); + + +// scss-docs-start import-stack +// Configuration +@import "bootstrap-5.3.8/functions"; +@import "bootstrap-5.3.8/variables"; +@import "bootstrap-5.3.8/variables-dark"; +@import "bootstrap-5.3.8/maps"; +@import "bootstrap-5.3.8/mixins"; +@import "bootstrap-5.3.8/utilities"; + +// Layout & components +@import "bootstrap-5.3.8/root"; +@import "bootstrap-5.3.8/reboot"; +@import "bootstrap-5.3.8/type"; +@import "bootstrap-5.3.8/images"; +@import "bootstrap-5.3.8/containers"; +@import "bootstrap-5.3.8/grid"; +@import "bootstrap-5.3.8/tables"; +@import "bootstrap-5.3.8/forms"; +@import "bootstrap-5.3.8/buttons"; +@import "bootstrap-5.3.8/transitions"; +@import "bootstrap-5.3.8/dropdown"; +@import "bootstrap-5.3.8/button-group"; +@import "bootstrap-5.3.8/nav"; +@import "bootstrap-5.3.8/navbar"; +@import "bootstrap-5.3.8/card"; +@import "bootstrap-5.3.8/accordion"; +@import "bootstrap-5.3.8/breadcrumb"; +@import "bootstrap-5.3.8/pagination"; +@import "bootstrap-5.3.8/badge"; +@import "bootstrap-5.3.8/alert"; +@import "bootstrap-5.3.8/progress"; +@import "bootstrap-5.3.8/list-group"; +@import "bootstrap-5.3.8/close"; +@import "bootstrap-5.3.8/toasts"; +@import "bootstrap-5.3.8/modal"; +@import "bootstrap-5.3.8/tooltip"; +@import "bootstrap-5.3.8/popover"; +@import "bootstrap-5.3.8/carousel"; +@import "bootstrap-5.3.8/spinners"; +@import "bootstrap-5.3.8/offcanvas"; +@import "bootstrap-5.3.8/placeholders"; + +// Helpers +@import "bootstrap-5.3.8/helpers"; + +// Custom definitions +@import "customs"; + +// Utilities +@import "bootstrap-5.3.8/utilities/api"; +// scss-docs-end import-stack diff --git a/extensions/pagetop-bootsier/static/bootstrap/_accordion.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_accordion.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_accordion.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_accordion.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_alert.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_alert.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_alert.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_badge.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_badge.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_badge.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_badge.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_breadcrumb.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_breadcrumb.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_breadcrumb.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_breadcrumb.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_button-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_button-group.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_button-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_button-group.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_buttons.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_buttons.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_buttons.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_card.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_card.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_card.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_card.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_carousel.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_carousel.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_carousel.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_carousel.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_close.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_close.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_close.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_close.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_containers.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_containers.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_containers.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_containers.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_dropdown.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_dropdown.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_dropdown.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_dropdown.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_forms.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_forms.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_forms.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_functions.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_functions.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_functions.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_functions.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_grid.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_grid.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_helpers.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_helpers.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_helpers.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_helpers.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_images.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_images.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_images.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_images.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_list-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_list-group.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_list-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_list-group.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_maps.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_maps.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_maps.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_maps.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_mixins.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_mixins.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_mixins.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_mixins.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_modal.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_modal.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_modal.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_modal.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_nav.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_nav.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_nav.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_nav.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_navbar.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_navbar.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_navbar.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_navbar.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_offcanvas.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_offcanvas.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_offcanvas.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_offcanvas.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_pagination.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_pagination.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_pagination.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_pagination.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_placeholders.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_placeholders.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_placeholders.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_placeholders.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_popover.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_popover.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_popover.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_popover.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_progress.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_progress.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_progress.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_progress.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_reboot.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_reboot.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_reboot.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_reboot.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_root.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_root.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_root.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_root.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_spinners.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_spinners.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_spinners.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_spinners.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_tables.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tables.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_tables.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tables.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_toasts.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_toasts.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_toasts.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_toasts.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_tooltip.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tooltip.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_tooltip.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tooltip.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_transitions.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_transitions.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_transitions.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_transitions.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_type.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_type.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_type.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_type.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_variables-dark.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables-dark.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_variables-dark.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables-dark.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/_variables.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/_variables.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/bootstrap-grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-grid.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/bootstrap-grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-grid.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/bootstrap-reboot.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-reboot.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/bootstrap-reboot.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-reboot.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/bootstrap-utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/bootstrap-utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/bootstrap.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/bootstrap.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_floating-labels.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_floating-labels.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_floating-labels.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_floating-labels.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_form-check.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-check.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_form-check.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-check.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_form-control.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-control.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_form-control.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-control.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_form-range.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-range.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_form-range.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-range.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_form-select.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-select.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_form-select.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-select.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_form-text.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-text.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_form-text.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-text.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_input-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_input-group.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_input-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_input-group.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_labels.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_labels.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_labels.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_labels.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/forms/_validation.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_validation.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/forms/_validation.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_validation.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_clearfix.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_clearfix.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_clearfix.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_color-bg.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_color-bg.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_color-bg.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_color-bg.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_colored-links.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_colored-links.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_colored-links.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_colored-links.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_focus-ring.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_focus-ring.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_focus-ring.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_focus-ring.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_icon-link.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_icon-link.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_icon-link.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_icon-link.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_position.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_position.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_position.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_position.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_ratio.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_ratio.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_ratio.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_ratio.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_stacks.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stacks.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_stacks.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stacks.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_stretched-link.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stretched-link.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_stretched-link.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stretched-link.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_text-truncation.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_text-truncation.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_text-truncation.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_text-truncation.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_visually-hidden.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_visually-hidden.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_visually-hidden.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/helpers/_vr.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_vr.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/helpers/_vr.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_vr.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_alert.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_alert.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_alert.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_backdrop.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_backdrop.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_backdrop.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_backdrop.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_banner.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_banner.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_banner.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_banner.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_border-radius.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_border-radius.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_border-radius.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_border-radius.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_box-shadow.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_box-shadow.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_box-shadow.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_breakpoints.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_breakpoints.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_breakpoints.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_breakpoints.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_buttons.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_buttons.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_buttons.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_caret.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_caret.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_caret.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_caret.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_clearfix.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_clearfix.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_clearfix.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_color-mode.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-mode.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_color-mode.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-mode.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_color-scheme.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-scheme.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_color-scheme.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-scheme.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_container.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_container.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_container.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_container.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_deprecate.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_deprecate.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_deprecate.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_deprecate.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_forms.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_forms.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_forms.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_gradients.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_gradients.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_gradients.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_gradients.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_grid.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_grid.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_image.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_image.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_image.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_image.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_list-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_list-group.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_list-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_list-group.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_lists.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_lists.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_lists.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_lists.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_pagination.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_pagination.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_pagination.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_pagination.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_reset-text.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_reset-text.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_reset-text.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_reset-text.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_resize.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_resize.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_resize.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_resize.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_table-variants.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_table-variants.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_table-variants.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_table-variants.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_text-truncate.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_text-truncate.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_text-truncate.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_text-truncate.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_transition.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_transition.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_transition.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_transition.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_utilities.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/mixins/_visually-hidden.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_visually-hidden.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/mixins/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_visually-hidden.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/jasmine.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/jasmine.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/jasmine.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/jasmine.js diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_auto-import-of-variables-dark.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_auto-import-of-variables-dark.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_auto-import-of-variables-dark.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_auto-import-of-variables-dark.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_box-shadow.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_box-shadow.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_box-shadow.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-contrast.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-contrast.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-contrast.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-modes.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-modes.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_color-modes.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-modes.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_media-query-color-mode-full.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_media-query-color-mode-full.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_media-query-color-mode-full.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_media-query-color-mode-full.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_utilities.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_utilities.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/mixins/_utilities.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_utilities.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/register.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/register.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/register.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/register.js diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/runner.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/runner.js similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/sass-true/runner.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/runner.js diff --git a/extensions/pagetop-bootsier/static/bootstrap/tests/utilities/_api.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/utilities/_api.test.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/tests/utilities/_api.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/utilities/_api.test.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/utilities/_api.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/utilities/_api.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/utilities/_api.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/utilities/_api.scss diff --git a/extensions/pagetop-bootsier/static/bootstrap/vendor/_rfs.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/vendor/_rfs.scss similarity index 100% rename from extensions/pagetop-bootsier/static/bootstrap/vendor/_rfs.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/vendor/_rfs.scss From 39a3dabcfb5b9f62414816c9134b6fbd6f5d2365 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 13 Oct 2025 13:13:33 +0200 Subject: [PATCH 158/224] =?UTF-8?q?=E2=9C=A8=20(html):=20A=C3=B1ade=20sopo?= =?UTF-8?q?rte=20para=20unidades=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 9 +- Cargo.toml | 1 + src/html.rs | 5 + src/html/unit.rs | 259 ++++++++++++++++++++++++++++++++++ src/prelude.rs | 2 +- tests/{html.rs => html_pm.rs} | 0 tests/html_unit.rs | 223 +++++++++++++++++++++++++++++ 7 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/html/unit.rs rename tests/{html.rs => html_pm.rs} (100%) create mode 100644 tests/html_unit.rs diff --git a/Cargo.lock b/Cargo.lock index ebcebd39..f1b09f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,7 @@ dependencies = [ "parking_lot", "pastey", "serde", + "serde_json", "substring", "tempfile", "terminal_size", @@ -1914,9 +1915,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" dependencies = [ "aho-corasick", "memchr", @@ -1926,9 +1927,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 103f6a94..0d17082b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ testing = [] [dev-dependencies] tempfile = "3.23" +serde_json = "1.0" pagetop-aliner.workspace = true pagetop-bootsier.workspace = true diff --git a/src/html.rs b/src/html.rs index abc8e8cf..f85c24ed 100644 --- a/src/html.rs +++ b/src/html.rs @@ -84,6 +84,11 @@ use crate::{core, AutoDefault}; #[allow(type_alias_bounds)] pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>; +mod unit; +pub use unit::UnitValue; + +// **< HTML PrepareMarkup >************************************************************************* + /// Prepara contenido HTML para su conversión a [`Markup`]. /// /// Este tipo encapsula distintos orígenes de contenido HTML (texto plano, HTML sin escapar o diff --git a/src/html/unit.rs b/src/html/unit.rs new file mode 100644 index 00000000..b6a9bc76 --- /dev/null +++ b/src/html/unit.rs @@ -0,0 +1,259 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +use std::fmt; +use std::str::FromStr; + +/// Representa una **unidad CSS** lista para formatear o deserializar. +/// +/// ## Unidades soportadas +/// +/// - **Absolutas** *(valores enteros, `isize`)*: +/// - `Cm(isize)` - `cm` (centímetros) +/// - `In(isize)` - `in` (pulgadas; `1in = 96px = 2.54cm`) +/// - `Mm(isize)` - `mm` (milímetros) +/// - `Pc(isize)` - `pc` (picas; `1pc = 12pt`) +/// - `Pt(isize)` - `pt` (puntos; `1pt = 1/72in`) +/// - `Px(isize)` - `px` (píxeles; `1px = 1/96in`) +/// +/// - **Relativas** *(valores decimales, `f32`)*: +/// - `RelEm(f32)` - `em` (relativa al tamaño de fuente del elemento) +/// - `RelRem(f32)` - `rem` (relativa al tamaño de fuente de `:root`) +/// - `RelPct(f32)` - `%` (porcentaje relativo al elemento padre) +/// - `RelVh(f32)` - `vh` (1% de la **altura** del viewport) +/// - `RelVw(f32)` - `vw` (1% del **ancho** del viewport) +/// +/// ## Valores especiales +/// +/// - `None` - equivale a un texto vacío (`""`), útil para atributos opcionales. +/// - `Auto` - equivale a `"auto"`. +/// - `Zero` - equivale a `"0"` (cero sin unidad). +/// +/// ## Características +/// +/// - Soporta unidades **absolutas** (`cm`, `in`, `mm`, `pc`, `pt`, `px`) y **relativas** (`em`, +/// `rem`, `%`, `vh`, `vw`). +/// - `FromStr` para convertir desde texto (p.ej. `"12px"`, `"1.25rem"`, `"auto"`). +/// - `Display` para formatear a cadena (p.ej. `UnitValue::Px(12)` genera `"12px"`). +/// - `Deserialize` delega en `FromStr`, garantizando una gramática única. +/// +/// ## Ejemplos +/// +/// ```rust +/// # use pagetop::prelude::*; +/// use std::str::FromStr; +/// +/// assert_eq!(UnitValue::from_str("16px").unwrap(), UnitValue::Px(16)); +/// assert_eq!(UnitValue::from_str("1.25rem").unwrap(), UnitValue::RelRem(1.25)); +/// assert_eq!(UnitValue::from_str("33%").unwrap(), UnitValue::RelPct(33.0)); +/// assert_eq!(UnitValue::from_str("auto").unwrap(), UnitValue::Auto); +/// assert_eq!(UnitValue::from_str("").unwrap(), UnitValue::None); +/// assert_eq!(UnitValue::from_str("0").unwrap(), UnitValue::Zero); +/// ``` +/// +/// ## Notas +/// +/// - Las absolutas **no aceptan** decimales (p.ej., `"1.5px"` sería erróneo). +/// - Se aceptan signos `+`/`-` en todas las unidades (p.ej., `"-12px"`, `"+0.5em"`). +/// - La comparación de unidad es *case-insensitive* al interpretar el texto (`"PX"`, `"Px"`, …). +/// - **Sobre píxeles**: Los píxeles (px) son relativos al dispositivo de visualización. En +/// dispositivos con baja densidad de píxeles (dpi), 1px equivale a un píxel (punto) del +/// dispositivo. En impresoras y pantallas de alta resolución, 1px implica múltiples píxeles del +/// dispositivo. +/// - **Sobre `em` y `rem`**: +/// - `em` es **relativo al tamaño de fuente *del propio elemento***. Si el elemento hereda o +/// cambia su `font-size`, todos los valores en `em` dentro de él **se escalan en cascada**. +/// - `rem` es **relativo al tamaño de fuente del elemento raíz** (`:root`/`html`), **no se verá +/// afectado** por cambios de `font-size` en elementos anidados. +/// - Ejemplo: si `:root { font-size: 16px }` y un contenedor tiene `font-size: 20px`, entonces +/// dentro del contenedor `1em == 20px` pero `1rem == 16px`. +/// - Uso típico: `rem` para tipografía y espaciados globales (consistencia al cambiar la base del +/// sitio); `em` para tamaños que deban escalar **con el propio componente** (p.ej., +/// `padding: 0.5em` que crece si el componente aumenta su `font-size`). +/// - **Sobre el viewport**: Si el ancho de la ventana del navegador es de 50cm, 1vw equivale a +/// 0.5cm (1vw siempre es 1% del ancho del viewport, independientemente del zoom del navegador o +/// la densidad de píxeles del dispositivo). +#[rustfmt::skip] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum UnitValue { + #[default] + None, + Auto, + /// Cero sin unidad. + Zero, + /// Centímetros. + Cm(isize), + /// Pulgadas (1in = 96px = 2.54cm). + In(isize), + /// Milímetros. + Mm(isize), + /// Picas (1pc = 12pt). + Pc(isize), + /// Puntos (1pt = 1/72in). + Pt(isize), + /// Píxeles (1px = 1/96in). + Px(isize), + /// Relativo al tamaño de la fuente del elemento. + RelEm(f32), + /// Relativo al tamaño de la fuente del elemento raíz. + RelRem(f32), + /// Porcentaje relativo al elemento padre. + RelPct(f32), + /// Relativo al 1% de la altura del viewport. + RelVh(f32), + /// Relativo al 1% del ancho del viewport. + RelVw(f32), +} + +/// Formatea la unidad como cadena CSS. +/// +/// Reglas: +/// +/// - `None` - `""` (cadena vacía). +/// - `Auto` - `"auto"`. +/// - `Zero` - `"0"` (cero sin unidad). +/// - Absolutas - entero con su unidad: `Px(12)` a `"12px"`. +/// - Relativas - número en punto flotante; si es entero, se imprime sin decimales: +/// - `RelEm(2.0)` a `"2em"` +/// - `RelPct(33.5)` a `"33.5%"` +#[rustfmt::skip] +impl fmt::Display for UnitValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UnitValue::None => write!(f, ""), + UnitValue::Auto => write!(f, "auto"), + UnitValue::Zero => write!(f, "0"), + // Valor absoluto. + UnitValue::Cm(av) => write!(f, "{av}cm"), + UnitValue::In(av) => write!(f, "{av}in"), + UnitValue::Mm(av) => write!(f, "{av}mm"), + UnitValue::Pc(av) => write!(f, "{av}pc"), + UnitValue::Pt(av) => write!(f, "{av}pt"), + UnitValue::Px(av) => write!(f, "{av}px"), + // Valor relativo. + UnitValue::RelEm(rv) => write!(f, "{rv}em"), + UnitValue::RelRem(rv) => write!(f, "{rv}rem"), + UnitValue::RelPct(rv) => write!(f, "{rv}%"), + UnitValue::RelVh(rv) => write!(f, "{rv}vh"), + UnitValue::RelVw(rv) => write!(f, "{rv}vw"), + } + } +} + +/// Convierte una cadena a [`UnitValue`] siguiendo una gramática CSS acotada. +/// +/// ## Acepta +/// +/// - `""` para `UnitValue::None` +/// - `"auto"` +/// - **Cero sin unidad**: `"0"`, `"+0"`, `"-0"`, `"0.0"`, `"0."`, `".0"` para `UnitValue::Zero` +/// - Porcentaje: `"<n>%"` (p.ej. `"33%"`, `"33 %"`) +/// - Absolutas enteras: `"<entero><unidad>"`, p.ej. `"12px"`, `"-5pt"` +/// - Relativas decimales: `"<float><unidad>"`, p.ej. `"1.25rem"`, `"-0.5vh"`, `".5em"`, `"1.rem"` +/// +/// (Se toleran espacios entre número y unidad: `"12 px"`, `"1.5 rem"`). +/// +/// ## Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// use std::str::FromStr; +/// +/// assert_eq!(UnitValue::from_str("12px").unwrap(), UnitValue::Px(12)); +/// assert!(UnitValue::from_str("12").is_err()); +/// ``` +/// +/// ## Errores de interpretación +/// +/// - Falta la unidad cuando es necesaria (p.ej. `"12"`, excepto para el valor cero). +/// - Decimales en valores que deben ser absolutos (p.ej. `"1.5px"`). +/// - Unidades desconocidas (p.ej. `"10ch"`, no soportada aún). +/// - Notación científica o bases no decimales: `"1e3vw"`, `"0x10px"` (no soportadas). Los ceros a +/// la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`). +/// +/// La comparación de la unidad es *case-insensitive*. +impl FromStr for UnitValue { + type Err = String; + + fn from_str(input: &str) -> Result<Self, Self::Err> { + let s = input.trim(); + if s.is_empty() { + return Ok(UnitValue::None); + } + if s.eq_ignore_ascii_case("auto") { + return Ok(UnitValue::Auto); + } + + match s.find(|c: char| c.is_ascii_alphabetic() || c == '%') { + None => { + let n: f32 = s + .parse() + .map_err(|e| format!("Invalid number `{s}`: {e}"))?; + if n == 0.0 { + Ok(UnitValue::Zero) + } else { + Err( + "Missing unit (expected one of cm,in,mm,pc,pt,px,em,rem,vh,vw, or %)" + .to_string(), + ) + } + } + Some(split_pos) => { + let (num_str, unit_str) = s.split_at(split_pos); + let u = unit_str.trim(); + let n = num_str.trim(); + + let parse_abs = |n_s: &str| -> Result<isize, String> { + n_s.parse::<isize>() + .map_err(|e| format!("Invalid integer `{n_s}`: {e}")) + }; + let parse_rel = |n_s: &str| -> Result<f32, String> { + n_s.parse::<f32>() + .map_err(|e| format!("Invalid float `{n_s}`: {e}")) + }; + + match u.to_ascii_lowercase().as_str() { + // Unidades absolutas. + "cm" => Ok(UnitValue::Cm(parse_abs(n)?)), + "in" => Ok(UnitValue::In(parse_abs(n)?)), + "mm" => Ok(UnitValue::Mm(parse_abs(n)?)), + "pc" => Ok(UnitValue::Pc(parse_abs(n)?)), + "pt" => Ok(UnitValue::Pt(parse_abs(n)?)), + "px" => Ok(UnitValue::Px(parse_abs(n)?)), + // Unidades relativas. + "em" => Ok(UnitValue::RelEm(parse_rel(n)?)), + "rem" => Ok(UnitValue::RelRem(parse_rel(n)?)), + "vh" => Ok(UnitValue::RelVh(parse_rel(n)?)), + "vw" => Ok(UnitValue::RelVw(parse_rel(n)?)), + // Porcentaje como unidad. + "%" => Ok(UnitValue::RelPct(parse_rel(n)?)), + // Unidad desconocida. + _ => Err(format!("Unknown unit: `{u}`")), + } + } + } + } +} + +/// Deserializa desde una cadena usando la misma gramática que [`FromStr`]. +/// +/// ### Ejemplo con `serde_json` +/// ```rust +/// # use pagetop::prelude::*; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Style { width: UnitValue } +/// +/// // "{\"width\":\"12px\"}" deserializa como `Style { width: UnitValue::Px(12) }` +/// ``` +impl<'de> Deserialize<'de> for UnitValue { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + raw.parse().map_err(serde::de::Error::custom) + } +} diff --git a/src/prelude.rs b/src/prelude.rs index a71375e1..cd3191f6 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -34,7 +34,7 @@ pub use crate::trace; pub use crate::html::{ display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, ClassesOp, Escaper, Favicon, JavaScript, Markup, PreEscaped, PrepareMarkup, StyleSheet, - TargetMedia, DOCTYPE, + TargetMedia, UnitValue, DOCTYPE, }; pub use crate::locale::*; diff --git a/tests/html.rs b/tests/html_pm.rs similarity index 100% rename from tests/html.rs rename to tests/html_pm.rs diff --git a/tests/html_unit.rs b/tests/html_unit.rs new file mode 100644 index 00000000..9316af89 --- /dev/null +++ b/tests/html_unit.rs @@ -0,0 +1,223 @@ +use pagetop::prelude::*; + +use std::str::FromStr; + +#[pagetop::test] +async fn unit_value_empty_and_auto_and_zero_without_unit() { + assert_eq!(UnitValue::from_str("").unwrap(), UnitValue::None); + assert_eq!(UnitValue::from_str("auto").unwrap(), UnitValue::Auto); + assert_eq!(UnitValue::from_str("AUTO").unwrap(), UnitValue::Auto); + + // Cero sin unidad. + assert_eq!(UnitValue::from_str("0").unwrap(), UnitValue::Zero); + assert_eq!(UnitValue::from_str("+0").unwrap(), UnitValue::Zero); + assert_eq!(UnitValue::from_str("-0").unwrap(), UnitValue::Zero); +} + +#[pagetop::test] +async fn unit_value_absolute_integers_with_signs_and_spaces_and_case() { + // Positivos, negativos y con espacios. + assert_eq!(UnitValue::from_str("12px").unwrap(), UnitValue::Px(12)); + assert_eq!(UnitValue::from_str("-5pt").unwrap(), UnitValue::Pt(-5)); + assert_eq!(UnitValue::from_str(" 7 cm ").unwrap(), UnitValue::Cm(7)); + assert_eq!(UnitValue::from_str("+9 in").unwrap(), UnitValue::In(9)); + assert_eq!(UnitValue::from_str(" 13 mm ").unwrap(), UnitValue::Mm(13)); + assert_eq!(UnitValue::from_str("4 pc").unwrap(), UnitValue::Pc(4)); + + // Insensibilidad a mayúsculas. + assert_eq!(UnitValue::from_str("10PX").unwrap(), UnitValue::Px(10)); + assert_eq!(UnitValue::from_str("15Pt").unwrap(), UnitValue::Pt(15)); +} + +#[pagetop::test] +async fn unit_value_relative_floats_with_signs_and_spaces_and_case() { + assert_eq!( + UnitValue::from_str("1.25rem").unwrap(), + UnitValue::RelRem(1.25) + ); + assert_eq!( + UnitValue::from_str("-0.5em").unwrap(), + UnitValue::RelEm(-0.5) + ); + assert_eq!( + UnitValue::from_str(" 33% ").unwrap(), + UnitValue::RelPct(33.0) + ); + assert_eq!( + UnitValue::from_str(" -12.5 vh").unwrap(), + UnitValue::RelVh(-12.5) + ); + assert_eq!( + UnitValue::from_str(" 8.0 VW ").unwrap(), + UnitValue::RelVw(8.0) + ); +} + +#[pagetop::test] +async fn unit_value_whitespace_between_number_and_unit_is_allowed() { + // Hay espacio entre número y unidad (la implementación actual lo admite). + assert_eq!(UnitValue::from_str("12 px").unwrap(), UnitValue::Px(12)); + assert_eq!( + UnitValue::from_str("1.5 rem").unwrap(), + UnitValue::RelRem(1.5) + ); + assert_eq!( + UnitValue::from_str("25 %").unwrap(), + UnitValue::RelPct(25.0) + ); +} + +#[pagetop::test] +async fn unit_value_roundtrip_display_keeps_expected_format() { + let cases = [ + ("", UnitValue::None, ""), + ("auto", UnitValue::Auto, "auto"), + ("0", UnitValue::Zero, "0"), + ("12px", UnitValue::Px(12), "12px"), + ("-5pt", UnitValue::Pt(-5), "-5pt"), + ("7cm", UnitValue::Cm(7), "7cm"), + ("33%", UnitValue::RelPct(33.0), "33%"), + ("1.25rem", UnitValue::RelRem(1.25), "1.25rem"), + ("2em", UnitValue::RelEm(2.0), "2em"), + ("-0.5vh", UnitValue::RelVh(-0.5), "-0.5vh"), + ("8vw", UnitValue::RelVw(8.0), "8vw"), + ]; + + for (input, expected_value, expected_display) in cases { + let parsed = UnitValue::from_str(input).unwrap(); + assert_eq!( + parsed, expected_value, + "parsed mismatch for input `{input}`" + ); + assert_eq!( + parsed.to_string(), + expected_display, + "display mismatch for input `{input}`" + ); + } +} + +#[pagetop::test] +async fn unit_value_percentage_trimming_and_signs() { + assert_eq!( + UnitValue::from_str(" 12.5 % ").unwrap(), + UnitValue::RelPct(12.5) + ); + assert_eq!( + UnitValue::from_str("-0.0%").unwrap(), + UnitValue::RelPct(-0.0) + ); + assert_eq!( + UnitValue::from_str("+15%").unwrap(), + UnitValue::RelPct(15.0) + ); +} + +// ERRORES ESPERADOS (no cambiar los mensajes; con is_err() basta). + +#[pagetop::test] +async fn unit_value_errors_missing_unit_for_non_zero() { + assert!( + UnitValue::from_str("12").is_err(), + "non-zero without unit must error" + ); + assert!( + UnitValue::from_str(" -3 ").is_err(), + "non-zero without unit must error" + ); +} + +#[pagetop::test] +async fn unit_value_errors_decimals_in_absolute_units() { + assert!(UnitValue::from_str("1.5px").is_err()); + assert!(UnitValue::from_str("-2.0pt").is_err()); + assert!(UnitValue::from_str("+0.1cm").is_err()); +} + +#[pagetop::test] +async fn unit_value_errors_unknown_units_or_bad_percentages() { + // Unidad no soportada. + assert!(UnitValue::from_str("10ch").is_err()); + assert!(UnitValue::from_str("2q").is_err()); + // Falta número. + assert!(UnitValue::from_str("%").is_err()); + assert!(UnitValue::from_str(" % ").is_err()); +} + +#[pagetop::test] +async fn unit_value_errors_non_numeric_numbers() { + assert!(UnitValue::from_str("NaNem").is_err()); + // Decimal no permitido por FromStr. + assert!(UnitValue::from_str("1,5rem").is_err()); +} + +#[pagetop::test] +async fn unit_value_serde_deserialize_struct_and_array() { + use serde::Deserialize; + + #[derive(Deserialize, Debug, PartialEq)] + struct BoxStyle { + width: UnitValue, + height: UnitValue, + margin: UnitValue, + } + + let json = r#"{ "width": "12px", "height": "1.5rem", "margin": "0" }"#; + let s: BoxStyle = serde_json::from_str(json).unwrap(); + assert_eq!(s.width, UnitValue::Px(12)); + assert_eq!(s.height, UnitValue::RelRem(1.5)); + assert_eq!(s.margin, UnitValue::Zero); + + #[derive(Deserialize, Debug, PartialEq)] + struct Many { + values: Vec<UnitValue>, + } + + let json_arr = r#"{ "values": ["", "auto", "33%", "8vw", "7 cm", "-5pt"] }"#; + let m: Many = serde_json::from_str(json_arr).unwrap(); + assert_eq!( + m.values, + vec![ + UnitValue::None, + UnitValue::Auto, + UnitValue::RelPct(33.0), + UnitValue::RelVw(8.0), + UnitValue::Cm(7), + UnitValue::Pt(-5), + ] + ); +} + +#[pagetop::test] +async fn unit_value_accepts_dot5_and_1dot_shorthand_for_relatives() { + // `.5` y `1.` se parsean correctamente en relativas. + assert_eq!(UnitValue::from_str(".5em").unwrap(), UnitValue::RelEm(0.5)); + assert_eq!( + UnitValue::from_str("1.rem").unwrap(), + UnitValue::RelRem(1.0) + ); + assert_eq!(UnitValue::from_str("1.vh").unwrap(), UnitValue::RelVh(1.0)); + // Sin unidad debe seguir fallando. + assert!(UnitValue::from_str("1.").is_err()); +} + +#[pagetop::test] +async fn unit_value_display_keeps_minus_zero_for_relatives() { + // Comportamiento actual: f32 Display muestra "-0" si el valor es -0.0. + let v = UnitValue::RelEm(-0.0); + // Se acepta cualquiera de los dos formatos como válidos. + let s = v.to_string(); + assert!( + s == "-0em" || s == "0em", + "current Display prints `{s}` for -0.0; both are acceptable in tests" + ); +} + +#[pagetop::test] +async fn unit_value_rejects_non_decimal_notations() { + // Octal, los ceros a la izquierda (p.ej. `"020px"`) se interpretan en **base 10** (`20px`). + assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20)); + // Notación científica y bases no decimales (p.ej. `"1e3vw"`, `"0x10px"`) no están soportadas. + assert!(UnitValue::from_str("1e3vw").is_err()); + assert!(UnitValue::from_str("0x10px").is_err()); +} From d46de78367f96776b2ac90ca298706852bc2c891 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 13 Oct 2025 18:51:32 +0200 Subject: [PATCH 159/224] =?UTF-8?q?=E2=9C=A8=20(unit):=20A=C3=B1ade=20m?= =?UTF-8?q?=C3=A9todo=20`is=5Fnumeric`=20en=20`UnitValue`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/unit.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/html/unit.rs b/src/html/unit.rs index b6a9bc76..b67e8b17 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -106,6 +106,31 @@ pub enum UnitValue { RelVw(f32), } +impl UnitValue { + /// Indica si el valor es **numérico**. + /// + /// Devuelve `true` para `Zero` y las unidades absolutas/relativas, y `false` para + /// [`UnitValue::None`] y [`UnitValue::Auto`]. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop::prelude::*; + /// // Numéricos (incluido el cero sin unidad). + /// assert!(UnitValue::Zero.is_numeric()); + /// assert!(UnitValue::Px(0).is_numeric()); + /// assert!(UnitValue::Px(10).is_numeric()); + /// assert!(UnitValue::RelPct(33.0).is_numeric()); + /// // No numéricos. + /// assert!(!UnitValue::None.is_numeric()); + /// assert!(!UnitValue::Auto.is_numeric()); + /// ``` + #[inline] + pub const fn is_numeric(&self) -> bool { + !matches!(self, UnitValue::None | UnitValue::Auto) + } +} + /// Formatea la unidad como cadena CSS. /// /// Reglas: From 6368e71413fce6cf42d037f95d44162da24a884b Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Fri, 17 Oct 2025 18:14:20 +0200 Subject: [PATCH 160/224] =?UTF-8?q?=F0=9F=8E=A8=20(pagetop):=20Mejoras=20s?= =?UTF-8?q?encillas=20en=20doc.=20y=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component.rs | 2 +- src/config.rs | 18 +++++++-------- src/core/action/definition.rs | 3 ++- src/global.rs | 4 ++-- src/html/assets/favicon.rs | 4 ++-- src/html/unit.rs | 41 +++++++++++++++++------------------ src/locale.rs | 12 +++++----- tests/html_unit.rs | 4 ++-- 8 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/base/component.rs b/src/base/component.rs index 6c3b028f..8991d721 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,6 +1,6 @@ //! Componentes nativos proporcionados por PageTop. -use crate::AutoDefault; +use crate::prelude::*; use std::fmt; diff --git a/src/config.rs b/src/config.rs index f2fb9f7f..1607a874 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,9 +23,9 @@ //! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y PageTop intentará //! cargar *config/default.toml* si el archivo existe. //! -//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.ej. -//! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, -//! URLs o ajustes de rendimiento, sin afectar a los demás. +//! * Permite definir configuraciones específicas por entorno, garantizando que cada uno (p. ej., +//! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, URLs +//! o ajustes de rendimiento, sin afectar a los demás. //! //! 3. **config/local.{rm}.toml**, útil para configuraciones locales específicas de la máquina o de //! la ejecución: @@ -132,15 +132,15 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new( let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir)); // Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa - // por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production). + // por defecto DEFAULT_RUN_MODE (p. ej. PAGETOP_RUN_MODE=production). let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); Config::builder() // 1. Configuración común para todos los entornos (common.toml). .add_source(File::from(config_dir.join("common.toml")).required(false)) - // 2. Configuración específica del entorno (p.ej.: default.toml, production.toml). + // 2. Configuración específica del entorno (p. ej. default.toml o production.toml). .add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false)) - // 3. Configuración local reservada para cada entorno (p.ej.: local.default.toml). + // 3. Configuración local reservada para cada entorno (p. ej. local.default.toml). .add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false)) // 4. Configuración local común (local.toml). .add_source(File::from(config_dir.join("local.toml")).required(false)) @@ -206,7 +206,7 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new( /// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves /// opcionales pueden ser `Option<T>`. /// -/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.ej. `[blog]`) para +/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p. ej. `[blog]`) para /// evitar colisiones con otras librerías. /// /// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para @@ -229,9 +229,7 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new( macro_rules! include_config { ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { #[doc = concat!( - "Referencia y valores por defecto de los ajustes de configuración para [`", - stringify!($Settings_Type), - "`]." + "Instancia los ajustes de configuración para [`", stringify!($Settings_Type), "`]." )] #[doc = ""] #[doc = "Valores por defecto:"] diff --git a/src/core/action/definition.rs b/src/core/action/definition.rs index 9ec9af1f..7ebe4104 100644 --- a/src/core/action/definition.rs +++ b/src/core/action/definition.rs @@ -24,7 +24,8 @@ impl ActionKey { /// - `action_type_id`: Tipo de la acción. /// - `theme_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del tema asociado. /// - `referer_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del componente referido. - /// - `referer_id`: Opcional, identificador de la instancia (p.ej. para un formulario concreto). + /// - `referer_id`: Opcional, identificador de la instancia (p. ej. para asociar la acción a un + /// componente concreto). /// /// Esta clave permitirá seleccionar las funciones a ejecutar para ese tipo de acción, con /// filtros opcionales por tema, componente, o una instancia concreta según su identificador. diff --git a/src/global.rs b/src/global.rs index c8805a30..ee07d818 100644 --- a/src/global.rs +++ b/src/global.rs @@ -30,7 +30,7 @@ include_config!(SETTINGS: Settings => [ ]); #[derive(Debug, Deserialize)] -/// Ajustes para las secciones globales [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log) y +/// Tipos para las secciones globales [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log) y /// [`[server]`](Server) de [`SETTINGS`]. pub struct Settings { pub app: App, @@ -105,7 +105,7 @@ pub struct Server { pub bind_address: String, /// Puerto de escucha del servidor web. pub bind_port: u16, - /// Duración de la cookie de sesión en segundos (p.ej. `604_800` para una semana). + /// Duración de la cookie de sesión en segundos (p. ej., `604_800` para una semana). /// /// El valor `0` indica que la cookie permanecerá activa hasta que se cierre el navegador. pub session_lifetime: i64, diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index dce3e1bd..7598603c 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -63,7 +63,7 @@ impl Favicon { /// Le añade un icono genérico con atributo `sizes`, útil para indicar resoluciones específicas. /// /// El atributo `sizes` informa al navegador de las dimensiones de la imagen para que seleccione - /// el recurso más adecuado. Puede enumerar varias dimensiones separadas por espacios, p.ej. + /// el recurso más adecuado. Puede enumerar varias dimensiones separadas por espacios, p. ej. /// `"16x16 32x32 48x48"` o usar `any` para iconos escalables (SVG). /// /// No es imprescindible, pero puede mejorar la selección del icono más adecuado. @@ -73,7 +73,7 @@ impl Favicon { /// Le añade un *Apple Touch Icon*, usado por dispositivos iOS para las pantallas de inicio. /// - /// Se recomienda indicar también el tamaño, p.ej. `"256x256"`. + /// Se recomienda indicar también el tamaño, p. ej. `"256x256"`. pub fn with_apple_touch_icon(self, image: impl Into<String>, sizes: impl Into<String>) -> Self { self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None) } diff --git a/src/html/unit.rs b/src/html/unit.rs index b67e8b17..53319dc8 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -34,8 +34,8 @@ use std::str::FromStr; /// /// - Soporta unidades **absolutas** (`cm`, `in`, `mm`, `pc`, `pt`, `px`) y **relativas** (`em`, /// `rem`, `%`, `vh`, `vw`). -/// - `FromStr` para convertir desde texto (p.ej. `"12px"`, `"1.25rem"`, `"auto"`). -/// - `Display` para formatear a cadena (p.ej. `UnitValue::Px(12)` genera `"12px"`). +/// - `FromStr` para convertir desde texto (p. ej., `"12px"`, `"1.25rem"`, `"auto"`). +/// - `Display` para formatear a cadena (p. ej., `UnitValue::Px(12)` genera `"12px"`). /// - `Deserialize` delega en `FromStr`, garantizando una gramática única. /// /// ## Ejemplos @@ -54,8 +54,8 @@ use std::str::FromStr; /// /// ## Notas /// -/// - Las absolutas **no aceptan** decimales (p.ej., `"1.5px"` sería erróneo). -/// - Se aceptan signos `+`/`-` en todas las unidades (p.ej., `"-12px"`, `"+0.5em"`). +/// - Las absolutas **no aceptan** decimales (p. ej., `"1.5px"` sería erróneo). +/// - Se aceptan signos `+`/`-` en todas las unidades (p. ej., `"-12px"`, `"+0.5em"`). /// - La comparación de unidad es *case-insensitive* al interpretar el texto (`"PX"`, `"Px"`, …). /// - **Sobre píxeles**: Los píxeles (px) son relativos al dispositivo de visualización. En /// dispositivos con baja densidad de píxeles (dpi), 1px equivale a un píxel (punto) del @@ -69,7 +69,7 @@ use std::str::FromStr; /// - Ejemplo: si `:root { font-size: 16px }` y un contenedor tiene `font-size: 20px`, entonces /// dentro del contenedor `1em == 20px` pero `1rem == 16px`. /// - Uso típico: `rem` para tipografía y espaciados globales (consistencia al cambiar la base del -/// sitio); `em` para tamaños que deban escalar **con el propio componente** (p.ej., +/// sitio); `em` para tamaños que deban escalar **con el propio componente** (p. ej., /// `padding: 0.5em` que crece si el componente aumenta su `font-size`). /// - **Sobre el viewport**: Si el ancho de la ventana del navegador es de 50cm, 1vw equivale a /// 0.5cm (1vw siempre es 1% del ancho del viewport, independientemente del zoom del navegador o @@ -107,26 +107,25 @@ pub enum UnitValue { } impl UnitValue { - /// Indica si el valor es **numérico**. + /// Indica si el valor es **medible**, incluyendo `Zero` sin unidad. /// - /// Devuelve `true` para `Zero` y las unidades absolutas/relativas, y `false` para - /// [`UnitValue::None`] y [`UnitValue::Auto`]. + /// Devuelve `false` para [`UnitValue::None`] y [`UnitValue::Auto`]. /// /// # Ejemplos /// /// ```rust /// # use pagetop::prelude::*; /// // Numéricos (incluido el cero sin unidad). - /// assert!(UnitValue::Zero.is_numeric()); - /// assert!(UnitValue::Px(0).is_numeric()); - /// assert!(UnitValue::Px(10).is_numeric()); - /// assert!(UnitValue::RelPct(33.0).is_numeric()); + /// assert!(UnitValue::Zero.is_measurable()); + /// assert!(UnitValue::Px(0).is_measurable()); + /// assert!(UnitValue::Px(10).is_measurable()); + /// assert!(UnitValue::RelPct(33.0).is_measurable()); /// // No numéricos. - /// assert!(!UnitValue::None.is_numeric()); - /// assert!(!UnitValue::Auto.is_numeric()); + /// assert!(!UnitValue::None.is_measurable()); + /// assert!(!UnitValue::Auto.is_measurable()); /// ``` #[inline] - pub const fn is_numeric(&self) -> bool { + pub const fn is_measurable(&self) -> bool { !matches!(self, UnitValue::None | UnitValue::Auto) } } @@ -173,9 +172,9 @@ impl fmt::Display for UnitValue { /// - `""` para `UnitValue::None` /// - `"auto"` /// - **Cero sin unidad**: `"0"`, `"+0"`, `"-0"`, `"0.0"`, `"0."`, `".0"` para `UnitValue::Zero` -/// - Porcentaje: `"<n>%"` (p.ej. `"33%"`, `"33 %"`) -/// - Absolutas enteras: `"<entero><unidad>"`, p.ej. `"12px"`, `"-5pt"` -/// - Relativas decimales: `"<float><unidad>"`, p.ej. `"1.25rem"`, `"-0.5vh"`, `".5em"`, `"1.rem"` +/// - Porcentaje: `"<n>%"` (p. ej., `"33%"`, `"33 %"`) +/// - Absolutas enteras: `"<entero><unidad>"`, p. ej., `"12px"`, `"-5pt"` +/// - Relativas decimales: `"<float><unidad>"`, p. ej., `"1.25rem"`, `"-0.5vh"`, `".5em"`, `"1.rem"` /// /// (Se toleran espacios entre número y unidad: `"12 px"`, `"1.5 rem"`). /// @@ -191,9 +190,9 @@ impl fmt::Display for UnitValue { /// /// ## Errores de interpretación /// -/// - Falta la unidad cuando es necesaria (p.ej. `"12"`, excepto para el valor cero). -/// - Decimales en valores que deben ser absolutos (p.ej. `"1.5px"`). -/// - Unidades desconocidas (p.ej. `"10ch"`, no soportada aún). +/// - Falta la unidad cuando es necesaria (p. ej., `"12"`, excepto para el valor cero). +/// - Decimales en valores que deben ser absolutos (p. ej. `"1.5px"`). +/// - Unidades desconocidas (p. ej., `"10ch"`, no soportada aún). /// - Notación científica o bases no decimales: `"1e3vw"`, `"0x10px"` (no soportadas). Los ceros a /// la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`). /// diff --git a/src/locale.rs b/src/locale.rs index 7c913ec9..5c000f7c 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -181,8 +181,8 @@ pub enum LangMatch { /// Cuando el identificador de idioma es una cadena vacía. Unspecified, /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que - /// coincide exactamente con el identificador de idioma (p.ej. "es-ES"), o con el identificador - /// del idioma base (p.ej. "es"). + /// coincide exactamente con el identificador de idioma (p. ej. "es-ES"), o con el identificador + /// del idioma base (p. ej. "es"). Found(&'static LanguageIdentifier), /// Si el identificador de idioma no está entre los soportados por PageTop. Unsupported(String), @@ -205,13 +205,13 @@ impl LangMatch { return Self::Unspecified; } - // Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). + // Intenta aplicar coincidencia exacta con el código completo (p. ej. "es-MX"). let lang = language.to_ascii_lowercase(); if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) { return Self::Found(langid); } - // Si la variante regional no existe, retrocede al idioma base (p.ej. "es"). + // Si la variante regional no existe, retrocede al idioma base (p. ej. "es"). if let Some((base_lang, _)) = lang.split_once('-') { if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { return Self::Found(langid); @@ -375,13 +375,13 @@ impl L10n { } } - /// Añade un argumento `{$arg}` → `value` a la traducción. + /// Añade un argumento `{$arg}` => `value` a la traducción. pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self { self.args.insert(arg.into(), value.into()); self } - /// Añade varios argumentos a la traducción de una sola vez (p.ej. usando la macro [`hm!`], + /// Añade varios argumentos a la traducción de una sola vez (p. ej. usando la macro [`hm!`], /// también vec![("k", "v")], incluso un array de duplas u otras colecciones). pub fn with_args<I, K, V>(mut self, args: I) -> Self where diff --git a/tests/html_unit.rs b/tests/html_unit.rs index 9316af89..6acae935 100644 --- a/tests/html_unit.rs +++ b/tests/html_unit.rs @@ -215,9 +215,9 @@ async fn unit_value_display_keeps_minus_zero_for_relatives() { #[pagetop::test] async fn unit_value_rejects_non_decimal_notations() { - // Octal, los ceros a la izquierda (p.ej. `"020px"`) se interpretan en **base 10** (`20px`). + // Octal, los ceros a la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`). assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20)); - // Notación científica y bases no decimales (p.ej. `"1e3vw"`, `"0x10px"`) no están soportadas. + // Notación científica y bases no decimales (p. ej., `"1e3vw"`, `"0x10px"`) no están soportadas. assert!(UnitValue::from_str("1e3vw").is_err()); assert!(UnitValue::from_str("0x10px").is_err()); } From 5eab417b11a3e98f1cd7f4effcdd1de959c86f1e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 18 Oct 2025 21:33:29 +0200 Subject: [PATCH 161/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`add=5Fcompon?= =?UTF-8?q?ent`=20por=20`add=5Fchild`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- README.md | 2 +- examples/hello-name.rs | 2 +- examples/hello-world.rs | 2 +- extensions/pagetop-aliner/README.md | 4 ++-- extensions/pagetop-aliner/src/lib.rs | 4 ++-- src/base/component/block.rs | 2 +- src/base/component/intro.rs | 6 +++--- src/base/extension/welcome.rs | 10 +++++----- src/lib.rs | 2 +- src/response/page.rs | 23 +++++++++++------------ src/response/page/error.rs | 4 ++-- 12 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2926a69..bfc9067a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ internos pueden omitirse si no afectan al uso del proyecto. - [context] Generaliza los parámetros de contexto - [context] Define un `trait` común de contexto - Modifica tipos para atributos HTML a minúsculas -- Renombra `with_component` por `add_component` +- Renombra `with_component` por `add_child` ### Corregido diff --git a/README.md b/README.md index be720910..986db58b 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/examples/hello-name.rs b/examples/hello-name.rs index e1285d09..c6a82aaf 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,7 @@ async fn hello_name( ) -> ResultPage<Markup, ErrorPage> { let name = path.into_inner(); Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) + .add_child(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) .render() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index d56f2105..64817466 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 0560a067..43fb65a5 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -67,10 +67,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_theme("Aliner") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index b51800c2..edbb5040 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -68,10 +68,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_theme("Aliner") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 9a04c4ea..17af50c2 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -71,7 +71,7 @@ impl Block { } /// Añade un nuevo componente hijo al bloque. - pub fn add_component(mut self, component: impl Component) -> Self { + pub fn add_child(mut self, component: impl Component) -> Self { self.children .alter_child(ChildOp::Add(Child::with(component))); self diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 5d3440e9..7e5c3931 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -65,10 +65,10 @@ pub enum IntroOpening { /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() -/// .add_component( +/// .add_child( /// Block::new() /// .with_title(L10n::l("intro_custom_block_title")) -/// .add_component(Html::with(move |cx| { +/// .add_child(Html::with(move |cx| { /// html! { /// p { (L10n::l("intro_custom_paragraph_1").using(cx)) } /// p { (L10n::l("intro_custom_paragraph_2").using(cx)) } @@ -301,7 +301,7 @@ impl Intro { /// Añade un nuevo componente hijo a la intro. /// /// Si es un bloque ([`Block`]) aplica estilos específicos para destacarlo. - pub fn add_component(mut self, component: impl Component) -> Self { + pub fn add_child(mut self, component: impl Component) -> Self { self.children .alter_child(ChildOp::Add(Child::with(component))); self diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index d94afad7..b875163b 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -26,22 +26,22 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_title(L10n::l("welcome_title")) - .add_component( + .add_child( Intro::new() - .add_component( + .add_child( Block::new() .with_title(L10n::l("welcome_status_title")) - .add_component(Html::with(move |cx| { + .add_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_status_1").using(cx)) } p { (L10n::l("welcome_status_2").using(cx)) } } })), ) - .add_component( + .add_child( Block::new() .with_title(L10n::l("welcome_support_title")) - .add_component(Html::with(move |cx| { + .add_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_support_1").using(cx)) } p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } diff --git a/src/lib.rs b/src/lib.rs index de6d9e68..a2683495 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/src/response/page.rs b/src/response/page.rs index 1649d543..036c999c 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -93,29 +93,28 @@ impl Page { self } - /// **Obsoleto desde la versión 0.4.0**: usar [`add_component()`](Self::add_component) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `add_component()` instead")] + /// **Obsoleto desde la versión 0.4.0**: usar [`add_child()`](Self::add_child) en su lugar. + #[deprecated(since = "0.4.0", note = "Use `add_child()` instead")] pub fn with_component(self, component: impl Component) -> Self { - self.add_component(component) + self.add_child(component) } - /// **Obsoleto desde la versión 0.4.0**: usar [`add_component_in()`](Self::add_component_in) en - /// su lugar. - #[deprecated(since = "0.4.0", note = "Use `add_component_in()` instead")] + /// **Obsoleto desde la versión 0.4.0**: usar [`add_child_in()`](Self::add_child_in) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `add_child_in()` instead")] pub fn with_component_in(self, region_key: &'static str, component: impl Component) -> Self { - self.add_component_in(region_key, component) + self.add_child_in(region_key, component) } - /// Añade un componente a la región de contenido por defecto. - pub fn add_component(mut self, component: impl Component) -> Self { + /// Añade un componente hijo a la región de contenido por defecto. + pub fn add_child(mut self, component: impl Component) -> Self { self.context .alter_child_in(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } - /// Añade un componente en una región (`region_key`) de la página. - pub fn add_component_in(mut self, region_key: &'static str, component: impl Component) -> Self { + /// Añade un componente hijo en una región (`region_key`) de la página. + pub fn add_child_in(mut self, region_key: &'static str, component: impl Component) -> Self { self.context .alter_child_in(region_key, ChildOp::Add(Child::with(component))); self diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 50e1c77b..6fe6451f 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -34,7 +34,7 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) .with_layout("error") - .add_component(Html::with(move |_| error403.clone())) + .add_child(Html::with(move |_| error403.clone())) .render() { write!(f, "{}", page.into_string()) @@ -49,7 +49,7 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) .with_layout("error") - .add_component(Html::with(move |_| error404.clone())) + .add_child(Html::with(move |_| error404.clone())) .render() { write!(f, "{}", page.into_string()) From 708dc9244b057dbe3a034e501f855636feb7ded1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 19 Oct 2025 21:39:14 +0200 Subject: [PATCH 162/224] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Mejora=20macro=20`?= =?UTF-8?q?join=5Fopt!`=20y=20retoca=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component.rs | 5 +++++ src/util.rs | 30 ++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/core/component.rs b/src/core/component.rs index be9bbad1..a7faa2fb 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -10,4 +10,9 @@ pub use children::{Typed, TypedOp}; mod context; pub use context::{Context, ContextError, ContextOp, Contextual}; + +/// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. +/// +/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). Debe +/// devolver una referencia válida durante el renderizado. pub type FnPathByContext = fn(cx: &Context) -> &str; diff --git a/src/util.rs b/src/util.rs index f73cf78a..3d07361a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -70,14 +70,12 @@ macro_rules! join { }; } -/// Concatena los fragmentos no vacíos en un [`Option<String>`] con un separador opcional. +/// Concatena los fragmentos **no vacíos** en un [`Option<String>`] con un separador opcional. /// -/// Esta macro acepta cualquier número de fragmentos que implementen [`AsRef<str>`] para concatenar -/// todos los fragmentos no vacíos usando opcionalmente un separador. +/// Acepta cualquier número de fragmentos que implementen [`AsRef<str>`]. Si todos los fragmentos +/// están vacíos, devuelve `None`. /// -/// Si todos los fragmentos están vacíos, devuelve [`None`]. -/// -/// # Ejemplo +/// # Ejemplos /// /// ```rust /// # use pagetop::prelude::*; @@ -90,7 +88,7 @@ macro_rules! join { /// assert_eq!(result_without_separator, Some("HelloWorld".to_string())); /// /// // Devuelve `None` si todos los fragmentos están vacíos. -/// let result_empty = join_opt!(["", "", ""]); +/// let result_empty = join_opt!(["", "", ""]; ","); /// assert_eq!(result_empty, None); /// ``` #[macro_export] @@ -100,12 +98,16 @@ macro_rules! join_opt { (!s.is_empty()).then_some(s) }}; ([$($arg:expr),* $(,)?]; $separator:expr) => {{ - let s = [$($arg),*] - .iter() - .filter(|&item| !item.is_empty()) - .cloned() - .collect::<Vec<_>>() - .join($separator); + let sep = ($separator).as_ref(); + let mut s = String::new(); + for part in [ $( ($arg).as_ref() ),* ] { + if !(part as &str).is_empty() { + if !s.is_empty() { + s.push_str(sep); + } + s.push_str(part); + } + } (!s.is_empty()).then_some(s) }}; } @@ -153,7 +155,7 @@ macro_rules! join_pair { }}; } -/// Concatena varios fragmentos en un [`Option<String>`] si ninguno está vacío. +/// Concatena varios fragmentos en un [`Option<String>`] **si ninguno está vacío**. /// /// Si alguno de los fragmentos, que deben implementar [`AsRef<str>`], está vacío, devuelve /// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados. From 44ddfa4a5136897da3d3561592041caee7528d8e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 19 Oct 2025 21:40:42 +0200 Subject: [PATCH 163/224] =?UTF-8?q?=E2=9C=A8=20Simplifica=20`Display`=20co?= =?UTF-8?q?n=20`f.write=5Fstr()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html/unit.rs | 6 +++--- src/response/page/error.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/html/unit.rs b/src/html/unit.rs index 53319dc8..4a99de16 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -145,9 +145,9 @@ impl UnitValue { impl fmt::Display for UnitValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - UnitValue::None => write!(f, ""), - UnitValue::Auto => write!(f, "auto"), - UnitValue::Zero => write!(f, "0"), + UnitValue::None => Ok(()), + UnitValue::Auto => f.write_str("auto"), + UnitValue::Zero => f.write_str("0"), // Valor absoluto. UnitValue::Cm(av) => write!(f, "{av}cm"), UnitValue::In(av) => write!(f, "{av}in"), diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 6fe6451f..9945a948 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -24,9 +24,9 @@ impl Display for ErrorPage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // Error 304. - ErrorPage::NotModified(_) => write!(f, "Not Modified"), + ErrorPage::NotModified(_) => f.write_str("Not Modified"), // Error 400. - ErrorPage::BadRequest(_) => write!(f, "Bad Client Data"), + ErrorPage::BadRequest(_) => f.write_str("Bad Client Data"), // Error 403. ErrorPage::AccessDenied(request) => { let mut error_page = Page::new(request.clone()); @@ -39,7 +39,7 @@ impl Display for ErrorPage { { write!(f, "{}", page.into_string()) } else { - write!(f, "Access Denied") + f.write_str("Access Denied") } } // Error 404. @@ -54,15 +54,15 @@ impl Display for ErrorPage { { write!(f, "{}", page.into_string()) } else { - write!(f, "Not Found") + f.write_str("Not Found") } } // Error 412. - ErrorPage::PreconditionFailed(_) => write!(f, "Precondition Failed"), + ErrorPage::PreconditionFailed(_) => f.write_str("Precondition Failed"), // Error 500. - ErrorPage::InternalError(_) => write!(f, "Internal Error"), + ErrorPage::InternalError(_) => f.write_str("Internal Error"), // Error 504. - ErrorPage::Timeout(_) => write!(f, "Timeout"), + ErrorPage::Timeout(_) => f.write_str("Timeout"), } } } From d6e176cc197d94d0c9d95222a9913551fe1033eb Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 19 Oct 2025 21:47:19 +0200 Subject: [PATCH 164/224] =?UTF-8?q?=E2=9E=95=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?dependencia=20`serde`=20y=20edita=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 3 ++- README.md | 2 +- extensions/pagetop-bootsier/Cargo.toml | 3 ++- extensions/pagetop-bootsier/README.md | 6 +++--- extensions/pagetop-bootsier/src/lib.rs | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1b09f9d..2f9fa42e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1593,6 +1593,7 @@ version = "0.0.18" dependencies = [ "pagetop", "pagetop-build", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d17082b..b1bae0a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ actix-web = { workspace = true, default-features = true } actix-session = { version = "0.11", features = ["cookie-session"] } actix-web-files = { package = "actix-files", version = "0.6" } -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true pagetop-macros.workspace = true pagetop-statics.workspace = true @@ -78,6 +78,7 @@ authors = ["Manuel Cillero <manuel@cillero.es>"] [workspace.dependencies] actix-web = { version = "4.11", default-features = false } +serde = { version = "1.0", features = ["derive"] } # Helpers pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } diff --git a/README.md b/README.md index 986db58b..9a12c845 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui es un tema para demos y pruebas que muestra esquemáticamente la composición de las páginas HTML. * **[pagetop-bootsier](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier)**, - tema basado en [Bootstrap](https://getbootstrap.com) para ofrecer su catálogo de estilos y + tema basado en [Bootstrap](https://getbootstrap.com) para integrar su catálogo de estilos y componentes flexibles. diff --git a/extensions/pagetop-bootsier/Cargo.toml b/extensions/pagetop-bootsier/Cargo.toml index 11306b63..6df6cf69 100644 --- a/extensions/pagetop-bootsier/Cargo.toml +++ b/extensions/pagetop-bootsier/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.18" edition = "2021" description = """ - Tema de PageTop basado en Bootstrap para dar vida a tus diseños web. + Tema de PageTop basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles. """ categories = ["web-programming", "gui"] keywords = ["pagetop", "theme", "bootstrap", "css", "js"] @@ -16,6 +16,7 @@ authors.workspace = true [dependencies] pagetop.workspace = true +serde.workspace = true [build-dependencies] pagetop-build.workspace = true diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 7495bc22..84e11b57 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -2,7 +2,7 @@ <h1>PageTop Bootsier</h1> -<p>Tema de <strong>PageTop</strong> basado en Bootstrap para dar vida a tus diseños web.</p> +<p>Tema de <strong>PageTop</strong> basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles.</p> [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) @@ -67,10 +67,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_theme("Bootsier") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index a6583333..777f71e1 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -3,7 +3,7 @@ <h1>PageTop Bootsier</h1> -<p>Tema de <strong>PageTop</strong> basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.</p> +<p>Tema de <strong>PageTop</strong> basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles.</p> [![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) From 8978506c39b8eee428875c9766c5d2460d30aa09 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 19 Oct 2025 21:57:15 +0200 Subject: [PATCH 165/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?m=C3=A1s=20componentes=20y=20repasa=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Se incorpora nuevo componente Dropdown. - Se crea un componente Navbar con soporte para marca, elementos de navegación. - Se implementa el componente Offcanvas con opciones de posición, visibilidad y fondo personalizables. - Mejora el manejo de imágenes con un nuevo componente de Image. - Se reorganizan los componentes del tema para una mejor estructura y usabilidad. --- extensions/pagetop-bootsier/build.rs | 2 +- extensions/pagetop-bootsier/src/config.rs | 41 +++ extensions/pagetop-bootsier/src/lib.rs | 30 +- .../src/locale/en-US/bootsier.ftl | 5 + .../src/locale/en-US/components.ftl | 5 + .../src/locale/en-US/regions.ftl | 9 + .../src/locale/es-ES/bootsier.ftl | 5 + .../src/locale/es-ES/components.ftl | 5 + .../src/locale/es-ES/regions.ftl | 9 + extensions/pagetop-bootsier/src/theme.rs | 27 ++ extensions/pagetop-bootsier/src/theme/aux.rs | 18 ++ .../pagetop-bootsier/src/theme/aux/border.rs | 202 ++++++++++++ .../src/theme/aux/breakpoint.rs | 127 ++++++++ .../pagetop-bootsier/src/theme/aux/color.rs | 155 +++++++++ .../pagetop-bootsier/src/theme/aux/opacity.rs | 109 +++++++ .../pagetop-bootsier/src/theme/aux/rounded.rs | 214 +++++++++++++ .../pagetop-bootsier/src/theme/container.rs | 269 ++++++++++++++++ .../pagetop-bootsier/src/theme/dropdown.rs | 5 + .../src/theme/dropdown/component.rs | 99 ++++++ .../src/theme/dropdown/item.rs | 109 +++++++ .../pagetop-bootsier/src/theme/image.rs | 186 +++++++++++ .../pagetop-bootsier/src/theme/navbar.rs | 17 + .../src/theme/navbar/brand.rs | 104 ++++++ .../src/theme/navbar/button_toggler.rs | 73 +++++ .../src/theme/navbar/component.rs | 233 ++++++++++++++ .../src/theme/navbar/content.rs | 69 ++++ .../pagetop-bootsier/src/theme/navbar/item.rs | 113 +++++++ .../pagetop-bootsier/src/theme/navbar/nav.rs | 75 +++++ .../pagetop-bootsier/src/theme/offcanvas.rs | 302 ++++++++++++++++++ 29 files changed, 2608 insertions(+), 9 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/config.rs create mode 100644 extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/en-US/components.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/en-US/regions.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/es-ES/components.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl create mode 100644 extensions/pagetop-bootsier/src/theme.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux/border.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux/color.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux/opacity.rs create mode 100644 extensions/pagetop-bootsier/src/theme/aux/rounded.rs create mode 100644 extensions/pagetop-bootsier/src/theme/container.rs create mode 100644 extensions/pagetop-bootsier/src/theme/dropdown.rs create mode 100644 extensions/pagetop-bootsier/src/theme/dropdown/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/dropdown/item.rs create mode 100644 extensions/pagetop-bootsier/src/theme/image.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/brand.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/content.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/item.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/nav.rs create mode 100644 extensions/pagetop-bootsier/src/theme/offcanvas.rs diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs index 5945660a..a96301c5 100644 --- a/extensions/pagetop-bootsier/build.rs +++ b/extensions/pagetop-bootsier/build.rs @@ -15,5 +15,5 @@ fn main() -> std::io::Result<()> { fn bootstrap_js_files(path: &Path) -> bool { // No filtra durante el desarrollo, solo en la compilación "release". env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release" - || path.file_name().map_or(false, |n| n == "bootstrap.min.js") + || path.file_name().is_some_and(|n| n == "bootstrap.min.js") } diff --git a/extensions/pagetop-bootsier/src/config.rs b/extensions/pagetop-bootsier/src/config.rs new file mode 100644 index 00000000..6c2365ba --- /dev/null +++ b/extensions/pagetop-bootsier/src/config.rs @@ -0,0 +1,41 @@ +//! Opciones de configuración del tema. +//! +//! Ejemplo: +//! +//! ```toml +//! [bootsier] +//! max_width = "90rem" +//! ``` +//! +//! Uso: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! use pagetop_bootsier::config; +//! +//! assert_eq!(config::SETTINGS.bootsier.max_width, UnitValue::Px(1440)); +//! ``` +//! +//! Consulta [`pagetop::config`] para ver cómo PageTop lee los archivos de configuración y aplica +//! los valores a los ajustes. + +use pagetop::prelude::*; + +use serde::Deserialize; + +include_config!(SETTINGS: Settings => [ + // [bootsier] + "bootsier.max_width" => "1440px", +]); + +#[derive(Debug, Deserialize)] +/// Tipos para la sección [`[bootsier]`](Bootsier) de [`SETTINGS`]. +pub struct Settings { + pub bootsier: Bootsier, +} +#[derive(Debug, Deserialize)] +/// Sección `[bootsier]` de la configuración. Forma parte de [`Settings`]. +pub struct Bootsier { + /// Ancho máximo predeterminado para la página, por ejemplo "100%" o "90rem". + pub max_width: UnitValue, +} diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 777f71e1..a0e6c703 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -68,10 +68,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) .with_theme("Bootsier") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -80,18 +80,32 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { ``` */ +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + use pagetop::prelude::*; -/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. -pub type BootsierRegion = ThemeRegion; +include_locales!(LOCALES_BOOTSIER); // Versión de la librería Bootstrap. const BOOTSTRAP_VERSION: &str = "5.3.8"; -/// Tema basado en [Bootstrap](https://getbootstrap.com/) para los componentes base de PageTop. -/// -/// Ofrece composición de páginas *responsive*, utilidades y componentes listos para usar, con -/// estilos coherentes y enfoque en accesibilidad. +pub mod config; + +pub mod theme; + +/// *Prelude* del tema. +pub mod prelude { + pub use crate::config::*; + pub use crate::theme::aux::*; + pub use crate::theme::*; +} + +/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. +pub type BootsierRegion = ThemeRegion; + +/// Implementa el tema. pub struct Bootsier; impl Extension for Bootsier { diff --git a/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl b/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl new file mode 100644 index 00000000..0e8969cd --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl @@ -0,0 +1,5 @@ +e404-description = Oops! Page Not Found +e404-message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable. +e500-description = Oops! Unexpected Error +e500-message = We're having an issue. Please report this error to an administrator. +back-homepage = Back to homepage diff --git a/extensions/pagetop-bootsier/src/locale/en-US/components.ftl b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl new file mode 100644 index 00000000..83dde396 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl @@ -0,0 +1,5 @@ +# Offcanvas +close = Close + +# Navbar +toggle = Toggle navigation diff --git a/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl b/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl new file mode 100644 index 00000000..f3b76e22 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl @@ -0,0 +1,9 @@ +header = Header +nav_branding = Navigation branding region +nav_main = Main navigation region +nav_additional = Additional navigation region (eg search form, social icons, etc) +breadcrumb = Breadcrumb +content = Main content +sidebar_first = Sidebar first +sidebar_second = Sidebar second +footer = Footer diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl new file mode 100644 index 00000000..998b54f2 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl @@ -0,0 +1,5 @@ +e404-description = ¡Vaya! Página No Encontrada +e404-message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente. +e500-description = ¡Vaya! Error Inesperado +e500-message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador. +back-homepage = Volver al inicio diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl new file mode 100644 index 00000000..1ae97888 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl @@ -0,0 +1,5 @@ +# Offcanvas +close = Cerrar + +# Navbar +toggle = Mostrar/ocultar navegación diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl new file mode 100644 index 00000000..674fc4b1 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl @@ -0,0 +1,9 @@ +header = Cabecera +nav_branding = Navegación y marca +nav_main = Navegación principal +nav_additional = Navegación adicional (p.e. formulario de búsqueda, iconos sociales, etc.) +breadcrumb = Ruta de posicionamiento +content = Contenido principal +sidebar_first = Barra lateral primera +sidebar_second = Barra lateral segunda +footer = Pie diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs new file mode 100644 index 00000000..51073d43 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -0,0 +1,27 @@ +//! Definiciones y componentes del tema. + +pub mod aux; + +// Container. +mod container; +pub use container::{Container, ContainerType}; + +// Dropdown. +pub mod dropdown; +#[doc(inline)] +pub use dropdown::Dropdown; + +// Image. +mod image; +pub use image::{Image, ImageSize}; + +// Navbar. +pub mod navbar; +#[doc(inline)] +pub use navbar::{Navbar, NavbarToggler}; + +// Offcanvas. +mod offcanvas; +pub use offcanvas::{ + Offcanvas, OffcanvasBackdrop, OffcanvasBodyScroll, OffcanvasPlacement, OffcanvasVisibility, +}; diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs new file mode 100644 index 00000000..72ebf066 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -0,0 +1,18 @@ +//! Coleción de elementos auxiliares de Bootstrap para Bootsier. + +mod breakpoint; +pub use breakpoint::BreakPoint; + +mod color; +pub use color::Color; +pub use color::{BgColor, BorderColor, TextColor}; + +mod opacity; +pub use opacity::Opacity; +pub use opacity::{BgOpacity, BorderOpacity, TextOpacity}; + +mod border; +pub use border::{Border, BorderSize}; + +mod rounded; +pub use rounded::{Rounded, RoundedRadius}; diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs new file mode 100644 index 00000000..840f03c7 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -0,0 +1,202 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +use std::fmt; + +// **< BorderSize >********************************************************************************* + +/// Tamaño (**ancho**) para los bordes ([`Border`]). +/// +/// Mapea a `border`, `border-0` y `border-{1..5}`: +/// +/// - `None` no añade clase (devuelve `""`). +/// - `Default` genera `border` (borde por defecto del tema). +/// - `Zero` genera `border-0` (sin borde). +/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente). +#[derive(AutoDefault)] +pub enum BorderSize { + #[default] + None, + Default, + Zero, + Scale1, + Scale2, + Scale3, + Scale4, + Scale5, +} + +impl BorderSize { + #[rustfmt::skip] + fn to_class(&self, prefix: impl AsRef<str>) -> String { + match self { + Self::None => String::new(), + Self::Default => String::from(prefix.as_ref()), + Self::Zero => join!(prefix, "-0"), + Self::Scale1 => join!(prefix, "-1"), + Self::Scale2 => join!(prefix, "-2"), + Self::Scale3 => join!(prefix, "-3"), + Self::Scale4 => join!(prefix, "-4"), + Self::Scale5 => join!(prefix, "-5"), + } + } +} + +impl fmt::Display for BorderSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_class("border")) + } +} + +// **< Border >************************************************************************************* + +/// Agrupa propiedades para crear **bordes**. +/// +/// Permite: +/// +/// - Definir un tamaño **global** para todo el borde (`size`). +/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, +/// respetando LTR/RTL). +/// - Aplicar un **color** al borde (`BorderColor`). +/// - Aplicar un nivel de **opacidad** (`BorderOpacity`). +/// +/// # Comportamiento aditivo / sustractivo +/// +/// - **Aditivo**: basta con crear un borde sin tamaño con `Border::new()` para ir añadiendo cada +/// lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. +/// +/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando +/// `Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. +/// +/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. +/// +/// # Ejemplos +/// +/// **Borde global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = Border::with(BorderSize::Scale2); +/// assert_eq!(b.to_string(), "border-2"); +/// ``` +/// +/// **Aditivo (solo borde superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = Border::new().with_top(BorderSize::Scale1); +/// assert_eq!(b.to_string(), "border-top-1"); +/// ``` +/// +/// **Sustractivo (borde global menos el superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = Border::with(BorderSize::Default).with_top(BorderSize::Zero); +/// assert_eq!(b.to_string(), "border border-top-0"); +/// ``` +/// +/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); +/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = Border::with(BorderSize::Default) // Borde global por defecto. +/// .with_top(BorderSize::Zero) // Quita borde superior. +/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. +/// .with_color(BorderColor::Theme(Color::Primary)) +/// .with_opacity(BorderOpacity::Theme(Opacity::Half)); +/// +/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Border { + size : BorderSize, + top : BorderSize, + end : BorderSize, + bottom : BorderSize, + start : BorderSize, + color : BorderColor, + opacity: BorderOpacity, +} + +impl Border { + /// Prepara un borde **sin tamaño global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea un borde **con tamaño global** (`size`). + pub fn with(size: BorderSize) -> Self { + Self::default().with_size(size) + } + + // **< Border BUILDER >************************************************************************* + + /// Establece el tamaño global del borde (`border*`). + pub fn with_size(mut self, size: BorderSize) -> Self { + self.size = size; + self + } + + /// Establece el tamaño del borde superior (`border-top-*`). + pub fn with_top(mut self, size: BorderSize) -> Self { + self.top = size; + self + } + + /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, size: BorderSize) -> Self { + self.end = size; + self + } + + /// Establece el tamaño del borde inferior (`border-bottom-*`). + pub fn with_bottom(mut self, size: BorderSize) -> Self { + self.bottom = size; + self + } + + /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. + pub fn with_start(mut self, size: BorderSize) -> Self { + self.start = size; + self + } + + /// Establece el **color** del borde. + pub fn with_color(mut self, color: BorderColor) -> Self { + self.color = color; + self + } + + /// Establece la **opacidad** del borde. + pub fn with_opacity(mut self, opacity: BorderOpacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Border { + /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, *color* y + /// *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + join_opt!([ + self.size.to_string(), + self.top.to_class("border-top"), + self.end.to_class("border-end"), + self.bottom.to_class("border-bottom"), + self.start.to_class("border-start"), + self.color.to_string(), + self.opacity.to_string(), + ]; " ") + .unwrap_or_default() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs new file mode 100644 index 00000000..963bb3b5 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -0,0 +1,127 @@ +use pagetop::prelude::*; + +use std::fmt; + +/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. +/// +/// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o +/// `XXL`, respectivamente. +/// - `""` (cadena vacía) para `None`. +/// - `"fluid"` para las variantes `Fluid` y `FluidMax(_)`, útil para modelar `container-fluid` en +/// [`Container`](crate::theme::Container). Se debe tener en cuenta que `"fluid"` **no** es un +/// sufijo de *breakpoint*. Para construir clases válidas con prefijo (p. ej., `"col-md"`), se +/// recomienda usar `to_class()` (Self::to_class) o `try_class()` (Self::try_class). +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// assert_eq!(BreakPoint::MD.to_string(), "md"); +/// assert_eq!(BreakPoint::None.to_string(), ""); +/// assert_eq!(BreakPoint::Fluid.to_string(), "fluid"); +/// +/// // Forma correcta para clases con prefijo: +/// //assert_eq!(BreakPoint::MD.to_class("col"), "col-md"); +/// //assert_eq!(BreakPoint::Fluid.to_class("offcanvas"), "offcanvas"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum BreakPoint { + /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. + #[default] + None, + /// **576px o más** - Dispositivos pequeños: teléfonos en modo horizontal. + SM, + /// **768px o más** - Dispositivos medianos: tabletas. + MD, + /// **992px o más** - Dispositivos grandes: puestos de escritorio. + LG, + /// **1200px o más** - Dispositivos muy grandes: puestos de escritorio grandes. + XL, + /// **1400px o más** - Dispositivos extragrandes: puestos de escritorio más grandes. + XXL, + /// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo. + Fluid, + /// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo hasta + /// un ancho máximo indicado. + FluidMax(UnitValue) +} + +impl BreakPoint { + /// Comprueba si es un punto de ruptura efectivo. + /// + /// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso. + #[inline] + pub const fn is_breakpoint(&self) -> bool { + matches!(self, Self::SM | Self::MD | Self::LG | Self::XL | Self::XXL) + } + + /// Genera un nombre de clase CSS basado en el punto de ruptura. + /// + /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), concatena + /// el prefijo, un guion (`-`) y el sufijo asociado. En otro caso devuelve únicamente el + /// prefijo. + /// + /// # Ejemplo + /// + /// ```rust + /// let breakpoint = BreakPoint::MD; + /// let class = breakpoint.to_class("col"); + /// assert_eq!(class, "col-md".to_string()); + /// + /// let breakpoint = BreakPoint::Fluid; + /// let class = breakpoint.to_class("offcanvas"); + /// assert_eq!(class, "offcanvas".to_string()); + /// ``` + #[inline] + pub fn to_class(&self, prefix: impl AsRef<str>) -> String { + if self.is_breakpoint() { + join!(prefix, "-", self.to_string()) + } else { + String::from(prefix.as_ref()) + } + } + + /// Intenta generar un nombre de clase CSS basado en el punto de ruptura. + /// + /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), devuelve + /// `Some(String)` concatenando el prefijo, un guion (`-`) y el sufijo asociado. En otro caso + /// devuelve `None`. + /// + /// # Ejemplo + /// + /// ```rust + /// let breakpoint = BreakPoint::MD; + /// let class = breakpoint.try_class("col"); + /// assert_eq!(class, Some("col-md".to_string())); + /// + /// let breakpoint = BreakPoint::Fluid; + /// let class = breakpoint.try_class("navbar-expanded"); + /// assert_eq!(class, None); + /// ``` + #[inline] + pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> { + if self.is_breakpoint() { + Some(join!(prefix, "-", self.to_string())) + } else { + None + } + } +} + +#[rustfmt::skip] +impl fmt::Display for BreakPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => Ok(()), + Self::SM => f.write_str("sm"), + Self::MD => f.write_str("md"), + Self::LG => f.write_str("lg"), + Self::XL => f.write_str("xl"), + Self::XXL => f.write_str("xxl"), + // Devuelven "fluid" (para modelar `container-fluid`). + Self::Fluid => f.write_str("fluid"), + Self::FluidMax(_) => f.write_str("fluid"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs new file mode 100644 index 00000000..3a288550 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -0,0 +1,155 @@ +use pagetop::prelude::*; + +use std::fmt; + +// **< Color >************************************************************************************** + +/// Paleta de colores **temáticos**. +/// +/// Equivalente a los nombres estándar de Bootstrap (`primary`, `secondary`, `success`, etc.). Sirve +/// como base para componer clases de fondo ([`BgColor`]), borde ([`BorderColor`]) y texto +/// ([`TextColor`]). +#[derive(AutoDefault)] +pub enum Color { + #[default] + Primary, + Secondary, + Success, + Info, + Warning, + Danger, + Light, + Dark, +} + +#[rustfmt::skip] +impl fmt::Display for Color { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Primary => f.write_str("primary"), + Self::Secondary => f.write_str("secondary"), + Self::Success => f.write_str("success"), + Self::Info => f.write_str("info"), + Self::Warning => f.write_str("warning"), + Self::Danger => f.write_str("danger"), + Self::Light => f.write_str("light"), + Self::Dark => f.write_str("dark"), + } + } +} + +// **< BgColor >************************************************************************************ + +/// Colores de fondo (`bg-*`). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Body*` usa fondos predefinidos del tema (`bg-body`, `bg-body-secondary`, `bg-body-tertiary`). +/// - `Theme(Color)` genera `bg-{color}` (p. ej., `bg-primary`). +/// - `Subtle(Color)` genera `bg-{color}-subtle` (tono suave). +/// - `Black` y `White` son colores explícitos. +/// - `Transparent` no aplica color de fondo (`bg-transparent`). +#[derive(AutoDefault)] +pub enum BgColor { + #[default] + Default, + Body, + BodySecondary, + BodyTertiary, + Theme(Color), + Subtle(Color), + Black, + White, + Transparent, +} + +#[rustfmt::skip] +impl fmt::Display for BgColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Body => f.write_str("bg-body"), + Self::BodySecondary => f.write_str("bg-body-secondary"), + Self::BodyTertiary => f.write_str("bg-body-tertiary"), + Self::Theme(c) => write!(f, "bg-{c}"), + Self::Subtle(c) => write!(f, "bg-{c}-subtle"), + Self::Black => f.write_str("bg-black"), + Self::White => f.write_str("bg-white"), + Self::Transparent => f.write_str("bg-transparent"), + } + } +} + +// **< BorderColor >******************************************************************************** + +/// Colores (`border-*`) para los bordes ([`Border`](crate::theme::aux::Border)). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Theme(Color)` genera `border-{color}`. +/// - `Subtle(Color)` genera `border-{color}-subtle` (versión suavizada). +/// - `Black` y `White` son colores explícitos. +#[derive(AutoDefault)] +pub enum BorderColor { + #[default] + Default, + Theme(Color), + Subtle(Color), + Black, + White, +} + +#[rustfmt::skip] +impl fmt::Display for BorderColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(c) => write!(f, "border-{c}"), + Self::Subtle(c) => write!(f, "border-{c}-subtle"), + Self::Black => f.write_str("border-black"), + Self::White => f.write_str("border-white"), + } + } +} + +// **< TextColor >********************************************************************************** + +/// Colores de texto y fondos de texto (`text-*`). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Body*` aplica colores predefinidos del tema (`text-body`, `text-body-emphasis`, +/// `text-body-secondary`, `text-body-tertiary`). +/// - `Theme(Color)` genera `text-{color}`. +/// - `Emphasis(Color)` genera `text-{color}-emphasis` (contraste mayor acorde al tema). +/// - `Background(Color)` genera `text-bg-{color}` (para color de fondo del texto). +/// - `Black` y `White` son colores explícitos. +#[derive(AutoDefault)] +pub enum TextColor { + #[default] + Default, + Body, + BodyEmphasis, + BodySecondary, + BodyTertiary, + Theme(Color), + Emphasis(Color), + Background(Color), + Black, + White, +} + +#[rustfmt::skip] +impl fmt::Display for TextColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Body => f.write_str("text-body"), + Self::BodyEmphasis => f.write_str("text-body-emphasis"), + Self::BodySecondary => f.write_str("text-body-secondary"), + Self::BodyTertiary => f.write_str("text-body-tertiary"), + Self::Theme(c) => write!(f, "text-{c}"), + Self::Emphasis(c) => write!(f, "text-{c}-emphasis"), + Self::Background(c) => write!(f, "text-bg-{c}"), + Self::Black => f.write_str("text-black"), + Self::White => f.write_str("text-white"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/opacity.rs b/extensions/pagetop-bootsier/src/theme/aux/opacity.rs new file mode 100644 index 00000000..96724d91 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/opacity.rs @@ -0,0 +1,109 @@ +use pagetop::prelude::*; + +use std::fmt; + +// **< Opacity >************************************************************************************ + +/// Niveles de **opacidad** (`opacity-*`). +/// +/// Se usa para modular la transparencia del color de fondo `bg-opacity-*` ([`BgOpacity`]), borde +/// `border-opacity-*` ([`BorderOpacity`]) o texto `text-opacity-*` ([`TextOpacity`]), según las +/// siguientes equivalencias: +/// +/// - `Opaque` => `opacity-100` (100% de opacidad). +/// - `SemiOpaque` => `opacity-75` (75%). +/// - `Half` => `opacity-50` (50%). +/// - `SemiTransparent` => `opacity-25` (25%). +/// - `AlmostTransparent` => `opacity-10` (10%). +/// - `Transparent` => `opacity-0` (0%, totalmente transparente). +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum Opacity { + #[default] + Opaque, // 100% + SemiOpaque, // 75% + Half, // 50% + SemiTransparent, // 25% + AlmostTransparent, // 10% + Transparent, // 0% +} + +#[rustfmt::skip] +impl fmt::Display for Opacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Opaque => f.write_str("opacity-100"), + Self::SemiOpaque => f.write_str("opacity-75"), + Self::Half => f.write_str("opacity-50"), + Self::SemiTransparent => f.write_str("opacity-25"), + Self::AlmostTransparent => f.write_str("opacity-10"), + Self::Transparent => f.write_str("opacity-0"), + } + } +} + +// **< BgOpacity >********************************************************************************** + +/// Opacidad para el fondo (`bg-opacity-*`). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Theme(Opacity)` genera `bg-{opacity}` (p. ej., `bg-opacity-50`). +#[derive(AutoDefault)] +pub enum BgOpacity { + #[default] + Default, + Theme(Opacity), +} + +impl fmt::Display for BgOpacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(o) => write!(f, "bg-{o}"), + } + } +} + +// **< BorderOpacity >****************************************************************************** + +/// Opacidad (`border-opacity-*`) para los bordes ([`Border`](crate::theme::aux::Border)). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Theme(Opacity)` genera `border-{opacity}` (p. ej., `border-opacity-25`). +#[derive(AutoDefault)] +pub enum BorderOpacity { + #[default] + Default, + Theme(Opacity), +} + +impl fmt::Display for BorderOpacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(o) => write!(f, "border-{o}"), + } + } +} + +// **< TextOpacity >******************************************************************************** + +/// Opacidad para el texto (`text-opacity-*`). +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Theme(Opacity)` genera `text-{opacity}` (p. ej., `text-opacity-100`). +#[derive(AutoDefault)] +pub enum TextOpacity { + #[default] + Default, + Theme(Opacity), +} + +impl fmt::Display for TextOpacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(o) => write!(f, "text-{o}"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs new file mode 100644 index 00000000..f7f6e3fe --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -0,0 +1,214 @@ +use pagetop::prelude::*; + +use std::fmt; + +// **< RoundedRadius >****************************************************************************** + +/// Radio (**redondeo**) para las esquinas ([`Rounded`]). +/// +/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`. +/// +/// - `None` no añade clase (devuelve `""`). +/// - `Default` genera `rounded` (radio por defecto del tema). +/// - `Zero` genera `rounded-0` (sin redondeo). +/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente). +/// - `Circle` genera `rounded-circle`. +/// - `Pill` genera `rounded-pill`. +#[derive(AutoDefault)] +pub enum RoundedRadius { + #[default] + None, + Default, + Zero, + Scale1, + Scale2, + Scale3, + Scale4, + Scale5, + Circle, + Pill, +} + +impl RoundedRadius { + #[rustfmt::skip] + fn to_class(&self, base: impl AsRef<str>) -> String { + match self { + RoundedRadius::None => String::new(), + RoundedRadius::Default => String::from(base.as_ref()), + RoundedRadius::Zero => join!(base, "-0"), + RoundedRadius::Scale1 => join!(base, "-1"), + RoundedRadius::Scale2 => join!(base, "-2"), + RoundedRadius::Scale3 => join!(base, "-3"), + RoundedRadius::Scale4 => join!(base, "-4"), + RoundedRadius::Scale5 => join!(base, "-5"), + RoundedRadius::Circle => join!(base, "-circle"), + RoundedRadius::Pill => join!(base, "-pill"), + } + } +} + +impl fmt::Display for RoundedRadius { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_class("rounded")) + } +} + +// **< Rounded >************************************************************************************ + +/// Agrupa propiedades para crear **esquinas redondeadas**. +/// +/// Permite: +/// +/// - Definir un radio **global para todas las esquinas** (`radius`). +/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`, +/// `start`, **en este orden**, respetando LTR/RTL). +/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`, +/// `bottom-end`, **en este orden**, respetando LTR/RTL). +/// +/// # Ejemplos +/// +/// **Radio global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = Rounded::with(RoundedRadius::Default); +/// assert_eq!(r.to_string(), "rounded"); +/// ``` +/// +/// **Sin redondeo:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = Rounded::new(); +/// assert_eq!(r.to_string(), ""); +/// ``` +/// +/// **Radio en las esquinas de un lado lógico:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = Rounded::new().with_end(RoundedRadius::Scale2); +/// assert_eq!(r.to_string(), "rounded-end-2"); +/// ``` +/// +/// **Radio en una esquina concreta:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = Rounded::new().with_top_start(RoundedRadius::Scale3); +/// assert_eq!(r.to_string(), "rounded-top-start-3"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = Rounded::new() +/// .with_top(RoundedRadius::Default) // Añade redondeo arriba. +/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. +/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. +/// +/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Rounded { + radius : RoundedRadius, + top : RoundedRadius, + end : RoundedRadius, + bottom : RoundedRadius, + start : RoundedRadius, + top_start : RoundedRadius, + top_end : RoundedRadius, + bottom_start: RoundedRadius, + bottom_end : RoundedRadius, +} + +impl Rounded { + /// Prepara las esquinas **sin redondeo global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea las esquinas **con redondeo global** (`radius`). + pub fn with(radius: RoundedRadius) -> Self { + Self::default().with_radius(radius) + } + + // **< Rounded BUILDER >************************************************************************ + + /// Establece el radio global de las esquinas (`rounded*`). + pub fn with_radius(mut self, radius: RoundedRadius) -> Self { + self.radius = radius; + self + } + + /// Establece el radio en las esquinas del lado superior (`rounded-top-*`). + pub fn with_top(mut self, radius: RoundedRadius) -> Self { + self.top = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, radius: RoundedRadius) -> Self { + self.end = radius; + self + } + + /// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`). + pub fn with_bottom(mut self, radius: RoundedRadius) -> Self { + self.bottom = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta + /// LTR/RTL. + pub fn with_start(mut self, radius: RoundedRadius) -> Self { + self.start = radius; + self + } + + /// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL. + pub fn with_top_start(mut self, radius: RoundedRadius) -> Self { + self.top_start = radius; + self + } + + /// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL. + pub fn with_top_end(mut self, radius: RoundedRadius) -> Self { + self.top_end = radius; + self + } + + /// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta + /// LTR/RTL. + pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self { + self.bottom_start = radius; + self + } + + /// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL. + pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self { + self.bottom_end = radius; + self + } +} + +impl fmt::Display for Rounded { + /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, + /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las + /// definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + join_opt!([ + self.radius.to_string(), + self.top.to_class("rounded-top"), + self.end.to_class("rounded-end"), + self.bottom.to_class("rounded-bottom"), + self.start.to_class("rounded-start"), + self.top_start.to_class("rounded-top-start"), + self.top_end.to_class("rounded-top-end"), + self.bottom_start.to_class("rounded-bottom-start"), + self.bottom_end.to_class("rounded-bottom-end"), + ]; " ") + .unwrap_or_default() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs new file mode 100644 index 00000000..65ce9230 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -0,0 +1,269 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +/// Tipo de contenedor ([`Container`]). +/// +/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API +/// común a todos los contenedores. +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum ContainerType { + /// Contenedor genérico (`<div>`). + #[default] + Default, + /// Contenido principal de la página (`<main>`). + Main, + /// Encabezado de la página o de sección (`<header>`). + Header, + /// Pie de la página o de sección (`<footer>`). + Footer, + /// Sección de contenido (`<section>`). + Section, + /// Artículo de contenido (`<article>`). + Article, +} + +/// Componente genérico para crear un contenedor de componentes. +/// +/// Envuelve el contenido con la etiqueta HTML indicada por [`ContainerType`]. Sólo se renderiza si +/// existen componentes hijos (*children*). +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Container { + id : AttrId, + classes : AttrClasses, + container_type: ContainerType, + breakpoint : BreakPoint, + children : Children, + bg_color : BgColor, + text_color : TextColor, + border : Border, + rounded : Rounded, +} + +impl Component for Container { + fn new() -> Self { + Container::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + join_pair!("container", "-", self.breakpoint().to_string()), + self.bg_color().to_string(), + self.text_color().to_string(), + self.border().to_string(), + self.rounded().to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let output = self.children().render(cx); + if output.is_empty() { + return PrepareMarkup::None; + } + let style = match self.breakpoint() { + BreakPoint::FluidMax(w) if w.is_measurable() => { + Some(join!("max-width: ", w.to_string(), ";")) + } + _ => None, + }; + match self.container_type() { + ContainerType::Default => PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + ContainerType::Main => PrepareMarkup::With(html! { + main id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + ContainerType::Header => PrepareMarkup::With(html! { + header id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + ContainerType::Footer => PrepareMarkup::With(html! { + footer id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + ContainerType::Section => PrepareMarkup::With(html! { + section id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + ContainerType::Article => PrepareMarkup::With(html! { + article id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + } + } +} + +impl Container { + /// Crea un contenedor de tipo `Main` (`<main>`). + pub fn main() -> Self { + Container { + container_type: ContainerType::Main, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Header` (`<header>`). + pub fn header() -> Self { + Container { + container_type: ContainerType::Header, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Footer` (`<footer>`). + pub fn footer() -> Self { + Container { + container_type: ContainerType::Footer, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Section` (`<section>`). + pub fn section() -> Self { + Container { + container_type: ContainerType::Section, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Article` (`<article>`). + pub fn article() -> Self { + Container { + container_type: ContainerType::Article, + ..Default::default() + } + } + + // **< Container BUILDER >********************************************************************** + + /// Establece el identificador único (`id`) del contenedor. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al contenedor. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Establece el *punto de ruptura* del contenedor. + #[builder_fn] + pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { + self.breakpoint = bp; + self + } + + /// Añade un nuevo componente hijo al contenedor. + pub fn add_child(mut self, component: impl Component) -> Self { + self.children + .alter_child(ChildOp::Add(Child::with(component))); + self + } + + /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + /// Establece el color de fondo ([`BgColor`]). + #[builder_fn] + pub fn with_bg_color(mut self, color: BgColor) -> Self { + self.bg_color = color; + self + } + + /// Establece el color del texto ([`TextColor`]). + #[builder_fn] + pub fn with_text_color(mut self, color: TextColor) -> Self { + self.text_color = color; + self + } + + /// Atajo para definir los colores de fondo y texto a la vez. + #[builder_fn] + pub fn with_colors(mut self, bg_color: BgColor, text_color: TextColor) -> Self { + self.bg_color = bg_color; + self.text_color = text_color; + self + } + + /// Establece el borde del contenedor ([`Border`]). + #[builder_fn] + pub fn with_border(mut self, border: Border) -> Self { + self.border = border; + self + } + + /// Establece esquinas redondeadas para el contenedor. + #[builder_fn] + pub fn with_rounded(mut self, rounded: Rounded) -> Self { + self.rounded = rounded; + self + } + + // **< Container GETTERS >********************************************************************** + + /// Devuelve las clases CSS asociadas al contenedor. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el tipo semántico del contenedor. + pub fn container_type(&self) -> &ContainerType { + &self.container_type + } + + /// Devuelve el *punto de ruptura* actualmente configurado. + pub fn breakpoint(&self) -> &BreakPoint { + &self.breakpoint + } + + /// Devuelve la lista de hijos (`children`) del contenedor. + pub fn children(&self) -> &Children { + &self.children + } + + /// Devuelve el color de fondo del contenedor. + pub fn bg_color(&self) -> &BgColor { + &self.bg_color + } + + /// Devuelve el color del texto del contenedor. + pub fn text_color(&self) -> &TextColor { + &self.text_color + } + + /// Devuelve el borde del contenedor. + pub fn border(&self) -> &Border { + &self.border + } + + /// Devuelve las esquinas redondeadas del contenedor. + pub fn rounded(&self) -> &Rounded { + &self.rounded + } +} diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs new file mode 100644 index 00000000..a04f8dd6 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -0,0 +1,5 @@ +mod component; +pub use component::Dropdown; + +mod item; +pub use item::Item; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs new file mode 100644 index 00000000..2e0ea700 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -0,0 +1,99 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Dropdown { + id : AttrId, + classes: AttrClasses, + items : Children, +} + +impl Component for Dropdown { + fn new() -> Self { + Dropdown::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "dropdown"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let items = self.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + button + type="button" + class="btn btn-secondary dropdown-toggle" + data-bs-toggle="dropdown" + aria-expanded="false" + { + ("Dropdown button") + } + ul class="dropdown-menu" { + li { + a class="dropdown-item" href="#" { + ("Action") + } + } + li { + a class="dropdown-item" href="#" { + ("Another action") + } + } + li { + a class="dropdown-item" href="#" { + ("Something else here") + } + } + } + } + }) + } +} + +impl Dropdown { + // **< Dropdown BUILDER >*********************************************************************** + + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + pub fn add_item(mut self, item: dropdown::Item) -> Self { + self.items.add(Child::with(item)); + self + } + + #[builder_fn] + pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self { + self.items.alter_typed(op); + self + } + + // **< Dropdown GETTERS >*********************************************************************** + + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs new file mode 100644 index 00000000..1edd64b1 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -0,0 +1,109 @@ +use pagetop::prelude::*; + +// **< ItemType >*********************************************************************************** + +#[derive(AutoDefault)] +pub enum ItemType { + #[default] + Void, + Label(L10n), + Link(L10n, FnPathByContext), + LinkBlank(L10n, FnPathByContext), +} + +// **< Item >*************************************************************************************** + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + item_type: ItemType, +} + +impl Component for Item { + fn new() -> Self { + Item::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let description: Option<String> = None; + + // Obtiene la URL actual desde `cx.request`. + let current_path = cx.request().map(|request| request.path()); + + match self.item_type() { + ItemType::Void => PrepareMarkup::None, + ItemType::Label(label) => PrepareMarkup::With(html! { + li class="dropdown-item" { + span title=[description] { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }), + ItemType::Link(label, path) => { + let item_path = path(cx); + let (class, aria) = if current_path == Some(item_path) { + ("dropdown-item active", Some("page")) + } else { + ("dropdown-item", None) + }; + PrepareMarkup::With(html! { + li class=(class) aria-current=[aria] { + a class="nav-link" href=(item_path) title=[description] { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }) + } + ItemType::LinkBlank(label, path) => { + let item_path = path(cx); + let (class, aria) = if current_path == Some(item_path) { + ("dropdown-item active", Some("page")) + } else { + ("dropdown-item", None) + }; + PrepareMarkup::With(html! { + li class=(class) aria-current=[aria] { + a class="nav-link" href=(item_path) title=[description] target="_blank" { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }) + } + } + } +} + +impl Item { + pub fn label(label: L10n) -> Self { + Item { + item_type: ItemType::Label(label), + ..Default::default() + } + } + + pub fn link(label: L10n, path: FnPathByContext) -> Self { + Item { + item_type: ItemType::Link(label, path), + ..Default::default() + } + } + + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + Item { + item_type: ItemType::LinkBlank(label, path), + ..Default::default() + } + } + + // Item GETTERS. + + pub fn item_type(&self) -> &ItemType { + &self.item_type + } +} diff --git a/extensions/pagetop-bootsier/src/theme/image.rs b/extensions/pagetop-bootsier/src/theme/image.rs new file mode 100644 index 00000000..6c602780 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/image.rs @@ -0,0 +1,186 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +#[derive(AutoDefault)] +pub enum ImageSource { + #[default] + //Logo(PageTopLogo), + Responsive(String), + Thumbnail(String), + Static(String), +} + +#[derive(AutoDefault)] +pub enum ImageSize { + #[default] + Auto, + Dimensions(UnitValue, UnitValue), + Width(UnitValue), + Height(UnitValue), + Both(UnitValue), +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Image { + id : AttrId, + classes: AttrClasses, + source : ImageSource, + alt : AttrL10n, + size : ImageSize, + border : Border, + rounded: Rounded, +} + +impl Component for Image { + fn new() -> Self { + Image::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + String::from(match self.source() { + //ImageSource::Logo(_) => "img-fluid", + ImageSource::Responsive(_) => "img-fluid", + ImageSource::Thumbnail(_) => "img-thumbnail", + _ => "", + }), + self.border().to_string(), + self.rounded().to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let dimensions = match self.size() { + ImageSize::Auto => None, + ImageSize::Dimensions(w, h) => { + let w = w.to_string(); + let h = h.to_string(); + Some(join!("width: ", w, "; height: ", h, ";")) + } + ImageSize::Width(w) => { + let w = w.to_string(); + Some(join!("width: ", w, ";")) + } + ImageSize::Height(h) => { + let h = h.to_string(); + Some(join!("height: ", h, ";")) + } + ImageSize::Both(v) => { + let v = v.to_string(); + Some(join!("width: ", v, "; height: ", v, ";")) + } + }; + let source = match self.source() { + /* + ImageSource::Logo(logo) => { + return PrepareMarkup::With(html! { + span + id=[self.id()] + class=[self.classes().get()] + style=[dimensions] + { + (logo.render(cx)) + } + }) + } + */ + ImageSource::Responsive(source) => Some(source), + ImageSource::Thumbnail(source) => Some(source), + ImageSource::Static(source) => Some(source), + }; + PrepareMarkup::With(html! { + img + src=[source] + alt=[self.alternative().lookup(cx)] + id=[self.id()] + class=[self.classes().get()] + style=[dimensions] {} + }) + } +} + +impl Image { + pub fn with(source: ImageSource) -> Self { + Image::default().with_source(source) + } + + // **< Image BUILDER >************************************************************************** + + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[builder_fn] + pub fn with_source(mut self, source: ImageSource) -> Self { + self.source = source; + self + } + + #[builder_fn] + pub fn with_alternative(mut self, alt: L10n) -> Self { + self.alt.alter_value(alt); + self + } + + #[builder_fn] + pub fn with_size(mut self, size: ImageSize) -> Self { + self.size = size; + self + } + + #[builder_fn] + pub fn with_border(mut self, border: Border) -> Self { + self.border = border; + self + } + + #[builder_fn] + pub fn with_rounded(mut self, rounded: Rounded) -> Self { + self.rounded = rounded; + self + } + + // **< Image GETTERS >************************************************************************** + + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + pub fn source(&self) -> &ImageSource { + &self.source + } + + pub fn alternative(&self) -> &AttrL10n { + &self.alt + } + + pub fn size(&self) -> &ImageSize { + &self.size + } + + pub fn border(&self) -> &Border { + &self.border + } + + pub fn rounded(&self) -> &Rounded { + &self.rounded + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs new file mode 100644 index 00000000..3745b116 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -0,0 +1,17 @@ +mod component; +pub use component::{Navbar, NavbarToggler, NavbarType}; + +mod button_toggler; +pub use button_toggler::ButtonToggler; + +mod content; +pub use content::{Content, ContentType}; + +mod brand; +pub use brand::Brand; + +mod nav; +pub use nav::Nav; + +mod item; +pub use item::{Item, ItemType}; diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs new file mode 100644 index 00000000..cfeb45aa --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -0,0 +1,104 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Brand { + id : AttrId, + #[default(_code = "global::SETTINGS.app.name.to_owned()")] + app_name : String, + slogan : AttrL10n, + logo : Typed<Image>, + #[default(_code = "|_| \"/\"")] + home : FnPathByContext, +} + +impl Component for Brand { + fn new() -> Self { + Brand::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let logo = self.logo().render(cx); + let home = self.home()(cx); + let title = &L10n::l("site_home").lookup(cx); + PrepareMarkup::With(html! { + div id=[self.id()] class="branding__container" { + div class="branding__content" { + @if !logo.is_empty() { + a class="branding__logo" href=(home) title=[title] rel="home" { + (logo) + } + } + div class="branding__text" { + a class="branding__name" href=(home) title=[title] rel="home" { + (self.app_name()) + } + @if let Some(slogan) = self.slogan().lookup(cx) { + div class="branding__slogan" { + (slogan) + } + } + } + } + } + }) + } +} + +impl Brand { + // Brand BUILDER. + + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self { + self.app_name = app_name.into(); + self + } + + #[builder_fn] + pub fn with_slogan(mut self, slogan: L10n) -> Self { + self.slogan.alter_value(slogan); + self + } + + #[builder_fn] + pub fn with_logo(mut self, logo: Option<Image>) -> Self { + self.logo.alter_component(logo); + self + } + + #[builder_fn] + pub fn with_home(mut self, home: FnPathByContext) -> Self { + self.home = home; + self + } + + // Brand GETTERS. + + pub fn app_name(&self) -> &String { + &self.app_name + } + + pub fn slogan(&self) -> &AttrL10n { + &self.slogan + } + + pub fn logo(&self) -> &Typed<Image> { + &self.logo + } + + pub fn home(&self) -> &FnPathByContext { + &self.home + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs b/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs new file mode 100644 index 00000000..036182a7 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs @@ -0,0 +1,73 @@ +use pagetop::prelude::*; + +use crate::LOCALES_BOOTSIER; + +use std::fmt; + +#[derive(AutoDefault, PartialEq)] +pub(crate) enum Toggle { + #[default] + Collapse, + Offcanvas, +} + +#[rustfmt::skip] +impl fmt::Display for Toggle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Toggle::Collapse => write!(f, "collapse"), + Toggle::Offcanvas => write!(f, "offcanvas"), + } + } +} + +#[derive(AutoDefault)] +pub struct ButtonToggler; + +impl Component for ButtonToggler { + fn new() -> Self { + ButtonToggler::default() + } + + fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { + PrepareMarkup::With(html! { + button + type="button" + class="navbar-toggler" + { + span class="navbar-toggler-icon" {} + } + }) + } +} + +impl ButtonToggler { + // ButtonToggler PRIVATE RENDER. + + pub(crate) fn render( + &self, + cx: &mut Context, + id_content: String, + data_bs_toggle: Toggle, + ) -> Markup { + let id_content_target = join!("#", id_content); + let aria_expanded = if data_bs_toggle == Toggle::Collapse { + Some("false") + } else { + None + }; + html! { + button + type="button" + class="navbar-toggler" + data-bs-toggle=(data_bs_toggle) + data-bs-target=(id_content_target) + aria-controls=(id_content) + aria-expanded=[aria_expanded] + aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)] + { + span class="navbar-toggler-icon" {} + } + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs new file mode 100644 index 00000000..4a203c3f --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -0,0 +1,233 @@ +use pagetop::prelude::*; + +use crate::prelude::*; +use crate::LOCALES_BOOTSIER; + +const TOGGLE_COLLAPSE: &str = "collapse"; +const TOGGLE_OFFCANVAS: &str = "offcanvas"; + +#[derive(AutoDefault)] +pub enum NavbarToggler { + #[default] + Enabled, + Disabled, +} + +#[derive(AutoDefault)] +pub enum NavbarType { + #[default] + None, + Nav(Typed<navbar::Nav>), + Offcanvas(Typed<Offcanvas>), + Text(L10n), +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Navbar { + id : AttrId, + classes : AttrClasses, + expand : BreakPoint, + toggler : NavbarToggler, + navbar_type: NavbarType, + contents : Children, + brand : Typed<navbar::Brand>, +} + +impl Component for Navbar { + fn new() -> Self { + Navbar::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + "navbar".to_string(), + self.expand().try_class("navbar-expand").unwrap_or_default(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let id = cx.required_id::<Self>(self.id()); + + let navbar_type = match self.navbar_type() { + NavbarType::None => return PrepareMarkup::None, + NavbarType::Nav(nav) => { + let id_content = join!(id, "-content"); + match self.toggler() { + NavbarToggler::Enabled => self.toggler_wrapper( + TOGGLE_COLLAPSE, + L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx), + id_content, + self.brand().render(cx), + nav.render(cx), + ), + NavbarToggler::Disabled => nav.render(cx), + } + } + NavbarType::Offcanvas(oc) => { + let id_content = oc.id().unwrap_or_default(); + self.toggler_wrapper( + TOGGLE_OFFCANVAS, + L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx), + id_content, + self.brand().render(cx), + oc.render(cx), + ) + } + NavbarType::Text(text) => html! { + span class="navbar-text" { + (text.using(cx)) + } + }, + }; + + self.nav_wrapper(id, self.brand().render(cx), navbar_type) + } +} + +impl Navbar { + pub fn with_nav(nav: navbar::Nav) -> Self { + Navbar::default().with_navbar_type(NavbarType::Nav(Typed::with(nav))) + } + + pub fn with_offcanvas(offcanvas: Offcanvas) -> Self { + Navbar::default().with_navbar_type(NavbarType::Offcanvas(Typed::with(offcanvas))) + } + + // Navbar BUILDER. + + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[builder_fn] + pub fn with_expand(mut self, bp: BreakPoint) -> Self { + self.expand = bp; + self + } + + #[builder_fn] + pub fn with_toggler(mut self, toggler: NavbarToggler) -> Self { + self.toggler = toggler; + self + } + + #[builder_fn] + pub fn with_navbar_type(mut self, navbar_type: NavbarType) -> Self { + self.navbar_type = navbar_type; + self + } + + pub fn with_content(mut self, content: navbar::Content) -> Self { + self.contents.add(Child::with(content)); + self + } + + #[builder_fn] + pub fn with_contents(mut self, op: TypedOp<navbar::Content>) -> Self { + self.contents.alter_typed(op); + self + } + + #[builder_fn] + pub fn with_brand(mut self, brand: Option<navbar::Brand>) -> Self { + self.brand.alter_component(brand); + self + } + + // Navbar GETTERS. + + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + pub fn expand(&self) -> &BreakPoint { + &self.expand + } + + pub fn toggler(&self) -> &NavbarToggler { + &self.toggler + } + + pub fn navbar_type(&self) -> &NavbarType { + &self.navbar_type + } + + pub fn contents(&self) -> &Children { + &self.contents + } + + pub fn brand(&self) -> &Typed<navbar::Brand> { + &self.brand + } + + // Navbar HELPERS. + + fn nav_wrapper(&self, id: String, brand: Markup, content: Markup) -> PrepareMarkup { + if content.is_empty() { + PrepareMarkup::None + } else { + PrepareMarkup::With(html! { + (brand) + nav id=(id) class=[self.classes().get()] { + div class="container-fluid" { + (content) + } + } + }) + } + } + + fn toggler_wrapper( + &self, + data_bs_toggle: &str, + aria_label: Option<String>, + id_content: String, + brand: Markup, + content: Markup, + ) -> Markup { + if content.is_empty() { + html! {} + } else { + let id_content_target = join!("#", id_content); + let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE { + Some("false") + } else { + None + }; + html! { + (brand) + button + type="button" + class="navbar-toggler" + data-bs-toggle=(data_bs_toggle) + data-bs-target=(id_content_target) + aria-controls=(id_content) + aria-expanded=[aria_expanded] + aria-label=[aria_label] + { + span class="navbar-toggler-icon" {} + } + div id=(id_content) class="collapse navbar-collapse" { + (content) + } + } + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/content.rs b/extensions/pagetop-bootsier/src/theme/navbar/content.rs new file mode 100644 index 00000000..2efb4a05 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/content.rs @@ -0,0 +1,69 @@ +use pagetop::prelude::*; + +use crate::theme::navbar; + +#[derive(AutoDefault)] +pub enum ContentType { + #[default] + None, + Brand(Typed<navbar::Brand>), + Nav(Typed<navbar::Nav>), + Text(L10n), +} + +// Item. + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Content { + content: ContentType, +} + +impl Component for Content { + fn new() -> Self { + Content::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self.content() { + ContentType::None => PrepareMarkup::None, + ContentType::Brand(brand) => PrepareMarkup::With(html! { + (brand.render(cx)) + }), + ContentType::Nav(nav) => PrepareMarkup::With(html! { + (nav.render(cx)) + }), + ContentType::Text(text) => PrepareMarkup::With(html! { + span class="navbar-text" { + (text.using(cx)) + } + }), + } + } +} + +impl Content { + pub fn brand(content: navbar::Brand) -> Self { + Content { + content: ContentType::Brand(Typed::with(content)), + } + } + + pub fn nav(content: navbar::Nav) -> Self { + Content { + content: ContentType::Nav(Typed::with(content)), + } + } + + pub fn text(content: L10n) -> Self { + Content { + content: ContentType::Text(content), + } + } + + // Content GETTERS. + + pub fn content(&self) -> &ContentType { + &self.content + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs new file mode 100644 index 00000000..08a2aeee --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -0,0 +1,113 @@ +use pagetop::prelude::*; + +use crate::theme::Dropdown; + +type Label = L10n; + +#[derive(AutoDefault)] +pub enum ItemType { + #[default] + Void, + Label(Label), + Link(Label, FnPathByContext), + LinkBlank(Label, FnPathByContext), + Dropdown(Typed<Dropdown>), +} + +// Item. + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + item_type: ItemType, +} + +impl Component for Item { + fn new() -> Self { + Item::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let description: Option<String> = None; + + // Obtiene la URL actual desde `cx.request`. + let current_path = cx.request().map(|request| request.path()); + + match self.item_type() { + ItemType::Void => PrepareMarkup::None, + ItemType::Label(label) => PrepareMarkup::With(html! { + li class="nav-item" { + span title=[description] { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }), + ItemType::Link(label, path) => { + let item_path = path(cx); + let (class, aria) = if current_path == Some(item_path) { + ("nav-item active", Some("page")) + } else { + ("nav-item", None) + }; + PrepareMarkup::With(html! { + li class=(class) aria-current=[aria] { + a class="nav-link" href=(item_path) title=[description] { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }) + } + ItemType::LinkBlank(label, path) => { + let item_path = path(cx); + let (class, aria) = if current_path == Some(item_path) { + ("nav-item active", Some("page")) + } else { + ("nav-item", None) + }; + PrepareMarkup::With(html! { + li class=(class) aria-current=[aria] { + a class="nav-link" href=(item_path) title=[description] target="_blank" { + //(left_icon) + (label.using(cx)) + //(right_icon) + } + } + }) + } + ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }), + } + } +} + +impl Item { + pub fn label(label: L10n) -> Self { + Item { + item_type: ItemType::Label(label), + ..Default::default() + } + } + + pub fn link(label: L10n, path: FnPathByContext) -> Self { + Item { + item_type: ItemType::Link(label, path), + ..Default::default() + } + } + + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + Item { + item_type: ItemType::LinkBlank(label, path), + ..Default::default() + } + } + + // Item GETTERS. + + pub fn item_type(&self) -> &ItemType { + &self.item_type + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/nav.rs b/extensions/pagetop-bootsier/src/theme/navbar/nav.rs new file mode 100644 index 00000000..bc03a291 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/nav.rs @@ -0,0 +1,75 @@ +use pagetop::prelude::*; + +use crate::theme::navbar; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Nav { + id : AttrId, + classes: AttrClasses, + items : Children, +} + +impl Component for Nav { + fn new() -> Self { + Nav::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "navbar-nav"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let items = self.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + + PrepareMarkup::With(html! { + ul id=[self.id()] class=[self.classes().get()] { + (items) + } + }) + } +} + +impl Nav { + // Nav BUILDER. + + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + pub fn with_item(mut self, item: navbar::Item) -> Self { + self.items.add(Child::with(item)); + self + } + + #[builder_fn] + pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self { + self.items.alter_typed(op); + self + } + + // Nav GETTERS. + + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs new file mode 100644 index 00000000..b790522b --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -0,0 +1,302 @@ +use pagetop::prelude::*; + +use crate::prelude::*; +use crate::LOCALES_BOOTSIER; + +use std::fmt; + +// **< OffcanvasPlacement >************************************************************************* + +/// Posición de aparición del panel **deslizante** ([`Offcanvas`]). +/// +/// Define desde qué borde de la ventana entra y se ancla el panel. +#[derive(AutoDefault)] +pub enum OffcanvasPlacement { + /// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL). + #[default] + Start, + /// Desde el borde final según dirección de lectura (respetando LTR/RTL). + End, + /// Desde la parte superior. + Top, + /// Desde la parte inferior. + Bottom, +} + +#[rustfmt::skip] +impl fmt::Display for OffcanvasPlacement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OffcanvasPlacement::Start => f.write_str("offcanvas-start"), + OffcanvasPlacement::End => f.write_str("offcanvas-end"), + OffcanvasPlacement::Top => f.write_str("offcanvas-top"), + OffcanvasPlacement::Bottom => f.write_str("offcanvas-bottom"), + } + } +} + +// **< OffcanvasVisibility >************************************************************************ + +/// Estado inicial del panel ([`Offcanvas`]). +#[derive(AutoDefault)] +pub enum OffcanvasVisibility { + /// El panel **permanece oculto** desde el principio. + #[default] + Default, + /// El panel **se muestra abierto** al cargar. + Show, +} + +impl fmt::Display for OffcanvasVisibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OffcanvasVisibility::Default => Ok(()), + OffcanvasVisibility::Show => f.write_str("show"), + } + } +} + +// **< OffcanvasBodyScroll >************************************************************************ + +/// Controla si la página principal puede **desplazarse** al abrir el panel ([`Offcanvas`]). +#[derive(AutoDefault)] +pub enum OffcanvasBodyScroll { + /// Opción por defecto, la página principal se **bloquea** centrando la interacción en el panel. + #[default] + Disabled, + /// **Permite** el desplazamiento de la página principal. + Enabled, +} + +// **< OffcanvasBackdrop >************************************************************************** + +/// Comportamiento de la **capa de fondo** (*backdrop*) del panel ([`Offcanvas`]) al desplegarse. +#[derive(AutoDefault)] +pub enum OffcanvasBackdrop { + /// **Sin capa** de fondo; la página principal permanece visible e interactiva. + Disabled, + /// Opción por defecto, se **oscurece** el fondo; un clic fuera del panel suele cerrarlo. + #[default] + Enabled, + /// Se muestra capa de fondo pero **no** se cierra al pulsar fuera (útil cuando se requiere + /// completar una acción antes de salir). + Static, +} + +// **< Offcanvas >********************************************************************************** + +/// Panel lateral **deslizante** para contenido complementario. +/// +/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes +/// características principales: +/// +/// - **Entrada configurable desde un borde** de la ventana. +/// - **Encabezado con título** y **botón de cierre** integrado. +/// - **Accesibilidad**: asocia título y controles a un identificador único y expone atributos +/// adecuados para lectores de pantalla y navegación por teclado. +/// - **Opcionalmente** bloquea el desplazamiento del documento y/o muestra una capa de fondo para +/// centrar la atención del usuario. +/// - **Responsive**: puede cambiar su comportamiento según el punto de ruptura indicado. +/// - **No se renderiza** si no tiene contenido. +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Offcanvas { + id : AttrId, + classes : AttrClasses, + title : L10n, + breakpoint: BreakPoint, + placement : OffcanvasPlacement, + visibility: OffcanvasVisibility, + scrolling : OffcanvasBodyScroll, + backdrop : OffcanvasBackdrop, + children : Children, +} + +impl Component for Offcanvas { + fn new() -> Self { + Offcanvas::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + self.breakpoint().to_class("offcanvas"), + self.placement().to_string(), + self.visibility().to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let body = self.children().render(cx); + if body.is_empty() { + return PrepareMarkup::None; + } + + let id = cx.required_id::<Self>(self.id()); + let id_label = join!(id, "-label"); + let id_target = join!("#", id); + + let body_scroll = match self.body_scroll() { + OffcanvasBodyScroll::Disabled => None, + OffcanvasBodyScroll::Enabled => Some("true"), + }; + + let backdrop = match self.backdrop() { + OffcanvasBackdrop::Disabled => Some("false"), + OffcanvasBackdrop::Enabled => None, + OffcanvasBackdrop::Static => Some("static"), + }; + + let title = self.title().using(cx); + + PrepareMarkup::With(html! { + div + id=(id) + class=[self.classes().get()] + tabindex="-1" + data-bs-scroll=[body_scroll] + data-bs-backdrop=[backdrop] + aria-labelledby=(id_label) + { + div class="offcanvas-header" { + @if !title.is_empty() { + h5 class="offcanvas-title" id=(id_label) { (title) } + } + button + type="button" + class="btn-close" + data-bs-dismiss="offcanvas" + data-bs-target=(id_target) + aria-label=[L10n::t("close", &LOCALES_BOOTSIER).lookup(cx)] + {} + } + div class="offcanvas-body" { + (body) + } + } + }) + } +} + +impl Offcanvas { + // **< Offcanvas BUILDER >********************************************************************** + + /// Establece el identificador único (`id`) del panel. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al panel. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Establece el **título** del encabezado. + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; + self + } + + /// Configura el **punto de ruptura** para activar el comportamiento responsive del panel. + #[builder_fn] + pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { + self.breakpoint = bp; + self + } + + /// Indica la **posición** desde la que entra el panel. + #[builder_fn] + pub fn with_placement(mut self, placement: OffcanvasPlacement) -> Self { + self.placement = placement; + self + } + + /// Fija el **estado inicial** del panel (oculto o visible al cargar). + #[builder_fn] + pub fn with_visibility(mut self, visibility: OffcanvasVisibility) -> Self { + self.visibility = visibility; + self + } + + /// Permite o bloquea el **desplazamiento** de la página principal mientras el panel está + /// abierto. + #[builder_fn] + pub fn with_body_scroll(mut self, scrolling: OffcanvasBodyScroll) -> Self { + self.scrolling = scrolling; + self + } + + /// Ajusta la **capa de fondo** del panel para definir su comportamiento al interactuar fuera. + #[builder_fn] + pub fn with_backdrop(mut self, backdrop: OffcanvasBackdrop) -> Self { + self.backdrop = backdrop; + self + } + + /// Añade un nuevo componente hijo al panel. + pub fn add_child(mut self, child: impl Component) -> Self { + self.children.add(Child::with(child)); + self + } + + /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_children(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // **< Offcanvas GETTERS >********************************************************************** + + /// Devuelve las clases CSS asociadas al panel. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el título del panel como [`L10n`]. + pub fn title(&self) -> &L10n { + &self.title + } + + /// Devuelve el punto de ruptura configurado. + pub fn breakpoint(&self) -> &BreakPoint { + &self.breakpoint + } + + /// Devuelve la posición del panel. + pub fn placement(&self) -> &OffcanvasPlacement { + &self.placement + } + + /// Devuelve el estado inicial del panel. + pub fn visibility(&self) -> &OffcanvasVisibility { + &self.visibility + } + + /// Indica si la página principal puede desplazarse mientras el panel está abierto. + pub fn body_scroll(&self) -> &OffcanvasBodyScroll { + &self.scrolling + } + + /// Devuelve la configuración de la capa de fondo. + pub fn backdrop(&self) -> &OffcanvasBackdrop { + &self.backdrop + } + + /// Devuelve la lista de hijos (`children`) del panel. + pub fn children(&self) -> &Children { + &self.children + } +} From bf310bec7e59a148f0c834b6bf182bdd46209fcd Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 07:03:14 +0200 Subject: [PATCH 166/224] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Mejora=20adici?= =?UTF-8?q?=C3=B3n=20de=20componentes=20hijos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/block.rs | 8 ++++---- src/base/component/intro.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 17af50c2..76d094ff 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -71,13 +71,13 @@ impl Block { } /// Añade un nuevo componente hijo al bloque. + #[inline] pub fn add_child(mut self, component: impl Component) -> Self { - self.children - .alter_child(ChildOp::Add(Child::with(component))); + self.children.add(Child::with(component)); self } - /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. #[builder_fn] pub fn with_child(mut self, op: ChildOp) -> Self { self.children.alter_child(op); @@ -96,7 +96,7 @@ impl Block { &self.title } - /// Devuelve la lista de hijos (`children`) del bloque. + /// Devuelve la lista de componentes (`children`) del bloque. pub fn children(&self) -> &Children { &self.children } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 7e5c3931..6fd4521e 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -301,13 +301,13 @@ impl Intro { /// Añade un nuevo componente hijo a la intro. /// /// Si es un bloque ([`Block`]) aplica estilos específicos para destacarlo. + #[inline] pub fn add_child(mut self, component: impl Component) -> Self { - self.children - .alter_child(ChildOp::Add(Child::with(component))); + self.children.add(Child::with(component)); self } - /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. #[builder_fn] pub fn with_child(mut self, op: ChildOp) -> Self { self.children.alter_child(op); @@ -336,7 +336,7 @@ impl Intro { self.opening } - /// Devuelve la lista de hijos (`children`) de la intro. + /// Devuelve la lista de componentes (`children`) de la intro. pub fn children(&self) -> &Children { &self.children } From d21e1a21682ed87c758d25856259e6bbd1be886c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 07:13:15 +0200 Subject: [PATCH 167/224] =?UTF-8?q?=F0=9F=94=92=20Mejora=20seguridad=20de?= =?UTF-8?q?=20enlaces=20con=20`noopener`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/intro.rs | 8 ++++---- src/base/component/poweredby.rs | 2 +- src/locale/en-US/welcome.ftl | 2 +- src/locale/es-ES/welcome.ftl | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 6fd4521e..a20d6232 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -166,7 +166,7 @@ impl Component for Intro { class="intro-button__link" href=((lnk)(cx)) target="_blank" - rel="noreferrer" + rel="noopener noreferrer" { span {} span {} span {} div class="intro-button__text" { @@ -216,9 +216,9 @@ impl Component for Intro { } } div class="intro-footer__links" { - a href="https://crates.io/crates/pagetop" target="_blank" rel="noreferrer" { ("Crates.io") } - a href="https://docs.rs/pagetop" target="_blank" rel="noreferrer" { ("Docs.rs") } - a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noreferrer" { (L10n::l("intro_code").using(cx)) } + a href="https://crates.io/crates/pagetop" target="_blank" rel="noopener noreferrer" { ("Crates.io") } + a href="https://docs.rs/pagetop" target="_blank" rel="noopener noreferrer" { ("Docs.rs") } + a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noopener noreferrer" { (L10n::l("intro_code").using(cx)) } em { (L10n::l("intro_have_fun").using(cx)) } } } diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index d77d65c6..51ab79d8 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -1,7 +1,7 @@ use crate::prelude::*; // Enlace a la página oficial de PageTop. -const LINK: &str = "<a href=\"https://pagetop.cillero.es\" rel=\"noreferrer\">PageTop</a>"; +const LINK: &str = "<a href=\"https://pagetop.cillero.es\" rel=\"noopener noreferrer\">PageTop</a>"; /// Componente que renderiza la sección 'Powered by' (*Funciona con*) típica del pie de página. /// diff --git a/src/locale/en-US/welcome.ftl b/src/locale/en-US/welcome.ftl index 34028804..ec691d1b 100644 --- a/src/locale/en-US/welcome.ftl +++ b/src/locale/en-US/welcome.ftl @@ -8,5 +8,5 @@ welcome_status_1 = If you can see this page, it means the <strong>PageTop</stron welcome_status_2 = If the issue persists, please <strong>contact the system administrator</strong>. welcome_support_title = Support -welcome_support_1 = To report issues with the <strong>PageTop</strong> framework, use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">GitHub</a>. Remember, before opening a new issue, review the existing ones to avoid duplicates. +welcome_support_1 = To report issues with the <strong>PageTop</strong> framework, use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noopener noreferrer">GitHub</a>. Remember, before opening a new issue, review the existing ones to avoid duplicates. welcome_support_2 = For issues specific to the application (<strong>{ $app }</strong>), please use its official repository or support channel. diff --git a/src/locale/es-ES/welcome.ftl b/src/locale/es-ES/welcome.ftl index 605d1e2c..a3e3a184 100644 --- a/src/locale/es-ES/welcome.ftl +++ b/src/locale/es-ES/welcome.ftl @@ -8,5 +8,5 @@ welcome_status_1 = Si puedes ver esta página, es porque el servidor de <strong> welcome_status_2 = Si el problema persiste, por favor, <strong>contacta con el administrador del sistema</strong>. welcome_support_title = Soporte -welcome_support_1 = Para comunicar incidencias del propio entorno <strong>PageTop</strong>, utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noreferrer">GitHub</a>. Recuerda, antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. +welcome_support_1 = Para comunicar incidencias del propio entorno <strong>PageTop</strong>, utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank" rel="noopener noreferrer">GitHub</a>. Recuerda, antes de abrir una nueva incidencia, revisa las existentes para evitar duplicados. welcome_support_2 = Para fallos específicos de la aplicación (<strong>{ $app }</strong>), utiliza su repositorio oficial o su canal de soporte. From 3841d1d3f3fcb9a427ac1b14d9dab9ae910e93d2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 09:02:58 +0200 Subject: [PATCH 168/224] =?UTF-8?q?=F0=9F=8E=A8=20(bootsier):=20Mejora=20m?= =?UTF-8?q?en=C3=BAs=20desplegables=20`Dropdown`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/locale/en-US/components.ftl | 3 + .../src/locale/es-ES/components.ftl | 3 + extensions/pagetop-bootsier/src/theme/aux.rs | 7 +- .../pagetop-bootsier/src/theme/aux/color.rs | 29 ++ .../pagetop-bootsier/src/theme/aux/size.rs | 31 ++ .../pagetop-bootsier/src/theme/dropdown.rs | 15 +- .../src/theme/dropdown/component.rs | 378 ++++++++++++++++-- .../src/theme/dropdown/item.rs | 260 ++++++++++-- 8 files changed, 654 insertions(+), 72 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/aux/size.rs diff --git a/extensions/pagetop-bootsier/src/locale/en-US/components.ftl b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl index 83dde396..0bb7fba6 100644 --- a/extensions/pagetop-bootsier/src/locale/en-US/components.ftl +++ b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl @@ -1,3 +1,6 @@ +# Dropdown +dropdown_toggle = Toggle Dropdown + # Offcanvas close = Close diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl index 1ae97888..045191e3 100644 --- a/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl +++ b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl @@ -1,3 +1,6 @@ +# Dropdown +dropdown_toggle = Mostrar/ocultar menú + # Offcanvas close = Cerrar diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs index 72ebf066..f3cc127d 100644 --- a/extensions/pagetop-bootsier/src/theme/aux.rs +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -1,11 +1,11 @@ -//! Coleción de elementos auxiliares de Bootstrap para Bootsier. +//! Colección de elementos auxiliares de Bootstrap para Bootsier. mod breakpoint; pub use breakpoint::BreakPoint; mod color; pub use color::Color; -pub use color::{BgColor, BorderColor, TextColor}; +pub use color::{BgColor, BorderColor, ButtonColor, TextColor}; mod opacity; pub use opacity::Opacity; @@ -16,3 +16,6 @@ pub use border::{Border, BorderSize}; mod rounded; pub use rounded::{Rounded, RoundedRadius}; + +mod size; +pub use size::ButtonSize; diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index 3a288550..c071e407 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -110,6 +110,35 @@ impl fmt::Display for BorderColor { } } +// **< ButtonColor >******************************************************************************** + +/// Variantes de color (`btn-*`) para **botones**. +/// +/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). +/// - `Background(Color)` genera `btn-{color}` (botón relleno). +/// - `Outline(Color)` genera `btn-outline-{color}` (contorno: texto y borde, fondo transparente). +/// - `Link` aplica estilo de enlace (`btn-link`), sin caja ni fondo, heredando el color de texto. +#[derive(AutoDefault)] +pub enum ButtonColor { + #[default] + Default, + Background(Color), + Outline(Color), + Link, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Background(c) => write!(f, "btn-{c}"), + Self::Outline(c) => write!(f, "btn-outline-{c}"), + Self::Link => f.write_str("btn-link"), + } + } +} + // **< TextColor >********************************************************************************** /// Colores de texto y fondos de texto (`text-*`). diff --git a/extensions/pagetop-bootsier/src/theme/aux/size.rs b/extensions/pagetop-bootsier/src/theme/aux/size.rs new file mode 100644 index 00000000..e5cff63c --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/size.rs @@ -0,0 +1,31 @@ +use pagetop::prelude::*; + +use std::fmt; + +// **< ButtonSize >********************************************************************************* + +/// Tamaño visual de un botón. +/// +/// Controla la escala del botón según el diseño del tema: +/// +/// - `Default`, tamaño por defecto del tema (no añade clase). +/// - `Small`, botón compacto. +/// - `Large`, botón destacado/grande. +#[derive(AutoDefault)] +pub enum ButtonSize { + #[default] + Default, + Small, + Large, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Small => f.write_str("btn-sm"), + Self::Large => f.write_str("btn-lg"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index a04f8dd6..9e81c7a3 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -1,5 +1,18 @@ +//! Definiciones para crear menús desplegables [`Dropdown`]. +//! +//! Cada [`dropdown::Item`](crate::theme::dropdown::Item) representa un elemento individual del +//! desplegable [`Dropdown`], con distintos comportamientos según su finalidad: enlaces de +//! navegación, botones de acción, encabezados o divisores visuales. +//! +//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y +//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). +//! +//! Su propósito es ofrecer una base uniforme sobre la que construir menús consistentes, adaptados +//! al contexto de cada aplicación. + mod component; pub use component::Dropdown; +pub use component::{AutoClose, Direction, MenuAlign, MenuPosition}; mod item; -pub use item::Item; +pub use item::{Item, ItemKind}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 2e0ea700..9aef2562 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -1,13 +1,134 @@ use pagetop::prelude::*; use crate::prelude::*; +use crate::LOCALES_BOOTSIER; +// **< AutoClose >********************************************************************************** + +/// Estrategia para el cierre automático de un menú [`Dropdown`]. +/// +/// Define cuándo se cierra el menú desplegado según la interacción del usuario. +#[derive(AutoDefault)] +pub enum AutoClose { + /// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`. + #[default] + Default, + /// Sólo se cierra con clics dentro del menú. + ClickableInside, + /// Sólo se cierra con clics fuera del menú. + ClickableOutside, + /// Cierre manual, no se cierra con clics; sólo al pulsar nuevamente el botón del menú + /// (*toggle*), o pulsando `Esc`. + ManualClose, +} + +// **< Direction >********************************************************************************** + +/// Dirección de despliegue de un menú [`Dropdown`]. +/// +/// Controla desde qué posición se muestra el menú respecto al botón. +#[derive(AutoDefault)] +pub enum Direction { + /// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial, + /// respetando LTR/RTL). + #[default] + Default, + /// Centra horizontalmente el menú respecto al botón. + Centered, + /// Despliega el menú hacia arriba. + Dropup, + /// Despliega el menú hacia arriba y centrado. + DropupCentered, + /// Despliega el menú desde el lateral final, respetando LTR/RTL. + Dropend, + /// Despliega el menú desde el lateral inicial, respetando LTR/RTL. + Dropstart, +} + +// **< MenuAlign >********************************************************************************** + +/// Alineación horizontal del menú desplegable [`Dropdown`]. +/// +/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una +/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). +#[derive(AutoDefault)] +pub enum MenuAlign { + /// Alineación al inicio (comportamiento por defecto). + #[default] + Start, + /// Alineación al inicio a partir del punto de ruptura indicado. + StartAt(BreakPoint), + /// Alineación al inicio por defecto, y al final a partir de un punto de ruptura válido. + StartAndEnd(BreakPoint), + /// Alineación al final. + End, + /// Alineación al final a partir del punto de ruptura indicado. + EndAt(BreakPoint), + /// Alineación al final por defecto, y al inicio a partir de un punto de ruptura válido. + EndAndStart(BreakPoint), +} + +// **< MenuPosition >******************************************************************************* + +/// Posición relativa del menú desplegable [`Dropdown`]. +/// +/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el +/// cálculo de la posición. +#[derive(AutoDefault)] +pub enum MenuPosition { + /// Posicionamiento automático por defecto. + #[default] + Default, + /// Desplazamiento manual en píxeles `(x, y)` aplicado al menú. Se admiten valores negativos. + Offset(i8, i8), + /// Posiciona el menú tomando como referencia el botón padre. Especialmente útil cuando + /// [`button_split()`](crate::theme::Dropdown::button_split) es `true`. + Parent, +} + +// **< Dropdown >********************************************************************************** + +/// Componente para crear un **menú desplegable**. +/// +/// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split)) +/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra/oculta según la +/// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de +/// apertura, alineación o política de cierre. +/// +/// Sin título para el botón (ver [`with_button_title()`](Self::with_button_title)) se muestra +/// únicamente la lista de elementos sin ningún botón para interactuar. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::prelude::*; +/// let dd = Dropdown::new() +/// .with_button_title(L10n::n("Menu")) +/// .with_button_color(ButtonColor::Background(Color::Secondary)) +/// .with_auto_close(dropdown::AutoClose::ClickableInside) +/// .with_direction(dropdown::Direction::Dropend) +/// .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) +/// .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +/// .add_item(dropdown::Item::divider()) +/// .add_item(dropdown::Item::header(L10n::n("User session"))) +/// .add_item(dropdown::Item::button(L10n::n("Sign out"))); +/// ``` #[rustfmt::skip] #[derive(AutoDefault)] pub struct Dropdown { - id : AttrId, - classes: AttrClasses, - items : Children, + id : AttrId, + classes : AttrClasses, + button_title : L10n, + button_size : ButtonSize, + button_color : ButtonColor, + button_split : bool, + button_grouped: bool, + auto_close : dropdown::AutoClose, + direction : dropdown::Direction, + menu_align : dropdown::MenuAlign, + menu_position : dropdown::MenuPosition, + items : Children, } impl Component for Dropdown { @@ -19,42 +140,134 @@ impl Component for Dropdown { self.id.get() } + #[rustfmt::skip] fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes(ClassesOp::Prepend, "dropdown"); + let g = self.button_grouped(); + self.alter_classes(ClassesOp::Prepend, [ + if g { "btn-group" } else { "" }, + match self.direction() { + Direction::Default if g => "", + Direction::Default => "dropdown", + Direction::Centered => "dropdown-center", + Direction::Dropup => "dropup", + Direction::DropupCentered => "dropup-center", + Direction::Dropend => "dropend", + Direction::Dropstart => "dropstart", + } + ].join(" ")); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + // Si no hay elementos en el menú, no se prepara. let items = self.items().render(cx); if items.is_empty() { return PrepareMarkup::None; } + // Título opcional para el menú desplegable. + let button_title = self.button_title().using(cx); + PrepareMarkup::With(html! { div id=[self.id()] class=[self.classes().get()] { - button - type="button" - class="btn btn-secondary dropdown-toggle" - data-bs-toggle="dropdown" - aria-expanded="false" - { - ("Dropdown button") - } - ul class="dropdown-menu" { - li { - a class="dropdown-item" href="#" { - ("Action") + @if !button_title.is_empty() { + @let mut btn_classes = AttrClasses::new([ + "btn", + &self.button_size().to_string(), + &self.button_color().to_string(), + ].join(" ")); + @let (offset, reference) = match self.menu_position() { + MenuPosition::Default => (None, None), + MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None), + MenuPosition::Parent => (None, Some("parent")), + }; + @let auto_close = match self.auto_close { + AutoClose::Default => None, + AutoClose::ClickableInside => Some("inside"), + AutoClose::ClickableOutside => Some("outside"), + AutoClose::ManualClose => Some("false"), + }; + @let menu_classes = AttrClasses::new("dropdown-menu") + .with_value(ClassesOp::Add, match self.menu_align() { + MenuAlign::Start => String::new(), + MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu") + .map_or(String::new(), |class| join!(class, "-start")), + MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu") + .map_or( + "dropdown-menu-start".into(), + |class| join!("dropdown-menu-start ", class, "-end") + ), + MenuAlign::End => "dropdown-menu-end".into(), + MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu") + .map_or(String::new(), |class| join!(class, "-end")), + MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu") + .map_or( + "dropdown-menu-end".into(), + |class| join!("dropdown-menu-end ", class, "-start") + ), + }); + + // Renderizado en modo split (dos botones) o simple (un botón). + @if self.button_split() { + // Botón principal (acción/etiqueta). + @let btn = html! { + button + type="button" + class=[btn_classes.get()] + { + (button_title) + } + }; + // Botón *toggle* que abre/cierra el menú asociado. + @let btn_toggle = html! { + button + type="button" + class=[btn_classes.alter_value( + ClassesOp::Add, "dropdown-toggle dropdown-toggle-split" + ).get()] + data-bs-toggle="dropdown" + data-bs-offset=[offset] + data-bs-reference=[reference] + data-bs-auto-close=[auto_close] + aria-expanded="false" + { + span class="visually-hidden" { + (L10n::t("dropdown_toggle", &LOCALES_BOOTSIER).using(cx)) + } + } + }; + // Orden según dirección (en `dropstart` el *toggle* se sitúa antes). + @match self.direction() { + Direction::Dropstart => { + (btn_toggle) + ul class=[menu_classes.get()] { (items) } + (btn) + } + _ => { + (btn) + (btn_toggle) + ul class=[menu_classes.get()] { (items) } + } } - } - li { - a class="dropdown-item" href="#" { - ("Another action") - } - } - li { - a class="dropdown-item" href="#" { - ("Something else here") + } @else { + // Botón único con funcionalidad de *toggle*. + button + type="button" + class=[btn_classes.alter_value( + ClassesOp::Add, "dropdown-toggle" + ).get()] + data-bs-toggle="dropdown" + data-bs-offset=[offset] + data-bs-reference=[reference] + data-bs-auto-close=[auto_close] + aria-expanded="false" + { + (button_title) } + ul class=[menu_classes.get()] { (items) } } + } @else { + // Sin botón: sólo el listado como menú contextual. + ul class="dropdown-menu" { (items) } } } }) @@ -64,23 +277,91 @@ impl Component for Dropdown { impl Dropdown { // **< Dropdown BUILDER >*********************************************************************** + /// Establece el identificador único (`id`) del menú desplegable. #[builder_fn] pub fn with_id(mut self, id: impl AsRef<str>) -> Self { self.id.alter_value(id); self } + /// Modifica la lista de clases CSS aplicadas al menú desplegable. #[builder_fn] pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { self.classes.alter_value(op, classes); self } + /// Establece el título del botón. + #[builder_fn] + pub fn with_button_title(mut self, title: L10n) -> Self { + self.button_title = title; + self + } + + /// Ajusta el tamaño del botón. + #[builder_fn] + pub fn with_button_size(mut self, size: ButtonSize) -> Self { + self.button_size = size; + self + } + + /// Define el color/estilo del botón. + #[builder_fn] + pub fn with_button_color(mut self, color: ButtonColor) -> Self { + self.button_color = color; + self + } + + /// Activa/desactiva el modo *split* (botón de acción + *toggle*). + #[builder_fn] + pub fn with_button_split(mut self, split: bool) -> Self { + self.button_split = split; + self + } + + /// Indica si el botón del menú está integrado en un grupo de botones. + #[builder_fn] + pub fn with_button_grouped(mut self, grouped: bool) -> Self { + self.button_grouped = grouped; + self + } + + /// Establece la política de cierre automático del menú desplegable. + #[builder_fn] + pub fn with_auto_close(mut self, auto_close: dropdown::AutoClose) -> Self { + self.auto_close = auto_close; + self + } + + /// Establece la dirección de despliegue del menú. + #[builder_fn] + pub fn with_direction(mut self, direction: dropdown::Direction) -> Self { + self.direction = direction; + self + } + + /// Configura la alineación horizontal (con posible comportamiento *responsive* adicional). + #[builder_fn] + pub fn with_menu_align(mut self, align: dropdown::MenuAlign) -> Self { + self.menu_align = align; + self + } + + /// Configura la posición del menú. + #[builder_fn] + pub fn with_menu_position(mut self, position: dropdown::MenuPosition) -> Self { + self.menu_position = position; + self + } + + /// Añade un nuevo elemento hijo al menú. + #[inline] pub fn add_item(mut self, item: dropdown::Item) -> Self { self.items.add(Child::with(item)); self } + /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. #[builder_fn] pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self { self.items.alter_typed(op); @@ -89,10 +370,57 @@ impl Dropdown { // **< Dropdown GETTERS >*********************************************************************** + /// Devuelve las clases CSS asociadas al menú desplegable. pub fn classes(&self) -> &AttrClasses { &self.classes } + /// Devuelve el título del botón. + pub fn button_title(&self) -> &L10n { + &self.button_title + } + + /// Devuelve el tamaño configurado del botón. + pub fn button_size(&self) -> &ButtonSize { + &self.button_size + } + + /// Devuelve el color/estilo configurado del botón. + pub fn button_color(&self) -> &ButtonColor { + &self.button_color + } + + /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*). + pub fn button_split(&self) -> bool { + self.button_split + } + + /// Devuelve si el botón del menú está integrado en un grupo de botones. + pub fn button_grouped(&self) -> bool { + self.button_grouped + } + + /// Devuelve la política de cierre automático del menú desplegado. + pub fn auto_close(&self) -> &dropdown::AutoClose { + &self.auto_close + } + + /// Devuelve la dirección de despliegue configurada. + pub fn direction(&self) -> &dropdown::Direction { + &self.direction + } + + /// Devuelve la configuración de alineación horizontal del menú desplegable. + pub fn menu_align(&self) -> &dropdown::MenuAlign { + &self.menu_align + } + + /// Devuelve la posición configurada para el menú desplegable. + pub fn menu_position(&self) -> &dropdown::MenuPosition { + &self.menu_position + } + + /// Devuelve la lista de elementos (`children`) del menú. pub fn items(&self) -> &Children { &self.items } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 1edd64b1..548024c5 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -1,22 +1,53 @@ use pagetop::prelude::*; -// **< ItemType >*********************************************************************************** +// **< ItemKind >*********************************************************************************** +/// Tipos de [`dropdown::Item`](crate::theme::dropdown::Item) disponibles en un menú desplegable +/// [`Dropdown`](crate::theme::Dropdown). +/// +/// Define internamente la naturaleza del elemento y su comportamiento al mostrarse o interactuar +/// con él. #[derive(AutoDefault)] -pub enum ItemType { +pub enum ItemKind { + /// Elemento vacío, no produce salida. #[default] Void, + /// Etiqueta sin comportamiento interactivo. Label(L10n), - Link(L10n, FnPathByContext), - LinkBlank(L10n, FnPathByContext), + /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar + /// inicialmente deshabilitado. + Link { + label: L10n, + path: FnPathByContext, + blank: bool, + disabled: bool, + }, + /// Acción ejecutable en la propia página, sin navegación asociada. Inicialmente puede estar + /// deshabilitado. + Button { label: L10n, disabled: bool }, + /// Título o encabezado que separa grupos de opciones. + Header(L10n), + /// Separador visual entre bloques de elementos. + Divider, } // **< Item >*************************************************************************************** +/// Representa un **elemento individual** de un menú desplegable +/// [`Dropdown`](crate::theme::Dropdown). +/// +/// Cada instancia de [`dropdown::Item`](crate::theme::dropdown::Item) se traduce en un componente +/// visible que puede comportarse como texto, enlace, botón, encabezado o separador, según su +/// [`ItemKind`]. +/// +/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, +/// manteniendo una interfaz común para renderizar todos los ítems del menú. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Item { - item_type: ItemType, + id : AttrId, + classes : AttrClasses, + item_kind: ItemKind, } impl Component for Item { @@ -24,86 +55,227 @@ impl Component for Item { Item::default() } + fn id(&self) -> Option<String> { + self.id.get() + } + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let description: Option<String> = None; + match self.item_kind() { + ItemKind::Void => PrepareMarkup::None, - // Obtiene la URL actual desde `cx.request`. - let current_path = cx.request().map(|request| request.path()); - - match self.item_type() { - ItemType::Void => PrepareMarkup::None, - ItemType::Label(label) => PrepareMarkup::With(html! { - li class="dropdown-item" { - span title=[description] { - //(left_icon) + ItemKind::Label(label) => PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + span class="dropdown-item-text" { (label.using(cx)) - //(right_icon) } } }), - ItemType::Link(label, path) => { - let item_path = path(cx); - let (class, aria) = if current_path == Some(item_path) { - ("dropdown-item active", Some("page")) - } else { - ("dropdown-item", None) - }; + + ItemKind::Link { + label, + path, + blank, + disabled, + } => { + let path = path(cx); + let current_path = cx.request().map(|request| request.path()); + let is_current = !*disabled && current_path.map(|p| p == path).unwrap_or(false); + + let mut classes = "dropdown-item".to_string(); + if is_current { + classes.push_str(" active"); + } + if *disabled { + classes.push_str(" disabled"); + } + + let href = (!disabled).then_some(path); + let target = (!disabled && *blank).then_some("_blank"); + let rel = (!disabled && *blank).then_some("noopener noreferrer"); + + let aria_current = (href.is_some() && is_current).then_some("page"); + let aria_disabled = disabled.then_some("true"); + let tabindex = disabled.then_some("-1"); + PrepareMarkup::With(html! { - li class=(class) aria-current=[aria] { - a class="nav-link" href=(item_path) title=[description] { - //(left_icon) + li id=[self.id()] class=[self.classes().get()] { + a + class=(classes) + href=[href] + target=[target] + rel=[rel] + aria-current=[aria_current] + aria-disabled=[aria_disabled] + tabindex=[tabindex] + { (label.using(cx)) - //(right_icon) } } }) } - ItemType::LinkBlank(label, path) => { - let item_path = path(cx); - let (class, aria) = if current_path == Some(item_path) { - ("dropdown-item active", Some("page")) - } else { - ("dropdown-item", None) - }; + + ItemKind::Button { label, disabled } => { + let mut classes = "dropdown-item".to_owned(); + if *disabled { + classes.push_str(" disabled"); + } + + let aria_disabled = disabled.then_some("true"); + let disabled_attr = disabled.then_some("disabled"); + PrepareMarkup::With(html! { - li class=(class) aria-current=[aria] { - a class="nav-link" href=(item_path) title=[description] target="_blank" { - //(left_icon) + li id=[self.id()] class=[self.classes().get()] { + button + class=(classes) + type="button" + aria-disabled=[aria_disabled] + disabled=[disabled_attr] + { (label.using(cx)) - //(right_icon) } } }) } + + ItemKind::Header(label) => PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + h6 class="dropdown-header" { + (label.using(cx)) + } + } + }), + + ItemKind::Divider => PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { hr class="dropdown-divider" {} } + }), } } } impl Item { + /// Crea un elemento de tipo texto, mostrado sin interacción. pub fn label(label: L10n) -> Self { Item { - item_type: ItemType::Label(label), + item_kind: ItemKind::Label(label), ..Default::default() } } + /// Crea un enlace para la navegación. pub fn link(label: L10n, path: FnPathByContext) -> Self { Item { - item_type: ItemType::Link(label, path), + item_kind: ItemKind::Link { + label, + path, + blank: false, + disabled: false, + }, ..Default::default() } } + /// Crea un enlace deshabilitado que no permite la interacción. + pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: false, + disabled: true, + }, + ..Default::default() + } + } + + /// Crea un enlace que se abre en una nueva ventana o pestaña. pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { Item { - item_type: ItemType::LinkBlank(label, path), + item_kind: ItemKind::Link { + label, + path, + blank: true, + disabled: false, + }, ..Default::default() } } - // Item GETTERS. + /// Crea un enlace deshabilitado que se abriría en una nueva ventana. + pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: true, + disabled: true, + }, + ..Default::default() + } + } - pub fn item_type(&self) -> &ItemType { - &self.item_type + /// Crea un botón de acción local, sin navegación asociada. + pub fn button(label: L10n) -> Self { + Item { + item_kind: ItemKind::Button { + label, + disabled: false, + }, + ..Default::default() + } + } + + /// Crea un botón deshabilitado. + pub fn button_disabled(label: L10n) -> Self { + Item { + item_kind: ItemKind::Button { + label, + disabled: true, + }, + ..Default::default() + } + } + + /// Crea un encabezado para un grupo de elementos dentro del menú. + pub fn header(label: L10n) -> Self { + Item { + item_kind: ItemKind::Header(label), + ..Default::default() + } + } + + /// Crea un separador visual entre bloques de elementos. + pub fn divider() -> Self { + Item { + item_kind: ItemKind::Divider, + ..Default::default() + } + } + + // **< Item BUILDER >*************************************************************************** + + /// Establece el identificador único (`id`) del elemento. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al elemento. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + // **< Item GETTERS >*************************************************************************** + + /// Devuelve las clases CSS asociadas al elemento. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el tipo de elemento representado por este ítem. + pub fn item_kind(&self) -> &ItemKind { + &self.item_kind } } From c3a0255d441e02ba67a24952adb11499625bfa3e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 10:52:33 +0200 Subject: [PATCH 169/224] =?UTF-8?q?=F0=9F=9A=A7=20Cambia=20el=20uso=20de?= =?UTF-8?q?=20`BreakPoint`=20para=20`Container`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme.rs | 2 +- .../src/theme/aux/breakpoint.rs | 43 +++---- .../pagetop-bootsier/src/theme/container.rs | 108 +++++++++++------- 3 files changed, 87 insertions(+), 66 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index 51073d43..ec95165c 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -4,7 +4,7 @@ pub mod aux; // Container. mod container; -pub use container::{Container, ContainerType}; +pub use container::{Container, ContainerType, ContainerWidth}; // Dropdown. pub mod dropdown; diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index 963bb3b5..601aa547 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -7,10 +7,6 @@ use std::fmt; /// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o /// `XXL`, respectivamente. /// - `""` (cadena vacía) para `None`. -/// - `"fluid"` para las variantes `Fluid` y `FluidMax(_)`, útil para modelar `container-fluid` en -/// [`Container`](crate::theme::Container). Se debe tener en cuenta que `"fluid"` **no** es un -/// sufijo de *breakpoint*. Para construir clases válidas con prefijo (p. ej., `"col-md"`), se -/// recomienda usar `to_class()` (Self::to_class) o `try_class()` (Self::try_class). /// /// # Ejemplos /// @@ -18,13 +14,14 @@ use std::fmt; /// # use pagetop_bootsier::prelude::*; /// assert_eq!(BreakPoint::MD.to_string(), "md"); /// assert_eq!(BreakPoint::None.to_string(), ""); -/// assert_eq!(BreakPoint::Fluid.to_string(), "fluid"); /// /// // Forma correcta para clases con prefijo: -/// //assert_eq!(BreakPoint::MD.to_class("col"), "col-md"); -/// //assert_eq!(BreakPoint::Fluid.to_class("offcanvas"), "offcanvas"); +/// assert_eq!(BreakPoint::MD.to_class("col"), "col-md"); +/// assert_eq!(BreakPoint::None.to_class("offcanvas"), "offcanvas"); +/// +/// assert_eq!(BreakPoint::XXL.try_class("col"), Some("col-xxl".to_string())); +/// assert_eq!(BreakPoint::None.try_class("offcanvas"), None); /// ``` -#[rustfmt::skip] #[derive(AutoDefault)] pub enum BreakPoint { /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. @@ -40,11 +37,6 @@ pub enum BreakPoint { XL, /// **1400px o más** - Dispositivos extragrandes: puestos de escritorio más grandes. XXL, - /// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo. - Fluid, - /// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo hasta - /// un ancho máximo indicado. - FluidMax(UnitValue) } impl BreakPoint { @@ -53,7 +45,7 @@ impl BreakPoint { /// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso. #[inline] pub const fn is_breakpoint(&self) -> bool { - matches!(self, Self::SM | Self::MD | Self::LG | Self::XL | Self::XXL) + !matches!(self, Self::None) } /// Genera un nombre de clase CSS basado en el punto de ruptura. @@ -65,11 +57,12 @@ impl BreakPoint { /// # Ejemplo /// /// ```rust + /// # use pagetop_bootsier::prelude::*; /// let breakpoint = BreakPoint::MD; /// let class = breakpoint.to_class("col"); /// assert_eq!(class, "col-md".to_string()); /// - /// let breakpoint = BreakPoint::Fluid; + /// let breakpoint = BreakPoint::None; /// let class = breakpoint.to_class("offcanvas"); /// assert_eq!(class, "offcanvas".to_string()); /// ``` @@ -91,12 +84,13 @@ impl BreakPoint { /// # Ejemplo /// /// ```rust + /// # use pagetop_bootsier::prelude::*; /// let breakpoint = BreakPoint::MD; /// let class = breakpoint.try_class("col"); /// assert_eq!(class, Some("col-md".to_string())); /// - /// let breakpoint = BreakPoint::Fluid; - /// let class = breakpoint.try_class("navbar-expanded"); + /// let breakpoint = BreakPoint::None; + /// let class = breakpoint.try_class("navbar-expand"); /// assert_eq!(class, None); /// ``` #[inline] @@ -113,15 +107,12 @@ impl BreakPoint { impl fmt::Display for BreakPoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::None => Ok(()), - Self::SM => f.write_str("sm"), - Self::MD => f.write_str("md"), - Self::LG => f.write_str("lg"), - Self::XL => f.write_str("xl"), - Self::XXL => f.write_str("xxl"), - // Devuelven "fluid" (para modelar `container-fluid`). - Self::Fluid => f.write_str("fluid"), - Self::FluidMax(_) => f.write_str("fluid"), + Self::None => Ok(()), + Self::SM => f.write_str("sm"), + Self::MD => f.write_str("md"), + Self::LG => f.write_str("lg"), + Self::XL => f.write_str("xl"), + Self::XXL => f.write_str("xxl"), } } } diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index 65ce9230..22f7fc6c 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -2,11 +2,12 @@ use pagetop::prelude::*; use crate::prelude::*; +// **< ContainerType >****************************************************************************** + /// Tipo de contenedor ([`Container`]). /// /// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API /// común a todos los contenedores. -#[rustfmt::skip] #[derive(AutoDefault)] pub enum ContainerType { /// Contenedor genérico (`<div>`). @@ -24,22 +25,42 @@ pub enum ContainerType { Article, } -/// Componente genérico para crear un contenedor de componentes. +// **< ContainerWidth >***************************************************************************** + +/// Define el comportamiento para ajustar el ancho de un contenedor ([`Container`]). +#[derive(AutoDefault)] +pub enum ContainerWidth { + /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de + /// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible. + #[default] + Default, + /// Aplica los anchos máximos predefinidos a partir del punto de ruptura indicado. Por debajo de + /// ese punto de ruptura ocupa el 100% del ancho disponible. + From(BreakPoint), + /// Ocupa el 100% del ancho disponible siempre. + Fluid, + /// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito. + FluidMax(UnitValue), +} + +// **< Container >********************************************************************************** + +/// Componente para crear un **contenedor de componentes**. /// /// Envuelve el contenido con la etiqueta HTML indicada por [`ContainerType`]. Sólo se renderiza si /// existen componentes hijos (*children*). #[rustfmt::skip] #[derive(AutoDefault)] pub struct Container { - id : AttrId, - classes : AttrClasses, - container_type: ContainerType, - breakpoint : BreakPoint, - children : Children, - bg_color : BgColor, - text_color : TextColor, - border : Border, - rounded : Rounded, + id : AttrId, + classes : AttrClasses, + container_type : ContainerType, + container_width: ContainerWidth, + bg_color : BgColor, + text_color : TextColor, + border : Border, + rounded : Rounded, + children : Children, } impl Component for Container { @@ -55,7 +76,16 @@ impl Component for Container { self.alter_classes( ClassesOp::Prepend, [ - join_pair!("container", "-", self.breakpoint().to_string()), + join_pair!( + "container", + "-", + match self.width() { + ContainerWidth::Default => String::new(), + ContainerWidth::From(bp) => bp.to_string(), + ContainerWidth::Fluid => "fluid".to_string(), + ContainerWidth::FluidMax(_) => "fluid".to_string(), + } + ), self.bg_color().to_string(), self.text_color().to_string(), self.border().to_string(), @@ -70,8 +100,8 @@ impl Component for Container { if output.is_empty() { return PrepareMarkup::None; } - let style = match self.breakpoint() { - BreakPoint::FluidMax(w) if w.is_measurable() => { + let style = match self.width() { + ContainerWidth::FluidMax(w) if w.is_measurable() => { Some(join!("max-width: ", w.to_string(), ";")) } _ => None, @@ -168,24 +198,10 @@ impl Container { self } - /// Establece el *punto de ruptura* del contenedor. + /// Establece el comportamiento del ancho para el contenedor. #[builder_fn] - pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { - self.breakpoint = bp; - self - } - - /// Añade un nuevo componente hijo al contenedor. - pub fn add_child(mut self, component: impl Component) -> Self { - self.children - .alter_child(ChildOp::Add(Child::with(component))); - self - } - - /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. - #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_width(mut self, width: ContainerWidth) -> Self { + self.container_width = width; self } @@ -225,6 +241,20 @@ impl Container { self } + /// Añade un nuevo componente hijo al contenedor. + #[inline] + pub fn add_child(mut self, component: impl Component) -> Self { + self.children.add(Child::with(component)); + self + } + + /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + // **< Container GETTERS >********************************************************************** /// Devuelve las clases CSS asociadas al contenedor. @@ -237,14 +267,9 @@ impl Container { &self.container_type } - /// Devuelve el *punto de ruptura* actualmente configurado. - pub fn breakpoint(&self) -> &BreakPoint { - &self.breakpoint - } - - /// Devuelve la lista de hijos (`children`) del contenedor. - pub fn children(&self) -> &Children { - &self.children + /// Devuelve el comportamiento para el ancho del contenedor. + pub fn width(&self) -> &ContainerWidth { + &self.container_width } /// Devuelve el color de fondo del contenedor. @@ -266,4 +291,9 @@ impl Container { pub fn rounded(&self) -> &Rounded { &self.rounded } + + /// Devuelve la lista de componentes (`children`) del contenedor. + pub fn children(&self) -> &Children { + &self.children + } } From c58b011943d9146dfb69068ff4c88afeb22328f1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 10:55:34 +0200 Subject: [PATCH 170/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactoriza=20`Dro?= =?UTF-8?q?pdown`=20para=20separar=20propiedades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/dropdown.rs | 4 +- .../src/theme/dropdown/component.rs | 127 +++--------------- .../src/theme/dropdown/props.rs | 86 ++++++++++++ 3 files changed, 110 insertions(+), 107 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/dropdown/props.rs diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index 9e81c7a3..02df7fd0 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -10,9 +10,11 @@ //! Su propósito es ofrecer una base uniforme sobre la que construir menús consistentes, adaptados //! al contexto de cada aplicación. +mod props; +pub use props::{AutoClose, Direction, MenuAlign, MenuPosition}; + mod component; pub use component::Dropdown; -pub use component::{AutoClose, Direction, MenuAlign, MenuPosition}; mod item; pub use item::{Item, ItemKind}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 9aef2562..e9d36d7f 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -3,91 +3,6 @@ use pagetop::prelude::*; use crate::prelude::*; use crate::LOCALES_BOOTSIER; -// **< AutoClose >********************************************************************************** - -/// Estrategia para el cierre automático de un menú [`Dropdown`]. -/// -/// Define cuándo se cierra el menú desplegado según la interacción del usuario. -#[derive(AutoDefault)] -pub enum AutoClose { - /// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`. - #[default] - Default, - /// Sólo se cierra con clics dentro del menú. - ClickableInside, - /// Sólo se cierra con clics fuera del menú. - ClickableOutside, - /// Cierre manual, no se cierra con clics; sólo al pulsar nuevamente el botón del menú - /// (*toggle*), o pulsando `Esc`. - ManualClose, -} - -// **< Direction >********************************************************************************** - -/// Dirección de despliegue de un menú [`Dropdown`]. -/// -/// Controla desde qué posición se muestra el menú respecto al botón. -#[derive(AutoDefault)] -pub enum Direction { - /// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial, - /// respetando LTR/RTL). - #[default] - Default, - /// Centra horizontalmente el menú respecto al botón. - Centered, - /// Despliega el menú hacia arriba. - Dropup, - /// Despliega el menú hacia arriba y centrado. - DropupCentered, - /// Despliega el menú desde el lateral final, respetando LTR/RTL. - Dropend, - /// Despliega el menú desde el lateral inicial, respetando LTR/RTL. - Dropstart, -} - -// **< MenuAlign >********************************************************************************** - -/// Alineación horizontal del menú desplegable [`Dropdown`]. -/// -/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una -/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). -#[derive(AutoDefault)] -pub enum MenuAlign { - /// Alineación al inicio (comportamiento por defecto). - #[default] - Start, - /// Alineación al inicio a partir del punto de ruptura indicado. - StartAt(BreakPoint), - /// Alineación al inicio por defecto, y al final a partir de un punto de ruptura válido. - StartAndEnd(BreakPoint), - /// Alineación al final. - End, - /// Alineación al final a partir del punto de ruptura indicado. - EndAt(BreakPoint), - /// Alineación al final por defecto, y al inicio a partir de un punto de ruptura válido. - EndAndStart(BreakPoint), -} - -// **< MenuPosition >******************************************************************************* - -/// Posición relativa del menú desplegable [`Dropdown`]. -/// -/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el -/// cálculo de la posición. -#[derive(AutoDefault)] -pub enum MenuPosition { - /// Posicionamiento automático por defecto. - #[default] - Default, - /// Desplazamiento manual en píxeles `(x, y)` aplicado al menú. Se admiten valores negativos. - Offset(i8, i8), - /// Posiciona el menú tomando como referencia el botón padre. Especialmente útil cuando - /// [`button_split()`](crate::theme::Dropdown::button_split) es `true`. - Parent, -} - -// **< Dropdown >********************************************************************************** - /// Componente para crear un **menú desplegable**. /// /// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split)) @@ -146,13 +61,13 @@ impl Component for Dropdown { self.alter_classes(ClassesOp::Prepend, [ if g { "btn-group" } else { "" }, match self.direction() { - Direction::Default if g => "", - Direction::Default => "dropdown", - Direction::Centered => "dropdown-center", - Direction::Dropup => "dropup", - Direction::DropupCentered => "dropup-center", - Direction::Dropend => "dropend", - Direction::Dropstart => "dropstart", + dropdown::Direction::Default if g => "", + dropdown::Direction::Default => "dropdown", + dropdown::Direction::Centered => "dropdown-center", + dropdown::Direction::Dropup => "dropup", + dropdown::Direction::DropupCentered => "dropup-center", + dropdown::Direction::Dropend => "dropend", + dropdown::Direction::Dropstart => "dropstart", } ].join(" ")); } @@ -176,30 +91,30 @@ impl Component for Dropdown { &self.button_color().to_string(), ].join(" ")); @let (offset, reference) = match self.menu_position() { - MenuPosition::Default => (None, None), - MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None), - MenuPosition::Parent => (None, Some("parent")), + dropdown::MenuPosition::Default => (None, None), + dropdown::MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None), + dropdown::MenuPosition::Parent => (None, Some("parent")), }; @let auto_close = match self.auto_close { - AutoClose::Default => None, - AutoClose::ClickableInside => Some("inside"), - AutoClose::ClickableOutside => Some("outside"), - AutoClose::ManualClose => Some("false"), + dropdown::AutoClose::Default => None, + dropdown::AutoClose::ClickableInside => Some("inside"), + dropdown::AutoClose::ClickableOutside => Some("outside"), + dropdown::AutoClose::ManualClose => Some("false"), }; @let menu_classes = AttrClasses::new("dropdown-menu") .with_value(ClassesOp::Add, match self.menu_align() { - MenuAlign::Start => String::new(), - MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu") + dropdown::MenuAlign::Start => String::new(), + dropdown::MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu") .map_or(String::new(), |class| join!(class, "-start")), - MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu") + dropdown::MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu") .map_or( "dropdown-menu-start".into(), |class| join!("dropdown-menu-start ", class, "-end") ), - MenuAlign::End => "dropdown-menu-end".into(), - MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu") + dropdown::MenuAlign::End => "dropdown-menu-end".into(), + dropdown::MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu") .map_or(String::new(), |class| join!(class, "-end")), - MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu") + dropdown::MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu") .map_or( "dropdown-menu-end".into(), |class| join!("dropdown-menu-end ", class, "-start") @@ -237,7 +152,7 @@ impl Component for Dropdown { }; // Orden según dirección (en `dropstart` el *toggle* se sitúa antes). @match self.direction() { - Direction::Dropstart => { + dropdown::Direction::Dropstart => { (btn_toggle) ul class=[menu_classes.get()] { (items) } (btn) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs new file mode 100644 index 00000000..da305ea7 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs @@ -0,0 +1,86 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +// **< AutoClose >********************************************************************************** + +/// Estrategia para el cierre automático de un menú [`Dropdown`]. +/// +/// Define cuándo se cierra el menú desplegado según la interacción del usuario. +#[derive(AutoDefault)] +pub enum AutoClose { + /// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`. + #[default] + Default, + /// Sólo se cierra con clics dentro del menú. + ClickableInside, + /// Sólo se cierra con clics fuera del menú. + ClickableOutside, + /// Cierre manual, no se cierra con clics; sólo al pulsar nuevamente el botón del menú + /// (*toggle*), o pulsando `Esc`. + ManualClose, +} + +// **< Direction >********************************************************************************** + +/// Dirección de despliegue de un menú [`Dropdown`]. +/// +/// Controla desde qué posición se muestra el menú respecto al botón. +#[derive(AutoDefault)] +pub enum Direction { + /// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial, + /// respetando LTR/RTL). + #[default] + Default, + /// Centra horizontalmente el menú respecto al botón. + Centered, + /// Despliega el menú hacia arriba. + Dropup, + /// Despliega el menú hacia arriba y centrado. + DropupCentered, + /// Despliega el menú desde el lateral final, respetando LTR/RTL. + Dropend, + /// Despliega el menú desde el lateral inicial, respetando LTR/RTL. + Dropstart, +} + +// **< MenuAlign >********************************************************************************** + +/// Alineación horizontal del menú desplegable [`Dropdown`]. +/// +/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una +/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). +#[derive(AutoDefault)] +pub enum MenuAlign { + /// Alineación al inicio (comportamiento por defecto). + #[default] + Start, + /// Alineación al inicio a partir del punto de ruptura indicado. + StartAt(BreakPoint), + /// Alineación al inicio por defecto, y al final a partir de un punto de ruptura válido. + StartAndEnd(BreakPoint), + /// Alineación al final. + End, + /// Alineación al final a partir del punto de ruptura indicado. + EndAt(BreakPoint), + /// Alineación al final por defecto, y al inicio a partir de un punto de ruptura válido. + EndAndStart(BreakPoint), +} + +// **< MenuPosition >******************************************************************************* + +/// Posición relativa del menú desplegable [`Dropdown`]. +/// +/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el +/// cálculo de la posición. +#[derive(AutoDefault)] +pub enum MenuPosition { + /// Posicionamiento automático por defecto. + #[default] + Default, + /// Desplazamiento manual en píxeles `(x, y)` aplicado al menú. Se admiten valores negativos. + Offset(i8, i8), + /// Posiciona el menú tomando como referencia el botón padre. Especialmente útil cuando + /// [`button_split()`](crate::theme::Dropdown::button_split) es `true`. + Parent, +} From 1bf7ada6076e8220ab4318c8f177b5b5b296a631 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 19:02:47 +0200 Subject: [PATCH 171/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?paneles=20deslizables=20`Offcanvas`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/locale/en-US/components.ftl | 2 +- .../src/locale/es-ES/components.ftl | 2 +- extensions/pagetop-bootsier/src/theme.rs | 7 +- .../pagetop-bootsier/src/theme/offcanvas.rs | 305 +----------------- .../src/theme/offcanvas/component.rs | 261 +++++++++++++++ .../src/theme/offcanvas/props.rs | 60 ++++ 6 files changed, 331 insertions(+), 306 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/offcanvas/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/offcanvas/props.rs diff --git a/extensions/pagetop-bootsier/src/locale/en-US/components.ftl b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl index 0bb7fba6..e3b0d6e6 100644 --- a/extensions/pagetop-bootsier/src/locale/en-US/components.ftl +++ b/extensions/pagetop-bootsier/src/locale/en-US/components.ftl @@ -2,7 +2,7 @@ dropdown_toggle = Toggle Dropdown # Offcanvas -close = Close +offcanvas_close = Close # Navbar toggle = Toggle navigation diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl index 045191e3..ab7ff687 100644 --- a/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl +++ b/extensions/pagetop-bootsier/src/locale/es-ES/components.ftl @@ -2,7 +2,7 @@ dropdown_toggle = Mostrar/ocultar menú # Offcanvas -close = Cerrar +offcanvas_close = Cerrar # Navbar toggle = Mostrar/ocultar navegación diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index ec95165c..b5be6b71 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -21,7 +21,6 @@ pub mod navbar; pub use navbar::{Navbar, NavbarToggler}; // Offcanvas. -mod offcanvas; -pub use offcanvas::{ - Offcanvas, OffcanvasBackdrop, OffcanvasBodyScroll, OffcanvasPlacement, OffcanvasVisibility, -}; +pub mod offcanvas; +#[doc(inline)] +pub use offcanvas::Offcanvas; diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index b790522b..560bd30a 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -1,302 +1,7 @@ -use pagetop::prelude::*; +//! Definiciones para crear paneles laterales deslizantes [`Offcanvas`]. -use crate::prelude::*; -use crate::LOCALES_BOOTSIER; +mod props; +pub use props::{Backdrop, BodyScroll, Placement, Visibility}; -use std::fmt; - -// **< OffcanvasPlacement >************************************************************************* - -/// Posición de aparición del panel **deslizante** ([`Offcanvas`]). -/// -/// Define desde qué borde de la ventana entra y se ancla el panel. -#[derive(AutoDefault)] -pub enum OffcanvasPlacement { - /// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL). - #[default] - Start, - /// Desde el borde final según dirección de lectura (respetando LTR/RTL). - End, - /// Desde la parte superior. - Top, - /// Desde la parte inferior. - Bottom, -} - -#[rustfmt::skip] -impl fmt::Display for OffcanvasPlacement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - OffcanvasPlacement::Start => f.write_str("offcanvas-start"), - OffcanvasPlacement::End => f.write_str("offcanvas-end"), - OffcanvasPlacement::Top => f.write_str("offcanvas-top"), - OffcanvasPlacement::Bottom => f.write_str("offcanvas-bottom"), - } - } -} - -// **< OffcanvasVisibility >************************************************************************ - -/// Estado inicial del panel ([`Offcanvas`]). -#[derive(AutoDefault)] -pub enum OffcanvasVisibility { - /// El panel **permanece oculto** desde el principio. - #[default] - Default, - /// El panel **se muestra abierto** al cargar. - Show, -} - -impl fmt::Display for OffcanvasVisibility { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - OffcanvasVisibility::Default => Ok(()), - OffcanvasVisibility::Show => f.write_str("show"), - } - } -} - -// **< OffcanvasBodyScroll >************************************************************************ - -/// Controla si la página principal puede **desplazarse** al abrir el panel ([`Offcanvas`]). -#[derive(AutoDefault)] -pub enum OffcanvasBodyScroll { - /// Opción por defecto, la página principal se **bloquea** centrando la interacción en el panel. - #[default] - Disabled, - /// **Permite** el desplazamiento de la página principal. - Enabled, -} - -// **< OffcanvasBackdrop >************************************************************************** - -/// Comportamiento de la **capa de fondo** (*backdrop*) del panel ([`Offcanvas`]) al desplegarse. -#[derive(AutoDefault)] -pub enum OffcanvasBackdrop { - /// **Sin capa** de fondo; la página principal permanece visible e interactiva. - Disabled, - /// Opción por defecto, se **oscurece** el fondo; un clic fuera del panel suele cerrarlo. - #[default] - Enabled, - /// Se muestra capa de fondo pero **no** se cierra al pulsar fuera (útil cuando se requiere - /// completar una acción antes de salir). - Static, -} - -// **< Offcanvas >********************************************************************************** - -/// Panel lateral **deslizante** para contenido complementario. -/// -/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes -/// características principales: -/// -/// - **Entrada configurable desde un borde** de la ventana. -/// - **Encabezado con título** y **botón de cierre** integrado. -/// - **Accesibilidad**: asocia título y controles a un identificador único y expone atributos -/// adecuados para lectores de pantalla y navegación por teclado. -/// - **Opcionalmente** bloquea el desplazamiento del documento y/o muestra una capa de fondo para -/// centrar la atención del usuario. -/// - **Responsive**: puede cambiar su comportamiento según el punto de ruptura indicado. -/// - **No se renderiza** si no tiene contenido. -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Offcanvas { - id : AttrId, - classes : AttrClasses, - title : L10n, - breakpoint: BreakPoint, - placement : OffcanvasPlacement, - visibility: OffcanvasVisibility, - scrolling : OffcanvasBodyScroll, - backdrop : OffcanvasBackdrop, - children : Children, -} - -impl Component for Offcanvas { - fn new() -> Self { - Offcanvas::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - self.breakpoint().to_class("offcanvas"), - self.placement().to_string(), - self.visibility().to_string(), - ] - .join(" "), - ); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let body = self.children().render(cx); - if body.is_empty() { - return PrepareMarkup::None; - } - - let id = cx.required_id::<Self>(self.id()); - let id_label = join!(id, "-label"); - let id_target = join!("#", id); - - let body_scroll = match self.body_scroll() { - OffcanvasBodyScroll::Disabled => None, - OffcanvasBodyScroll::Enabled => Some("true"), - }; - - let backdrop = match self.backdrop() { - OffcanvasBackdrop::Disabled => Some("false"), - OffcanvasBackdrop::Enabled => None, - OffcanvasBackdrop::Static => Some("static"), - }; - - let title = self.title().using(cx); - - PrepareMarkup::With(html! { - div - id=(id) - class=[self.classes().get()] - tabindex="-1" - data-bs-scroll=[body_scroll] - data-bs-backdrop=[backdrop] - aria-labelledby=(id_label) - { - div class="offcanvas-header" { - @if !title.is_empty() { - h5 class="offcanvas-title" id=(id_label) { (title) } - } - button - type="button" - class="btn-close" - data-bs-dismiss="offcanvas" - data-bs-target=(id_target) - aria-label=[L10n::t("close", &LOCALES_BOOTSIER).lookup(cx)] - {} - } - div class="offcanvas-body" { - (body) - } - } - }) - } -} - -impl Offcanvas { - // **< Offcanvas BUILDER >********************************************************************** - - /// Establece el identificador único (`id`) del panel. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - /// Modifica la lista de clases CSS aplicadas al panel. - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - /// Establece el **título** del encabezado. - #[builder_fn] - pub fn with_title(mut self, title: L10n) -> Self { - self.title = title; - self - } - - /// Configura el **punto de ruptura** para activar el comportamiento responsive del panel. - #[builder_fn] - pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { - self.breakpoint = bp; - self - } - - /// Indica la **posición** desde la que entra el panel. - #[builder_fn] - pub fn with_placement(mut self, placement: OffcanvasPlacement) -> Self { - self.placement = placement; - self - } - - /// Fija el **estado inicial** del panel (oculto o visible al cargar). - #[builder_fn] - pub fn with_visibility(mut self, visibility: OffcanvasVisibility) -> Self { - self.visibility = visibility; - self - } - - /// Permite o bloquea el **desplazamiento** de la página principal mientras el panel está - /// abierto. - #[builder_fn] - pub fn with_body_scroll(mut self, scrolling: OffcanvasBodyScroll) -> Self { - self.scrolling = scrolling; - self - } - - /// Ajusta la **capa de fondo** del panel para definir su comportamiento al interactuar fuera. - #[builder_fn] - pub fn with_backdrop(mut self, backdrop: OffcanvasBackdrop) -> Self { - self.backdrop = backdrop; - self - } - - /// Añade un nuevo componente hijo al panel. - pub fn add_child(mut self, child: impl Component) -> Self { - self.children.add(Child::with(child)); - self - } - - /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. - #[builder_fn] - pub fn with_children(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); - self - } - - // **< Offcanvas GETTERS >********************************************************************** - - /// Devuelve las clases CSS asociadas al panel. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el título del panel como [`L10n`]. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve el punto de ruptura configurado. - pub fn breakpoint(&self) -> &BreakPoint { - &self.breakpoint - } - - /// Devuelve la posición del panel. - pub fn placement(&self) -> &OffcanvasPlacement { - &self.placement - } - - /// Devuelve el estado inicial del panel. - pub fn visibility(&self) -> &OffcanvasVisibility { - &self.visibility - } - - /// Indica si la página principal puede desplazarse mientras el panel está abierto. - pub fn body_scroll(&self) -> &OffcanvasBodyScroll { - &self.scrolling - } - - /// Devuelve la configuración de la capa de fondo. - pub fn backdrop(&self) -> &OffcanvasBackdrop { - &self.backdrop - } - - /// Devuelve la lista de hijos (`children`) del panel. - pub fn children(&self) -> &Children { - &self.children - } -} +mod component; +pub use component::Offcanvas; diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs new file mode 100644 index 00000000..7cd7dffe --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -0,0 +1,261 @@ +use pagetop::prelude::*; + +use crate::prelude::*; +use crate::LOCALES_BOOTSIER; + +/// Componente para crear un **panel lateral deslizante** con contenidos adicionales. +/// +/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes +/// características principales: +/// +/// - Puede mostrar una capa de fondo para centrar la atención del usuario en el panel +/// ([`with_backdrop()`](Self::with_backdrop)); o puede bloquear el desplazamiento del documento +/// principal ([`with_body_scroll()`](Self::with_body_scroll)). +/// - Se puede configurar el borde de la ventana desde el que se desliza el panel +/// ([`with_placement()`](Self::with_placement)). +/// - Encabezado con título ([`with_title()`](Self::with_title)) y **botón de cierre** integrado. +/// - Puede cambiar su comportamiento a partir de un punto de ruptura +/// ([`with_breakpoint()`](Self::with_breakpoint)). +/// - Asocia título y controles de accesibilidad a un identificador único y expone atributos +/// adecuados para lectores de pantalla y navegación por teclado. +/// - **No se renderiza** si no tiene contenido. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::prelude::*; +/// let panel = Offcanvas::new() +/// .with_id("offcanvas_example") +/// .with_title(L10n::n("Offcanvas title")) +/// .with_placement(offcanvas::Placement::End) +/// .with_backdrop(offcanvas::Backdrop::Enabled) +/// .with_body_scroll(offcanvas::BodyScroll::Enabled) +/// .with_visibility(offcanvas::Visibility::Default) +/// .add_child(Dropdown::new() +/// .with_button_title(L10n::n("Menu")) +/// .add_item(dropdown::Item::label(L10n::n("Label"))) +/// .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es")) +/// .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout")) +/// ); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Offcanvas { + id : AttrId, + classes : AttrClasses, + title : L10n, + breakpoint: BreakPoint, + backdrop : offcanvas::Backdrop, + scrolling : offcanvas::BodyScroll, + placement : offcanvas::Placement, + visibility: offcanvas::Visibility, + children : Children, +} + +impl Component for Offcanvas { + fn new() -> Self { + Offcanvas::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + #[rustfmt::skip] + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + self.breakpoint().to_class("offcanvas"), + match self.placement() { + offcanvas::Placement::Start => "offcanvas-start", + offcanvas::Placement::End => "offcanvas-end", + offcanvas::Placement::Top => "offcanvas-top", + offcanvas::Placement::Bottom => "offcanvas-bottom", + }.to_string(), + match self.visibility() { + offcanvas::Visibility::Default => "", + offcanvas::Visibility::Show => "show", + }.to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let body = self.children().render(cx); + if body.is_empty() { + return PrepareMarkup::None; + } + + let id = cx.required_id::<Self>(self.id()); + let id_label = join!(id, "-label"); + let id_target = join!("#", id); + + let body_scroll = match self.body_scroll() { + offcanvas::BodyScroll::Disabled => None, + offcanvas::BodyScroll::Enabled => Some("true"), + }; + + let backdrop = match self.backdrop() { + offcanvas::Backdrop::Disabled => Some("false"), + offcanvas::Backdrop::Enabled => None, + offcanvas::Backdrop::Static => Some("static"), + }; + + let title = self.title().using(cx); + + PrepareMarkup::With(html! { + div + id=(id) + class=[self.classes().get()] + tabindex="-1" + data-bs-scroll=[body_scroll] + data-bs-backdrop=[backdrop] + aria-labelledby=(id_label) + { + div class="offcanvas-header" { + @if !title.is_empty() { + h5 class="offcanvas-title" id=(id_label) { (title) } + } + button + type="button" + class="btn-close" + data-bs-dismiss="offcanvas" + data-bs-target=(id_target) + aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)] + {} + } + div class="offcanvas-body" { + (body) + } + } + }) + } +} + +impl Offcanvas { + // **< Offcanvas BUILDER >********************************************************************** + + /// Establece el identificador único (`id`) del panel. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al panel. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Establece el título del encabezado. + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; + self + } + + /// Establece el punto de ruptura a partir del cual cambia el comportamiento del panel. + /// + /// - **Por debajo** de ese tamaño de pantalla, el componente actúa como panel deslizante + /// ([`Offcanvas`]). + /// - **Por encima**, el contenido del panel se muestra tal cual, integrado en la página. + /// + /// Por ejemplo, con `BreakPoint::LG`, será *offcanvas* en móviles y tabletas, y visible + /// directamente en pantallas grandes. Por defecto usa `BreakPoint::None` para que sea + /// *offcanvas* siempre. + #[builder_fn] + pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { + self.breakpoint = bp; + self + } + + /// Ajusta la capa de fondo del panel para definir su comportamiento al hacer clic fuera del + /// panel. + #[builder_fn] + pub fn with_backdrop(mut self, backdrop: offcanvas::Backdrop) -> Self { + self.backdrop = backdrop; + self + } + + /// Permite o bloquea el desplazamiento de la página principal mientras el panel está abierto. + #[builder_fn] + pub fn with_body_scroll(mut self, scrolling: offcanvas::BodyScroll) -> Self { + self.scrolling = scrolling; + self + } + + /// Indica desde qué borde de la ventana entra y se ancla el panel. + #[builder_fn] + pub fn with_placement(mut self, placement: offcanvas::Placement) -> Self { + self.placement = placement; + self + } + + /// Fija el estado inicial del panel (oculto o visible al cargar). + #[builder_fn] + pub fn with_visibility(mut self, visibility: offcanvas::Visibility) -> Self { + self.visibility = visibility; + self + } + + /// Añade un nuevo componente hijo al panel. + #[inline] + pub fn add_child(mut self, child: impl Component) -> Self { + self.children.add(Child::with(child)); + self + } + + /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_children(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // **< Offcanvas GETTERS >********************************************************************** + + /// Devuelve las clases CSS asociadas al panel. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el título del panel. + pub fn title(&self) -> &L10n { + &self.title + } + + /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. + pub fn breakpoint(&self) -> &BreakPoint { + &self.breakpoint + } + + /// Devuelve el comportamiento configurado para la capa de fondo. + pub fn backdrop(&self) -> &offcanvas::Backdrop { + &self.backdrop + } + + /// Indica si la página principal puede desplazarse mientras el panel está abierto. + pub fn body_scroll(&self) -> &offcanvas::BodyScroll { + &self.scrolling + } + + /// Devuelve la posición de inicio del panel. + pub fn placement(&self) -> &offcanvas::Placement { + &self.placement + } + + /// Devuelve el estado inicial del panel. + pub fn visibility(&self) -> &offcanvas::Visibility { + &self.visibility + } + + /// Devuelve la lista de componentes (`children`) del panel. + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs new file mode 100644 index 00000000..cbacbd74 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs @@ -0,0 +1,60 @@ +use pagetop::prelude::*; + +// **< Backdrop >*********************************************************************************** + +/// Comportamiento de la capa de fondo (*backdrop*) de un panel +/// [`Offcanvas`](crate::theme::Offcanvas) al deslizarse. +#[derive(AutoDefault)] +pub enum Backdrop { + /// Sin capa de fondo, la página principal permanece visible e interactiva. + Disabled, + /// Opción por defecto, se oscurece el fondo; un clic fuera del panel suele cerrarlo. + #[default] + Enabled, + /// Muestra la capa de fondo pero no se cierra al hacer clic fuera del panel. Útil si se + /// requiere completar una acción antes de salir. + Static, +} + +// **< BodyScroll >********************************************************************************* + +/// Controla si la página principal puede desplazarse al abrir un panel +/// [`Offcanvas`](crate::theme::Offcanvas). +#[derive(AutoDefault)] +pub enum BodyScroll { + /// Opción por defecto, la página principal se bloquea centrando la interacción en el panel. + #[default] + Disabled, + /// Permite el desplazamiento de la página principal. + Enabled, +} + +// **< Placement >********************************************************************************** + +/// Posición de aparición de un panel [`Offcanvas`](crate::theme::Offcanvas) al deslizarse. +/// +/// Define desde qué borde de la ventana entra y se ancla el panel. +#[derive(AutoDefault)] +pub enum Placement { + /// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL). + #[default] + Start, + /// Desde el borde final según dirección de lectura (respetando LTR/RTL). + End, + /// Desde la parte superior. + Top, + /// Desde la parte inferior. + Bottom, +} + +// **< Visibility >********************************************************************************* + +/// Estado inicial de un panel [`Offcanvas`](crate::theme::Offcanvas). +#[derive(AutoDefault)] +pub enum Visibility { + /// El panel permanece oculto desde el principio. + #[default] + Default, + /// El panel se muestra abierto al cargar. + Show, +} From 5ac26c0b0609cb7e953e74161b9331ebda650275 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 25 Oct 2025 19:04:35 +0200 Subject: [PATCH 172/224] =?UTF-8?q?=F0=9F=A9=B9=20Correcciones=20menores?= =?UTF-8?q?=20en=20comentarios=20y=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 7 +++---- src/base/component/block.rs | 2 +- src/base/component/intro.rs | 6 +++--- src/core/extension/definition.rs | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 2781aef8..732bbad7 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -277,8 +277,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { .collect(); // Documentación del método alter_...(). - let alter_doc = - format!("Equivalente a [`Self::{with_name_str}()`], pero fuera del patrón *builder*."); + let doc = format!("Equivale a [`Self::{with_name_str}()`], pero fuera del patrón *builder*."); // Genera el código final. let expanded = match body_opt { @@ -288,7 +287,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { fn #with_name #generics (self, #(#args),*) -> Self #where_clause; #(#non_doc_or_inline_attrs)* - #[doc = #alter_doc] + #[doc = #doc] fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause; } } @@ -322,7 +321,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { #with_fn #(#non_doc_or_inline_attrs)* - #[doc = #alter_doc] + #[doc = #doc] #vis_pub fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause { #body } diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 76d094ff..b2a754e5 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -91,7 +91,7 @@ impl Block { &self.classes } - /// Devuelve el título del bloque como [`L10n`]. + /// Devuelve el título del bloque. pub fn title(&self) -> &L10n { &self.title } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index a20d6232..49488ee4 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -237,7 +237,7 @@ impl Intro { /// /// ```rust /// # use pagetop::prelude::*; - /// let intro = Intro::default().with_title(L10n::n("Título de entrada")); + /// let intro = Intro::default().with_title(L10n::n("Intro title")); /// ``` #[builder_fn] pub fn with_title(mut self, title: L10n) -> Self { @@ -251,7 +251,7 @@ impl Intro { /// /// ```rust /// # use pagetop::prelude::*; - /// let intro = Intro::default().with_slogan(L10n::n("Un eslogan para la entrada")); + /// let intro = Intro::default().with_slogan(L10n::n("A short slogan")); /// ``` #[builder_fn] pub fn with_slogan(mut self, slogan: L10n) -> Self { @@ -270,7 +270,7 @@ impl Intro { /// ```rust /// # use pagetop::prelude::*; /// // Define un botón con texto y una URL fija. - /// let intro = Intro::default().with_button(Some((L10n::n("Pulsa este botón"), |_| "/start"))); + /// let intro = Intro::default().with_button(Some((L10n::n("Learn more"), |_| "/start"))); /// // Descarta el botón de la intro. /// let intro_no_button = Intro::default().with_button(None); /// ``` diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 5699130d..520153d0 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -21,7 +21,7 @@ pub type ExtensionRef = &'static dyn Extension; /// /// impl Extension for Blog { /// fn name(&self) -> L10n { L10n::n("Blog") } -/// fn description(&self) -> L10n { L10n::n("Sistema de blogs") } +/// fn description(&self) -> L10n { L10n::n("Blog system") } /// } /// ``` pub trait Extension: AnyInfo + Send + Sync { From 534b1f9615d0dab00d84c5ba6c65bec67d280a8c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 26 Oct 2025 06:38:10 +0100 Subject: [PATCH 173/224] =?UTF-8?q?=F0=9F=8D=B1=20Reemplaza=20im=C3=A1gene?= =?UTF-8?q?s=20del=20logo=20de=20PageTop=20con=20SVG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/intro.rs | 28 +-------- src/html.rs | 3 + src/html/logo.rs | 93 ++++++++++++++++++++++++++++ src/prelude.rs | 4 +- static/css/intro.css | 8 ++- static/img/monster-pagetop_250.avif | Bin 10834 -> 0 bytes static/img/monster-pagetop_250.png | Bin 32018 -> 0 bytes static/img/monster-pagetop_250.webp | Bin 12090 -> 0 bytes static/img/monster-pagetop_500.avif | Bin 20876 -> 0 bytes static/img/monster-pagetop_500.png | Bin 80504 -> 0 bytes static/img/monster-pagetop_500.webp | Bin 22974 -> 0 bytes 11 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 src/html/logo.rs delete mode 100644 static/img/monster-pagetop_250.avif delete mode 100644 static/img/monster-pagetop_250.png delete mode 100644 static/img/monster-pagetop_250.webp delete mode 100644 static/img/monster-pagetop_500.avif delete mode 100644 static/img/monster-pagetop_500.png delete mode 100644 static/img/monster-pagetop_500.webp diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 49488ee4..052f9c60 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -140,20 +140,7 @@ impl Component for Intro { } aside class="intro-header__image" aria-hidden="true" { div class="intro-header__monster" { - picture { - source - type="image/avif" - src="/img/monster-pagetop_250.avif" - srcset="/img/monster-pagetop_500.avif 1.5x"; - source - type="image/webp" - src="/img/monster-pagetop_250.webp" - srcset="/img/monster-pagetop_500.webp 1.5x"; - img - src="/img/monster-pagetop_250.png" - srcset="/img/monster-pagetop_500.png 1.5x" - alt="Monster PageTop"; - } + (PageTopSvg::Color.render(cx)) } } } @@ -202,18 +189,7 @@ impl Component for Intro { div class="intro-footer" { section class="intro-footer__body" { div class="intro-footer__logo" { - svg - viewBox="0 0 1614 1614" - xmlns="http://www.w3.org/2000/svg" - role="img" - aria-label=[L10n::l("pagetop_logo").lookup(cx)] - preserveAspectRatio="xMidYMid slice" - focusable="false" - { - path fill="rgb(255,255,255)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} - path fill="rgb(255,255,255)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} - path fill="rgb(255,255,255)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} - } + (PageTopSvg::LineLight.render(cx)) } div class="intro-footer__links" { a href="https://crates.io/crates/pagetop" target="_blank" rel="noopener noreferrer" { ("Crates.io") } diff --git a/src/html.rs b/src/html.rs index f85c24ed..5f5b833a 100644 --- a/src/html.rs +++ b/src/html.rs @@ -11,6 +11,9 @@ pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; pub use assets::{Asset, Assets}; +mod logo; +pub use logo::PageTopSvg; + // **< HTML DOCUMENT CONTEXT >********************************************************************** /// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Context`] en su lugar. diff --git a/src/html/logo.rs b/src/html/logo.rs new file mode 100644 index 00000000..f5057d94 --- /dev/null +++ b/src/html/logo.rs @@ -0,0 +1,93 @@ +use crate::core::component::Context; +use crate::html::{html, Markup}; +use crate::locale::L10n; +use crate::AutoDefault; + +/// Representación SVG del **logotipo de PageTop** para incrustar en HTML. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// fn render_logo(cx: &mut Context) -> PrepareMarkup { +/// PrepareMarkup::With(html! { +/// div class="logo_color" { +/// (PageTopSvg::Color.render(cx)) +/// } +/// div class="line_dark" { +/// (PageTopSvg::LineDark.render(cx)) +/// } +/// div class="line_light" { +/// (PageTopSvg::LineLight.render(cx)) +/// } +/// div class="line_red" { +/// (PageTopSvg::LineRGB(255, 0, 0).render(cx)) +/// } +/// }) +/// }; +/// ``` + +#[derive(AutoDefault)] +pub enum PageTopSvg { + /// Versión por defecto con el logotipo a color. + #[default] + Color, + /// Versión monocroma (línea) oscura, ideal sobre fondos claros. + LineDark, + /// Versión monocroma (línea) clara, ideal sobre fondos oscuros. + LineLight, + /// Versión monocroma configurable por RGB. + LineRGB(u8, u8, u8), +} + +impl PageTopSvg { + /// Renderiza el SVG del logotipo según la variante elegida. + pub fn render(&self, cx: &Context) -> Markup { + let path_fills = match self { + Self::Color => self.logo_color(), + Self::LineDark => self.logo_line(10, 11, 9), + Self::LineLight => self.logo_line(255, 255, 255), + Self::LineRGB(r, g, b) => self.logo_line(*r, *g, *b), + }; + html! { + svg + viewBox="0 0 1614 1614" + xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label=[L10n::l("pagetop_logo").lookup(cx)] + preserveAspectRatio="xMidYMid slice" + focusable="false" + { + (path_fills) + } + } + } + + // **< PageTopSvg HELPERS >********************************************************************* + + fn logo_color(&self) -> Markup { + html! { + path fill="rgb(255,184,75)" d="M 633,61 L 633,61 C 579,61 527,75 480,102 433,129 395,167 368,214 341,261 327,313 327,367 L 327,1244 327,1245 C 327,1299 341,1351 368,1398 395,1445 433,1483 480,1510 527,1537 579,1551 633,1551 L 982,1550 982,1551 C 1036,1551 1088,1537 1135,1510 1182,1483 1220,1445 1247,1398 1274,1351 1288,1299 1288,1245 L 1288,367 1288,367 1288,367 C 1288,313 1274,261 1247,214 1220,167 1182,129 1135,102 1088,75 1036,61 982,61 L 633,61 Z" {} + path fill="rgb(158,96,0)" d="M 1389,573 L 1328,573 1328,460 1449,460 1449,573 1389,573 Z M 1491,627 L 1430,627 1430,404 1551,404 1551,627 1491,627 Z M 222,573 L 161,573 161,460 282,460 282,573 222,573 Z M 120,627 L 59,627 59,404 180,404 180,627 120,627 Z" {} + path fill="rgb(150,0,184)" d="M 678,1040 L 678,1040 C 645,1040 612,1049 583,1065 554,1082 530,1106 513,1135 497,1164 488,1197 488,1230 L 488,1230 488,1230 C 488,1263 497,1296 513,1325 530,1354 554,1378 583,1395 612,1411 645,1420 678,1420 L 940,1420 940,1420 C 973,1420 1006,1411 1035,1395 1064,1378 1088,1354 1105,1325 1121,1296 1130,1263 1130,1230 L 1130,1230 1130,1230 1130,1230 C 1130,1197 1121,1164 1105,1135 1088,1106 1064,1082 1035,1065 1006,1049 973,1040 940,1040 L 678,1040 Z M 488,1040 L 488,1040 488,1040 488,1040 488,1040 488,1040 488,1238 488,1238 488,1238 488,1238 488,1238 1130,1238 1130,1238 1130,1238 1130,1238 1130,1238 1130,1238 1130,1040 1130,1040 1130,1040 1130,1040 1130,1040 488,1040 Z" {} + path fill="rgb(255,255,255)" d="M 518,1128 L 488,1128 488,1066 547,1066 547,1128 518,1128 Z M 966,1128 L 908,1128 908,1066 1024,1066 1024,1128 966,1128 Z" {} + path fill="rgb(221,255,149)" d="M 898,71 C 940,48 987,36 1035,36 1086,36 1137,50 1181,77 1226,104 1263,142 1289,189 1314,236 1328,288 1328,342 1328,396 1314,448 1289,495 1281,509 1272,522 1262,535 L 898,71 Z M 1337,429 C 1307,460 1272,480 1233,488 1192,496 1148,490 1107,470 1066,451 1029,418 999,375 969,332 948,281 938,227 927,173 928,117 940,66 943,51 948,36 953,22 L 1337,429 Z" {} + path fill="rgb(146,128,99)" d="M 703,99 C 717,121 722,149 719,178 715,208 702,238 682,267 661,295 634,320 602,340 571,360 536,373 501,379 467,385 434,383 405,373 377,363 355,346 341,323 327,301 322,273 325,244 329,214 342,184 362,155 383,127 410,102 442,82 473,62 508,49 543,43 577,37 610,39 639,49 667,59 689,76 703,99 L 703,99 Z" {} + path fill="rgb(146,128,99)" d="M 672,550 C 683,568 685,590 678,615 671,640 655,668 632,694 609,720 580,745 547,765 514,785 479,801 446,810 412,819 381,821 355,816 329,811 310,799 299,782 288,765 286,742 293,717 301,692 316,665 339,638 362,612 391,588 424,567 457,547 492,531 526,523 559,514 591,511 616,516 642,521 661,533 672,550 L 672,550 Z" {} + path fill="rgb(146,128,99)" d="M 455,160 L 456,160 C 430,160 404,167 381,180 359,193 340,212 327,234 314,257 307,283 307,309 L 307,580 307,580 C 307,606 314,632 327,655 340,677 359,696 381,709 404,722 430,729 456,729 L 529,729 529,729 C 555,729 581,722 604,709 626,696 645,677 658,655 671,632 678,606 678,580 L 677,308 678,309 678,309 C 678,283 671,257 658,234 645,212 626,193 604,180 581,167 555,160 529,160 L 455,160 Z" {} + path fill="rgb(240,0,0)" d="M 698,1332 L 699,1332 C 696,1332 694,1333 691,1334 689,1335 687,1337 686,1339 685,1342 684,1344 684,1347 L 684,1405 684,1405 C 684,1408 685,1410 686,1413 687,1415 689,1417 691,1418 694,1419 696,1420 699,1420 L 914,1419 914,1420 C 917,1420 919,1419 922,1418 924,1417 926,1415 927,1413 928,1410 929,1408 929,1405 L 929,1346 929,1347 929,1347 C 929,1344 928,1342 927,1339 926,1337 924,1335 922,1334 919,1333 917,1332 914,1332 L 698,1332 Z M 643,780 C 643,797 638,814 630,830 621,845 608,858 593,867 577,875 560,880 543,880 525,880 508,875 492,867 477,858 464,845 455,830 447,814 442,797 442,780 442,762 447,745 455,729 464,714 477,701 492,692 508,684 525,679 542,679 560,679 577,684 593,692 608,701 621,714 630,729 638,745 643,762 643,779 L 643,780 Z M 1171,780 C 1171,797 1166,814 1158,830 1149,845 1136,858 1121,867 1105,875 1088,880 1071,880 1053,880 1036,875 1020,867 1005,858 992,845 983,830 975,814 970,797 970,780 970,762 975,745 983,729 992,714 1005,701 1020,692 1036,684 1053,679 1071,679 1088,679 1105,684 1121,692 1136,701 1149,714 1158,729 1166,745 1171,762 1171,779 L 1171,780 Z" {} + path fill="rgb(10,11,9)" d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill="rgb(10,11,9)" d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill="rgb(10,11,9)" d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} + } + } + + fn logo_line(&self, r: u8, g: u8, b: u8) -> Markup { + let logo_rgb = format!("rgb({r},{g},{b})"); + html! { + path fill=(logo_rgb) d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill=(logo_rgb) d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill=(logo_rgb) d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} + } + } +} diff --git a/src/prelude.rs b/src/prelude.rs index cd3191f6..377dcdfd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -33,8 +33,8 @@ pub use crate::trace; // alias obsoletos se volverá a declarar como `pub use crate::html::*;`. pub use crate::html::{ display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, - ClassesOp, Escaper, Favicon, JavaScript, Markup, PreEscaped, PrepareMarkup, StyleSheet, - TargetMedia, UnitValue, DOCTYPE, + ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup, + StyleSheet, TargetMedia, UnitValue, DOCTYPE, }; pub use crate::locale::*; diff --git a/static/css/intro.css b/static/css/intro.css index 17ab5bef..dbc72252 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -103,9 +103,10 @@ width: 100%; } .intro-header__monster { - margin-right: 12rem; - margin-top: 1rem; + margin: 2rem; flex-shrink: 1; + width: 280px; + height: 280px; } @media (min-width: 64rem) { .intro-header { @@ -118,6 +119,9 @@ .intro-header__image { justify-content: flex-end; } + .intro-header__monster { + margin-right: 12rem; + } } /* diff --git a/static/img/monster-pagetop_250.avif b/static/img/monster-pagetop_250.avif deleted file mode 100644 index df864b85c67c980d485216b1ef18737713114554..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10834 zcmaKSWmH_vwr%6??(Xgo+}+)!vBuql1b26L_dsw65Foe&w*bN22|-@xJLkOfe%$+3 zk6kru)|$0yuc|#p?d}Eu0L0cFzAoloAZq~F{??8lYYs<{Ik*kY;b`Sy{+9<Eg{`H7 z+rJ<H-~s}A{0IN1Ms)#sIR4Xs0GHe#2WQ~lmXsI(68r%hv^L;x57-cR|AkOu|HBZE zf@yvb$mySH{Ns-UZdm;z1gp%w*g5_x|4V{<{!K!;fLz>M001ZjkPFZm0D!CmIy<<z zxH*Hzf-G_XI=Vx%0|1Ur9&Q@466AXN2IO$Z05|{{02@F7z%~cEyC`eQX#E}if3}CE zf6a1%<?mSkv(Nv^qF7qFgYy7E4)zcOy10SU24M!qyxtxze{mKVV*_o?Ey1`BjG5iQ z69nUxzr4l2@Y!E%^AG+fAJ}eM8j|39!v^!@HvbQ{_<u0a)(yNGklbJ&YD-5a@c58= z|H77kall{f=-~O!8iDcOl|i<0($@UTgD+x$EI=Nh3{V4*11tcZ01&_d-~nI(*G^!@ z9iRd3m-t`wg#YN(z$y!{3IqUx6;c3afFr>CA3fmj8i0Mk_TSvPTXS*#LqQ-*001ys z@9(!X003Mn0Pvdh{{E-<{r$BByv7><z<|?#_0C1$ZF3Lilm5$57XkoiVE{n;@P9dr zOaS0ZBmjWF>SFF@{!bhza0zJ*-Xk}a004?U0Dv_M03aIvD{tVwzx{yHWdJ}6oGYa% z03bUD0HC!2k8Sk7+&4T}@PG66|MmHI{=ojo=3X3<Az&HUutDxFR(}E5p8tJ;pDG{F zUjpLq==|oMKH!xH06g8CJ~+Bq+5A&Td;o%gE+5=|-94-v-9LbRKn@^tH{TED4lcI; z-0Z)jApe#APf^hS69oz${%;&okc;ELvINc%00KHB0xk*w3H=EwEF9cR2y(Of*K-O0 zpn{`0{Pp>}gJ@wKEzLbJ0f?~pByWKRTAbCGQs9{A5P;9hVnqjZvSpVrk8~7{$mPIS zE9l$QI$7OW`_LJRz#Cj#CJ`m^CX>=hxvJc2Ht&nm!7w&a`lGqM_8bmev>9hQ0j0%H zLX1qe#UQ3<{<Y}R->FXWFV70@oyynqo@`OY-b(>oJALr}KA7VL?>&ydnFFexu9FaL z6`JAv$Uob*!NDdqM*h78s;(4l@OUi?uUVUV^TREo{<xbwu7Nq>Jnz5Ca8(8jSUz|6 zx{*JSNl!?Dx1p%0z^RP=WXGb85OVgC{Xro_X&ni(ls>bHr+BD!X;$21yi&kK%HU*k zPC_e;z^@ns<y9W><M4=Xli?(>`?;}dIJrNq4&erz7Wf~3A6hc`Xyi253`}dxV5DAG ziM3v{kK9z}m)jo-7I3jCsCd7jp|;U&py0U7(ptKGfo8^wz5R_ws{_S6-Fn8|LEFNr z<q?ilTZK=~bTmP1I7lGbhAkjjGC8YXobkB2nVy5%HQnTHcVw)zJ#lXcnvE%DhaIfz zqw+ZaG5EFOJ%#aWSrhHZI=r---J*9O1BY9HX=j6~^CDe!3Y5KF9<Q{;>K|eb=6gGX z1i9-<N_(DIPh3-y6}Yi35pl=V>fJUW1Em#TDkoIcJa2ST8|LJVGh&;@R)&`Sr@pK+ z3`Iqm(SV&bJsd*JgFD`Us)L|ZQ3EKiOP?T~pL21EN4|X{SPjEm+qJK|Ya$Wv_YBt- z&McmJGCPWXC+OISykoPN1RHu5NJ^+a(oS|{){+NdXG@m@`$Q(UOMx#rHTLe^=`Kz{ z7771GiUY<?n$4&B>O7rR?Uj=IU2m(?OJhuGI*~IfSNpZ6lFit#@1&l~h?TEU17(xv z$h9t*#XJ=7bB~Br?!iM28K%ZMuJaqp?_}7gpYp3FfjAahbL_q(iNVh%Hq8XpsdYbY z>KL6#o+8CY>Gefc&s{Ks?N_tL{U$GCkKz<kgUq@RH7u`q8ob}#vghAEqZ5ebeG3vw z<_tMP<#&anmJhsOc)Dt49JF_SJAyG<j_}PKvefb@`=QS<h%OevF7V2qac8}c*_~Ye z!EdAIDSnQnQq!f&h5~8uXk+X8y>G45em2yRH$Hr5!piWfzKs@GZ$OW)#QoAo8p5Eq zDys@x3&5|(TZdK~Aw(U9;>y8y|KjZfGwH)F&Nt#4yYU<(_l4jWPdd!an8lyY+Jee+ zt>^px0)>4Wm^o!2UpiY5ZMVtz>s>|YN21L{`IB=6(H~r9q@m*-72ELfq*u<3NQlf& zw-scvzmo{idoZfyKa3L$d$^KB)@MV7PdLy?edAOxj2)3NSc;<SS1who)i3xO`%d+z z+r-Wz*)JZCmyh45O-Y8igW*q^-^#52VP@uHY;Xn@k$Be1N8j~dYnKL75t^m~t;&ZG zruxoO5QkpEMcD&5zfY$`2leY=i(dp<RWl?gIVWRqH~C3n?_g1%Ec1T_@T5_B=u=pm z*A$m)wL(J$<{J8lMMqAVr}k5zH7wX)lLsQO(WJNM=uzt48BYbGb^+iepr0nPCzerz zqE0Mr2Q(2eqoZ%2Y%l!F#<fQ;_V2O`Z(L75!{KfxX<9NbejCEFB1(KO9Q!$6+5N?q zRq)ia;BXtusUufm<2U5JHBuMrS6d?xvgV&;2NPpXIy_pe0TxF90e_u(n!~E8;}(yk z*Ji>2ibN(-axY8RT1?6d>MCkX3}GR8CpwOq0um}q6K36l6*0^08pR5+Z<7@NdD}N? zBnPDVJPu-n6n=9AssZDnH*&?eFBIG-m}Om$OSG%6entv)GZ`=*>^>n5dELQ;F38Vt zKS$G<ikw{<U>7FC=%}0ED$pP6i7wcatl!*-HJABaKgAZDL!K#3zLNVPh;M!|$+c;5 zo6AO%=PqiGaN5{KTb3gt^M^R5C)-xVn+>>WKWm8a$!vJ%_oP!vz>VF#=*8wHGZ(?P z0uZC2WYaCO@X_MM%6&g0V~x8!jP=s!oPlrO=292*Jd2V|OCUfdG<hrcw<+1vdn511 ziON8@5F|NfezWm{3nYWYXz2|{oOtwkB2)PRV@*+$6p6x`?fXEdLF}sXql#;zP{`6= z{hMvY!_5WJSZMlF@Z0fn;&@3kb)68eNPJSv>VRU>k>&y^(H;>o3x1?sKi1Ao6-;c3 z>ksT+5+2xmt~N(C6OjjbY5{CsiLsgS)sOQW*?J>_KU1yk&wf2GdGxjG=p~q~os3E+ zvN9{98+QBUr^62$u2X(OWTz%{ykO6MWE&7I{WF}0ts`b0s;*2|XqQUiEOxrp^_)9` z$k(}^Z^^UZ^MgfUv8^<Fxnyn%n!v?21ai7o72zr?IlWv`_-riI+xW}sOeTKCd(-8| zcRQEZuN1{rotEsO?NO65%(l~i*0Q!ZhBZ|_I|xi>-_jyiI2YmU+&OFAoe@?93<sS} z!4x%6D;60^w@`rYZA`Wtm~#{JRQv}OJL`m+oji^2FM`sf=^U(abp@HvdR$OeQsjQm z!dDL>{^D%MqCdHisa5hz8n~nNnMmkeihh5c<ifre*`VG^x)bF;A6IJWs!bzH5U)8% z@d|T4<==BYdE|rEHtNJp9!5!9Pc&+SHy4fFmiAoxR7dAX(lzCzGiHq)lU$gnc+XM) zu=)Io|76{20r$RTE)Z-)?3l@{lN{VQ_3_f`S3hV2iuzH~q%pt2)o9Rqn0gnU(O|c2 zfnO-By0bX<1Mw#Rz&edr6}?FYD|*RpHq^KgM?D95|Kk!P{l~uJD|-t_WcL069W>oa zMfmTDzm|1`g_F(RP$f&oEH1t)9>KTj2z~l$82dD6abs#>@jUGLu)e-H)U%O{z_(0L zMS+I}5_&ET!Kl8&Lkd>lqPn*;5q*2BcBVC@iW-L|(6TspS_!(2Q{qhP03Tq`aRU?? z1BWgAIU=7CV$=DPG-JNfq~bYF^UKMgjzZc7&ucFBO9-7Q<~g7#Vb%PMbEPLRFH7x# z@y_1xiIFvcnu5myrH@Ptfb7>Bo8kINzCK<03k@7A%XvoH?`px|Uw`KY%D*9$>v`hy z0kgY=V;6Al5VJBR{ci9E#5;I?AWvt~T(Phz<~ASfJ2z9LcrQDEPfdvboSMkR#Absz z+XeofoS?;_0b=OTYGd~~v~3e3K%X@U8KA2G>RkasT9{DMc)%BS6ynhFDyZI16wSMk zWr318FK-qHUbw5MQBzl`GJY=^b_oSPBay4-*0yNOJnwvI)A$ZYoIhZXl05AD{{Dc| z+xdMvf0HrvD<28ngjFZRiClG_8dS3jvR@XyQ$j1gIO`*xV>cle_6$6fs8KJ0DvXu1 zs>Wk0sz>q;aub0mo}_#FAkA{Au3H_CBX^I=BdTmlR3sMH-sLm3yr<Qz5<MYHXxwQX zS4aYFj6{hkOi&{;TP-^!7V$NL%E<Yr%T)I2Yt`Fi&klyochsyyDX-GgN#L4q2nH4u zuzF^aAuNsg#A0ce4HzQn$USlrCi~DZEC5h_u}Pc4_j>qV6?c65<96aMtHiSachmO_ zP*iJj!#KaDZ|>(ThdiH1H&B3QMcp>Yw;AQ(FK(*cjaC-m0NI;Sl5^vTw%v-t{9V;` z`H0K7p*9Q0W@pn!#<{lGJ2`K`k|BLyR=nj)39UpXEqnU*Mc0s9w~4O?gzVIJgJAzN zxkX1gS>v`zkxj02i|?T?)H>u267981dvWgr`VM_FVG^QR9AfHfJZ})D5Gy?NW&DU| zX)b1AuCQO1pbd$53UzYp8W9zAqE*`@N9nrgqnQ=4B19~d($+U@G8ClA=-&{DxGyRN zpwkh|lejEP-S1ng(y~Bix@Ok)dA=1GJgESY+p-_%uP6m#U4&OyTGNxB5fbBQt`19g z!Z3}=cYGgB)calESV-i9GK#f6KeH}EyniG&)9m7++dj>1kT{M<`W@vb4RPbLw>~X? zle}hytjoc)?%e|+66CM}ItXg4C55@~6o-Ez<W{@UJ2+A$v{-%PTOJEPgiyHKO@u_= zrG~x=%rHjKln8E3=AuYO23bW(DS$rfRjf2AJqMY%f?Q8kd8jGl4<OE}oo*%SI^rDN znbmn8$yn4WuEWppX&s66`QV`9*^Av6LD41?$C3QbPk1Wy4!E`~M`g3-NfxzjwYekK zjpw&71C+ljX~P+F%u;;V@O^`7QtZ(#3<oKkzbP1!E3fKEW2Db_r<nP}DIDQJ$hQON zhjeV8QtZ<>FjZ~1_tskZspAXfO`<CuMJ&|)585_dTBoSmom`NoW=k&AEk~=e3vsyD zr{(pjO-twY#Fk{RJd*r>s%$2^iZfsy>S_#U%gQyvB&-#?Of_-w^pMAHi**+6+>z*4 zgbKwsC={hGm3kSLdt7Dk{J!hJQ51w&F<s>RSb)10JP5_g^<ojt8m<Vro`|M`*Hu(g zg_lQuTzmtG>=?EGk*jdN%#3{&Auy8^AypIVyV~PTqrc`$x}Gi|)L-<E!SkQ0d5!!u z_OUmnNO{MB4WUVW#AUUvMRuceA7v|%Fbnx-TlD&UgQwwFlJ#uhtakX9&`^%XiS;0) z4T{`Z<z<G%Tu%B#pMZE%iyV9FWua~-)q)Ts*8yqgQ?)+}aR(xF8Quy^&OsTQNar(O zm;qJc!3DpYNl&}|jFF>UDWxNTmmRlkk$_JrD9QZFr=N?+T@{23FZKk}A2GNls%Wa# zgiCpcX5ab4^ERN^*xu}aZO{rZKs@F7XYS_F$(%FE1p0JEhfjK36~v~#3YIioHtj-F zGj#N&J4VFIhh7M?O`ZkB(PWvH7=DjDfFz0=YAA>|vOoF5y)y1UQ|OT|GsOVsbm2Ix zcvISGc7l1^0XpQoYB+Rx1gf*99C)QbxwGBM`KSgOpxB;db3WKvOfwDl#bqTBxyC?z z_ZAul4P@S|$kR5wrrsvF_gh8gN{}a2c<!W}_76V9D#Pn=n5{&7giYgloy65_5-VkZ zD!CP&;nLQ8X;$A4Q`a(?J*fEFEnr|?<n%ILY8nBMj_l+jpr8DXDj{n8%g<`GGBMSe zu22JSk17L!aV)Szf$lr+hc*ee$yTd};yo6rekXQ^mm4oGJ;+rSwBGQL?CA*zq9thK zu@s$QoTP<3EttqYAGs~sq1mIU#NV5XAbw3+Df^)CA-&jnchY`qIHfl~LWSUqt1p*h zgWKAN6su2BTlvsAh!thT`N*#vm6Pq&-_Cf*ZKuVP4Eznzn5;KJ%D4B)k^Qbbin?^p z(`yVH`$fo+u%9f%PqqtUQ!XVCgH0bSmd0W&tBMk_i_|1qe{VLD9XE>@ZLbkIx$>fa z(cCzYDM^5E=Ax8%TqQqYH&^?0m^(aEAY5F#gIK-2<>FaQgUvLcUgb%C4&<L+`4AX7 zJ0-?#T|2LZ`Qd2az4U5)3zcqPn|>JOchP>dMovn8oScW_G#l6Iw1|KFkA-jTyHj#z zwuHN%y2i~N`$u$Z6;AwCZL!xcU`yVaT}WD+5id6cMs$J6=U=)dZ5@x<h-qn(R5(2# z0~Sc5c6-Gt8zAH6qNpf|a1GAyoTeNtCy}4M^m@Ydo=3yH!+!AqoYT&G%U35h&ZzHO ztv=;#->G&86gAQAw1{2j{4TV2i=!+;+j2QaTHkXWn|#@O8f+k6PfW>g43=~Y`PC|J zt#-kdWT=%cmQ$SPy#|*s0v~cB_dALJ$Cu-n*C|hyRo*O0gNCrGTs}m6P%qrY<1v-8 zgIL<dcTpeoQv8h}E3YQ+Y-LUAYO9FqM5(BFst9Mg7~zX2eG0by`EKhYu8|1>)<BFb zg-I+286j{xyfIqqNK1vjqJ60aC`@~d0~FBJ^%yT(G|<oVUd;;#VPBUj>koisSrmBM zrtj6~9Yobi8R5)mNG>0I3F)1=&ZvMUm^FIDP}Sm<Uo)O<9-r^cennL0km-|!A5N^& z8#Tig85xOrXJtl2?JoF~HXBYaaH@!I@YuixRhi3J<_g=9u@PaxvdDO@Sfk6q?sV{) z3Mh{;N0d-ldjaJ|j=3J@8CG^U6uYe6OgE`Adbc#;&bUiCdXxks3^TB+|GM_psBIWe z|B0<kb}pX_jW(HTtpv%9e$59Tm(W*oh%cTx$AXC-re4_i1jjS7!7Y-%EW9|r^Q1~u zkNm9Mww^$CP7O4>a5~MP^peDVFs{Y+X87djr(y;d<n^&hC!5H*<_e-TL0W!U+baf6 zPb#xkR4oPd{w;MCbG{k;PQbdPX{iFEY!>M1XTIl0;f1K)wjgyE!c!tuzD0qX-(#3y zG7qw*i;}>Z6E_-SEP2?|ZsiQB$VEoxZoCfp9Ouzu7!aEk&}2rU_8E4Q1p!0XpVjFh zX2{SxBlMOO0|DkDbQd=Ury7C{7d`W2d=x#cRCkZ0$JBV_huxP3bhri+&Ys-y6>F0s z7tuqg`gOT(wTo=&$fnK`KRrSI$$<0y2^D7D<T2e*T$^8fXA1a1i;rJr!bbZ{HgXjo z7fhW9ZwZ#S(ScR+@s;-<EK)U9XFlo1HBm^}pb<+{!#?qf;~WJQL8Bg4t-V~6<EpVr zOp4cVz52Ok@lsGQeA)AltYaE1w=c~wy>sjKz2sPFZXvCO=&ub9dgSwMoO|f<{tA43 zRW}UV*wLeC8-ouJGJ-J3!!_Rbmu5dSDSQ`L*=X*AqFI>q`^>nW(<_wjJe(IPpfuhx zJ5NY(_RA)5@H0te9M+h6=|xtPE)*A5Bc8PC-G{^00VS#p@Y@sbLH~&8&h*Jq<xKUD zLSoVHj+o8-aQNz9=s83K5L!P77lP0IF*u|lb&9onH_7aPP;!e7GCgDq`KOz&M<WP| z1gD6ADIz^m#^TnH@?w#IO>JkdDe18JoaRN8Uu|F6pR8OcTW*im-djr7BYp+3uB8{- z_GQ*NJ%vj8+RQv}qVSmpWffypr54N8JlRM^l7Hn`WI0bp%}Hn>^3Od2O8&0Vxo>U~ zaAH6fDG)9L){H;<2U^s`Lw)$1Kj=T~zgN#Y8g-<pAh8pm<`XYm$tTZP7T-fSWYKJL zTOihf^D;F*{M2a4@Vll;_{?(t+LI)*&y7TCptsVWo^1uOvdv+cAX$6Hgh_XeSwc4q z{`3-rCUmf6(1RxbQTub~C5YgWdz!^l*Ln9(_I$bh;7}GXdU50IC5_FfV-|y<ikrMK zfgjhmE~!oVT(3<fSTTol2Vl)|&a1I0gcK=o=FjgJwVJ1vl6@N=dn+at8Gm=_(Nw}k zD$S-3s)$Tu<xKuR7F_YdS4-AD<_S{R7T+|u>fm$?2UXMFz9lL$A^l8ew|^zh(&Hka z1vXeiO;&MeV41}H`n~LiRUhA(jT9!$4ac(6^{uuR6+;NP#!ue|vSG4C=GqO7H#oDA zdXVZXfkkXmJSA-=%Kh|ZVKq31&*`SJ_h{ka$J)*1ImDEic)V@<NwLTecSP7Ovt_Qs z#+4$gF#OwVJ=ZZ+${ZuKZ-g&%(uGpWQsQkvNKA=W4m<2LfI(15%b4;GUME2=Q_mqH zS`ODdH(e)*T~R8Ptp{|`ka}xsa>VGCYRVm|ldVyYeo)JIjdkj@**l>ILcIAlsT{k3 z8h^4WE9DbIAVbEVH#yC>F)|V1kUbqytDsg}j;1+`2T$x=l!_BGA>Jq}{MB>GWs0U6 zvv8;^sPynnm1u?xl_GWW7xwTDt<ytnBJFq#i*$azZq!eiGuRmTh@mDPd2%7~753~H z4`_Zm=Aiy8`jdM-rxp4>H|6b=jwU*YP;PX=*nX5?5L6KhL;Uc`R&3(St)aE$#~{RP zg71-t%CXOaXd7-^_c)5;T_31Ab**BbZ6Ntd&A)482q=4EB2ke(r1vLhj0xdb^JR|T zUl2mMz9fiP`exa7jy(ZcW4cv&<+~gR8dZ2^1g*Td3|Wd&`NAgNw?t`%L_#BGNqd?} z<g$Cz7)q`%-00p~ykIPyP8mdLWN~ZD8BB|zWr#^M&0g+MB%*fspG-e+Yw;h*zf3eh zqP=x}3b6yy!LbNGwF}R%{ZxQ{D~fg2$wY7Bk2v~LNwdgSp2`mlXBHUqnnS}6XfXR0 z!lV_;ceIYsMkN~aaRgKCJtY^xH-E0?)k@G@;*?Vlb|GQCrcBDS8S(oR?w#kkHZa7f zY`>@oO+&vEA)kY<qk@m#v}BeHbBmYmHR}(oKc5j6uK+~M{?i&r9Y5v7ZQU>QrE>#j zV~xXSz)NlyZ_QRi8}3K^H+OO0b8k<31mdgv<I=>tzE_u}mAqET*7P~y7vxSj9!hf> z%S46yKy#X-@TZ?D1CGe3PEKfNnbHp^vyT_(wlGUrWchubHXJCLo>TKn%yU%i-)<$v z;X0H<MQNHY>0NH4h27<$%1P3r?(GwtFZUCo3NcL{pSKrr>4K9H)PLvRE;>zTrCI%; z5rQlZtMjislO39$%y+$MIp1B*>JGP~VHFM;d-$%En1+;!Dy_(%!_A?-*upp8rSL}u zPG>Rjg~hR(A``9ZYVl9-girtkp2m}FyFIf``;7(~Dq^#_=`q|kY+C^#EO&b0sof%I z#5KwE_bF=Vui<!M^bj;zmEKMM<E455?v>tdd|v*(?eb!yqG<nXb&oHw**H5kpT9`y zmfoV2`Y#8}-qwg0TK9K-=t?(j`|{v<7Ol2;rlVZv!(3Z8R`L3?wcCSl`GoD;P>Dr& zU_gl=o}%dEvqkfXANSXl!+<vQWt)!CszvELS}r}>7<3C?Ofq$5&^v|vglspr^j-dk zA4`Jl^fvc?5}>Q;vBY6N`+3%QDhtg&iU5ZWT4oUs%a3H4UGOsUE5ZGPi}F3DNZ%AL zRU_Dcr^2O<%^}6Xv-*ZtqrPC4iSf4FjJNdgA$Ko0vdX?`R%**Id?LauBJl6=lxm$^ zX1tdWH#nOVPG$K1nb@x_;GRxTb=tV7@Qti|vUI2WgX?K2)~H!d$anaw=j**!bh~q; z2e#0kAC|Pyi=%Y2Q9+z#bRhj92O#xCJDe4)|E@$;wqSVUwXI;<Q<(W}7KIEY!KJ{( z=976519zs-Fx;H1K|ZW|jBAdk{Pr21`Hc;wc^QM}?f85(72}z@kGv$3gFllPr`qjs z$%Z^Ac9sc}Rb*f0Y<VPB>DgN?6lsukl#$52)B`#%_m?8sTx&i)11Z#qxAj+9V4EL8 zea^uQP7_&_W(5^Uw9C#dL)RWv0&anwp~V-y$&ZFFi;qj7#w)97XS|N$q9+P-+6@(V zkwgF616@=@&?;fuid!*f-Mj%5$_w2a)H<3<2QuC3_5~_Z7%`fPYHSW0TH8Zux}$8| zinnv5<V<Adg9xMM#&t$NHG8B&5Z*8R^4M+#JqWtXlzJi%T78WKMKLnN7GdSlD}po3 zPh2<6RfAHBS2vD~ncD3BFd`{_{iiTFj61^|!*XJ=QwW}}PMum6SY)3qZV@$j<jTE; zu>F_4$bd2;*a~1n0aLzC)fN9Dty_26Ofx;9p)?oFSUfk%V%HJBniWOlDGYH7yL29M z+ieOaweOZHi(MP<kHQ11g1OZaX_$+oxEG1mge$(pQ~v8R;y2z^Phr+&#?G&y${Yp} z5}r1<&l@|)_5MA5GoZ}PxAGRf2tn$e+%@yLJxXjkY$GT5svEQoYmufIhEEvNn0j(_ zRX3TA{NNulq!Q(36>OgIUWyuc`rZlFU4C-8*6*X^80`>9!u-&PR6ki~&4u%S;p`+$ zP!0d{$B2KpY?0I9a{n_)oCqcD;7HK9WDrq+VdcG`>(^23L!T}J+t&E%LPZWMTI@A| z^3eA;nISK2Gef2l)#>P;_VCj$;r@t6VPb`BPothk%yYg$n(>BBU7a>B7R>=D%m<!J z_KRcpP3SG}p}D%0&37k*)7THn&PLQ%rYXuV63xsQQ+<p~!M;;K>qOhq`A6>6WFpC^ zuhz!l_pipIgZh|WP59^twBDtZJfl%4p+-1tvPQp}Kuss4tsFDgENeV*LR}{PA{w5; z`lH?Kjlxu6t6i&8$vx2o6(-_7)S6R27Oo^K!i;vVd|R6NzCuXRU;*^k8E2etrf|+8 zcPSg}qjC8_h|4hO%QS)4In{VtPivnkJ33>JNmSSHaRRQ`?WI{+c2-$1rmXXVo+%gV z9;{AVv`f`Q<L%QD{j@DR8?+U&US$RyH<D@gJEaa71KN8R&aSkCOP6b%HgK;8=Gm68 z(C#X{;aE#Qa~Wyw7`+GgQJz%#i1CElnu7lLA#1n;3xkPPxA&9R*`k_{x0Yi(s|un= zFHU0d_o`zCf|<gEnk-v1QEx!ET=@pt5eK)3T9%MR5a|t_(&YzAbqPW))p_1{!T5o7 z)IqJU7d(X;wINXkhplkoVSUG_v^M;JRuLZ}j@At>Rl&UIG<>aY*5=qZ997+&loXmn zxJrXR3m8p31non%liU(G=dkrAvZq4CE)`Rk+_(B-@($ws4+X-02vsF#PcJt-=zVsg zhc9R<zVstKsCGDEzil_>y9D>PCej+nhg~{^d+YLkG)@pB?uBqf5i1xq8QOIlw)Bim zYxly91e?Ek?hVzIkzh6{my1e$wuk>vrLjfqRnzfcP^E>=8^NPwS(r~m9>}yU9xO51 zO#jAEP$nhkVHxlhjhf5s=!A$V?OU1*VF257<K`j?J<D~+XYa@Q<AL|K>5uG8&bOap z-^?+RmT9i+7<>a0sgQd;^#L<z%Au7rj12A)CQDXjO?<fSBTS9m9QTl}R=R1U-_5e| zpVK)Nm<g#pV>oTH*Xk3`7hvZIEb(zCbvgKwlFDNi0`i(uCq|v8^jl*INcPbfFNZUm z*b~)ck5qi|{F;tm#bvnL+Wg%<X67{D;A+le1q6en9;(-taG&?^E2Zi=V{VQh?pe|c zgUmm{PEV(x{1`bI3$#8C&&oa*GNmA*CoNIEefB?gWiFr5FoQ7{`hi<CIesj2$ndI` zk^?uvQ^vvjnea$`{qZu?Zi6gq6Q57j*E>-_PbH~&lOa(0UVZ<NS-_h(Cn1t`j=o;| zh{~aM252`^#qu2LpfoalZzkm4-*2$;5GLHkfmOzT(bBK$4ZT>n<wOBxW%g}N$3C)M zJUnS4hK>uOvyIM}j9HsECS(-n7hCm7G(xj%#|M!qOtx*;(zQ_<iB}#(=AF8`gu-2( zA1N8VNOGAGZ8q^Vfsn|t07dVsV-0W0JeS<_<pmKEP8E%u>F0_O1VHyA<0D?Np=+&j z^FFfNZ?DZy7^Uu*fObhvR%%u;&}1NBTw`pO>g4cfT-0tl5@(Ae%AWT_dnkr&@R^%~ zi$c{=G=sH)rI)gbD$#<<`jH9px09q_tuA|GiD-WUOl(Q1&SJj<wnO}5nicMH%UE=1 zZYK;T*jl@7zNO56N}vi0R#+wzOQv<(>F`4mw2ZRYK^P$Hl8A=FfY{hVm9#;CQa%tJ zcKf8ShYDu_dph%5%41ah3^f{<-5+v>Ft$P~(dKDd$kc97o9(al(QsUuqf<GD-d%d> z@Hd*coww`dPw5F1?H%;Y2<>ef&h|_1p$c24%;^jie5XlmqBscv-sSP87dEMHMm)<9 zbC4|#uG*GXmDES;>yv~$G$$f17K&`XhEsMv<vJp$we{AOu6Y&NkL;q$F8d2xpK8>; zVrMPLt>bOD`1id*|8)57MXF-bX?8adj4Nar=EV*nj-O<Nig(NMDkq~*I6^#dsy_}H z&CTrLvpX^%d)dXAKUkg-=xsu)XJ=Y{`O&j35pYSs<|!$gv+#{WmL=GR`<zKwY^YYP z1(+_lTFE;~b|AF>W?*42#W_N=&pEd<(bCmeNN2hA4PM{qn<o4E$Isu6D4`B%lf*_` zARDllzaH9m>DId2ULu$My5YON{Ncb*h4oD*-hoX$dsRV98{}#w{Br6Iw~7>3KP2|9 z%|Nim(OWLUnFd<u_i1i;yva(E6k!<c>E=*c7US8v`9{9LPg2a^8v##L6J8zh&egMN zJzh#&k{5}s6~HR>P`C$2X~yQP&4BmIz0G_56JwOmN0Fs>*)YipQ;=sb=j8kRl$gJu zHFP4Bz1x&2vuPh667sQJ_0Ic3hx>f;c_@?rHu$?2eyFx^pae_J2YaP047WlSp3}r$ ztChg@XJz|_kuJ6YvORm}Sflfas|EIna)tWV9oYfq0&B18co<>VY!N=v1!R13qd|<p z(ixRSZD<xHJOQCYIVZZrXpU%$l#{DrO}C$gHT7TZTxuDQ+{0T3^M<aO0kq_Eam*#S z2dw6hMF0rfPFKnV87hrahQuKm;u~r#5_COk1RHrf!Sb?(c!g3ZGBPpyESjkOdbg6v z3U;Uo>>NM;WiP>4s&@jld&@BDenmNat#k_Bb7&&a$a{{@?ohZ8I_GeGpai)x_>WQ{ z248+>1NLEK^~Wd-Uq$)IoM4cbtdZ&RvN*5K^MhOG{tOu&>Zwm;?Vku1x}OksD?^YH zB*BGM@4|`^mwIwOmrf8c7jQbYJ~t3bkVNf{Y(ZIXwfosStd^EPU|?<(_B2F%w4Iol zZExeLx0)I;CKN$(i`p<!&tfmFuRu(hLhjdfMhav&@^1ke*XzIVNZCQO%KB(j7zY?H z6UEIb^zc<!Xloz#eGHz7`hzCVmQpHMGuKu?q9H!Na>TvzMsHr`F0S@9N^Du!EhLz* zSLGF3NaPIB*e6*?4|tk3zDyx3DC=XmrTLPH(nL6G=Gj8K)Jdqv6XLL8ISgYKcr_=U zR4~SENwa3cp1}DmI6^Y=Q=@myX!vy19u_e1oqoP*)bY}~-Z86&ey;3cWAq-8;50{< r&$5_?Rz}2Jx>;Kq-?!d=K(yOnpRBDWyHLt}qJE#@D=yq;Ir9GlZ7P*K diff --git a/static/img/monster-pagetop_250.png b/static/img/monster-pagetop_250.png deleted file mode 100644 index baa7d1f895f60e0966aa09e926922e653ef469f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32018 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 diff --git a/static/img/monster-pagetop_250.webp b/static/img/monster-pagetop_250.webp deleted file mode 100644 index dcc5ef26308dd2f26d1e9c2c1c688f750d544984..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12090 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 YceFZN(cAxq=dFKgq5Ge|ng6l>1Gp;yN&o-= diff --git a/static/img/monster-pagetop_500.avif b/static/img/monster-pagetop_500.avif deleted file mode 100644 index d07f359de0e7a7c5d1d9378c55a8819a257838ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20876 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 LA51~|H)nvri_}K9 diff --git a/static/img/monster-pagetop_500.png b/static/img/monster-pagetop_500.png deleted file mode 100644 index d0b9e0b1399bf5939bd0e0f784f3105d4c52d0b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80504 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 diff --git a/static/img/monster-pagetop_500.webp b/static/img/monster-pagetop_500.webp deleted file mode 100644 index 18896fe50ed014ffe5571b8d0a115b755d63823e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22974 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 F006P8s)7Ij From c3fd566025cfa53d14d58582e15ae91d763b74d9 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 26 Oct 2025 09:18:33 +0100 Subject: [PATCH 174/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?componente=20`Image`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme.rs | 5 +- .../pagetop-bootsier/src/theme/container.rs | 6 +- .../pagetop-bootsier/src/theme/image.rs | 189 +---------------- .../src/theme/image/component.rs | 194 ++++++++++++++++++ .../pagetop-bootsier/src/theme/image/props.rs | 53 +++++ 5 files changed, 258 insertions(+), 189 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/image/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/image/props.rs diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index b5be6b71..ece34f06 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -12,8 +12,9 @@ pub mod dropdown; pub use dropdown::Dropdown; // Image. -mod image; -pub use image::{Image, ImageSize}; +pub mod image; +#[doc(inline)] +pub use image::Image; // Navbar. pub mod navbar; diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index 22f7fc6c..17512505 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -234,7 +234,7 @@ impl Container { self } - /// Establece esquinas redondeadas para el contenedor. + /// Establece esquinas redondeadas para el contenedor ([`Rounded`]). #[builder_fn] pub fn with_rounded(mut self, rounded: Rounded) -> Self { self.rounded = rounded; @@ -282,12 +282,12 @@ impl Container { &self.text_color } - /// Devuelve el borde del contenedor. + /// Devuelve el borde configurado del contenedor. pub fn border(&self) -> &Border { &self.border } - /// Devuelve las esquinas redondeadas del contenedor. + /// Devuelve las esquinas redondeadas configuradas para el contenedor. pub fn rounded(&self) -> &Rounded { &self.rounded } diff --git a/extensions/pagetop-bootsier/src/theme/image.rs b/extensions/pagetop-bootsier/src/theme/image.rs index 6c602780..837d25a1 100644 --- a/extensions/pagetop-bootsier/src/theme/image.rs +++ b/extensions/pagetop-bootsier/src/theme/image.rs @@ -1,186 +1,7 @@ -use pagetop::prelude::*; +//! Definiciones para renderizar imágenes ([`Image`]). -use crate::prelude::*; +mod props; +pub use props::{Size, Source}; -#[derive(AutoDefault)] -pub enum ImageSource { - #[default] - //Logo(PageTopLogo), - Responsive(String), - Thumbnail(String), - Static(String), -} - -#[derive(AutoDefault)] -pub enum ImageSize { - #[default] - Auto, - Dimensions(UnitValue, UnitValue), - Width(UnitValue), - Height(UnitValue), - Both(UnitValue), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Image { - id : AttrId, - classes: AttrClasses, - source : ImageSource, - alt : AttrL10n, - size : ImageSize, - border : Border, - rounded: Rounded, -} - -impl Component for Image { - fn new() -> Self { - Image::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - String::from(match self.source() { - //ImageSource::Logo(_) => "img-fluid", - ImageSource::Responsive(_) => "img-fluid", - ImageSource::Thumbnail(_) => "img-thumbnail", - _ => "", - }), - self.border().to_string(), - self.rounded().to_string(), - ] - .join(" "), - ); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let dimensions = match self.size() { - ImageSize::Auto => None, - ImageSize::Dimensions(w, h) => { - let w = w.to_string(); - let h = h.to_string(); - Some(join!("width: ", w, "; height: ", h, ";")) - } - ImageSize::Width(w) => { - let w = w.to_string(); - Some(join!("width: ", w, ";")) - } - ImageSize::Height(h) => { - let h = h.to_string(); - Some(join!("height: ", h, ";")) - } - ImageSize::Both(v) => { - let v = v.to_string(); - Some(join!("width: ", v, "; height: ", v, ";")) - } - }; - let source = match self.source() { - /* - ImageSource::Logo(logo) => { - return PrepareMarkup::With(html! { - span - id=[self.id()] - class=[self.classes().get()] - style=[dimensions] - { - (logo.render(cx)) - } - }) - } - */ - ImageSource::Responsive(source) => Some(source), - ImageSource::Thumbnail(source) => Some(source), - ImageSource::Static(source) => Some(source), - }; - PrepareMarkup::With(html! { - img - src=[source] - alt=[self.alternative().lookup(cx)] - id=[self.id()] - class=[self.classes().get()] - style=[dimensions] {} - }) - } -} - -impl Image { - pub fn with(source: ImageSource) -> Self { - Image::default().with_source(source) - } - - // **< Image BUILDER >************************************************************************** - - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - #[builder_fn] - pub fn with_source(mut self, source: ImageSource) -> Self { - self.source = source; - self - } - - #[builder_fn] - pub fn with_alternative(mut self, alt: L10n) -> Self { - self.alt.alter_value(alt); - self - } - - #[builder_fn] - pub fn with_size(mut self, size: ImageSize) -> Self { - self.size = size; - self - } - - #[builder_fn] - pub fn with_border(mut self, border: Border) -> Self { - self.border = border; - self - } - - #[builder_fn] - pub fn with_rounded(mut self, rounded: Rounded) -> Self { - self.rounded = rounded; - self - } - - // **< Image GETTERS >************************************************************************** - - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - pub fn source(&self) -> &ImageSource { - &self.source - } - - pub fn alternative(&self) -> &AttrL10n { - &self.alt - } - - pub fn size(&self) -> &ImageSize { - &self.size - } - - pub fn border(&self) -> &Border { - &self.border - } - - pub fn rounded(&self) -> &Rounded { - &self.rounded - } -} +mod component; +pub use component::Image; diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs new file mode 100644 index 00000000..0345adb2 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -0,0 +1,194 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +/// Componente para renderizar una **imagen**. +/// +/// - Ajusta su disposición según el origen definido en [`image::Source`]. +/// - Permite configurar **dimensiones** ([`with_size()`](Self::with_size)), **borde** +/// ([`with_border()`](Self::with_border)) y **redondeo de esquinas** +/// ([`with_rounded()`](Self::with_rounded)). +/// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`]. +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Image { + id : AttrId, + classes: AttrClasses, + size : image::Size, + source : image::Source, + alt : AttrL10n, + border : Border, + rounded: Rounded, +} + +impl Component for Image { + fn new() -> Self { + Image::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + String::from(match self.source() { + image::Source::Logo(_) => "img-fluid", + image::Source::Responsive(_) => "img-fluid", + image::Source::Thumbnail(_) => "img-thumbnail", + image::Source::Plain(_) => "", + }), + self.border().to_string(), + self.rounded().to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let dimensions = match self.size() { + image::Size::Auto => None, + image::Size::Dimensions(w, h) => { + let w = w.to_string(); + let h = h.to_string(); + Some(join!("width: ", w, "; height: ", h, ";")) + } + image::Size::Width(w) => { + let w = w.to_string(); + Some(join!("width: ", w, ";")) + } + image::Size::Height(h) => { + let h = h.to_string(); + Some(join!("height: ", h, ";")) + } + image::Size::Both(v) => { + let v = v.to_string(); + Some(join!("width: ", v, "; height: ", v, ";")) + } + }; + let alt_text = self.alternative().lookup(cx).unwrap_or_default(); + let is_decorative = alt_text.is_empty(); + let source = match self.source() { + image::Source::Logo(logo) => { + return PrepareMarkup::With(html! { + span + id=[self.id()] + class=[self.classes().get()] + style=[dimensions] + role=[(!is_decorative).then_some("img")] + aria-label=[(!is_decorative).then_some(alt_text)] + aria-hidden=[is_decorative.then_some("true")] + { + (logo.render(cx)) + } + }) + } + image::Source::Responsive(source) => Some(source), + image::Source::Thumbnail(source) => Some(source), + image::Source::Plain(source) => Some(source), + }; + PrepareMarkup::With(html! { + img + src=[source] + alt=(alt_text) + id=[self.id()] + class=[self.classes().get()] + style=[dimensions] {} + }) + } +} + +impl Image { + /// Crea rápidamente una imagen especificando su origen. + pub fn with(source: image::Source) -> Self { + Image::default().with_source(source) + } + + // **< Image BUILDER >************************************************************************** + + /// Establece el identificador único (`id`) de la imagen. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas a la imagen. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Define las dimensiones de la imagen (auto, ancho/alto, ambos). + #[builder_fn] + pub fn with_size(mut self, size: image::Size) -> Self { + self.size = size; + self + } + + /// Establece el origen de la imagen, influyendo en su disposición en el contenido. + #[builder_fn] + pub fn with_source(mut self, source: image::Source) -> Self { + self.source = source; + self + } + + /// Define el texto alternativo localizado ([`L10n`]) para la imagen. + /// + /// Se recomienda siempre aportar un texto alternativo salvo que la imagen sea puramente + /// decorativa. + #[builder_fn] + pub fn with_alternative(mut self, alt: L10n) -> Self { + self.alt.alter_value(alt); + self + } + + /// Establece el borde de la imagen ([`Border`]). + #[builder_fn] + pub fn with_border(mut self, border: Border) -> Self { + self.border = border; + self + } + + /// Establece esquinas redondeadas para la imagen ([`Rounded`]). + #[builder_fn] + pub fn with_rounded(mut self, rounded: Rounded) -> Self { + self.rounded = rounded; + self + } + + // **< Image GETTERS >************************************************************************** + + /// Devuelve las clases CSS asociadas a la imagen. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve las dimensiones de la imagen. + pub fn size(&self) -> &image::Size { + &self.size + } + + /// Devuelve el origen de la imagen. + pub fn source(&self) -> &image::Source { + &self.source + } + + /// Devuelve el texto alternativo localizado. + pub fn alternative(&self) -> &AttrL10n { + &self.alt + } + + /// Devuelve el borde configurado de la imagen. + pub fn border(&self) -> &Border { + &self.border + } + + /// Devuelve las esquinas redondeadas configuradas para la imagen. + pub fn rounded(&self) -> &Rounded { + &self.rounded + } +} diff --git a/extensions/pagetop-bootsier/src/theme/image/props.rs b/extensions/pagetop-bootsier/src/theme/image/props.rs new file mode 100644 index 00000000..f871de70 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/image/props.rs @@ -0,0 +1,53 @@ +use pagetop::prelude::*; + +// **< Size >*************************************************************************************** + +/// Define las **dimensiones** de una imagen ([`Image`](crate::theme::Image)). +#[derive(AutoDefault)] +pub enum Size { + /// Ajuste automático por defecto. + /// + /// La imagen usa su tamaño natural o se ajusta al contenedor donde se publica. + #[default] + Auto, + /// Establece explícitamente el **ancho y alto** de la imagen. + /// + /// Útil cuando se desea fijar ambas dimensiones de forma exacta. Ten en cuenta que la imagen + /// puede distorsionarse si no se mantiene la proporción original. + Dimensions(UnitValue, UnitValue), + /// Establece sólo el **ancho** de la imagen. + /// + /// La altura se ajusta proporcionalmente de manera automática. + Width(UnitValue), + /// Establece sólo la **altura** de la imagen. + /// + /// El ancho se ajusta proporcionalmente de manera automática. + Height(UnitValue), + /// Establece **el mismo valor** para el ancho y el alto de la imagen. + /// + /// Práctico para forzar rápidamente un área cuadrada. Ten en cuenta que la imagen puede + /// distorsionarse si la original no es cuadrada. + Both(UnitValue), +} + +// **< Source >************************************************************************************* + +/// Especifica la **fuente** para publicar una imagen ([`Image`](crate::theme::Image)). +#[derive(AutoDefault)] +pub enum Source { + /// Imagen con el logotipo de PageTop. + #[default] + Logo(PageTopSvg), + /// Imagen que se adapta automáticamente a su contenedor. + /// + /// El `String` asociado es la URL (o ruta) de la imagen. + Responsive(String), + /// Imagen que aplica el estilo **miniatura** de Bootstrap. + /// + /// El `String` asociado es la URL (o ruta) de la imagen. + Thumbnail(String), + /// Imagen sin clases específicas de Bootstrap, útil para controlar con CSS propio. + /// + /// El `String` asociado es la URL (o ruta) de la imagen. + Plain(String), +} From 80b18cc38063f5de790e1d2d7a888785d3fb25a2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 29 Oct 2025 13:47:59 +0100 Subject: [PATCH 175/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?componente=20`Nav`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adapta `Dropdown` para poder integrarlo en los menús `Nav`. - Actualiza `Children` para soportar operaciones múltiples. --- extensions/pagetop-bootsier/src/theme.rs | 5 + .../pagetop-bootsier/src/theme/dropdown.rs | 5 +- .../src/theme/dropdown/component.rs | 32 ++- .../src/theme/dropdown/item.rs | 8 +- extensions/pagetop-bootsier/src/theme/nav.rs | 17 ++ .../src/theme/nav/component.rs | 168 ++++++++++++ .../pagetop-bootsier/src/theme/nav/item.rs | 257 ++++++++++++++++++ .../pagetop-bootsier/src/theme/nav/props.rs | 39 +++ .../pagetop-bootsier/src/theme/navbar.rs | 6 - .../pagetop-bootsier/src/theme/navbar/item.rs | 113 -------- .../pagetop-bootsier/src/theme/navbar/nav.rs | 75 ----- src/core/component/children.rs | 103 ++++++- 12 files changed, 602 insertions(+), 226 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/nav.rs create mode 100644 extensions/pagetop-bootsier/src/theme/nav/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/nav/item.rs create mode 100644 extensions/pagetop-bootsier/src/theme/nav/props.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/navbar/item.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/navbar/nav.rs diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index ece34f06..d34c0a9f 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -16,6 +16,11 @@ pub mod image; #[doc(inline)] pub use image::Image; +// Nav. +pub mod nav; +#[doc(inline)] +pub use nav::Nav; + // Navbar. pub mod navbar; #[doc(inline)] diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index 02df7fd0..eb00e4c8 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -1,14 +1,11 @@ //! Definiciones para crear menús desplegables [`Dropdown`]. //! //! Cada [`dropdown::Item`](crate::theme::dropdown::Item) representa un elemento individual del -//! desplegable [`Dropdown`], con distintos comportamientos según su finalidad: enlaces de +//! desplegable [`Dropdown`], con distintos comportamientos según su finalidad, como enlaces de //! navegación, botones de acción, encabezados o divisores visuales. //! //! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y //! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). -//! -//! Su propósito es ofrecer una base uniforme sobre la que construir menús consistentes, adaptados -//! al contexto de cada aplicación. mod props; pub use props::{AutoClose, Direction, MenuAlign, MenuPosition}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index e9d36d7f..984a3e06 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -10,8 +10,12 @@ use crate::LOCALES_BOOTSIER; /// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de /// apertura, alineación o política de cierre. /// -/// Sin título para el botón (ver [`with_button_title()`](Self::with_button_title)) se muestra -/// únicamente la lista de elementos sin ningún botón para interactuar. +/// Si no tiene título (ver [`with_title()`](Self::with_title)) se muestra únicamente la lista de +/// elementos sin ningún botón para interactuar. +/// +/// Si este componente se usa en un menú [`Nav`] (ver [`nav::Item::dropdown()`]) sólo se tendrán en +/// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el +/// resto de propiedades no afectarán a su representación en [`Nav`]. /// /// # Ejemplo /// @@ -19,7 +23,7 @@ use crate::LOCALES_BOOTSIER; /// # use pagetop::prelude::*; /// # use pagetop_bootsier::prelude::*; /// let dd = Dropdown::new() -/// .with_button_title(L10n::n("Menu")) +/// .with_title(L10n::n("Menu")) /// .with_button_color(ButtonColor::Background(Color::Secondary)) /// .with_auto_close(dropdown::AutoClose::ClickableInside) /// .with_direction(dropdown::Direction::Dropend) @@ -34,7 +38,7 @@ use crate::LOCALES_BOOTSIER; pub struct Dropdown { id : AttrId, classes : AttrClasses, - button_title : L10n, + title : L10n, button_size : ButtonSize, button_color : ButtonColor, button_split : bool, @@ -80,11 +84,11 @@ impl Component for Dropdown { } // Título opcional para el menú desplegable. - let button_title = self.button_title().using(cx); + let title = self.title().using(cx); PrepareMarkup::With(html! { div id=[self.id()] class=[self.classes().get()] { - @if !button_title.is_empty() { + @if !title.is_empty() { @let mut btn_classes = AttrClasses::new([ "btn", &self.button_size().to_string(), @@ -129,7 +133,7 @@ impl Component for Dropdown { type="button" class=[btn_classes.get()] { - (button_title) + (title) } }; // Botón *toggle* que abre/cierra el menú asociado. @@ -176,7 +180,7 @@ impl Component for Dropdown { data-bs-auto-close=[auto_close] aria-expanded="false" { - (button_title) + (title) } ul class=[menu_classes.get()] { (items) } } @@ -206,10 +210,10 @@ impl Dropdown { self } - /// Establece el título del botón. + /// Establece el título del menú desplegable. #[builder_fn] - pub fn with_button_title(mut self, title: L10n) -> Self { - self.button_title = title; + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; self } @@ -290,9 +294,9 @@ impl Dropdown { &self.classes } - /// Devuelve el título del botón. - pub fn button_title(&self) -> &L10n { - &self.button_title + /// Devuelve el título del menú desplegable. + pub fn title(&self) -> &L10n { + &self.title } /// Devuelve el tamaño configurado del botón. diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 548024c5..1aed83b4 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -41,7 +41,7 @@ pub enum ItemKind { /// [`ItemKind`]. /// /// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, -/// manteniendo una interfaz común para renderizar todos los ítems del menú. +/// manteniendo una interfaz común para renderizar todos los elementos del menú. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Item { @@ -79,7 +79,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && current_path.map(|p| p == path).unwrap_or(false); + let is_current = !*disabled && current_path.map_or(false, |p| p == path); let mut classes = "dropdown-item".to_string(); if is_current { @@ -200,7 +200,7 @@ impl Item { } } - /// Crea un enlace deshabilitado que se abriría en una nueva ventana. + /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { @@ -274,7 +274,7 @@ impl Item { &self.classes } - /// Devuelve el tipo de elemento representado por este ítem. + /// Devuelve el tipo de elemento representado por este elemento. pub fn item_kind(&self) -> &ItemKind { &self.item_kind } diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs new file mode 100644 index 00000000..b540c323 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -0,0 +1,17 @@ +//! Definiciones para crear menús [`Nav`] o alguna de sus variantes de presentación. +//! +//! Cada [`nav::Item`](crate::theme::nav::Item) representa un elemento individual del menú [`Nav`], +//! con distintos comportamientos según su finalidad, como enlaces de navegación o menús +//! desplegables [`Dropdown`](crate::theme::Dropdown). +//! +//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y +//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). + +mod props; +pub use props::{Kind, Layout}; + +mod component; +pub use component::Nav; + +mod item; +pub use item::{Item, ItemKind}; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs new file mode 100644 index 00000000..34c33b94 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -0,0 +1,168 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +/// Componente para crear un **menú** o alguna de sus variantes ([`nav::Kind`]). +/// +/// Presenta un menú con una lista de elementos usando una vista básica, o alguna de sus variantes +/// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite +/// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)). +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::prelude::*; +/// let nav = Nav::tabs() +/// .with_layout(nav::Layout::End) +/// .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +/// .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +/// .add_item(nav::Item::dropdown( +/// Dropdown::new() +/// .with_title(L10n::n("Options")) +/// .with_items(TypedOp::AddMany(vec![ +/// Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")), +/// Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")), +/// ])), +/// )) +/// .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Nav { + id : AttrId, + classes : AttrClasses, + items : Children, + nav_kind : nav::Kind, + nav_layout: nav::Layout, +} + +impl Component for Nav { + fn new() -> Self { + Nav::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + "nav", + match self.nav_kind() { + nav::Kind::Default => "", + nav::Kind::Tabs => "nav-tabs", + nav::Kind::Pills => "nav-pills", + nav::Kind::Underline => "nav-underline", + }, + match self.nav_layout() { + nav::Layout::Default => "", + nav::Layout::Start => "justify-content-start", + nav::Layout::Center => "justify-content-center", + nav::Layout::End => "justify-content-end", + nav::Layout::Vertical => "flex-column", + nav::Layout::Fill => "nav-fill", + nav::Layout::Justified => "nav-justified", + }, + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let items = self.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + + PrepareMarkup::With(html! { + ul id=[self.id()] class=[self.classes().get()] { + (items) + } + }) + } +} + +impl Nav { + /// Crea un `Nav` usando pestañas para los elementos (*Tabs*). + pub fn tabs() -> Self { + Nav::default().with_kind(nav::Kind::Tabs) + } + + /// Crea un `Nav` usando botones para los elementos (*Pills*). + pub fn pills() -> Self { + Nav::default().with_kind(nav::Kind::Pills) + } + + /// Crea un `Nav` usando elementos subrayados (*Underline*). + pub fn underline() -> Self { + Nav::default().with_kind(nav::Kind::Underline) + } + + // **< Nav BUILDER >**************************************************************************** + + /// Establece el identificador único (`id`) del menú. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al menú. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Cambia el estilo del menú (*Tabs*, *Pills*, *Underline* o *Default*). + #[builder_fn] + pub fn with_kind(mut self, kind: nav::Kind) -> Self { + self.nav_kind = kind; + self + } + + /// Selecciona la distribución y orientación del menú. + #[builder_fn] + pub fn with_layout(mut self, layout: nav::Layout) -> Self { + self.nav_layout = layout; + self + } + + /// Añade un nuevo elemento hijo al menú. + pub fn add_item(mut self, item: nav::Item) -> Self { + self.items.add(Child::with(item)); + self + } + + /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_items(mut self, op: TypedOp<nav::Item>) -> Self { + self.items.alter_typed(op); + self + } + + // **< Nav GETTERS >**************************************************************************** + + /// Devuelve las clases CSS asociadas al menú. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el estilo visual seleccionado. + pub fn nav_kind(&self) -> &nav::Kind { + &self.nav_kind + } + + /// Devuelve la distribución y orientación seleccionada. + pub fn nav_layout(&self) -> &nav::Layout { + &self.nav_layout + } + + /// Devuelve la lista de elementos (`children`) del menú. + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs new file mode 100644 index 00000000..63248f8d --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -0,0 +1,257 @@ +use pagetop::prelude::*; + +use crate::prelude::*; +use crate::LOCALES_BOOTSIER; + +// **< ItemKind >*********************************************************************************** + +/// Tipos de [`nav::Item`](crate::theme::nav::Item) disponibles en un menú +/// [`Nav`](crate::theme::Nav). +/// +/// Define internamente la naturaleza del elemento y su comportamiento al mostrarse o interactuar +/// con él. +#[derive(AutoDefault)] +pub enum ItemKind { + /// Elemento vacío, no produce salida. + #[default] + Void, + /// Etiqueta sin comportamiento interactivo. + Label(L10n), + /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar + /// inicialmente deshabilitado. + Link { + label: L10n, + path: FnPathByContext, + blank: bool, + disabled: bool, + }, + /// Elemento que despliega un menú [`Dropdown`]. + Dropdown(Typed<Dropdown>), +} + +// **< Item >*************************************************************************************** + +/// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav). +/// +/// Cada instancia de [`nav::Item`](crate::theme::nav::Item) se traduce en un componente visible que +/// puede comportarse como texto, enlace, botón o menú desplegable según su [`ItemKind`]. +/// +/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, +/// manteniendo una interfaz común para renderizar todos los elementos del menú. +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + id : AttrId, + classes : AttrClasses, + item_kind: ItemKind, +} + +impl Component for Item { + fn new() -> Self { + Item::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + if matches!(self.item_kind(), ItemKind::Dropdown(_)) { + "nav-item dropdown" + } else { + "nav-item" + }, + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self.item_kind() { + ItemKind::Void => PrepareMarkup::None, + + ItemKind::Label(label) => PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + span { + (label.using(cx)) + } + } + }), + + ItemKind::Link { + label, + path, + blank, + disabled, + } => { + let path = path(cx); + let current_path = cx.request().map(|request| request.path()); + let is_current = !*disabled && current_path.map_or(false, |p| p == path); + + let mut classes = "nav-link".to_string(); + if is_current { + classes.push_str(" active"); + } + if *disabled { + classes.push_str(" disabled"); + } + + let href = (!disabled).then_some(path); + let target = (!disabled && *blank).then_some("_blank"); + let rel = (!disabled && *blank).then_some("noopener noreferrer"); + + let aria_current = (href.is_some() && is_current).then_some("page"); + let aria_disabled = disabled.then_some("true"); + + PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + a + class=(classes) + href=[href] + target=[target] + rel=[rel] + aria-current=[aria_current] + aria-disabled=[aria_disabled] + { + (label.using(cx)) + } + } + }) + } + + ItemKind::Dropdown(menu) => { + if let Some(dd) = menu.borrow() { + let items = dd.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + let title = dd.title().lookup(cx).unwrap_or_else(|| { + L10n::t("dropdown", &LOCALES_BOOTSIER) + .lookup(cx) + .unwrap_or_else(|| "Dropdown".to_string()) + }); + PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + a + class="nav-link dropdown-toggle" + data-bs-toggle="dropdown" + href="#" + role="button" + aria-expanded="false" + { + (title) + } + ul class="dropdown-menu" { + (items) + } + } + }) + } else { + PrepareMarkup::None + } + } + } + } +} + +impl Item { + /// Crea un elemento de tipo texto, mostrado sin interacción. + pub fn label(label: L10n) -> Self { + Item { + item_kind: ItemKind::Label(label), + ..Default::default() + } + } + + /// Crea un enlace para la navegación. + pub fn link(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: false, + disabled: false, + }, + ..Default::default() + } + } + + /// Crea un enlace deshabilitado que no permite la interacción. + pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: false, + disabled: true, + }, + ..Default::default() + } + } + + /// Crea un enlace que se abre en una nueva ventana o pestaña. + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: true, + disabled: false, + }, + ..Default::default() + } + } + + /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. + pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { + Item { + item_kind: ItemKind::Link { + label, + path, + blank: true, + disabled: true, + }, + ..Default::default() + } + } + + /// Crea un elemento de navegación que contiene un menú desplegable [`Dropdown`]. + /// + /// Sólo se tienen en cuenta **el título** (si no existe le asigna uno por defecto) y **la lista + /// de elementos** del [`Dropdown`]; el resto de propiedades del componente no afectarán a su + /// representación en [`Nav`]. + pub fn dropdown(menu: Dropdown) -> Self { + Item { + item_kind: ItemKind::Dropdown(Typed::with(menu)), + ..Default::default() + } + } + + // **< Item BUILDER >*************************************************************************** + + /// Establece el identificador único (`id`) del elemento. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al elemento. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + // **< Item GETTERS >*************************************************************************** + + /// Devuelve las clases CSS asociadas al elemento. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el tipo de elemento representado por este elemento. + pub fn item_kind(&self) -> &ItemKind { + &self.item_kind + } +} diff --git a/extensions/pagetop-bootsier/src/theme/nav/props.rs b/extensions/pagetop-bootsier/src/theme/nav/props.rs new file mode 100644 index 00000000..bd8ac1e1 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/nav/props.rs @@ -0,0 +1,39 @@ +use pagetop::prelude::*; + +// **< Kind >*************************************************************************************** + +/// Define la variante de presentación de un menú [`Nav`](crate::theme::Nav). +#[derive(AutoDefault)] +pub enum Kind { + /// Estilo por defecto, lista de enlaces flexible y minimalista. + #[default] + Default, + /// Pestañas con borde para cambiar entre secciones. + Tabs, + /// Botones con fondo que resaltan el elemento activo. + Pills, + /// Variante con subrayado del elemento activo, estética ligera. + Underline, +} + +// **< Layout >************************************************************************************* + +/// Distribución y orientación de un menú [`Nav`](crate::theme::Nav). +#[derive(AutoDefault)] +pub enum Layout { + /// Comportamiento por defecto, ancho definido por el contenido y sin alineación forzada. + #[default] + Default, + /// Alinea los elementos al inicio de la fila. + Start, + /// Centra horizontalmente los elementos. + Center, + /// Alinea los elementos al final de la fila. + End, + /// Apila los elementos en columna. + Vertical, + /// Los elementos se expanden para rellenar la fila. + Fill, + /// Todos los elementos ocupan el mismo ancho rellenando la fila. + Justified, +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 3745b116..72a3af3d 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -9,9 +9,3 @@ pub use content::{Content, ContentType}; mod brand; pub use brand::Brand; - -mod nav; -pub use nav::Nav; - -mod item; -pub use item::{Item, ItemType}; diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs deleted file mode 100644 index 08a2aeee..00000000 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ /dev/null @@ -1,113 +0,0 @@ -use pagetop::prelude::*; - -use crate::theme::Dropdown; - -type Label = L10n; - -#[derive(AutoDefault)] -pub enum ItemType { - #[default] - Void, - Label(Label), - Link(Label, FnPathByContext), - LinkBlank(Label, FnPathByContext), - Dropdown(Typed<Dropdown>), -} - -// Item. - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Item { - item_type: ItemType, -} - -impl Component for Item { - fn new() -> Self { - Item::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let description: Option<String> = None; - - // Obtiene la URL actual desde `cx.request`. - let current_path = cx.request().map(|request| request.path()); - - match self.item_type() { - ItemType::Void => PrepareMarkup::None, - ItemType::Label(label) => PrepareMarkup::With(html! { - li class="nav-item" { - span title=[description] { - //(left_icon) - (label.using(cx)) - //(right_icon) - } - } - }), - ItemType::Link(label, path) => { - let item_path = path(cx); - let (class, aria) = if current_path == Some(item_path) { - ("nav-item active", Some("page")) - } else { - ("nav-item", None) - }; - PrepareMarkup::With(html! { - li class=(class) aria-current=[aria] { - a class="nav-link" href=(item_path) title=[description] { - //(left_icon) - (label.using(cx)) - //(right_icon) - } - } - }) - } - ItemType::LinkBlank(label, path) => { - let item_path = path(cx); - let (class, aria) = if current_path == Some(item_path) { - ("nav-item active", Some("page")) - } else { - ("nav-item", None) - }; - PrepareMarkup::With(html! { - li class=(class) aria-current=[aria] { - a class="nav-link" href=(item_path) title=[description] target="_blank" { - //(left_icon) - (label.using(cx)) - //(right_icon) - } - } - }) - } - ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }), - } - } -} - -impl Item { - pub fn label(label: L10n) -> Self { - Item { - item_type: ItemType::Label(label), - ..Default::default() - } - } - - pub fn link(label: L10n, path: FnPathByContext) -> Self { - Item { - item_type: ItemType::Link(label, path), - ..Default::default() - } - } - - pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { - Item { - item_type: ItemType::LinkBlank(label, path), - ..Default::default() - } - } - - // Item GETTERS. - - pub fn item_type(&self) -> &ItemType { - &self.item_type - } -} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/nav.rs b/extensions/pagetop-bootsier/src/theme/navbar/nav.rs deleted file mode 100644 index bc03a291..00000000 --- a/extensions/pagetop-bootsier/src/theme/navbar/nav.rs +++ /dev/null @@ -1,75 +0,0 @@ -use pagetop::prelude::*; - -use crate::theme::navbar; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Nav { - id : AttrId, - classes: AttrClasses, - items : Children, -} - -impl Component for Nav { - fn new() -> Self { - Nav::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes(ClassesOp::Prepend, "navbar-nav"); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let items = self.items().render(cx); - if items.is_empty() { - return PrepareMarkup::None; - } - - PrepareMarkup::With(html! { - ul id=[self.id()] class=[self.classes().get()] { - (items) - } - }) - } -} - -impl Nav { - // Nav BUILDER. - - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - pub fn with_item(mut self, item: navbar::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - #[builder_fn] - pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self { - self.items.alter_typed(op); - self - } - - // Nav GETTERS. - - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - pub fn items(&self) -> &Children { - &self.items - } -} diff --git a/src/core/component/children.rs b/src/core/component/children.rs index c0c8841e..3b8f2abf 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -4,6 +4,9 @@ use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; +pub use parking_lot::RwLockReadGuard as ComponentReadGuard; +pub use parking_lot::RwLockWriteGuard as ComponentWriteGuard; + use std::sync::Arc; use std::vec::IntoIter; @@ -93,6 +96,57 @@ impl<C: Component> Typed<C> { self.0.as_ref().and_then(|c| c.read().id()) } + /// Devuelve una **referencia inmutable** al componente interno. + /// + /// - Devuelve `Some(ComponentReadGuard<C>)` si existe el componente, o `None` si está vacío. + /// - Permite realizar **múltiples lecturas concurrentes**. + /// - Mientras el *guard* esté activo, no se pueden realizar escrituras concurrentes (ver + /// [`borrow_mut`](Self::borrow_mut)). + /// - Se recomienda mantener el *guard* **el menor tiempo posible** para evitar bloqueos + /// innecesarios. + /// + /// # Ejemplo + /// + /// Lectura del nombre del componente: + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let typed = Typed::with(Html::with(|_| html! { "Prueba" })); + /// { + /// if let Some(component) = typed.borrow() { + /// assert_eq!(component.name(), "Html"); + /// } + /// }; // El *guard* se libera aquí, antes del *drop* de `typed`. + /// ``` + pub fn borrow(&self) -> Option<ComponentReadGuard<'_, C>> { + self.0.as_ref().map(|a| a.read()) + } + + /// Obtiene una **referencia mutable exclusiva** al componente interno. + /// + /// - Devuelve `Some(ComponentWriteGuard<C>)` si existe el componente, o `None` si está vacío. + /// - **Exclusivo**: mientras el *guard* esté activo, no habrá otros lectores ni escritores. + /// - Usar sólo para operaciones que **modifican** el estado interno. + /// - Igual que con [`borrow`](Self::borrow), se recomienda mantener el *guard* en un **ámbito + /// reducido**. + /// + /// # Ejemplo + /// + /// Acceso mutable (ámbito corto): + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let typed = Typed::with(Block::new().with_title(L10n::n("Título"))); + /// { + /// if let Some(mut component) = typed.borrow_mut() { + /// component.alter_title(L10n::n("Nuevo título")); + /// } + /// }; // El *guard* se libera aquí, antes del *drop* de `typed`. + /// ``` + pub fn borrow_mut(&self) -> Option<ComponentWriteGuard<'_, C>> { + self.0.as_ref().map(|a| a.write()) + } + // **< Typed RENDER >*************************************************************************** /// Renderiza el componente con el contexto proporcionado. @@ -102,9 +156,9 @@ impl<C: Component> Typed<C> { // **< Typed HELPERS >************************************************************************** - // Convierte el componente tipado en un [`Child`]. + // Método interno para convertir un componente tipado en un [`Child`]. #[inline] - fn into_child(self) -> Child { + fn into(self) -> Child { if let Some(c) = &self.0 { Child(Some(c.clone())) } else { @@ -115,12 +169,14 @@ impl<C: Component> Typed<C> { // ************************************************************************************************* -/// Operaciones con un componente hijo [`Child`] en una lista [`Children`]. +/// Operaciones para componentes hijo [`Child`] en una lista [`Children`]. pub enum ChildOp { Add(Child), + AddMany(Vec<Child>), InsertAfterId(&'static str, Child), InsertBeforeId(&'static str, Child), Prepend(Child), + PrependMany(Vec<Child>), RemoveById(&'static str), ReplaceById(&'static str, Child), Reset, @@ -129,9 +185,11 @@ pub enum ChildOp { /// Operaciones con un componente hijo tipado [`Typed<C>`] en una lista [`Children`]. pub enum TypedOp<C: Component> { Add(Typed<C>), + AddMany(Vec<Typed<C>>), InsertAfterId(&'static str, Typed<C>), InsertBeforeId(&'static str, Typed<C>), Prepend(Typed<C>), + PrependMany(Vec<Typed<C>>), RemoveById(&'static str), ReplaceById(&'static str, Typed<C>), Reset, @@ -172,9 +230,11 @@ impl Children { pub fn with_child(mut self, op: ChildOp) -> Self { match op { ChildOp::Add(any) => self.add(any), + ChildOp::AddMany(many) => self.add_many(many), ChildOp::InsertAfterId(id, any) => self.insert_after_id(id, any), ChildOp::InsertBeforeId(id, any) => self.insert_before_id(id, any), ChildOp::Prepend(any) => self.prepend(any), + ChildOp::PrependMany(many) => self.prepend_many(many), ChildOp::RemoveById(id) => self.remove_by_id(id), ChildOp::ReplaceById(id, any) => self.replace_by_id(id, any), ChildOp::Reset => self.reset(), @@ -183,14 +243,16 @@ impl Children { /// Ejecuta una operación con [`TypedOp`] en la lista. #[builder_fn] - pub fn with_typed<C: Component + Default>(mut self, op: TypedOp<C>) -> Self { + pub fn with_typed<C: Component>(mut self, op: TypedOp<C>) -> Self { match op { - TypedOp::Add(typed) => self.add(typed.into_child()), - TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.into_child()), - TypedOp::InsertBeforeId(id, typed) => self.insert_before_id(id, typed.into_child()), - TypedOp::Prepend(typed) => self.prepend(typed.into_child()), + TypedOp::Add(typed) => self.add(typed.into()), + TypedOp::AddMany(many) => self.add_many(many.into_iter().map(Typed::<C>::into)), + TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.into()), + TypedOp::InsertBeforeId(id, typed) => self.insert_before_id(id, typed.into()), + TypedOp::Prepend(typed) => self.prepend(typed.into()), + TypedOp::PrependMany(many) => self.prepend_many(many.into_iter().map(Typed::<C>::into)), TypedOp::RemoveById(id) => self.remove_by_id(id), - TypedOp::ReplaceById(id, typed) => self.replace_by_id(id, typed.into_child()), + TypedOp::ReplaceById(id, typed) => self.replace_by_id(id, typed.into()), TypedOp::Reset => self.reset(), } } @@ -230,7 +292,7 @@ impl Children { /// Devuelve un iterador sobre los componentes hijo con el identificador de tipo ([`UniqueId`]) /// indicado. pub fn iter_by_type_id(&self, type_id: UniqueId) -> impl Iterator<Item = &Child> { - self.0.iter().filter(move |&c| c.type_id() == Some(type_id)) + self.0.iter().filter(move |c| c.type_id() == Some(type_id)) } // **< Children RENDER >************************************************************************ @@ -246,6 +308,16 @@ impl Children { // **< Children HELPERS >*********************************************************************** + // Añade más de un componente hijo al final de la lista (en el orden recibido). + #[inline] + fn add_many<I>(&mut self, iter: I) -> &mut Self + where + I: IntoIterator<Item = Child>, + { + self.0.extend(iter); + self + } + // Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra. #[inline] fn insert_after_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self { @@ -275,6 +347,17 @@ impl Children { self } + // Inserta más de un componente hijo al principio de la lista (manteniendo el orden recibido). + #[inline] + fn prepend_many<I>(&mut self, iter: I) -> &mut Self + where + I: IntoIterator<Item = Child>, + { + let buf: Vec<Child> = iter.into_iter().collect(); + self.0.splice(0..0, buf); + self + } + // Elimina el primer hijo con el `id` dado. #[inline] fn remove_by_id(&mut self, id: impl AsRef<str>) -> &mut Self { From 5a00968223c26b1b8de4c2418a44c925b327a972 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 2 Nov 2025 12:40:26 +0100 Subject: [PATCH 176/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?componente=20`Navbar`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/build.rs | 3 +- extensions/pagetop-bootsier/src/lib.rs | 2 +- extensions/pagetop-bootsier/src/theme.rs | 2 +- .../pagetop-bootsier/src/theme/navbar.rs | 139 +++++- .../src/theme/navbar/brand.rs | 105 +++-- .../src/theme/navbar/button_toggler.rs | 73 --- .../src/theme/navbar/component.rs | 421 ++++++++++-------- .../src/theme/navbar/content.rs | 69 --- .../pagetop-bootsier/src/theme/navbar/item.rs | 108 +++++ .../src/theme/navbar/props.rs | 64 +++ .../static/js/bootstrap.bundle.min.js | 7 + .../static/js/bootstrap.bundle.min.js.map | 1 + .../static/js/bootstrap.min.js | 7 - .../static/js/bootstrap.min.js.map | 1 - 14 files changed, 615 insertions(+), 387 deletions(-) delete mode 100644 extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/navbar/content.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/item.rs create mode 100644 extensions/pagetop-bootsier/src/theme/navbar/props.rs create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js create mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map delete mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.min.js delete mode 100644 extensions/pagetop-bootsier/static/js/bootstrap.min.js.map diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs index a96301c5..df7a2750 100644 --- a/extensions/pagetop-bootsier/build.rs +++ b/extensions/pagetop-bootsier/build.rs @@ -13,7 +13,8 @@ fn main() -> std::io::Result<()> { } fn bootstrap_js_files(path: &Path) -> bool { + let bootstrap_js = "bootstrap.bundle.min.js"; // No filtra durante el desarrollo, solo en la compilación "release". env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release" - || path.file_name().is_some_and(|n| n == "bootstrap.min.js") + || path.file_name().is_some_and(|f| f == bootstrap_js) } diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index a0e6c703..6df5341c 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -127,7 +127,7 @@ impl Theme for Bootsier { .with_weight(-90), )) .alter_assets(ContextOp::AddJavaScript( - JavaScript::defer("/bootsier/js/bootstrap.min.js") + JavaScript::defer("/bootsier/js/bootstrap.bundle.min.js") .with_version(BOOTSTRAP_VERSION) .with_weight(-90), )); diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index d34c0a9f..4c547f6a 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -24,7 +24,7 @@ pub use nav::Nav; // Navbar. pub mod navbar; #[doc(inline)] -pub use navbar::{Navbar, NavbarToggler}; +pub use navbar::Navbar; // Offcanvas. pub mod offcanvas; diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 72a3af3d..7b958d7f 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -1,11 +1,136 @@ -mod component; -pub use component::{Navbar, NavbarToggler, NavbarType}; +//! Definiciones para crear barras de navegación [`Navbar`]. +//! +//! Cada [`navbar::Item`](crate::theme::navbar::Item) representa un elemento individual de la barra +//! de navegación [`Navbar`], con distintos comportamientos según su finalidad, como menús +//! [`Nav`](crate::theme::Nav) o textos localizados usando [`L10n`](pagetop::locale::L10n). +//! +//! También puede mostrar una marca de identidad ([`navbar::Brand`](crate::theme::navbar::Brand)) +//! que identifique la compañía, producto o nombre del proyecto asociado a la solución web. +//! +//! # Ejemplos +//! +//! Barra **simple**, sólo con un menú horizontal: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let navbar = Navbar::simple() +//! .add_item(navbar::Item::nav( +//! Nav::new() +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about")) +//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact")) +//! )); +//! ``` +//! +//! Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let navbar = Navbar::simple_toggle() +//! .with_expand(BreakPoint::MD) +//! .add_item(navbar::Item::nav( +//! Nav::new() +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.example.com")) +//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support")) +//! )); +//! ``` +//! +//! Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let brand = navbar::Brand::new() +//! .with_title(L10n::n("PageTop")) +//! .with_path(Some(|_| "/")); +//! +//! let navbar = Navbar::brand_left(brand) +//! .add_item(navbar::Item::nav( +//! Nav::new() +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::dropdown( +//! Dropdown::new() +//! .with_title(L10n::n("Tools")) +//! .add_item(dropdown::Item::link(L10n::n("Generator"), |_| "/tools/gen")) +//! .add_item(dropdown::Item::link(L10n::n("Reports"), |_| "/tools/reports")) +//! )) +//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")) +//! )); +//! ``` +//! +//! Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let brand = navbar::Brand::new() +//! .with_title(L10n::n("Intranet")) +//! .with_path(Some(|_| "/")); +//! +//! let navbar = Navbar::brand_right(brand) +//! .with_expand(BreakPoint::LG) +//! .add_item(navbar::Item::nav( +//! Nav::pills() +//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard")) +//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users")) +//! )); +//! ``` +//! +//! Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let oc = Offcanvas::new() +//! .with_id("main_offcanvas") +//! .with_title(L10n::n("Main menu")) +//! .with_placement(offcanvas::Placement::Start) +//! .with_backdrop(offcanvas::Backdrop::Enabled); +//! +//! let navbar = Navbar::offcanvas(oc) +//! .add_item(navbar::Item::nav( +//! Nav::new() +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile")) +//! .add_item(nav::Item::dropdown( +//! Dropdown::new() +//! .with_title(L10n::n("More")) +//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings")) +//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help")) +//! )) +//! )); +//! ``` +//! +//! Barra **fija arriba**: +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let brand = navbar::Brand::new() +//! .with_title(L10n::n("Main App")) +//! .with_path(Some(|_| "/")); +//! +//! let navbar = Navbar::brand_left(brand) +//! .with_position(navbar::Position::FixedTop) +//! .add_item(navbar::Item::nav( +//! Nav::new() +//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/")) +//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors")) +//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock")) +//! )); +//! ``` -mod button_toggler; -pub use button_toggler::ButtonToggler; - -mod content; -pub use content::{Content, ContentType}; +mod props; +pub use props::{Layout, Position}; mod brand; pub use brand::Brand; + +mod component; +pub use component::Navbar; + +mod item; +pub use item::Item; diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index cfeb45aa..e969b0e4 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -2,16 +2,25 @@ use pagetop::prelude::*; use crate::prelude::*; +/// Marca de identidad para mostrar en una barra de navegación [`Navbar`]. +/// +/// Representa la identidad del sitio con una imagen, título y eslogan: +/// +/// - Si hay URL ([`with_path()`](Self::with_path)), el bloque completo actúa como enlace. Por +/// defecto enlaza a la raíz del sitio (`/`). +/// - Si no hay imagen ([`with_image()`](Self::with_image)) ni título +/// ([`with_title()`](Self::with_title)), la marca de identidad no se renderiza. +/// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Brand { - id : AttrId, - #[default(_code = "global::SETTINGS.app.name.to_owned()")] - app_name : String, - slogan : AttrL10n, - logo : Typed<Image>, - #[default(_code = "|_| \"/\"")] - home : FnPathByContext, + id : AttrId, + image : Typed<Image>, + #[default(_code = "L10n::n(&global::SETTINGS.app.name)")] + title : L10n, + slogan: L10n, + #[default(_code = "Some(|_| \"/\")")] + path : Option<FnPathByContext>, } impl Component for Brand { @@ -24,81 +33,79 @@ impl Component for Brand { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let logo = self.logo().render(cx); - let home = self.home()(cx); - let title = &L10n::l("site_home").lookup(cx); + let image = self.image().render(cx); + let title = self.title().using(cx); + if title.is_empty() && image.is_empty() { + return PrepareMarkup::None; + } + let slogan = self.slogan().using(cx); PrepareMarkup::With(html! { - div id=[self.id()] class="branding__container" { - div class="branding__content" { - @if !logo.is_empty() { - a class="branding__logo" href=(home) title=[title] rel="home" { - (logo) - } - } - div class="branding__text" { - a class="branding__name" href=(home) title=[title] rel="home" { - (self.app_name()) - } - @if let Some(slogan) = self.slogan().lookup(cx) { - div class="branding__slogan" { - (slogan) - } - } - } - } + @if let Some(path) = self.path() { + a class="navbar-brand" href=(path(cx)) { (image) (title) (slogan) } + } @else { + span class="navbar-brand" { (image) (title) (slogan) } } }) } } impl Brand { - // Brand BUILDER. + // **< Brand BUILDER >************************************************************************** + /// Establece el identificador único (`id`) de la marca. #[builder_fn] pub fn with_id(mut self, id: impl AsRef<str>) -> Self { self.id.alter_value(id); self } + /// Asigna o quita la imagen de marca. Si se pasa `None`, no se mostrará. #[builder_fn] - pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self { - self.app_name = app_name.into(); + pub fn with_image(mut self, image: Option<Image>) -> Self { + self.image.alter_component(image); self } + /// Establece el título de la identidad de marca. + #[builder_fn] + pub fn with_title(mut self, title: L10n) -> Self { + self.title = title; + self + } + + /// Define el eslogan de la marca. #[builder_fn] pub fn with_slogan(mut self, slogan: L10n) -> Self { - self.slogan.alter_value(slogan); + self.slogan = slogan; self } + /// Define la URL de destino. Si es `None`, la marca no será un enlace. #[builder_fn] - pub fn with_logo(mut self, logo: Option<Image>) -> Self { - self.logo.alter_component(logo); + pub fn with_path(mut self, path: Option<FnPathByContext>) -> Self { + self.path = path; self } - #[builder_fn] - pub fn with_home(mut self, home: FnPathByContext) -> Self { - self.home = home; - self + // **< Brand GETTERS >************************************************************************** + + /// Devuelve la imagen de marca (si la hay). + pub fn image(&self) -> &Typed<Image> { + &self.image } - // Brand GETTERS. - - pub fn app_name(&self) -> &String { - &self.app_name + /// Devuelve el título de la identidad de marca. + pub fn title(&self) -> &L10n { + &self.title } - pub fn slogan(&self) -> &AttrL10n { + /// Devuelve el eslogan de la marca. + pub fn slogan(&self) -> &L10n { &self.slogan } - pub fn logo(&self) -> &Typed<Image> { - &self.logo - } - - pub fn home(&self) -> &FnPathByContext { - &self.home + /// Devuelve la función que resuelve la URL asociada a la marca (si existe). + pub fn path(&self) -> &Option<FnPathByContext> { + &self.path } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs b/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs deleted file mode 100644 index 036182a7..00000000 --- a/extensions/pagetop-bootsier/src/theme/navbar/button_toggler.rs +++ /dev/null @@ -1,73 +0,0 @@ -use pagetop::prelude::*; - -use crate::LOCALES_BOOTSIER; - -use std::fmt; - -#[derive(AutoDefault, PartialEq)] -pub(crate) enum Toggle { - #[default] - Collapse, - Offcanvas, -} - -#[rustfmt::skip] -impl fmt::Display for Toggle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Toggle::Collapse => write!(f, "collapse"), - Toggle::Offcanvas => write!(f, "offcanvas"), - } - } -} - -#[derive(AutoDefault)] -pub struct ButtonToggler; - -impl Component for ButtonToggler { - fn new() -> Self { - ButtonToggler::default() - } - - fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - button - type="button" - class="navbar-toggler" - { - span class="navbar-toggler-icon" {} - } - }) - } -} - -impl ButtonToggler { - // ButtonToggler PRIVATE RENDER. - - pub(crate) fn render( - &self, - cx: &mut Context, - id_content: String, - data_bs_toggle: Toggle, - ) -> Markup { - let id_content_target = join!("#", id_content); - let aria_expanded = if data_bs_toggle == Toggle::Collapse { - Some("false") - } else { - None - }; - html! { - button - type="button" - class="navbar-toggler" - data-bs-toggle=(data_bs_toggle) - data-bs-target=(id_content_target) - aria-controls=(id_content) - aria-expanded=[aria_expanded] - aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)] - { - span class="navbar-toggler-icon" {} - } - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 4a203c3f..fc46175a 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -6,32 +6,23 @@ use crate::LOCALES_BOOTSIER; const TOGGLE_COLLAPSE: &str = "collapse"; const TOGGLE_OFFCANVAS: &str = "offcanvas"; -#[derive(AutoDefault)] -pub enum NavbarToggler { - #[default] - Enabled, - Disabled, -} - -#[derive(AutoDefault)] -pub enum NavbarType { - #[default] - None, - Nav(Typed<navbar::Nav>), - Offcanvas(Typed<Offcanvas>), - Text(L10n), -} - +/// Componente para crear una **barra de navegación**. +/// +/// Permite mostrar enlaces, menús y una marca de identidad en distintas disposiciones (simples, con +/// botón de despliegue o dentro de un [`offcanvas`]), controladas por [`navbar::Layout`]. También +/// puede fijarse en la parte superior o inferior del documento mediante [`navbar::Position`]. +/// +/// Ver ejemplos en el módulo [`navbar`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Navbar { - id : AttrId, - classes : AttrClasses, - expand : BreakPoint, - toggler : NavbarToggler, - navbar_type: NavbarType, - contents : Children, - brand : Typed<navbar::Brand>, + id : AttrId, + classes : AttrClasses, + expand : BreakPoint, + layout : navbar::Layout, + position: navbar::Position, + items : Children, } impl Component for Navbar { @@ -49,162 +40,22 @@ impl Component for Navbar { [ "navbar".to_string(), self.expand().try_class("navbar-expand").unwrap_or_default(), + match self.position() { + navbar::Position::Static => "", + navbar::Position::FixedTop => "fixed-top", + navbar::Position::FixedBottom => "fixed-bottom", + navbar::Position::StickyTop => "sticky-top", + navbar::Position::StickyBottom => "sticky-bottom", + } + .to_string(), ] .join(" "), ); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let id = cx.required_id::<Self>(self.id()); - - let navbar_type = match self.navbar_type() { - NavbarType::None => return PrepareMarkup::None, - NavbarType::Nav(nav) => { - let id_content = join!(id, "-content"); - match self.toggler() { - NavbarToggler::Enabled => self.toggler_wrapper( - TOGGLE_COLLAPSE, - L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx), - id_content, - self.brand().render(cx), - nav.render(cx), - ), - NavbarToggler::Disabled => nav.render(cx), - } - } - NavbarType::Offcanvas(oc) => { - let id_content = oc.id().unwrap_or_default(); - self.toggler_wrapper( - TOGGLE_OFFCANVAS, - L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx), - id_content, - self.brand().render(cx), - oc.render(cx), - ) - } - NavbarType::Text(text) => html! { - span class="navbar-text" { - (text.using(cx)) - } - }, - }; - - self.nav_wrapper(id, self.brand().render(cx), navbar_type) - } -} - -impl Navbar { - pub fn with_nav(nav: navbar::Nav) -> Self { - Navbar::default().with_navbar_type(NavbarType::Nav(Typed::with(nav))) - } - - pub fn with_offcanvas(offcanvas: Offcanvas) -> Self { - Navbar::default().with_navbar_type(NavbarType::Offcanvas(Typed::with(offcanvas))) - } - - // Navbar BUILDER. - - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - #[builder_fn] - pub fn with_expand(mut self, bp: BreakPoint) -> Self { - self.expand = bp; - self - } - - #[builder_fn] - pub fn with_toggler(mut self, toggler: NavbarToggler) -> Self { - self.toggler = toggler; - self - } - - #[builder_fn] - pub fn with_navbar_type(mut self, navbar_type: NavbarType) -> Self { - self.navbar_type = navbar_type; - self - } - - pub fn with_content(mut self, content: navbar::Content) -> Self { - self.contents.add(Child::with(content)); - self - } - - #[builder_fn] - pub fn with_contents(mut self, op: TypedOp<navbar::Content>) -> Self { - self.contents.alter_typed(op); - self - } - - #[builder_fn] - pub fn with_brand(mut self, brand: Option<navbar::Brand>) -> Self { - self.brand.alter_component(brand); - self - } - - // Navbar GETTERS. - - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - pub fn expand(&self) -> &BreakPoint { - &self.expand - } - - pub fn toggler(&self) -> &NavbarToggler { - &self.toggler - } - - pub fn navbar_type(&self) -> &NavbarType { - &self.navbar_type - } - - pub fn contents(&self) -> &Children { - &self.contents - } - - pub fn brand(&self) -> &Typed<navbar::Brand> { - &self.brand - } - - // Navbar HELPERS. - - fn nav_wrapper(&self, id: String, brand: Markup, content: Markup) -> PrepareMarkup { - if content.is_empty() { - PrepareMarkup::None - } else { - PrepareMarkup::With(html! { - (brand) - nav id=(id) class=[self.classes().get()] { - div class="container-fluid" { - (content) - } - } - }) - } - } - - fn toggler_wrapper( - &self, - data_bs_toggle: &str, - aria_label: Option<String>, - id_content: String, - brand: Markup, - content: Markup, - ) -> Markup { - if content.is_empty() { - html! {} - } else { + // Botón de despliegue (colapso u offcanvas) para la barra. + fn button(cx: &mut Context, data_bs_toggle: &str, id_content: &str) -> Markup { let id_content_target = join!("#", id_content); let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE { Some("false") @@ -212,7 +63,6 @@ impl Navbar { None }; html! { - (brand) button type="button" class="navbar-toggler" @@ -220,14 +70,229 @@ impl Navbar { data-bs-target=(id_content_target) aria-controls=(id_content) aria-expanded=[aria_expanded] - aria-label=[aria_label] + aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)] { span class="navbar-toggler-icon" {} } - div id=(id_content) class="collapse navbar-collapse" { - (content) - } } } + + // Si no hay contenidos, no tiene sentido mostrar una barra vacía. + let items = self.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + + // Asegura que la barra tiene un id estable para poder asociarlo al colapso/offcanvas. + let id = cx.required_id::<Self>(self.id()); + + PrepareMarkup::With(html! { + nav id=(id) class=[self.classes().get()] { + div class="container-fluid" { + @match self.layout() { + // Barra más sencilla: sólo contenido. + navbar::Layout::Simple => { + (items) + }, + + // Barra sencilla que se puede contraer/expandir. + navbar::Layout::SimpleToggle => { + @let id_content = join!(id, "-content"); + + (button(cx, TOGGLE_COLLAPSE, &id_content)) + div id=(id_content) class="collapse navbar-collapse" { + (items) + } + }, + + // Barra con marca a la izquierda, siempre visible. + navbar::Layout::SimpleBrandLeft(brand) => { + (brand.render(cx)) + (items) + }, + + // Barra con marca a la izquierda y botón a la derecha. + navbar::Layout::BrandLeft(brand) => { + @let id_content = join!(id, "-content"); + + (brand.render(cx)) + (button(cx, TOGGLE_COLLAPSE, &id_content)) + div id=(id_content) class="collapse navbar-collapse" { + (items) + } + }, + + // Barra con botón a la izquierda y marca a la derecha. + navbar::Layout::BrandRight(brand) => { + @let id_content = join!(id, "-content"); + + (button(cx, TOGGLE_COLLAPSE, &id_content)) + (brand.render(cx)) + div id=(id_content) class="collapse navbar-collapse" { + (items) + } + }, + + // Barra cuyo contenido se muestra en un offcanvas, sin marca. + navbar::Layout::Offcanvas(offcanvas) => { + @let id_content = offcanvas.id().unwrap_or_default(); + + (button(cx, TOGGLE_OFFCANVAS, &id_content)) + @if let Some(oc) = offcanvas.borrow() { + (oc.render_offcanvas(cx, Some(self.items()))) + } + }, + + // Barra con marca a la izquierda y contenido en offcanvas. + navbar::Layout::OffcanvasBrandLeft(brand, offcanvas) => { + @let id_content = offcanvas.id().unwrap_or_default(); + + (brand.render(cx)) + (button(cx, TOGGLE_OFFCANVAS, &id_content)) + @if let Some(oc) = offcanvas.borrow() { + (oc.render_offcanvas(cx, Some(self.items()))) + } + }, + + // Barra con contenido en offcanvas y marca a la derecha. + navbar::Layout::OffcanvasBrandRight(brand, offcanvas) => { + @let id_content = offcanvas.id().unwrap_or_default(); + + (button(cx, TOGGLE_OFFCANVAS, &id_content)) + (brand.render(cx)) + @if let Some(oc) = offcanvas.borrow() { + (oc.render_offcanvas(cx, Some(self.items()))) + } + }, + } + } + } + }) + } +} + +impl Navbar { + /// Crea una barra de navegación **simple**, sin marca y sin botón. + pub fn simple() -> Self { + Navbar::default().with_layout(navbar::Layout::Simple) + } + + /// Crea una barra de navegación **simple pero colapsable**, con botón a la izquierda. + pub fn simple_toggle() -> Self { + Navbar::default().with_layout(navbar::Layout::SimpleToggle) + } + + /// Crea una barra de navegación **con marca a la izquierda**, siempre visible. + pub fn simple_brand_left(brand: navbar::Brand) -> Self { + Navbar::default().with_layout(navbar::Layout::SimpleBrandLeft(Typed::with(brand))) + } + + /// Crea una barra de navegación con **marca a la izquierda** y **botón a la derecha**. + pub fn brand_left(brand: navbar::Brand) -> Self { + Navbar::default().with_layout(navbar::Layout::BrandLeft(Typed::with(brand))) + } + + /// Crea una barra de navegación con **botón a la izquierda** y **marca a la derecha**. + pub fn brand_right(brand: navbar::Brand) -> Self { + Navbar::default().with_layout(navbar::Layout::BrandRight(Typed::with(brand))) + } + + /// Crea una barra de navegación cuyo contenido se muestra en un **offcanvas**. + pub fn offcanvas(oc: Offcanvas) -> Self { + Navbar::default().with_layout(navbar::Layout::Offcanvas(Typed::with(oc))) + } + + /// Crea una barra de navegación con **marca a la izquierda** y contenido en **offcanvas**. + pub fn offcanvas_brand_left(brand: navbar::Brand, oc: Offcanvas) -> Self { + Navbar::default().with_layout(navbar::Layout::OffcanvasBrandLeft( + Typed::with(brand), + Typed::with(oc), + )) + } + + /// Crea una barra de navegación con **marca a la derecha** y contenido en **offcanvas**. + pub fn offcanvas_brand_right(brand: navbar::Brand, oc: Offcanvas) -> Self { + Navbar::default().with_layout(navbar::Layout::OffcanvasBrandRight( + Typed::with(brand), + Typed::with(oc), + )) + } + + // **< Navbar BUILDER >************************************************************************* + + /// Establece el identificador único (`id`) de la barra de navegación. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas a la barra de navegación. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Define a partir de qué punto de ruptura la barra de navegación deja de colapsar. + #[builder_fn] + pub fn with_expand(mut self, bp: BreakPoint) -> Self { + self.expand = bp; + self + } + + /// Define el tipo de disposición que tendrá la barra de navegación. + #[builder_fn] + pub fn with_layout(mut self, layout: navbar::Layout) -> Self { + self.layout = layout; + self + } + + /// Define dónde se mostrará la barra de navegación dentro del documento. + #[builder_fn] + pub fn with_position(mut self, position: navbar::Position) -> Self { + self.position = position; + self + } + + /// Añade un nuevo contenido hijo. + #[inline] + pub fn add_item(mut self, item: navbar::Item) -> Self { + self.items.add(Child::with(item)); + self + } + + /// Modifica la lista de contenidos (`children`) aplicando una operación [`TypedOp`]. + #[builder_fn] + pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self { + self.items.alter_typed(op); + self + } + + // **< Navbar GETTERS >************************************************************************* + + /// Devuelve las clases CSS asociadas a la barra de navegación. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el punto de ruptura configurado. + pub fn expand(&self) -> &BreakPoint { + &self.expand + } + + /// Devuelve la disposición configurada para la barra de navegación. + pub fn layout(&self) -> &navbar::Layout { + &self.layout + } + + /// Devuelve la posición configurada para la barra de navegación. + pub fn position(&self) -> &navbar::Position { + &self.position + } + + /// Devuelve la lista de contenidos (`children`). + pub fn items(&self) -> &Children { + &self.items } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/content.rs b/extensions/pagetop-bootsier/src/theme/navbar/content.rs deleted file mode 100644 index 2efb4a05..00000000 --- a/extensions/pagetop-bootsier/src/theme/navbar/content.rs +++ /dev/null @@ -1,69 +0,0 @@ -use pagetop::prelude::*; - -use crate::theme::navbar; - -#[derive(AutoDefault)] -pub enum ContentType { - #[default] - None, - Brand(Typed<navbar::Brand>), - Nav(Typed<navbar::Nav>), - Text(L10n), -} - -// Item. - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Content { - content: ContentType, -} - -impl Component for Content { - fn new() -> Self { - Content::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - match self.content() { - ContentType::None => PrepareMarkup::None, - ContentType::Brand(brand) => PrepareMarkup::With(html! { - (brand.render(cx)) - }), - ContentType::Nav(nav) => PrepareMarkup::With(html! { - (nav.render(cx)) - }), - ContentType::Text(text) => PrepareMarkup::With(html! { - span class="navbar-text" { - (text.using(cx)) - } - }), - } - } -} - -impl Content { - pub fn brand(content: navbar::Brand) -> Self { - Content { - content: ContentType::Brand(Typed::with(content)), - } - } - - pub fn nav(content: navbar::Nav) -> Self { - Content { - content: ContentType::Nav(Typed::with(content)), - } - } - - pub fn text(content: L10n) -> Self { - Content { - content: ContentType::Text(content), - } - } - - // Content GETTERS. - - pub fn content(&self) -> &ContentType { - &self.content - } -} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs new file mode 100644 index 00000000..f0646406 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -0,0 +1,108 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +/// Elementos que puede contener una barra de navegación [`Navbar`](crate::theme::Navbar). +/// +/// Cada variante determina qué se renderiza y cómo. Estos elementos se colocan **dentro del +/// contenido** de la barra (la parte colapsable, el *offcanvas* o el bloque simple), por lo que son +/// independientes de la marca o del botón que ya pueda definir el propio [`navbar::Layout`]. +#[derive(AutoDefault)] +pub enum Item { + /// Sin contenido, no produce salida. + #[default] + Void, + /// Marca de identidad mostrada dentro del contenido de la barra de navegación. + /// + /// Útil cuando el [`navbar::Layout`] no incluye marca, y se quiere incluir dentro del área + /// colapsable/*offcanvas*. Si el *layout* ya muestra una marca, esta variante no la sustituye, + /// sólo añade otra dentro del bloque de contenidos. + Brand(Typed<navbar::Brand>), + /// Representa un menú de navegación [`Nav`](crate::theme::Nav). + Nav(Typed<Nav>), + /// Representa un texto libre localizado. + Text(L10n), +} + +impl Component for Item { + fn new() -> Self { + Item::default() + } + + fn id(&self) -> Option<String> { + match self { + Self::Void => None, + Self::Brand(brand) => brand.id(), + Self::Nav(nav) => nav.id(), + Self::Text(_) => None, + } + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self { + Self::Void => PrepareMarkup::None, + Self::Brand(brand) => PrepareMarkup::With(html! { (brand.render(cx)) }), + Self::Nav(nav) => { + if let Some(nav) = nav.borrow() { + let items = nav.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + let classes = AttrClasses::new( + [ + "navbar-nav", + match nav.nav_kind() { + nav::Kind::Default => "", + nav::Kind::Tabs => "nav-tabs", + nav::Kind::Pills => "nav-pills", + nav::Kind::Underline => "nav-underline", + }, + match nav.nav_layout() { + nav::Layout::Default => "", + nav::Layout::Start => "justify-content-start", + nav::Layout::Center => "justify-content-center", + nav::Layout::End => "justify-content-end", + nav::Layout::Vertical => "flex-column", + nav::Layout::Fill => "nav-fill", + nav::Layout::Justified => "nav-justified", + }, + ] + .join(" "), + ); + PrepareMarkup::With(html! { + ul id=[nav.id()] class=[classes.get()] { + (items) + } + }) + } else { + PrepareMarkup::None + } + } + Self::Text(text) => PrepareMarkup::With(html! { + span class="navbar-text" { + (text.using(cx)) + } + }), + } + } +} + +impl Item { + /// Crea un elemento de tipo [`navbar::Brand`] para añadir en el contenido de [`Navbar`]. + /// + /// Pensado para barras colapsables u offcanvas donde se quiere que la marca aparezca en la zona + /// desplegable. + pub fn brand(brand: navbar::Brand) -> Self { + Self::Brand(Typed::with(brand)) + } + + /// Crea un elemento de tipo [`Nav`] para añadir al contenido de [`Navbar`]. + pub fn nav(item: Nav) -> Self { + Self::Nav(Typed::with(item)) + } + + /// Crea un elemento de texto localizado, mostrado sin interacción. + pub fn text(item: L10n) -> Self { + Self::Text(item) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/props.rs b/extensions/pagetop-bootsier/src/theme/navbar/props.rs new file mode 100644 index 00000000..86326574 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/navbar/props.rs @@ -0,0 +1,64 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +// **< Layout >************************************************************************************* + +/// Representa los diferentes tipos de presentación de una barra de navegación [`Navbar`]. +#[derive(AutoDefault)] +pub enum Layout { + /// Barra simple, sin marca de identidad y sin botón de despliegue. + /// + /// La barra de navegación no se colapsa. + #[default] + Simple, + + /// Barra simple, con botón de despliegue a la izquierda y sin marca de identidad. + SimpleToggle, + + /// Barra simple, con marca de identidad a la izquierda y sin botón de despliegue. + /// + /// La barra de navegación no se colapsa. + SimpleBrandLeft(Typed<navbar::Brand>), + + /// Barra con marca de identidad a la izquierda y botón de despliegue a la derecha. + BrandLeft(Typed<navbar::Brand>), + + /// Barra con botón de despliegue a la izquierda y marca de identidad a la derecha. + BrandRight(Typed<navbar::Brand>), + + /// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y sin marca de identidad. + Offcanvas(Typed<Offcanvas>), + + /// Contenido en [`Offcanvas`], con marca de identidad a la izquierda y botón de despliegue a la + /// derecha. + OffcanvasBrandLeft(Typed<navbar::Brand>, Typed<Offcanvas>), + + /// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y marca de identidad a la + /// derecha. + OffcanvasBrandRight(Typed<navbar::Brand>, Typed<Offcanvas>), +} + +// **< Position >*********************************************************************************** + +/// Posición global de una barra de navegación [`Navbar`] en el documento. +#[derive(AutoDefault)] +pub enum Position { + /// Barra normal, fluye con el documento. + #[default] + Static, + /// Barra fijada en la parte superior, siempre visible. + /// + /// Puede ser necesario reservar espacio en la parte superior del contenido que fluye debajo + /// para evitar que quede oculto por la barra. + FixedTop, + /// Barra fijada en la parte inferior, siempre visible. + /// + /// Puede ser necesario reservar espacio en la parte inferior del contenido que fluye debajo + /// para evitar que quede oculto por la barra. + FixedBottom, + /// La barra de navegación se fija en la parte superior al hacer *scroll*. + StickyTop, + /// La barra de navegación se fija en la parte inferior al hacer *scroll*. + StickyBottom, +} diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js new file mode 100644 index 00000000..0b873693 --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.8 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),s=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),o=t=>{t.dispatchEvent(new Event(i))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=t=>r(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,l=t=>{if(!r(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},_=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,b=(t,e,n=!0)=>{if(!n)return void _(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),_(t))};e.addEventListener(i,a),setTimeout(()=>{r||o(e)},s)},v=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,A=/::\d+$/,E={};let T=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},O=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=x(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function L(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function S(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return O.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=S(e,i,n);if(e in C){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=k(t),c=l[a]||(l[a]={}),h=L(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function $(t,e,i,n,s){const o=L(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&$(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(w,""),C[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=k(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))I(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(A,"");a&&!e.includes(s)||$(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;$(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f();let s=null,o=!0,r=!0,a=!1;e!==N(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=j(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function j(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function F(t){return t.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`)}const H={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter(t=>t.startsWith("bs")&&!t.startsWith("bsConfig"));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${F(e)}`))};class W{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=r(e)?H.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...r(e)?H.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[i,n]of Object.entries(e)){const e=t[i],o=r(e)?"element":s(e);if(!new RegExp(n).test(o))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${o}" but expected type "${n}".`)}}}class B extends W{constructor(t,i){super(),(t=a(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){b(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(a(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.8"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const z=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map(t=>n(t)).join(","):null},R={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,t).filter(t=>!c(t)&&l(t))},getSelectorFromElement(t){const e=z(t);return e&&R.findOne(e)?e:null},getElementFromSelector(t){const e=z(t);return e?R.findOne(e):null},getMultipleElementsFromSelector(t){const e=z(t);return e?R.find(e):[]}},q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;P.on(document,i,`[data-bs-dismiss="${n}"]`,function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const s=R.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()})},V=".bs.alert",K=`close${V}`,Q=`closed${V}`;class X extends B{static get NAME(){return"alert"}close(){if(P.trigger(this._element,K).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),P.trigger(this._element,Q),this.dispose()}static jQueryInterface(t){return this.each(function(){const e=X.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}q(X,"close"),g(X);const Y='[data-bs-toggle="button"]';class U extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each(function(){const e=U.getOrCreateInstance(this);"toggle"===t&&e[t]()})}}P.on(document,"click.bs.button.data-api",Y,t=>{t.preventDefault();const e=t.target.closest(Y);U.getOrCreateInstance(e).toggle()}),g(U);const G=".bs.swipe",J=`touchstart${G}`,Z=`touchmove${G}`,tt=`touchend${G}`,et=`pointerdown${G}`,it=`pointerup${G}`,nt={endCallback:null,leftCallback:null,rightCallback:null},st={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class ot extends W{constructor(t,e){super(),this._element=t,t&&ot.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return nt}static get DefaultType(){return st}static get NAME(){return"swipe"}dispose(){P.off(this._element,G)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),_(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&_(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(P.on(this._element,et,t=>this._start(t)),P.on(this._element,it,t=>this._end(t)),this._element.classList.add("pointer-event")):(P.on(this._element,J,t=>this._start(t)),P.on(this._element,Z,t=>this._move(t)),P.on(this._element,tt,t=>this._end(t)))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const rt=".bs.carousel",at=".data-api",lt="ArrowLeft",ct="ArrowRight",ht="next",dt="prev",ut="left",ft="right",pt=`slide${rt}`,mt=`slid${rt}`,gt=`keydown${rt}`,_t=`mouseenter${rt}`,bt=`mouseleave${rt}`,vt=`dragstart${rt}`,yt=`load${rt}${at}`,wt=`click${rt}${at}`,At="carousel",Et="active",Tt=".active",Ct=".carousel-item",Ot=Tt+Ct,xt={[lt]:ft,[ct]:ut},kt={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Lt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class St extends B{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=R.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===At&&this.cycle()}static get Default(){return kt}static get DefaultType(){return Lt}static get NAME(){return"carousel"}next(){this._slide(ht)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(dt)}pause(){this._isSliding&&o(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?P.one(this._element,mt,()=>this.cycle()):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void P.one(this._element,mt,()=>this.to(t));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ht:dt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&P.on(this._element,gt,t=>this._keydown(t)),"hover"===this._config.pause&&(P.on(this._element,_t,()=>this.pause()),P.on(this._element,bt,()=>this._maybeEnableCycle())),this._config.touch&&ot.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of R.find(".carousel-item img",this._element))P.on(t,vt,t=>t.preventDefault());const t={leftCallback:()=>this._slide(this._directionToOrder(ut)),rightCallback:()=>this._slide(this._directionToOrder(ft)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new ot(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=xt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=R.findOne(Tt,this._indicatorsElement);e.classList.remove(Et),e.removeAttribute("aria-current");const i=R.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(Et),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ht,s=e||v(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>P.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(pt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),u(s),i.classList.add(l),s.classList.add(l),this._queueCallback(()=>{s.classList.remove(l,c),s.classList.add(Et),i.classList.remove(Et,c,l),this._isSliding=!1,r(mt)},i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return R.findOne(Ot,this._element)}_getItems(){return R.find(Ct,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return m()?t===ut?dt:ht:t===ut?ht:dt}_orderToDirection(t){return m()?t===dt?ut:ft:t===dt?ft:ut}static jQueryInterface(t){return this.each(function(){const e=St.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)})}}P.on(document,wt,"[data-bs-slide], [data-bs-slide-to]",function(t){const e=R.getElementFromSelector(this);if(!e||!e.classList.contains(At))return;t.preventDefault();const i=St.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===H.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())}),P.on(window,yt,()=>{const t=R.find('[data-bs-ride="carousel"]');for(const e of t)St.getOrCreateInstance(e)}),g(St);const Dt=".bs.collapse",$t=`show${Dt}`,It=`shown${Dt}`,Nt=`hide${Dt}`,Pt=`hidden${Dt}`,jt=`click${Dt}.data-api`,Mt="show",Ft="collapse",Ht="collapsing",Wt=`:scope .${Ft} .${Ft}`,Bt='[data-bs-toggle="collapse"]',zt={parent:null,toggle:!0},Rt={parent:"(null|element)",toggle:"boolean"};class qt extends B{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=R.find(Bt);for(const t of i){const e=R.getSelectorFromElement(t),i=R.find(e).filter(t=>t===this._element);null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return zt}static get DefaultType(){return Rt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter(t=>t!==this._element).map(t=>qt.getOrCreateInstance(t,{toggle:!1}))),t.length&&t[0]._isTransitioning)return;if(P.trigger(this._element,$t).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Ft),this._element.classList.add(Ht),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(Ht),this._element.classList.add(Ft,Mt),this._element.style[e]="",P.trigger(this._element,It)},this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(P.trigger(this._element,Nt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(Ht),this._element.classList.remove(Ft,Mt);for(const t of this._triggerArray){const e=R.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(Ht),this._element.classList.add(Ft),P.trigger(this._element,Pt)},this._element,!0)}_isShown(t=this._element){return t.classList.contains(Mt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=a(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Bt);for(const e of t){const t=R.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=R.find(Wt,this._config.parent);return R.find(t,this._config.parent).filter(t=>!e.includes(t))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each(function(){const i=qt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}})}}P.on(document,jt,Bt,function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of R.getMultipleElementsFromSelector(this))qt.getOrCreateInstance(t,{toggle:!1}).toggle()}),g(qt);var Vt="top",Kt="bottom",Qt="right",Xt="left",Yt="auto",Ut=[Vt,Kt,Qt,Xt],Gt="start",Jt="end",Zt="clippingParents",te="viewport",ee="popper",ie="reference",ne=Ut.reduce(function(t,e){return t.concat([e+"-"+Gt,e+"-"+Jt])},[]),se=[].concat(Ut,[Yt]).reduce(function(t,e){return t.concat([e,e+"-"+Gt,e+"-"+Jt])},[]),oe="beforeRead",re="read",ae="afterRead",le="beforeMain",ce="main",he="afterMain",de="beforeWrite",ue="write",fe="afterWrite",pe=[oe,re,ae,le,ce,he,de,ue,fe];function me(t){return t?(t.nodeName||"").toLowerCase():null}function ge(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function _e(t){return t instanceof ge(t).Element||t instanceof Element}function be(t){return t instanceof ge(t).HTMLElement||t instanceof HTMLElement}function ve(t){return"undefined"!=typeof ShadowRoot&&(t instanceof ge(t).ShadowRoot||t instanceof ShadowRoot)}const ye={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach(function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];be(s)&&me(s)&&(Object.assign(s.style,i),Object.keys(n).forEach(function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)}))})},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach(function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce(function(t,e){return t[e]="",t},{});be(n)&&me(n)&&(Object.assign(n.style,o),Object.keys(s).forEach(function(t){n.removeAttribute(t)}))})}},requires:["computeStyles"]};function we(t){return t.split("-")[0]}var Ae=Math.max,Ee=Math.min,Te=Math.round;function Ce(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map(function(t){return t.brand+"/"+t.version}).join(" "):navigator.userAgent}function Oe(){return!/^((?!chrome|android).)*safari/i.test(Ce())}function xe(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&be(t)&&(s=t.offsetWidth>0&&Te(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Te(n.height)/t.offsetHeight||1);var r=(_e(t)?ge(t):window).visualViewport,a=!Oe()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function ke(t){var e=xe(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Le(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ve(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Se(t){return ge(t).getComputedStyle(t)}function De(t){return["table","td","th"].indexOf(me(t))>=0}function $e(t){return((_e(t)?t.ownerDocument:t.document)||window.document).documentElement}function Ie(t){return"html"===me(t)?t:t.assignedSlot||t.parentNode||(ve(t)?t.host:null)||$e(t)}function Ne(t){return be(t)&&"fixed"!==Se(t).position?t.offsetParent:null}function Pe(t){for(var e=ge(t),i=Ne(t);i&&De(i)&&"static"===Se(i).position;)i=Ne(i);return i&&("html"===me(i)||"body"===me(i)&&"static"===Se(i).position)?e:i||function(t){var e=/firefox/i.test(Ce());if(/Trident/i.test(Ce())&&be(t)&&"fixed"===Se(t).position)return null;var i=Ie(t);for(ve(i)&&(i=i.host);be(i)&&["html","body"].indexOf(me(i))<0;){var n=Se(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function je(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Me(t,e,i){return Ae(t,Ee(e,i))}function Fe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function He(t,e){return e.reduce(function(e,i){return e[i]=t,e},{})}const We={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=we(i.placement),l=je(a),c=[Xt,Qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Fe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:He(t,Ut))}(s.padding,i),d=ke(o),u="y"===l?Vt:Xt,f="y"===l?Kt:Qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=Pe(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Me(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Le(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Be(t){return t.split("-")[1]}var ze={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Re(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Xt,y=Vt,w=window;if(c){var A=Pe(i),E="clientHeight",T="clientWidth";A===ge(i)&&"static"!==Se(A=$e(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===Vt||(s===Xt||s===Qt)&&o===Jt)&&(y=Kt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Xt&&(s!==Vt&&s!==Kt||o!==Jt)||(v=Qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&ze),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:Te(i*s)/s||0,y:Te(n*s)/s||0}}({x:f,y:m},ge(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const qe={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:we(e.placement),variation:Be(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,Re(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,Re(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var Ve={passive:!0};const Ke={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=ge(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach(function(t){t.addEventListener("scroll",i.update,Ve)}),a&&l.addEventListener("resize",i.update,Ve),function(){o&&c.forEach(function(t){t.removeEventListener("scroll",i.update,Ve)}),a&&l.removeEventListener("resize",i.update,Ve)}},data:{}};var Qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Xe(t){return t.replace(/left|right|bottom|top/g,function(t){return Qe[t]})}var Ye={start:"end",end:"start"};function Ue(t){return t.replace(/start|end/g,function(t){return Ye[t]})}function Ge(t){var e=ge(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Je(t){return xe($e(t)).left+Ge(t).scrollLeft}function Ze(t){var e=Se(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ti(t){return["html","body","#document"].indexOf(me(t))>=0?t.ownerDocument.body:be(t)&&Ze(t)?t:ti(Ie(t))}function ei(t,e){var i;void 0===e&&(e=[]);var n=ti(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=ge(n),r=s?[o].concat(o.visualViewport||[],Ze(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ei(Ie(r)))}function ii(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ni(t,e,i){return e===te?ii(function(t,e){var i=ge(t),n=$e(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Oe();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Je(t),y:l}}(t,i)):_e(e)?function(t,e){var i=xe(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ii(function(t){var e,i=$e(t),n=Ge(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=Ae(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=Ae(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Je(t),l=-n.scrollTop;return"rtl"===Se(s||i).direction&&(a+=Ae(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}($e(t)))}function si(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?we(s):null,r=s?Be(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case Vt:e={x:a,y:i.y-n.height};break;case Kt:e={x:a,y:i.y+i.height};break;case Qt:e={x:i.x+i.width,y:l};break;case Xt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?je(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Gt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Jt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function oi(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Zt:a,c=i.rootBoundary,h=void 0===c?te:c,d=i.elementContext,u=void 0===d?ee:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Fe("number"!=typeof g?g:He(g,Ut)),b=u===ee?ie:ee,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ei(Ie(t)),i=["absolute","fixed"].indexOf(Se(t).position)>=0&&be(t)?Pe(t):t;return _e(i)?e.filter(function(t){return _e(t)&&Le(t,i)&&"body"!==me(t)}):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce(function(e,i){var s=ni(t,i,n);return e.top=Ae(s.top,e.top),e.right=Ee(s.right,e.right),e.bottom=Ee(s.bottom,e.bottom),e.left=Ae(s.left,e.left),e},ni(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(_e(y)?y:y.contextElement||$e(t.elements.popper),l,h,r),A=xe(t.elements.reference),E=si({reference:A,element:v,placement:s}),T=ii(Object.assign({},v,E)),C=u===ee?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===ee&&x){var k=x[s];Object.keys(O).forEach(function(t){var e=[Qt,Kt].indexOf(t)>=0?1:-1,i=[Vt,Kt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e})}return O}function ri(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?se:l,h=Be(n),d=h?a?ne:ne.filter(function(t){return Be(t)===h}):Ut,u=d.filter(function(t){return c.indexOf(t)>=0});0===u.length&&(u=d);var f=u.reduce(function(e,i){return e[i]=oi(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[we(i)],e},{});return Object.keys(f).sort(function(t,e){return f[t]-f[e]})}const ai={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=we(g),b=l||(_!==g&&p?function(t){if(we(t)===Yt)return[];var e=Xe(t);return[Ue(t),e,Ue(e)]}(g):[Xe(g)]),v=[g].concat(b).reduce(function(t,i){return t.concat(we(i)===Yt?ri(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)},[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C<v.length;C++){var O=v[C],x=we(O),k=Be(O)===Gt,L=[Vt,Kt].indexOf(x)>=0,S=L?"width":"height",D=oi(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?Qt:Xt:k?Kt:Vt;y[S]>w[S]&&($=Xe($));var I=Xe($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every(function(t){return t})){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find(function(e){var i=A.get(e);if(i)return i.slice(0,t).every(function(t){return t})});if(e)return T=e,"break"},j=p?3:1;j>0&&"break"!==P(j);j--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function li(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ci(t){return[Vt,Qt,Kt,Xt].some(function(e){return t[e]>=0})}const hi={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=oi(e,{elementContext:"reference"}),a=oi(e,{altBoundary:!0}),l=li(r,n),c=li(a,s,o),h=ci(l),d=ci(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},di={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=se.reduce(function(t,i){return t[i]=function(t,e,i){var n=we(t),s=[Xt,Vt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Xt,Qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t},{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ui={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=si({reference:e.rects.reference,element:e.rects.popper,placement:e.placement})},data:{}},fi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=oi(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=we(e.placement),b=Be(e.placement),v=!b,y=je(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?Vt:Xt,D="y"===y?Kt:Qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],j=f?-T[$]/2:0,M=b===Gt?E[$]:T[$],F=b===Gt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?ke(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Me(0,E[$],W[$]),V=v?E[$]/2-j-q-z-O.mainAxis:M-q-z-O.mainAxis,K=v?-E[$]/2+j+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&Pe(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Me(f?Ee(N,I+V-Y-X):N,I,f?Ae(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?Vt:Xt,tt="x"===y?Kt:Qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[Vt,Xt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Me(t,e,i);return n>i?i:n}(at,et,lt):Me(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function pi(t,e,i){void 0===i&&(i=!1);var n,s,o=be(e),r=be(e)&&function(t){var e=t.getBoundingClientRect(),i=Te(e.width)/t.offsetWidth||1,n=Te(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=$e(e),l=xe(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==me(e)||Ze(a))&&(c=(n=e)!==ge(n)&&be(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Ge(n)),be(e)?((h=xe(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Je(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function mi(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach(function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}}),n.push(t)}return t.forEach(function(t){e.set(t.name,t)}),t.forEach(function(t){i.has(t.name)||s(t)}),n}var gi={placement:"bottom",modifiers:[],strategy:"absolute"};function _i(){for(var t=arguments.length,e=new Array(t),i=0;i<t;i++)e[i]=arguments[i];return!e.some(function(t){return!(t&&"function"==typeof t.getBoundingClientRect)})}function bi(t){void 0===t&&(t={});var e=t,i=e.defaultModifiers,n=void 0===i?[]:i,s=e.defaultOptions,o=void 0===s?gi:s;return function(t,e,i){void 0===i&&(i=o);var s,r,a={placement:"bottom",orderedModifiers:[],options:Object.assign({},gi,o),modifiersData:{},elements:{reference:t,popper:e},attributes:{},styles:{}},l=[],c=!1,h={state:a,setOptions:function(i){var s="function"==typeof i?i(a.options):i;d(),a.options=Object.assign({},o,a.options,s),a.scrollParents={reference:_e(t)?ei(t):t.contextElement?ei(t.contextElement):[],popper:ei(e)};var r,c,u=function(t){var e=mi(t);return pe.reduce(function(t,i){return t.concat(e.filter(function(t){return t.phase===i}))},[])}((r=[].concat(n,a.options.modifiers),c=r.reduce(function(t,e){var i=t[e.name];return t[e.name]=i?Object.assign({},i,e,{options:Object.assign({},i.options,e.options),data:Object.assign({},i.data,e.data)}):e,t},{}),Object.keys(c).map(function(t){return c[t]})));return a.orderedModifiers=u.filter(function(t){return t.enabled}),a.orderedModifiers.forEach(function(t){var e=t.name,i=t.options,n=void 0===i?{}:i,s=t.effect;if("function"==typeof s){var o=s({state:a,name:e,instance:h,options:n});l.push(o||function(){})}}),h.update()},forceUpdate:function(){if(!c){var t=a.elements,e=t.reference,i=t.popper;if(_i(e,i)){a.rects={reference:pi(e,Pe(i),"fixed"===a.options.strategy),popper:ke(i)},a.reset=!1,a.placement=a.options.placement,a.orderedModifiers.forEach(function(t){return a.modifiersData[t.name]=Object.assign({},t.data)});for(var n=0;n<a.orderedModifiers.length;n++)if(!0!==a.reset){var s=a.orderedModifiers[n],o=s.fn,r=s.options,l=void 0===r?{}:r,d=s.name;"function"==typeof o&&(a=o({state:a,options:l,name:d,instance:h})||a)}else a.reset=!1,n=-1}}},update:(s=function(){return new Promise(function(t){h.forceUpdate(),t(a)})},function(){return r||(r=new Promise(function(t){Promise.resolve().then(function(){r=void 0,t(s())})})),r}),destroy:function(){d(),c=!0}};if(!_i(t,e))return h;function d(){l.forEach(function(t){return t()}),l=[]}return h.setOptions(i).then(function(t){!c&&i.onFirstUpdate&&i.onFirstUpdate(t)}),h}}var vi=bi(),yi=bi({defaultModifiers:[Ke,ui,qe,ye]}),wi=bi({defaultModifiers:[Ke,ui,qe,ye,di,ai,fi,We,hi]});const Ai=Object.freeze(Object.defineProperty({__proto__:null,afterMain:he,afterRead:ae,afterWrite:fe,applyStyles:ye,arrow:We,auto:Yt,basePlacements:Ut,beforeMain:le,beforeRead:oe,beforeWrite:de,bottom:Kt,clippingParents:Zt,computeStyles:qe,createPopper:wi,createPopperBase:vi,createPopperLite:yi,detectOverflow:oi,end:Jt,eventListeners:Ke,flip:ai,hide:hi,left:Xt,main:ce,modifierPhases:pe,offset:di,placements:se,popper:ee,popperGenerator:bi,popperOffsets:ui,preventOverflow:fi,read:re,reference:ie,right:Qt,start:Gt,top:Vt,variationPlacements:ne,viewport:te,write:ue},Symbol.toStringTag,{value:"Module"})),Ei="dropdown",Ti=".bs.dropdown",Ci=".data-api",Oi="ArrowUp",xi="ArrowDown",ki=`hide${Ti}`,Li=`hidden${Ti}`,Si=`show${Ti}`,Di=`shown${Ti}`,$i=`click${Ti}${Ci}`,Ii=`keydown${Ti}${Ci}`,Ni=`keyup${Ti}${Ci}`,Pi="show",ji='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Mi=`${ji}.${Pi}`,Fi=".dropdown-menu",Hi=m()?"top-end":"top-start",Wi=m()?"top-start":"top-end",Bi=m()?"bottom-end":"bottom-start",zi=m()?"bottom-start":"bottom-end",Ri=m()?"left-start":"right-start",qi=m()?"right-start":"left-start",Vi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Ki={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Qi extends B{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=R.next(this._element,Fi)[0]||R.prev(this._element,Fi)[0]||R.findOne(Fi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Vi}static get DefaultType(){return Ki}static get NAME(){return Ei}toggle(){return this._isShown()?this.hide():this.show()}show(){if(c(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!P.trigger(this._element,Si,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))P.on(t,"mouseover",d);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Pi),this._element.classList.add(Pi),P.trigger(this._element,Di,t)}}hide(){if(c(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!P.trigger(this._element,ki,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",d);this._popper&&this._popper.destroy(),this._menu.classList.remove(Pi),this._element.classList.remove(Pi),this._element.setAttribute("aria-expanded","false"),H.removeDataAttribute(this._menu,"popper"),P.trigger(this._element,Li,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ei.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===Ai)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let t=this._element;"parent"===this._config.reference?t=this._parent:r(this._config.reference)?t=a(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=wi(t,this._menu,e)}_isShown(){return this._menu.classList.contains(Pi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return Ri;if(t.classList.contains("dropstart"))return qi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Wi:Hi:e?zi:Bi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(H.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..._(this._config.popperConfig,[void 0,t])}}_selectMenuItem({key:t,target:e}){const i=R.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(t=>l(t));i.length&&v(i,e,t===xi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each(function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=R.find(Mi);for(const i of e){const e=Qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Oi,xi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(ji)?this:R.prev(this,ji)[0]||R.next(this,ji)[0]||R.findOne(ji,t.delegateTarget.parentNode),o=Qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}P.on(document,Ii,ji,Qi.dataApiKeydownHandler),P.on(document,Ii,Fi,Qi.dataApiKeydownHandler),P.on(document,$i,Qi.clearMenus),P.on(document,Ni,Qi.clearMenus),P.on(document,$i,ji,function(t){t.preventDefault(),Qi.getOrCreateInstance(this).toggle()}),g(Qi);const Xi="backdrop",Yi="show",Ui=`mousedown.bs.${Xi}`,Gi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ji={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Zi extends W{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Gi}static get DefaultType(){return Ji}static get NAME(){return Xi}show(t){if(!this._config.isVisible)return void _(t);this._append();const e=this._getElement();this._config.isAnimated&&u(e),e.classList.add(Yi),this._emulateAnimation(()=>{_(t)})}hide(t){this._config.isVisible?(this._getElement().classList.remove(Yi),this._emulateAnimation(()=>{this.dispose(),_(t)})):_(t)}dispose(){this._isAppended&&(P.off(this._element,Ui),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=a(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),P.on(t,Ui,()=>{_(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const tn=".bs.focustrap",en=`focusin${tn}`,nn=`keydown.tab${tn}`,sn="backward",on={autofocus:!0,trapElement:null},rn={autofocus:"boolean",trapElement:"element"};class an extends W{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return on}static get DefaultType(){return rn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),P.off(document,tn),P.on(document,en,t=>this._handleFocusin(t)),P.on(document,nn,t=>this._handleKeydown(t)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,P.off(document,tn))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=R.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===sn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?sn:"forward")}}const ln=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",cn=".sticky-top",hn="padding-right",dn="margin-right";class un{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,hn,e=>e+t),this._setElementAttributes(ln,hn,e=>e+t),this._setElementAttributes(cn,dn,e=>e-t)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,hn),this._resetElementAttributes(ln,hn),this._resetElementAttributes(cn,dn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)})}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&H.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=H.getDataAttribute(t,e);null!==i?(H.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)})}_applyManipulationCallback(t,e){if(r(t))e(t);else for(const i of R.find(t,this._element))e(i)}}const fn=".bs.modal",pn=`hide${fn}`,mn=`hidePrevented${fn}`,gn=`hidden${fn}`,_n=`show${fn}`,bn=`shown${fn}`,vn=`resize${fn}`,yn=`click.dismiss${fn}`,wn=`mousedown.dismiss${fn}`,An=`keydown.dismiss${fn}`,En=`click${fn}.data-api`,Tn="modal-open",Cn="show",On="modal-static",xn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends B{constructor(t,e){super(t,e),this._dialog=R.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new un,this._addEventListeners()}static get Default(){return xn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,_n,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show(()=>this._showElement(t)))}hide(){this._isShown&&!this._isTransitioning&&(P.trigger(this._element,pn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Cn),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated())))}dispose(){P.off(window,fn),P.off(this._dialog,fn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Zi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new an({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=R.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),u(this._element),this._element.classList.add(Cn),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,P.trigger(this._element,bn,{relatedTarget:t})},this._dialog,this._isAnimated())}_addEventListeners(){P.on(this._element,An,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())}),P.on(window,vn,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),P.on(this._element,wn,t=>{P.one(this._element,yn,e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Tn),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,gn)})}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,mn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(On)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(On),this._queueCallback(()=>{this._element.classList.remove(On),this._queueCallback(()=>{this._element.style.overflowY=e},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=m()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=m()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each(function(){const i=Ln.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}})}}P.on(document,En,'[data-bs-toggle="modal"]',function(t){const e=R.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,_n,t=>{t.defaultPrevented||P.one(e,gn,()=>{l(this)&&this.focus()})});const i=R.findOne(".modal.show");i&&Ln.getInstance(i).hide(),Ln.getOrCreateInstance(e).toggle(this)}),q(Ln),g(Ln);const Sn=".bs.offcanvas",Dn=".data-api",$n=`load${Sn}${Dn}`,In="show",Nn="showing",Pn="hiding",jn=".offcanvas.show",Mn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Wn=`hidePrevented${Sn}`,Bn=`hidden${Sn}`,zn=`resize${Sn}`,Rn=`click${Sn}${Dn}`,qn=`keydown.dismiss${Sn}`,Vn={backdrop:!0,keyboard:!0,scroll:!1},Kn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Qn extends B{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Vn}static get DefaultType(){return Kn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,Mn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new un).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Nn),this._queueCallback(()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(In),this._element.classList.remove(Nn),P.trigger(this._element,Fn,{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,Hn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Pn),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(In,Pn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new un).reset(),P.trigger(this._element,Bn)},this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Zi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():P.trigger(this._element,Wn)}:null})}_initializeFocusTrap(){return new an({trapElement:this._element})}_addEventListeners(){P.on(this._element,qn,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():P.trigger(this._element,Wn))})}static jQueryInterface(t){return this.each(function(){const e=Qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}P.on(document,Rn,'[data-bs-toggle="offcanvas"]',function(t){const e=R.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;P.one(e,Bn,()=>{l(this)&&this.focus()});const i=R.findOne(jn);i&&i!==e&&Qn.getInstance(i).hide(),Qn.getOrCreateInstance(e).toggle(this)}),P.on(window,$n,()=>{for(const t of R.find(jn))Qn.getOrCreateInstance(t).show()}),P.on(window,zn,()=>{for(const t of R.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Qn.getOrCreateInstance(t).hide()}),q(Qn),g(Qn);const Xn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Yn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Un=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Gn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Yn.has(i)||Boolean(Un.test(t.nodeValue)):e.filter(t=>t instanceof RegExp).some(t=>t.test(i))},Jn={allowList:Xn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},Zn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ts={entry:"(string|element|function|null)",selector:"(string|element)"};class es extends W{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Jn}static get DefaultType(){return Zn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map(t=>this._resolvePossibleFunction(t)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},ts)}_setContent(t,e,i){const n=R.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?r(e)?this._putElementInTemplate(a(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Gn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return _(t,[void 0,this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const is=new Set(["sanitize","allowList","sanitizeFn"]),ns="fade",ss="show",os=".tooltip-inner",rs=".modal",as="hide.bs.modal",ls="hover",cs="focus",hs="click",ds={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},us={allowList:Xn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},fs={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class ps extends B{constructor(t,e){if(void 0===Ai)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return us}static get DefaultType(){return fs}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(rs),as,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.eventName("show")),e=(h(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),P.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.on(t,"mouseover",d);this._queueCallback(()=>{P.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(this._isShown()&&!P.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(ss),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",d);this._activeTrigger[hs]=!1,this._activeTrigger[cs]=!1,this._activeTrigger[ls]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.eventName("hidden")))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ns,ss),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ns),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new es({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[os]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ns)}_isShown(){return this.tip&&this.tip.classList.contains(ss)}_createPopper(t){const e=_(this._config.placement,[this,t,this._element]),i=ds[e.toUpperCase()];return wi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return _(t,[this._element,this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,..._(this._config.popperConfig,[void 0,e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)P.on(this._element,this.constructor.eventName("click"),this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger[hs]=!(e._isShown()&&e._activeTrigger[hs]),e.toggle()});else if("manual"!==e){const t=e===ls?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ls?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");P.on(this._element,t,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?cs:ls]=!0,e._enter()}),P.on(this._element,i,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?cs:ls]=e._element.contains(t.relatedTarget),e._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(rs),as,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=H.getDataAttributes(this._element);for(const t of Object.keys(e))is.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:a(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each(function(){const e=ps.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}g(ps);const ms=".popover-header",gs=".popover-body",_s={...ps.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},bs={...ps.DefaultType,content:"(null|string|element|function)"};class vs extends ps{static get Default(){return _s}static get DefaultType(){return bs}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ms]:this._getTitle(),[gs]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each(function(){const e=vs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}g(vs);const ys=".bs.scrollspy",ws=`activate${ys}`,As=`click${ys}`,Es=`load${ys}.data-api`,Ts="active",Cs="[href]",Os=".nav-link",xs=`${Os}, .nav-item > ${Os}, .list-group-item`,ks={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Ls={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ss extends B{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ks}static get DefaultType(){return Ls}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=a(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map(t=>Number.parseFloat(t))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(P.off(this._config.target,As),P.on(this._config.target,As,Cs,t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}}))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=R.find(Cs,this._config.target);for(const e of t){if(!e.hash||c(e))continue;const t=R.findOne(decodeURI(e.hash),this._element);l(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Ts),this._activateParents(t),P.trigger(this._element,ws,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))R.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Ts);else for(const e of R.parents(t,".nav, .list-group"))for(const t of R.prev(e,xs))t.classList.add(Ts)}_clearActiveClass(t){t.classList.remove(Ts);const e=R.find(`${Cs}.${Ts}`,t);for(const t of e)t.classList.remove(Ts)}static jQueryInterface(t){return this.each(function(){const e=Ss.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}P.on(window,Es,()=>{for(const t of R.find('[data-bs-spy="scroll"]'))Ss.getOrCreateInstance(t)}),g(Ss);const Ds=".bs.tab",$s=`hide${Ds}`,Is=`hidden${Ds}`,Ns=`show${Ds}`,Ps=`shown${Ds}`,js=`click${Ds}`,Ms=`keydown${Ds}`,Fs=`load${Ds}`,Hs="ArrowLeft",Ws="ArrowRight",Bs="ArrowUp",zs="ArrowDown",Rs="Home",qs="End",Vs="active",Ks="fade",Qs="show",Xs=".dropdown-toggle",Ys=`:not(${Xs})`,Us='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Gs=`.nav-link${Ys}, .list-group-item${Ys}, [role="tab"]${Ys}, ${Us}`,Js=`.${Vs}[data-bs-toggle="tab"], .${Vs}[data-bs-toggle="pill"], .${Vs}[data-bs-toggle="list"]`;class Zs extends B{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),P.on(this._element,Ms,t=>this._keydown(t)))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?P.trigger(e,$s,{relatedTarget:t}):null;P.trigger(t,Ns,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Vs),this._activate(R.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),P.trigger(t,Ps,{relatedTarget:e})):t.classList.add(Qs)},t,t.classList.contains(Ks)))}_deactivate(t,e){t&&(t.classList.remove(Vs),t.blur(),this._deactivate(R.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),P.trigger(t,Is,{relatedTarget:e})):t.classList.remove(Qs)},t,t.classList.contains(Ks)))}_keydown(t){if(![Hs,Ws,Bs,zs,Rs,qs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter(t=>!c(t));let i;if([Rs,qs].includes(t.key))i=e[t.key===Rs?0:e.length-1];else{const n=[Ws,zs].includes(t.key);i=v(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Zs.getOrCreateInstance(i).show())}_getChildren(){return R.find(Gs,this._parent)}_getActiveElem(){return this._getChildren().find(t=>this._elemIsActive(t))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=R.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=R.findOne(t,i);s&&s.classList.toggle(n,e)};n(Xs,Vs),n(".dropdown-menu",Qs),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Vs)}_getInnerElement(t){return t.matches(Gs)?t:R.findOne(Gs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each(function(){const e=Zs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}P.on(document,js,Us,function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||Zs.getOrCreateInstance(this).show()}),P.on(window,Fs,()=>{for(const t of R.find(Js))Zs.getOrCreateInstance(t)}),g(Zs);const to=".bs.toast",eo=`mouseover${to}`,io=`mouseout${to}`,no=`focusin${to}`,so=`focusout${to}`,oo=`hide${to}`,ro=`hidden${to}`,ao=`show${to}`,lo=`shown${to}`,co="hide",ho="show",uo="showing",fo={animation:"boolean",autohide:"boolean",delay:"number"},po={animation:!0,autohide:!0,delay:5e3};class mo extends B{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return po}static get DefaultType(){return fo}static get NAME(){return"toast"}show(){P.trigger(this._element,ao).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(co),u(this._element),this._element.classList.add(ho,uo),this._queueCallback(()=>{this._element.classList.remove(uo),P.trigger(this._element,lo),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this.isShown()&&(P.trigger(this._element,oo).defaultPrevented||(this._element.classList.add(uo),this._queueCallback(()=>{this._element.classList.add(co),this._element.classList.remove(uo,ho),P.trigger(this._element,ro)},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(ho),super.dispose()}isShown(){return this._element.classList.contains(ho)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,eo,t=>this._onInteraction(t,!0)),P.on(this._element,io,t=>this._onInteraction(t,!1)),P.on(this._element,no,t=>this._onInteraction(t,!0)),P.on(this._element,so,t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each(function(){const e=mo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}})}}return q(mo),g(mo),{Alert:X,Button:U,Carousel:St,Collapse:qt,Dropdown:Qi,Modal:Ln,Offcanvas:Qn,Popover:vs,ScrollSpy:Ss,Tab:Zs,Toast:mo,Tooltip:ps}}); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map new file mode 100644 index 00000000..3e678d4c --- /dev/null +++ b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map @@ -0,0 +1 @@ +{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","Object","prototype","toString","call","toLowerCase","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ARROW_LEFT_KEY$1","ARROW_RIGHT_KEY$1","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","top","bottom","right","left","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","forEach","styles","assign","effect","_ref2","initialStyles","position","options","strategy","margin","arrow","hasOwnProperty","attribute","requires","getBasePlacement","round","getUAString","uaData","userAgentData","brands","isArray","item","brand","version","userAgent","isLayoutViewport","includeScale","isFixedStrategy","clientRect","scaleX","scaleY","offsetWidth","width","height","visualViewport","addVisualOffsets","x","offsetLeft","y","offsetTop","getLayoutRect","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","len","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","offset","axisProp","centerOffset","_options$element","requiresIfExists","getVariation","unsetSides","mapToStyles","_Object$assign2","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","_ref4","dpr","devicePixelRatio","roundOffsetsByDPR","computeStyles$1","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","getScrollParent","listScrollParents","_element$ownerDocumen","isBody","updatedList","rectToClientRect","rect","getClientRectFromMixedType","clippingParent","html","layoutViewport","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","computeOffsets","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$strategy","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","clippingClientRect","mainClippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_mainSide","_altSide","_offset","_len","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","_preventedOffset","v","withinMaxClamp","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","offsetParentIsScaled","isElementScaled","modifiers","visited","result","modifier","dep","depModifier","DEFAULT_OPTIONS","areValidElements","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","setOptionsAction","cleanupModifierEffects","merged","orderModifiers","current","existing","m","_ref$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","display","popperConfig","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","Popper","referenceElement","_getPopperConfig","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_resetElementAttributes","isOverflowing","_saveInitialAttribute","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","initialOverflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","Offcanvas","blur","completeCallback","DefaultAllowlist","area","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","allowedAttributeList","attributeName","nodeValue","attributeRegex","regex","allowList","content","extraClass","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","scrollTo","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","activeNodes","spy","HOME_KEY","END_KEY","SELECTOR_DROPDOWN_TOGGLE","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/userAgent.js","../../node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.8'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n // Private\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n // Private\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If <html> has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on <html>\n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `<html>` and `<body>` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [undefined, this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org/docs/v2/)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element, this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK])\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"mappings":";;;;;yOAWA,MAAMA,EAAa,IAAIC,IAEvBC,EAAe,CACbC,IAAIC,EAASC,EAAKC,GACXN,EAAWO,IAAIH,IAClBJ,EAAWG,IAAIC,EAAS,IAAIH,KAG9B,MAAMO,EAAcR,EAAWS,IAAIL,GAI9BI,EAAYD,IAAIF,IAA6B,IAArBG,EAAYE,KAMzCF,EAAYL,IAAIE,EAAKC,GAJnBK,QAAQC,MAAM,+EAA+EC,MAAMC,KAAKN,EAAYO,QAAQ,MAKhI,EAEAN,IAAGA,CAACL,EAASC,IACPL,EAAWO,IAAIH,IACVJ,EAAWS,IAAIL,GAASK,IAAIJ,IAG9B,KAGTW,OAAOZ,EAASC,GACd,IAAKL,EAAWO,IAAIH,GAClB,OAGF,MAAMI,EAAcR,EAAWS,IAAIL,GAEnCI,EAAYS,OAAOZ,GAGM,IAArBG,EAAYE,MACdV,EAAWiB,OAAOb,EAEtB,GC5CIc,EAAiB,gBAOjBC,EAAgBC,IAChBA,GAAYC,OAAOC,KAAOD,OAAOC,IAAIC,SAEvCH,EAAWA,EAASI,QAAQ,gBAAiB,CAACC,EAAOC,IAAO,IAAIJ,IAAIC,OAAOG,OAGtEN,GAIHO,EAASC,GACTA,QACK,GAAGA,IAGLC,OAAOC,UAAUC,SAASC,KAAKJ,GAAQH,MAAM,eAAe,GAAGQ,cAsClEC,EAAuB9B,IAC3BA,EAAQ+B,cAAc,IAAIC,MAAMlB,KAG5BmB,EAAYT,MACXA,GAA4B,iBAAXA,UAIO,IAAlBA,EAAOU,SAChBV,EAASA,EAAO,SAGgB,IAApBA,EAAOW,UAGjBC,EAAaZ,GAEbS,EAAUT,GACLA,EAAOU,OAASV,EAAO,GAAKA,EAGf,iBAAXA,GAAuBA,EAAOa,OAAS,EACzCC,SAASC,cAAcxB,EAAcS,IAGvC,KAGHgB,EAAYxC,IAChB,IAAKiC,EAAUjC,IAAgD,IAApCA,EAAQyC,iBAAiBJ,OAClD,OAAO,EAGT,MAAMK,EAAgF,YAA7DC,iBAAiB3C,GAAS4C,iBAAiB,cAE9DC,EAAgB7C,EAAQ8C,QAAQ,uBAEtC,IAAKD,EACH,OAAOH,EAGT,GAAIG,IAAkB7C,EAAS,CAC7B,MAAM+C,EAAU/C,EAAQ8C,QAAQ,WAChC,GAAIC,GAAWA,EAAQC,aAAeH,EACpC,OAAO,EAGT,GAAgB,OAAZE,EACF,OAAO,CAEX,CAEA,OAAOL,GAGHO,EAAajD,IACZA,GAAWA,EAAQmC,WAAae,KAAKC,gBAItCnD,EAAQoD,UAAUC,SAAS,mBAIC,IAArBrD,EAAQsD,SACVtD,EAAQsD,SAGVtD,EAAQuD,aAAa,aAAoD,UAArCvD,EAAQwD,aAAa,aAG5DC,EAAiBzD,IACrB,IAAKsC,SAASoB,gBAAgBC,aAC5B,OAAO,KAIT,GAAmC,mBAAxB3D,EAAQ4D,YAA4B,CAC7C,MAAMC,EAAO7D,EAAQ4D,cACrB,OAAOC,aAAgBC,WAAaD,EAAO,IAC7C,CAEA,OAAI7D,aAAmB8D,WACd9D,EAIJA,EAAQgD,WAINS,EAAezD,EAAQgD,YAHrB,MAMLe,EAAOA,OAUPC,EAAShE,IACbA,EAAQiE,cAGJC,EAAYA,IACZjD,OAAOkD,SAAW7B,SAAS8B,KAAKb,aAAa,qBACxCtC,OAAOkD,OAGT,KAGHE,EAA4B,GAmB5BC,EAAQA,IAAuC,QAAjChC,SAASoB,gBAAgBa,IAEvCC,EAAqBC,IAnBAC,QAoBN,KACjB,MAAMC,EAAIT,IAEV,GAAIS,EAAG,CACL,MAAMC,EAAOH,EAAOI,KACdC,EAAqBH,EAAEI,GAAGH,GAChCD,EAAEI,GAAGH,GAAQH,EAAOO,gBACpBL,EAAEI,GAAGH,GAAMK,YAAcR,EACzBE,EAAEI,GAAGH,GAAMM,WAAa,KACtBP,EAAEI,GAAGH,GAAQE,EACNL,EAAOO,gBAElB,GA/B0B,YAAxB1C,SAAS6C,YAENd,EAA0BhC,QAC7BC,SAAS8C,iBAAiB,mBAAoB,KAC5C,IAAK,MAAMV,KAAYL,EACrBK,MAKNL,EAA0BgB,KAAKX,IAE/BA,KAuBEY,EAAUA,CAACC,EAAkBC,EAAO,GAAIC,EAAeF,IACxB,mBAArBA,EAAkCA,EAAiB3D,QAAQ4D,GAAQC,EAG7EC,EAAyBA,CAAChB,EAAUiB,EAAmBC,GAAoB,KAC/E,IAAKA,EAEH,YADAN,EAAQZ,GAIV,MACMmB,EA7LiC7F,KACvC,IAAKA,EACH,OAAO,EAIT,IAAI8F,mBAAEA,EAAkBC,gBAAEA,GAAoB9E,OAAO0B,iBAAiB3C,GAEtE,MAAMgG,EAA0BC,OAAOC,WAAWJ,GAC5CK,EAAuBF,OAAOC,WAAWH,GAG/C,OAAKC,GAA4BG,GAKjCL,EAAqBA,EAAmBM,MAAM,KAAK,GACnDL,EAAkBA,EAAgBK,MAAM,KAAK,GAxDf,KA0DtBH,OAAOC,WAAWJ,GAAsBG,OAAOC,WAAWH,KAPzD,GAgLgBM,CAAiCV,GADlC,EAGxB,IAAIW,GAAS,EAEb,MAAMC,EAAUA,EAAGC,aACbA,IAAWb,IAIfW,GAAS,EACTX,EAAkBc,oBAAoB3F,EAAgByF,GACtDjB,EAAQZ,KAGViB,EAAkBP,iBAAiBtE,EAAgByF,GACnDG,WAAW,KACJJ,GACHxE,EAAqB6D,IAEtBE,IAYCc,EAAuBA,CAACC,EAAMC,EAAeC,EAAeC,KAChE,MAAMC,EAAaJ,EAAKvE,OACxB,IAAI4E,EAAQL,EAAKM,QAAQL,GAIzB,OAAc,IAAVI,GACMH,GAAiBC,EAAiBH,EAAKI,EAAa,GAAKJ,EAAK,IAGxEK,GAASH,EAAgB,GAAI,EAEzBC,IACFE,GAASA,EAAQD,GAAcA,GAG1BJ,EAAKO,KAAKC,IAAI,EAAGD,KAAKE,IAAIJ,EAAOD,EAAa,OC7QjDM,EAAiB,qBACjBC,EAAiB,OACjBC,EAAgB,SAChBC,EAAgB,GACtB,IAAIC,EAAW,EACf,MAAMC,EAAe,CACnBC,WAAY,YACZC,WAAY,YAGRC,EAAe,IAAIC,IAAI,CAC3B,QACA,WACA,UACA,YACA,cACA,aACA,iBACA,YACA,WACA,YACA,cACA,YACA,UACA,WACA,QACA,oBACA,aACA,YACA,WACA,cACA,cACA,cACA,YACA,eACA,gBACA,eACA,gBACA,aACA,QACA,OACA,SACA,QACA,SACA,SACA,UACA,WACA,OACA,SACA,eACA,SACA,OACA,mBACA,mBACA,QACA,QACA,WAOF,SAASC,EAAahI,EAASiI,GAC7B,OAAQA,GAAO,GAAGA,MAAQP,OAAiB1H,EAAQ0H,UAAYA,GACjE,CAEA,SAASQ,EAAiBlI,GACxB,MAAMiI,EAAMD,EAAahI,GAKzB,OAHAA,EAAQ0H,SAAWO,EACnBR,EAAcQ,GAAOR,EAAcQ,IAAQ,GAEpCR,EAAcQ,EACvB,CAoCA,SAASE,EAAYC,EAAQC,EAAUC,EAAqB,MAC1D,OAAO7G,OAAO8G,OAAOH,GAClBI,KAAKC,GAASA,EAAMJ,WAAaA,GAAYI,EAAMH,qBAAuBA,EAC/E,CAEA,SAASI,EAAoBC,EAAmBpC,EAASqC,GACvD,MAAMC,EAAiC,iBAAZtC,EAErB8B,EAAWQ,EAAcD,EAAsBrC,GAAWqC,EAChE,IAAIE,EAAYC,EAAaJ,GAM7B,OAJKb,EAAa3H,IAAI2I,KACpBA,EAAYH,GAGP,CAACE,EAAaR,EAAUS,EACjC,CAEA,SAASE,EAAWhJ,EAAS2I,EAAmBpC,EAASqC,EAAoBK,GAC3E,GAAiC,iBAAtBN,IAAmC3I,EAC5C,OAGF,IAAK6I,EAAaR,EAAUS,GAAaJ,EAAoBC,EAAmBpC,EAASqC,GAIzF,GAAID,KAAqBhB,EAAc,CACrC,MAAMuB,EAAenE,GACZ,SAAU0D,GACf,IAAKA,EAAMU,eAAkBV,EAAMU,gBAAkBV,EAAMW,iBAAmBX,EAAMW,eAAe/F,SAASoF,EAAMU,eAChH,OAAOpE,EAAGnD,KAAKyH,KAAMZ,EAEzB,EAGFJ,EAAWa,EAAab,EAC1B,CAEA,MAAMD,EAASF,EAAiBlI,GAC1BsJ,EAAWlB,EAAOU,KAAeV,EAAOU,GAAa,IACrDS,EAAmBpB,EAAYmB,EAAUjB,EAAUQ,EAActC,EAAU,MAEjF,GAAIgD,EAGF,YAFAA,EAAiBN,OAASM,EAAiBN,QAAUA,GAKvD,MAAMhB,EAAMD,EAAaK,EAAUM,EAAkBvH,QAAQkG,EAAgB,KACvEvC,EAAK8D,EAxEb,SAAoC7I,EAASgB,EAAU+D,GACrD,OAAO,SAASwB,EAAQkC,GACtB,MAAMe,EAAcxJ,EAAQyJ,iBAAiBzI,GAE7C,IAAK,IAAIwF,OAAEA,GAAWiC,EAAOjC,GAAUA,IAAW6C,KAAM7C,EAASA,EAAOxD,WACtE,IAAK,MAAM0G,KAAcF,EACvB,GAAIE,IAAelD,EAUnB,OANAmD,EAAWlB,EAAO,CAAEW,eAAgB5C,IAEhCD,EAAQ0C,QACVW,EAAaC,IAAI7J,EAASyI,EAAMqB,KAAM9I,EAAU+D,GAG3CA,EAAGgF,MAAMvD,EAAQ,CAACiC,GAG/B,CACF,CAqDIuB,CAA2BhK,EAASuG,EAAS8B,GArFjD,SAA0BrI,EAAS+E,GACjC,OAAO,SAASwB,EAAQkC,GAOtB,OANAkB,EAAWlB,EAAO,CAAEW,eAAgBpJ,IAEhCuG,EAAQ0C,QACVW,EAAaC,IAAI7J,EAASyI,EAAMqB,KAAM/E,GAGjCA,EAAGgF,MAAM/J,EAAS,CAACyI,GAC5B,CACF,CA4EIwB,CAAiBjK,EAASqI,GAE5BtD,EAAGuD,mBAAqBO,EAActC,EAAU,KAChDxB,EAAGsD,SAAWA,EACdtD,EAAGkE,OAASA,EACZlE,EAAG2C,SAAWO,EACdqB,EAASrB,GAAOlD,EAEhB/E,EAAQoF,iBAAiB0D,EAAW/D,EAAI8D,EAC1C,CAEA,SAASqB,EAAclK,EAASoI,EAAQU,EAAWvC,EAAS+B,GAC1D,MAAMvD,EAAKoD,EAAYC,EAAOU,GAAYvC,EAAS+B,GAE9CvD,IAIL/E,EAAQyG,oBAAoBqC,EAAW/D,EAAIoF,QAAQ7B,WAC5CF,EAAOU,GAAW/D,EAAG2C,UAC9B,CAEA,SAAS0C,EAAyBpK,EAASoI,EAAQU,EAAWuB,GAC5D,MAAMC,EAAoBlC,EAAOU,IAAc,GAE/C,IAAK,MAAOyB,EAAY9B,KAAUhH,OAAO+I,QAAQF,GAC3CC,EAAWE,SAASJ,IACtBH,EAAclK,EAASoI,EAAQU,EAAWL,EAAMJ,SAAUI,EAAMH,mBAGtE,CAEA,SAASS,EAAaN,GAGpB,OADAA,EAAQA,EAAMrH,QAAQmG,EAAgB,IAC/BI,EAAac,IAAUA,CAChC,CAEA,MAAMmB,EAAe,CACnBc,GAAG1K,EAASyI,EAAOlC,EAASqC,GAC1BI,EAAWhJ,EAASyI,EAAOlC,EAASqC,GAAoB,EAC1D,EAEA+B,IAAI3K,EAASyI,EAAOlC,EAASqC,GAC3BI,EAAWhJ,EAASyI,EAAOlC,EAASqC,GAAoB,EAC1D,EAEAiB,IAAI7J,EAAS2I,EAAmBpC,EAASqC,GACvC,GAAiC,iBAAtBD,IAAmC3I,EAC5C,OAGF,MAAO6I,EAAaR,EAAUS,GAAaJ,EAAoBC,EAAmBpC,EAASqC,GACrFgC,EAAc9B,IAAcH,EAC5BP,EAASF,EAAiBlI,GAC1BsK,EAAoBlC,EAAOU,IAAc,GACzC+B,EAAclC,EAAkBmC,WAAW,KAEjD,QAAwB,IAAbzC,EAAX,CAUA,GAAIwC,EACF,IAAK,MAAME,KAAgBtJ,OAAOd,KAAKyH,GACrCgC,EAAyBpK,EAASoI,EAAQ2C,EAAcpC,EAAkBqC,MAAM,IAIpF,IAAK,MAAOC,EAAaxC,KAAUhH,OAAO+I,QAAQF,GAAoB,CACpE,MAAMC,EAAaU,EAAY7J,QAAQoG,EAAe,IAEjDoD,IAAejC,EAAkB8B,SAASF,IAC7CL,EAAclK,EAASoI,EAAQU,EAAWL,EAAMJ,SAAUI,EAAMH,mBAEpE,CAdA,KARA,CAEE,IAAK7G,OAAOd,KAAK2J,GAAmBjI,OAClC,OAGF6H,EAAclK,EAASoI,EAAQU,EAAWT,EAAUQ,EAActC,EAAU,KAE9E,CAeF,EAEA2E,QAAQlL,EAASyI,EAAOjD,GACtB,GAAqB,iBAAViD,IAAuBzI,EAChC,OAAO,KAGT,MAAM2E,EAAIT,IAIV,IAAIiH,EAAc,KACdC,GAAU,EACVC,GAAiB,EACjBC,GAAmB,EALH7C,IADFM,EAAaN,IAQZ9D,IACjBwG,EAAcxG,EAAE3C,MAAMyG,EAAOjD,GAE7Bb,EAAE3E,GAASkL,QAAQC,GACnBC,GAAWD,EAAYI,uBACvBF,GAAkBF,EAAYK,gCAC9BF,EAAmBH,EAAYM,sBAGjC,MAAMC,EAAM/B,EAAW,IAAI3H,MAAMyG,EAAO,CAAE2C,UAASO,YAAY,IAASnG,GAcxE,OAZI8F,GACFI,EAAIE,iBAGFP,GACFrL,EAAQ+B,cAAc2J,GAGpBA,EAAIJ,kBAAoBH,GAC1BA,EAAYS,iBAGPF,CACT,GAGF,SAAS/B,EAAWkC,EAAKC,EAAO,IAC9B,IAAK,MAAO7L,EAAK8L,KAAUtK,OAAO+I,QAAQsB,GACxC,IACED,EAAI5L,GAAO8L,CACb,CAAE,MAAAC,GACAvK,OAAOwK,eAAeJ,EAAK5L,EAAK,CAC9BiM,cAAc,EACd7L,IAAGA,IACM0L,GAGb,CAGF,OAAOF,CACT,CCnTA,SAASM,EAAcJ,GACrB,GAAc,SAAVA,EACF,OAAO,EAGT,GAAc,UAAVA,EACF,OAAO,EAGT,GAAIA,IAAU9F,OAAO8F,GAAOpK,WAC1B,OAAOsE,OAAO8F,GAGhB,GAAc,KAAVA,GAA0B,SAAVA,EAClB,OAAO,KAGT,GAAqB,iBAAVA,EACT,OAAOA,EAGT,IACE,OAAOK,KAAKC,MAAMC,mBAAmBP,GACvC,CAAE,MAAAC,GACA,OAAOD,CACT,CACF,CAEA,SAASQ,EAAiBtM,GACxB,OAAOA,EAAImB,QAAQ,SAAUoL,GAAO,IAAIA,EAAI3K,gBAC9C,CAEA,MAAM4K,EAAc,CAClBC,iBAAiB1M,EAASC,EAAK8L,GAC7B/L,EAAQ2M,aAAa,WAAWJ,EAAiBtM,KAAQ8L,EAC3D,EAEAa,oBAAoB5M,EAASC,GAC3BD,EAAQ6M,gBAAgB,WAAWN,EAAiBtM,KACtD,EAEA6M,kBAAkB9M,GAChB,IAAKA,EACH,MAAO,GAGT,MAAM+M,EAAa,GACbC,EAASvL,OAAOd,KAAKX,EAAQiN,SAASC,OAAOjN,GAAOA,EAAI6K,WAAW,QAAU7K,EAAI6K,WAAW,aAElG,IAAK,MAAM7K,KAAO+M,EAAQ,CACxB,IAAIG,EAAUlN,EAAImB,QAAQ,MAAO,IACjC+L,EAAUA,EAAQC,OAAO,GAAGvL,cAAgBsL,EAAQnC,MAAM,GAC1D+B,EAAWI,GAAWhB,EAAcnM,EAAQiN,QAAQhN,GACtD,CAEA,OAAO8M,CACT,EAEAM,iBAAgBA,CAACrN,EAASC,IACjBkM,EAAcnM,EAAQwD,aAAa,WAAW+I,EAAiBtM,QCpD1E,MAAMqN,EAEJ,kBAAWC,GACT,MAAO,EACT,CAEA,sBAAWC,GACT,MAAO,EACT,CAEA,eAAW3I,GACT,MAAM,IAAI4I,MAAM,sEAClB,CAEAC,WAAWC,GAIT,OAHAA,EAAStE,KAAKuE,gBAAgBD,GAC9BA,EAAStE,KAAKwE,kBAAkBF,GAChCtE,KAAKyE,iBAAiBH,GACfA,CACT,CAEAE,kBAAkBF,GAChB,OAAOA,CACT,CAEAC,gBAAgBD,EAAQ3N,GACtB,MAAM+N,EAAa9L,EAAUjC,GAAWyM,EAAYY,iBAAiBrN,EAAS,UAAY,GAE1F,MAAO,IACFqJ,KAAK2E,YAAYT,WACM,iBAAfQ,EAA0BA,EAAa,MAC9C9L,EAAUjC,GAAWyM,EAAYK,kBAAkB9M,GAAW,MAC5C,iBAAX2N,EAAsBA,EAAS,GAE9C,CAEAG,iBAAiBH,EAAQM,EAAc5E,KAAK2E,YAAYR,aACtD,IAAK,MAAOU,EAAUC,KAAkB1M,OAAO+I,QAAQyD,GAAc,CACnE,MAAMlC,EAAQ4B,EAAOO,GACfE,EAAYnM,EAAU8J,GAAS,UAAYxK,EAAOwK,GAExD,IAAK,IAAIsC,OAAOF,GAAeG,KAAKF,GAClC,MAAM,IAAIG,UACR,GAAGlF,KAAK2E,YAAYnJ,KAAK2J,0BAA0BN,qBAA4BE,yBAAiCD,MAGtH,CACF,ECvCF,MAAMM,UAAsBnB,EAC1BU,YAAYhO,EAAS2N,GACnBe,SAEA1O,EAAUoC,EAAWpC,MAKrBqJ,KAAKsF,SAAW3O,EAChBqJ,KAAKuF,QAAUvF,KAAKqE,WAAWC,GAE/B7N,EAAKC,IAAIsJ,KAAKsF,SAAUtF,KAAK2E,YAAYa,SAAUxF,MACrD,CAGAyF,UACEhP,EAAKc,OAAOyI,KAAKsF,SAAUtF,KAAK2E,YAAYa,UAC5CjF,EAAaC,IAAIR,KAAKsF,SAAUtF,KAAK2E,YAAYe,WAEjD,IAAK,MAAMC,KAAgBvN,OAAOwN,oBAAoB5F,MACpDA,KAAK2F,GAAgB,IAEzB,CAGAE,eAAexK,EAAU1E,EAASmP,GAAa,GAC7CzJ,EAAuBhB,EAAU1E,EAASmP,EAC5C,CAEAzB,WAAWC,GAIT,OAHAA,EAAStE,KAAKuE,gBAAgBD,EAAQtE,KAAKsF,UAC3ChB,EAAStE,KAAKwE,kBAAkBF,GAChCtE,KAAKyE,iBAAiBH,GACfA,CACT,CAGA,kBAAOyB,CAAYpP,GACjB,OAAOF,EAAKO,IAAI+B,EAAWpC,GAAUqJ,KAAKwF,SAC5C,CAEA,0BAAOQ,CAAoBrP,EAAS2N,EAAS,IAC3C,OAAOtE,KAAK+F,YAAYpP,IAAY,IAAIqJ,KAAKrJ,EAA2B,iBAAX2N,EAAsBA,EAAS,KAC9F,CAEA,kBAAW2B,GACT,MArDY,OAsDd,CAEA,mBAAWT,GACT,MAAO,MAAMxF,KAAKxE,MACpB,CAEA,oBAAWkK,GACT,MAAO,IAAI1F,KAAKwF,UAClB,CAEA,gBAAOU,CAAU3K,GACf,MAAO,GAAGA,IAAOyE,KAAK0F,WACxB,ECzEF,MAAMS,EAAcxP,IAClB,IAAIgB,EAAWhB,EAAQwD,aAAa,kBAEpC,IAAKxC,GAAyB,MAAbA,EAAkB,CACjC,IAAIyO,EAAgBzP,EAAQwD,aAAa,QAMzC,IAAKiM,IAAmBA,EAAchF,SAAS,OAASgF,EAAc3E,WAAW,KAC/E,OAAO,KAIL2E,EAAchF,SAAS,OAASgF,EAAc3E,WAAW,OAC3D2E,EAAgB,IAAIA,EAAcrJ,MAAM,KAAK,MAG/CpF,EAAWyO,GAAmC,MAAlBA,EAAwBA,EAAcC,OAAS,IAC7E,CAEA,OAAO1O,EAAWA,EAASoF,MAAM,KAAKuJ,IAAIC,GAAO7O,EAAc6O,IAAMC,KAAK,KAAO,MAG7EC,EAAiB,CACrBtH,KAAIA,CAACxH,EAAUhB,EAAUsC,SAASoB,kBACzB,GAAGqM,UAAUC,QAAQtO,UAAU+H,iBAAiB7H,KAAK5B,EAASgB,IAGvEiP,QAAOA,CAACjP,EAAUhB,EAAUsC,SAASoB,kBAC5BsM,QAAQtO,UAAUa,cAAcX,KAAK5B,EAASgB,GAGvDkP,SAAQA,CAAClQ,EAASgB,IACT,GAAG+O,UAAU/P,EAAQkQ,UAAUhD,OAAOiD,GAASA,EAAMC,QAAQpP,IAGtEqP,QAAQrQ,EAASgB,GACf,MAAMqP,EAAU,GAChB,IAAIC,EAAWtQ,EAAQgD,WAAWF,QAAQ9B,GAE1C,KAAOsP,GACLD,EAAQhL,KAAKiL,GACbA,EAAWA,EAAStN,WAAWF,QAAQ9B,GAGzC,OAAOqP,CACT,EAEAE,KAAKvQ,EAASgB,GACZ,IAAIwP,EAAWxQ,EAAQyQ,uBAEvB,KAAOD,GAAU,CACf,GAAIA,EAASJ,QAAQpP,GACnB,MAAO,CAACwP,GAGVA,EAAWA,EAASC,sBACtB,CAEA,MAAO,EACT,EAEAC,KAAK1Q,EAASgB,GACZ,IAAI0P,EAAO1Q,EAAQ2Q,mBAEnB,KAAOD,GAAM,CACX,GAAIA,EAAKN,QAAQpP,GACf,MAAO,CAAC0P,GAGVA,EAAOA,EAAKC,kBACd,CAEA,MAAO,EACT,EAEAC,kBAAkB5Q,GAChB,MAAM6Q,EAAa,CACjB,IACA,SACA,QACA,WACA,SACA,UACA,aACA,4BACAlB,IAAI3O,GAAY,GAAGA,0BAAiC6O,KAAK,KAE3D,OAAOxG,KAAKb,KAAKqI,EAAY7Q,GAASkN,OAAO4D,IAAO7N,EAAW6N,IAAOtO,EAAUsO,GAClF,EAEAC,uBAAuB/Q,GACrB,MAAMgB,EAAWwO,EAAYxP,GAE7B,OAAIgB,GACK8O,EAAeG,QAAQjP,GAAYA,EAGrC,IACT,EAEAgQ,uBAAuBhR,GACrB,MAAMgB,EAAWwO,EAAYxP,GAE7B,OAAOgB,EAAW8O,EAAeG,QAAQjP,GAAY,IACvD,EAEAiQ,gCAAgCjR,GAC9B,MAAMgB,EAAWwO,EAAYxP,GAE7B,OAAOgB,EAAW8O,EAAetH,KAAKxH,GAAY,EACpD,GC/GIkQ,EAAuBA,CAACC,EAAWC,EAAS,UAChD,MAAMC,EAAa,gBAAgBF,EAAUpC,YACvCnK,EAAOuM,EAAUtM,KAEvB+E,EAAac,GAAGpI,SAAU+O,EAAY,qBAAqBzM,MAAU,SAAU6D,GAK7E,GAJI,CAAC,IAAK,QAAQgC,SAASpB,KAAKiI,UAC9B7I,EAAMmD,iBAGJ3I,EAAWoG,MACb,OAGF,MAAM7C,EAASsJ,EAAekB,uBAAuB3H,OAASA,KAAKvG,QAAQ,IAAI8B,KAC9DuM,EAAU9B,oBAAoB7I,GAGtC4K,IACX,ICXIrC,EAAY,YAEZwC,EAAc,QAAQxC,IACtByC,EAAe,SAASzC,IAQ9B,MAAM0C,UAAchD,EAElB,eAAW5J,GACT,MAhBS,OAiBX,CAGA6M,QAGE,GAFmB9H,EAAasB,QAAQ7B,KAAKsF,SAAU4C,GAExCjG,iBACb,OAGFjC,KAAKsF,SAASvL,UAAUxC,OApBJ,QAsBpB,MAAMuO,EAAa9F,KAAKsF,SAASvL,UAAUC,SAvBvB,QAwBpBgG,KAAK6F,eAAe,IAAM7F,KAAKsI,kBAAmBtI,KAAKsF,SAAUQ,EACnE,CAGAwC,kBACEtI,KAAKsF,SAAS/N,SACdgJ,EAAasB,QAAQ7B,KAAKsF,SAAU6C,GACpCnI,KAAKyF,SACP,CAGA,sBAAO9J,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOJ,EAAMpC,oBAAoBhG,MAEvC,GAAsB,iBAAXsE,EAAX,CAIA,QAAqBmE,IAAjBD,EAAKlE,IAAyBA,EAAO7C,WAAW,MAAmB,gBAAX6C,EAC1D,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,GAAQtE,KANb,CAOF,EACF,EAOF6H,EAAqBO,EAAO,SAM5BjN,EAAmBiN,GCrEnB,MAMMM,EAAuB,4BAO7B,MAAMC,UAAevD,EAEnB,eAAW5J,GACT,MAhBS,QAiBX,CAGAoN,SAEE5I,KAAKsF,SAAShC,aAAa,eAAgBtD,KAAKsF,SAASvL,UAAU6O,OAjB7C,UAkBxB,CAGA,sBAAOjN,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOG,EAAO3C,oBAAoBhG,MAEzB,WAAXsE,GACFkE,EAAKlE,IAET,EACF,EAOF/D,EAAac,GAAGpI,SAlCa,2BAkCmByP,EAAsBtJ,IACpEA,EAAMmD,iBAEN,MAAMsG,EAASzJ,EAAMjC,OAAO1D,QAAQiP,GACvBC,EAAO3C,oBAAoB6C,GAEnCD,WAOPzN,EAAmBwN,GCtDnB,MACMjD,EAAY,YACZoD,EAAmB,aAAapD,IAChCqD,EAAkB,YAAYrD,IAC9BsD,GAAiB,WAAWtD,IAC5BuD,GAAoB,cAAcvD,IAClCwD,GAAkB,YAAYxD,IAM9BxB,GAAU,CACdiF,YAAa,KACbC,aAAc,KACdC,cAAe,MAGXlF,GAAc,CAClBgF,YAAa,kBACbC,aAAc,kBACdC,cAAe,mBAOjB,MAAMC,WAAcrF,EAClBU,YAAYhO,EAAS2N,GACnBe,QACArF,KAAKsF,SAAW3O,EAEXA,GAAY2S,GAAMC,gBAIvBvJ,KAAKuF,QAAUvF,KAAKqE,WAAWC,GAC/BtE,KAAKwJ,QAAU,EACfxJ,KAAKyJ,sBAAwB3I,QAAQlJ,OAAO8R,cAC5C1J,KAAK2J,cACP,CAGA,kBAAWzF,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MArDS,OAsDX,CAGAiK,UACElF,EAAaC,IAAIR,KAAKsF,SAAUI,EAClC,CAGAkE,OAAOxK,GACAY,KAAKyJ,sBAMNzJ,KAAK6J,wBAAwBzK,KAC/BY,KAAKwJ,QAAUpK,EAAM0K,SANrB9J,KAAKwJ,QAAUpK,EAAM2K,QAAQ,GAAGD,OAQpC,CAEAE,KAAK5K,GACCY,KAAK6J,wBAAwBzK,KAC/BY,KAAKwJ,QAAUpK,EAAM0K,QAAU9J,KAAKwJ,SAGtCxJ,KAAKiK,eACLhO,EAAQ+D,KAAKuF,QAAQ4D,YACvB,CAEAe,MAAM9K,GACJY,KAAKwJ,QAAUpK,EAAM2K,SAAW3K,EAAM2K,QAAQ/Q,OAAS,EACrD,EACAoG,EAAM2K,QAAQ,GAAGD,QAAU9J,KAAKwJ,OACpC,CAEAS,eACE,MAAME,EAAYrM,KAAKsM,IAAIpK,KAAKwJ,SAEhC,GAAIW,GAlFgB,GAmFlB,OAGF,MAAME,EAAYF,EAAYnK,KAAKwJ,QAEnCxJ,KAAKwJ,QAAU,EAEVa,GAILpO,EAAQoO,EAAY,EAAIrK,KAAKuF,QAAQ8D,cAAgBrJ,KAAKuF,QAAQ6D,aACpE,CAEAO,cACM3J,KAAKyJ,uBACPlJ,EAAac,GAAGrB,KAAKsF,SAAU2D,GAAmB7J,GAASY,KAAK4J,OAAOxK,IACvEmB,EAAac,GAAGrB,KAAKsF,SAAU4D,GAAiB9J,GAASY,KAAKgK,KAAK5K,IAEnEY,KAAKsF,SAASvL,UAAUuQ,IAvGG,mBAyG3B/J,EAAac,GAAGrB,KAAKsF,SAAUwD,EAAkB1J,GAASY,KAAK4J,OAAOxK,IACtEmB,EAAac,GAAGrB,KAAKsF,SAAUyD,EAAiB3J,GAASY,KAAKkK,MAAM9K,IACpEmB,EAAac,GAAGrB,KAAKsF,SAAU0D,GAAgB5J,GAASY,KAAKgK,KAAK5K,IAEtE,CAEAyK,wBAAwBzK,GACtB,OAAOY,KAAKyJ,wBAjHS,QAiHiBrK,EAAMmL,aAlHrB,UAkHyDnL,EAAMmL,YACxF,CAGA,kBAAOhB,GACL,MAAO,iBAAkBtQ,SAASoB,iBAAmBmQ,UAAUC,eAAiB,CAClF,ECrHF,MAEM/E,GAAY,eACZgF,GAAe,YAEfC,GAAiB,YACjBC,GAAkB,aAGlBC,GAAa,OACbC,GAAa,OACbC,GAAiB,OACjBC,GAAkB,QAElBC,GAAc,QAAQvF,KACtBwF,GAAa,OAAOxF,KACpByF,GAAgB,UAAUzF,KAC1B0F,GAAmB,aAAa1F,KAChC2F,GAAmB,aAAa3F,KAChC4F,GAAmB,YAAY5F,KAC/B6F,GAAsB,OAAO7F,KAAYgF,KACzCc,GAAuB,QAAQ9F,KAAYgF,KAE3Ce,GAAsB,WACtBC,GAAoB,SAOpBC,GAAkB,UAClBC,GAAgB,iBAChBC,GAAuBF,GAAkBC,GAMzCE,GAAmB,CACvBC,CAACpB,IAAiBK,GAClBgB,CAACpB,IAAkBG,IAGf7G,GAAU,CACd+H,SAAU,IACVC,UAAU,EACVC,MAAO,QACPC,MAAM,EACNC,OAAO,EACPC,MAAM,GAGFnI,GAAc,CAClB8H,SAAU,mBACVC,SAAU,UACVC,MAAO,mBACPC,KAAM,mBACNC,MAAO,UACPC,KAAM,WAOR,MAAMC,WAAiBnH,EACrBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAKwM,UAAY,KACjBxM,KAAKyM,eAAiB,KACtBzM,KAAK0M,YAAa,EAClB1M,KAAK2M,aAAe,KACpB3M,KAAK4M,aAAe,KAEpB5M,KAAK6M,mBAAqBpG,EAAeG,QAzCjB,uBAyC8C5G,KAAKsF,UAC3EtF,KAAK8M,qBAED9M,KAAKuF,QAAQ6G,OAASX,IACxBzL,KAAK+M,OAET,CAGA,kBAAW7I,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MA9FS,UA+FX,CAGA6L,OACErH,KAAKgN,OAAOnC,GACd,CAEAoC,mBAIOhU,SAASiU,QAAU/T,EAAU6G,KAAKsF,WACrCtF,KAAKqH,MAET,CAEAH,OACElH,KAAKgN,OAAOlC,GACd,CAEAqB,QACMnM,KAAK0M,YACPjU,EAAqBuH,KAAKsF,UAG5BtF,KAAKmN,gBACP,CAEAJ,QACE/M,KAAKmN,iBACLnN,KAAKoN,kBAELpN,KAAKwM,UAAYa,YAAY,IAAMrN,KAAKiN,kBAAmBjN,KAAKuF,QAAQ0G,SAC1E,CAEAqB,oBACOtN,KAAKuF,QAAQ6G,OAIdpM,KAAK0M,WACPnM,EAAae,IAAItB,KAAKsF,SAAU4F,GAAY,IAAMlL,KAAK+M,SAIzD/M,KAAK+M,QACP,CAEAQ,GAAG3P,GACD,MAAM4P,EAAQxN,KAAKyN,YACnB,GAAI7P,EAAQ4P,EAAMxU,OAAS,GAAK4E,EAAQ,EACtC,OAGF,GAAIoC,KAAK0M,WAEP,YADAnM,EAAae,IAAItB,KAAKsF,SAAU4F,GAAY,IAAMlL,KAAKuN,GAAG3P,IAI5D,MAAM8P,EAAc1N,KAAK2N,cAAc3N,KAAK4N,cAC5C,GAAIF,IAAgB9P,EAClB,OAGF,MAAMiQ,EAAQjQ,EAAQ8P,EAAc7C,GAAaC,GAEjD9K,KAAKgN,OAAOa,EAAOL,EAAM5P,GAC3B,CAEA6H,UACMzF,KAAK4M,cACP5M,KAAK4M,aAAanH,UAGpBJ,MAAMI,SACR,CAGAjB,kBAAkBF,GAEhB,OADAA,EAAOwJ,gBAAkBxJ,EAAO2H,SACzB3H,CACT,CAEAwI,qBACM9M,KAAKuF,QAAQ2G,UACf3L,EAAac,GAAGrB,KAAKsF,SAAU6F,GAAe/L,GAASY,KAAK+N,SAAS3O,IAG5C,UAAvBY,KAAKuF,QAAQ4G,QACf5L,EAAac,GAAGrB,KAAKsF,SAAU8F,GAAkB,IAAMpL,KAAKmM,SAC5D5L,EAAac,GAAGrB,KAAKsF,SAAU+F,GAAkB,IAAMrL,KAAKsN,sBAG1DtN,KAAKuF,QAAQ8G,OAAS/C,GAAMC,eAC9BvJ,KAAKgO,yBAET,CAEAA,0BACE,IAAK,MAAMC,KAAOxH,EAAetH,KAhKX,qBAgKmCa,KAAKsF,UAC5D/E,EAAac,GAAG4M,EAAK3C,GAAkBlM,GAASA,EAAMmD,kBAGxD,MAqBM2L,EAAc,CAClB9E,aAAcA,IAAMpJ,KAAKgN,OAAOhN,KAAKmO,kBAAkBpD,KACvD1B,cAAeA,IAAMrJ,KAAKgN,OAAOhN,KAAKmO,kBAAkBnD,KACxD7B,YAxBkBiF,KACS,UAAvBpO,KAAKuF,QAAQ4G,QAYjBnM,KAAKmM,QACDnM,KAAK2M,cACP0B,aAAarO,KAAK2M,cAGpB3M,KAAK2M,aAAetP,WAAW,IAAM2C,KAAKsN,oBAjNjB,IAiN+DtN,KAAKuF,QAAQ0G,aASvGjM,KAAK4M,aAAe,IAAItD,GAAMtJ,KAAKsF,SAAU4I,EAC/C,CAEAH,SAAS3O,GACP,GAAI,kBAAkB6F,KAAK7F,EAAMjC,OAAO8K,SACtC,OAGF,MAAMoC,EAAYyB,GAAiB1M,EAAMxI,KACrCyT,IACFjL,EAAMmD,iBACNvC,KAAKgN,OAAOhN,KAAKmO,kBAAkB9D,IAEvC,CAEAsD,cAAchX,GACZ,OAAOqJ,KAAKyN,YAAY5P,QAAQlH,EAClC,CAEA2X,2BAA2B1Q,GACzB,IAAKoC,KAAK6M,mBACR,OAGF,MAAM0B,EAAkB9H,EAAeG,QAAQ+E,GAAiB3L,KAAK6M,oBAErE0B,EAAgBxU,UAAUxC,OAAOmU,IACjC6C,EAAgB/K,gBAAgB,gBAEhC,MAAMgL,EAAqB/H,EAAeG,QAAQ,sBAAsBhJ,MAAWoC,KAAK6M,oBAEpF2B,IACFA,EAAmBzU,UAAUuQ,IAAIoB,IACjC8C,EAAmBlL,aAAa,eAAgB,QAEpD,CAEA8J,kBACE,MAAMzW,EAAUqJ,KAAKyM,gBAAkBzM,KAAK4N,aAE5C,IAAKjX,EACH,OAGF,MAAM8X,EAAkB7R,OAAO8R,SAAS/X,EAAQwD,aAAa,oBAAqB,IAElF6F,KAAKuF,QAAQ0G,SAAWwC,GAAmBzO,KAAKuF,QAAQuI,eAC1D,CAEAd,OAAOa,EAAOlX,EAAU,MACtB,GAAIqJ,KAAK0M,WACP,OAGF,MAAMlP,EAAgBwC,KAAK4N,aACrBe,EAASd,IAAUhD,GACnB+D,EAAcjY,GAAW2G,EAAqB0C,KAAKyN,YAAajQ,EAAemR,EAAQ3O,KAAKuF,QAAQ+G,MAE1G,GAAIsC,IAAgBpR,EAClB,OAGF,MAAMqR,EAAmB7O,KAAK2N,cAAciB,GAEtCE,EAAe5I,GACZ3F,EAAasB,QAAQ7B,KAAKsF,SAAUY,EAAW,CACpDpG,cAAe8O,EACfvE,UAAWrK,KAAK+O,kBAAkBlB,GAClCxW,KAAM2I,KAAK2N,cAAcnQ,GACzB+P,GAAIsB,IAMR,GAFmBC,EAAa7D,IAEjBhJ,iBACb,OAGF,IAAKzE,IAAkBoR,EAGrB,OAGF,MAAMI,EAAYlO,QAAQd,KAAKwM,WAC/BxM,KAAKmM,QAELnM,KAAK0M,YAAa,EAElB1M,KAAKsO,2BAA2BO,GAChC7O,KAAKyM,eAAiBmC,EAEtB,MAAMK,EAAuBN,EAnSR,sBADF,oBAqSbO,EAAiBP,EAnSH,qBACA,qBAoSpBC,EAAY7U,UAAUuQ,IAAI4E,GAE1BvU,EAAOiU,GAEPpR,EAAczD,UAAUuQ,IAAI2E,GAC5BL,EAAY7U,UAAUuQ,IAAI2E,GAa1BjP,KAAK6F,eAXoBsJ,KACvBP,EAAY7U,UAAUxC,OAAO0X,EAAsBC,GACnDN,EAAY7U,UAAUuQ,IAAIoB,IAE1BlO,EAAczD,UAAUxC,OAAOmU,GAAmBwD,EAAgBD,GAElEjP,KAAK0M,YAAa,EAElBoC,EAAa5D,KAGuB1N,EAAewC,KAAKoP,eAEtDJ,GACFhP,KAAK+M,OAET,CAEAqC,cACE,OAAOpP,KAAKsF,SAASvL,UAAUC,SAlUV,QAmUvB,CAEA4T,aACE,OAAOnH,EAAeG,QAAQiF,GAAsB7L,KAAKsF,SAC3D,CAEAmI,YACE,OAAOhH,EAAetH,KAAKyM,GAAe5L,KAAKsF,SACjD,CAEA6H,iBACMnN,KAAKwM,YACP6C,cAAcrP,KAAKwM,WACnBxM,KAAKwM,UAAY,KAErB,CAEA2B,kBAAkB9D,GAChB,OAAIpP,IACKoP,IAAcU,GAAiBD,GAAaD,GAG9CR,IAAcU,GAAiBF,GAAaC,EACrD,CAEAiE,kBAAkBlB,GAChB,OAAI5S,IACK4S,IAAU/C,GAAaC,GAAiBC,GAG1C6C,IAAU/C,GAAaE,GAAkBD,EAClD,CAGA,sBAAOpP,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAO+D,GAASvG,oBAAoBhG,KAAMsE,GAEhD,GAAsB,iBAAXA,GAKX,GAAsB,iBAAXA,EAAqB,CAC9B,QAAqBmE,IAAjBD,EAAKlE,IAAyBA,EAAO7C,WAAW,MAAmB,gBAAX6C,EAC1D,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IACP,OAVEkE,EAAK+E,GAAGjJ,EAWZ,EACF,EAOF/D,EAAac,GAAGpI,SAAUuS,GAlXE,sCAkXyC,SAAUpM,GAC7E,MAAMjC,EAASsJ,EAAekB,uBAAuB3H,MAErD,IAAK7C,IAAWA,EAAOpD,UAAUC,SAASyR,IACxC,OAGFrM,EAAMmD,iBAEN,MAAM+M,EAAW/C,GAASvG,oBAAoB7I,GACxCoS,EAAavP,KAAK7F,aAAa,oBAErC,OAAIoV,GACFD,EAAS/B,GAAGgC,QACZD,EAAShC,qBAIyC,SAAhDlK,EAAYY,iBAAiBhE,KAAM,UACrCsP,EAASjI,YACTiI,EAAShC,sBAIXgC,EAASpI,YACToI,EAAShC,oBACX,GAEA/M,EAAac,GAAGzJ,OAAQ2T,GAAqB,KAC3C,MAAMiE,EAAY/I,EAAetH,KA9YR,6BAgZzB,IAAK,MAAMmQ,KAAYE,EACrBjD,GAASvG,oBAAoBsJ,KAQjCnU,EAAmBoR,ICncnB,MAEM7G,GAAY,eAGZ+J,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KACtBiK,GAAa,OAAOjK,KACpBkK,GAAe,SAASlK,KACxB8F,GAAuB,QAAQ9F,cAE/BmK,GAAkB,OAClBC,GAAsB,WACtBC,GAAwB,aAExBC,GAA6B,WAAWF,OAAwBA,KAOhEpH,GAAuB,8BAEvBxE,GAAU,CACd+L,OAAQ,KACRrH,QAAQ,GAGJzE,GAAc,CAClB8L,OAAQ,iBACRrH,OAAQ,WAOV,MAAMsH,WAAiB9K,EACrBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAKmQ,kBAAmB,EACxBnQ,KAAKoQ,cAAgB,GAErB,MAAMC,EAAa5J,EAAetH,KAAKuJ,IAEvC,IAAK,MAAM4H,KAAQD,EAAY,CAC7B,MAAM1Y,EAAW8O,EAAeiB,uBAAuB4I,GACjDC,EAAgB9J,EAAetH,KAAKxH,GACvCkM,OAAO2M,GAAgBA,IAAiBxQ,KAAKsF,UAE/B,OAAb3N,GAAqB4Y,EAAcvX,QACrCgH,KAAKoQ,cAAcpU,KAAKsU,EAE5B,CAEAtQ,KAAKyQ,sBAEAzQ,KAAKuF,QAAQ0K,QAChBjQ,KAAK0Q,0BAA0B1Q,KAAKoQ,cAAepQ,KAAK2Q,YAGtD3Q,KAAKuF,QAAQqD,QACf5I,KAAK4I,QAET,CAGA,kBAAW1E,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MA9ES,UA+EX,CAGAoN,SACM5I,KAAK2Q,WACP3Q,KAAK4Q,OAEL5Q,KAAK6Q,MAET,CAEAA,OACE,GAAI7Q,KAAKmQ,kBAAoBnQ,KAAK2Q,WAChC,OAGF,IAAIG,EAAiB,GASrB,GANI9Q,KAAKuF,QAAQ0K,SACfa,EAAiB9Q,KAAK+Q,uBA9EH,wCA+EhBlN,OAAOlN,GAAWA,IAAYqJ,KAAKsF,UACnCgB,IAAI3P,GAAWuZ,GAASlK,oBAAoBrP,EAAS,CAAEiS,QAAQ,MAGhEkI,EAAe9X,QAAU8X,EAAe,GAAGX,iBAC7C,OAIF,GADmB5P,EAAasB,QAAQ7B,KAAKsF,SAAUmK,IACxCxN,iBACb,OAGF,IAAK,MAAM+O,KAAkBF,EAC3BE,EAAeJ,OAGjB,MAAMK,EAAYjR,KAAKkR,gBAEvBlR,KAAKsF,SAASvL,UAAUxC,OAAOuY,IAC/B9P,KAAKsF,SAASvL,UAAUuQ,IAAIyF,IAE5B/P,KAAKsF,SAAS6L,MAAMF,GAAa,EAEjCjR,KAAK0Q,0BAA0B1Q,KAAKoQ,eAAe,GACnDpQ,KAAKmQ,kBAAmB,EAExB,MAYMiB,EAAa,SADUH,EAAU,GAAG9L,cAAgB8L,EAAUtP,MAAM,KAG1E3B,KAAK6F,eAdYwL,KACfrR,KAAKmQ,kBAAmB,EAExBnQ,KAAKsF,SAASvL,UAAUxC,OAAOwY,IAC/B/P,KAAKsF,SAASvL,UAAUuQ,IAAIwF,GAAqBD,IAEjD7P,KAAKsF,SAAS6L,MAAMF,GAAa,GAEjC1Q,EAAasB,QAAQ7B,KAAKsF,SAAUoK,KAMR1P,KAAKsF,UAAU,GAC7CtF,KAAKsF,SAAS6L,MAAMF,GAAa,GAAGjR,KAAKsF,SAAS8L,MACpD,CAEAR,OACE,GAAI5Q,KAAKmQ,mBAAqBnQ,KAAK2Q,WACjC,OAIF,GADmBpQ,EAAasB,QAAQ7B,KAAKsF,SAAUqK,IACxC1N,iBACb,OAGF,MAAMgP,EAAYjR,KAAKkR,gBAEvBlR,KAAKsF,SAAS6L,MAAMF,GAAa,GAAGjR,KAAKsF,SAASgM,wBAAwBL,OAE1EtW,EAAOqF,KAAKsF,UAEZtF,KAAKsF,SAASvL,UAAUuQ,IAAIyF,IAC5B/P,KAAKsF,SAASvL,UAAUxC,OAAOuY,GAAqBD,IAEpD,IAAK,MAAMhO,KAAW7B,KAAKoQ,cAAe,CACxC,MAAMzZ,EAAU8P,EAAekB,uBAAuB9F,GAElDlL,IAAYqJ,KAAK2Q,SAASha,IAC5BqJ,KAAK0Q,0BAA0B,CAAC7O,IAAU,EAE9C,CAEA7B,KAAKmQ,kBAAmB,EASxBnQ,KAAKsF,SAAS6L,MAAMF,GAAa,GAEjCjR,KAAK6F,eATYwL,KACfrR,KAAKmQ,kBAAmB,EACxBnQ,KAAKsF,SAASvL,UAAUxC,OAAOwY,IAC/B/P,KAAKsF,SAASvL,UAAUuQ,IAAIwF,IAC5BvP,EAAasB,QAAQ7B,KAAKsF,SAAUsK,KAKR5P,KAAKsF,UAAU,EAC/C,CAGAqL,SAASha,EAAUqJ,KAAKsF,UACtB,OAAO3O,EAAQoD,UAAUC,SAAS6V,GACpC,CAEArL,kBAAkBF,GAGhB,OAFAA,EAAOsE,OAAS9H,QAAQwD,EAAOsE,QAC/BtE,EAAO2L,OAASlX,EAAWuL,EAAO2L,QAC3B3L,CACT,CAEA4M,gBACE,OAAOlR,KAAKsF,SAASvL,UAAUC,SAtLL,uBAEhB,QACC,QAoLb,CAEAyW,sBACE,IAAKzQ,KAAKuF,QAAQ0K,OAChB,OAGF,MAAMpJ,EAAW7G,KAAK+Q,uBAAuBrI,IAE7C,IAAK,MAAM/R,KAAWkQ,EAAU,CAC9B,MAAM0K,EAAW9K,EAAekB,uBAAuBhR,GAEnD4a,GACFvR,KAAK0Q,0BAA0B,CAAC/Z,GAAUqJ,KAAK2Q,SAASY,GAE5D,CACF,CAEAR,uBAAuBpZ,GACrB,MAAMkP,EAAWJ,EAAetH,KAAK6Q,GAA4BhQ,KAAKuF,QAAQ0K,QAE9E,OAAOxJ,EAAetH,KAAKxH,EAAUqI,KAAKuF,QAAQ0K,QAAQpM,OAAOlN,IAAYkQ,EAASzF,SAASzK,GACjG,CAEA+Z,0BAA0Bc,EAAcC,GACtC,GAAKD,EAAaxY,OAIlB,IAAK,MAAMrC,KAAW6a,EACpB7a,EAAQoD,UAAU6O,OAvNK,aAuNyB6I,GAChD9a,EAAQ2M,aAAa,gBAAiBmO,EAE1C,CAGA,sBAAO9V,CAAgB2I,GACrB,MAAMiB,EAAU,GAKhB,MAJsB,iBAAXjB,GAAuB,YAAYW,KAAKX,KACjDiB,EAAQqD,QAAS,GAGZ5I,KAAKuI,KAAK,WACf,MAAMC,EAAO0H,GAASlK,oBAAoBhG,KAAMuF,GAEhD,GAAsB,iBAAXjB,EAAqB,CAC9B,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IACP,CACF,EACF,EAOF/D,EAAac,GAAGpI,SAAUuS,GAAsB9C,GAAsB,SAAUtJ,IAEjD,MAAzBA,EAAMjC,OAAO8K,SAAoB7I,EAAMW,gBAAmD,MAAjCX,EAAMW,eAAekI,UAChF7I,EAAMmD,iBAGR,IAAK,MAAM5L,KAAW8P,EAAemB,gCAAgC5H,MACnEkQ,GAASlK,oBAAoBrP,EAAS,CAAEiS,QAAQ,IAASA,QAE7D,GAMAzN,EAAmB+U,ICtSZ,IAAIwB,GAAM,MACNC,GAAS,SACTC,GAAQ,QACRC,GAAO,OACPC,GAAO,OACPC,GAAiB,CAACL,GAAKC,GAAQC,GAAOC,IACtCG,GAAQ,QACRC,GAAM,MACNC,GAAkB,kBAClBC,GAAW,WACXC,GAAS,SACTC,GAAY,YACZC,GAAmCP,GAAeQ,OAAO,SAAUC,EAAKC,GACjF,OAAOD,EAAI9L,OAAO,CAAC+L,EAAY,IAAMT,GAAOS,EAAY,IAAMR,IAChE,EAAG,IACQS,GAA0B,GAAGhM,OAAOqL,GAAgB,CAACD,KAAOS,OAAO,SAAUC,EAAKC,GAC3F,OAAOD,EAAI9L,OAAO,CAAC+L,EAAWA,EAAY,IAAMT,GAAOS,EAAY,IAAMR,IAC3E,EAAG,IAEQU,GAAa,aACbC,GAAO,OACPC,GAAY,YAEZC,GAAa,aACbC,GAAO,OACPC,GAAY,YAEZC,GAAc,cACdC,GAAQ,QACRC,GAAa,aACbC,GAAiB,CAACT,GAAYC,GAAMC,GAAWC,GAAYC,GAAMC,GAAWC,GAAaC,GAAOC,IC9B5F,SAASE,GAAY1c,GAClC,OAAOA,GAAWA,EAAQ2c,UAAY,IAAI9a,cAAgB,IAC5D,CCFe,SAAS+a,GAAUC,GAChC,GAAY,MAARA,EACF,OAAO5b,OAGT,GAAwB,oBAApB4b,EAAKlb,WAAkC,CACzC,IAAImb,EAAgBD,EAAKC,cACzB,OAAOA,GAAgBA,EAAcC,aAAwB9b,MAC/D,CAEA,OAAO4b,CACT,CCTA,SAAS5a,GAAU4a,GAEjB,OAAOA,aADUD,GAAUC,GAAM7M,SACI6M,aAAgB7M,OACvD,CAEA,SAASgN,GAAcH,GAErB,OAAOA,aADUD,GAAUC,GAAMI,aACIJ,aAAgBI,WACvD,CAEA,SAASC,GAAaL,GAEpB,MAA0B,oBAAf/Y,aAKJ+Y,aADUD,GAAUC,GAAM/Y,YACI+Y,aAAgB/Y,WACvD,CCwDA,MAAAqZ,GAAe,CACbvY,KAAM,cACNwY,SAAS,EACTC,MAAO,QACPtY,GA5EF,SAAqBuY,GACnB,IAAIC,EAAQD,EAAKC,MACjB9b,OAAOd,KAAK4c,EAAMC,UAAUC,QAAQ,SAAU7Y,GAC5C,IAAI4V,EAAQ+C,EAAMG,OAAO9Y,IAAS,GAC9BmI,EAAawQ,EAAMxQ,WAAWnI,IAAS,GACvC5E,EAAUud,EAAMC,SAAS5Y,GAExBoY,GAAchd,IAAa0c,GAAY1c,KAO5CyB,OAAOkc,OAAO3d,EAAQwa,MAAOA,GAC7B/Y,OAAOd,KAAKoM,GAAY0Q,QAAQ,SAAU7Y,GACxC,IAAImH,EAAQgB,EAAWnI,IAET,IAAVmH,EACF/L,EAAQ6M,gBAAgBjI,GAExB5E,EAAQ2M,aAAa/H,GAAgB,IAAVmH,EAAiB,GAAKA,EAErD,GACF,EACF,EAoDE6R,OAlDF,SAAgBC,GACd,IAAIN,EAAQM,EAAMN,MACdO,EAAgB,CAClBrC,OAAQ,CACNsC,SAAUR,EAAMS,QAAQC,SACxB/C,KAAM,IACNH,IAAK,IACLmD,OAAQ,KAEVC,MAAO,CACLJ,SAAU,YAEZrC,UAAW,IASb,OAPAja,OAAOkc,OAAOJ,EAAMC,SAAS/B,OAAOjB,MAAOsD,EAAcrC,QACzD8B,EAAMG,OAASI,EAEXP,EAAMC,SAASW,OACjB1c,OAAOkc,OAAOJ,EAAMC,SAASW,MAAM3D,MAAOsD,EAAcK,OAGnD,WACL1c,OAAOd,KAAK4c,EAAMC,UAAUC,QAAQ,SAAU7Y,GAC5C,IAAI5E,EAAUud,EAAMC,SAAS5Y,GACzBmI,EAAawQ,EAAMxQ,WAAWnI,IAAS,GAGvC4V,EAFkB/Y,OAAOd,KAAK4c,EAAMG,OAAOU,eAAexZ,GAAQ2Y,EAAMG,OAAO9Y,GAAQkZ,EAAclZ,IAE7EgX,OAAO,SAAUpB,EAAOtM,GAElD,OADAsM,EAAMtM,GAAY,GACXsM,CACT,EAAG,IAEEwC,GAAchd,IAAa0c,GAAY1c,KAI5CyB,OAAOkc,OAAO3d,EAAQwa,MAAOA,GAC7B/Y,OAAOd,KAAKoM,GAAY0Q,QAAQ,SAAUY,GACxCre,EAAQ6M,gBAAgBwR,EAC1B,GACF,EACF,CACF,EASEC,SAAU,CAAC,kBCjFE,SAASC,GAAiBzC,GACvC,OAAOA,EAAU1V,MAAM,KAAK,EAC9B,CCHO,IAAIgB,GAAMD,KAAKC,IACXC,GAAMF,KAAKE,IACXmX,GAAQrX,KAAKqX,MCFT,SAASC,KACtB,IAAIC,EAAS7K,UAAU8K,cAEvB,OAAc,MAAVD,GAAkBA,EAAOE,QAAUne,MAAMoe,QAAQH,EAAOE,QACnDF,EAAOE,OAAOjP,IAAI,SAAUmP,GACjC,OAAOA,EAAKC,MAAQ,IAAMD,EAAKE,OACjC,GAAGnP,KAAK,KAGHgE,UAAUoL,SACnB,CCTe,SAASC,KACtB,OAAQ,iCAAiC5Q,KAAKmQ,KAChD,CCCe,SAAS9D,GAAsB3a,EAASmf,EAAcC,QAC9C,IAAjBD,IACFA,GAAe,QAGO,IAApBC,IACFA,GAAkB,GAGpB,IAAIC,EAAarf,EAAQ2a,wBACrB2E,EAAS,EACTC,EAAS,EAETJ,GAAgBnC,GAAchd,KAChCsf,EAAStf,EAAQwf,YAAc,GAAIhB,GAAMa,EAAWI,OAASzf,EAAQwf,aAAmB,EACxFD,EAASvf,EAAQiE,aAAe,GAAIua,GAAMa,EAAWK,QAAU1f,EAAQiE,cAAoB,GAG7F,IACI0b,GADO1d,GAAUjC,GAAW4c,GAAU5c,GAAWiB,QAC3B0e,eAEtBC,GAAoBV,MAAsBE,EAC1CS,GAAKR,EAAWnE,MAAQ0E,GAAoBD,EAAiBA,EAAeG,WAAa,IAAMR,EAC/FS,GAAKV,EAAWtE,KAAO6E,GAAoBD,EAAiBA,EAAeK,UAAY,IAAMT,EAC7FE,EAAQJ,EAAWI,MAAQH,EAC3BI,EAASL,EAAWK,OAASH,EACjC,MAAO,CACLE,MAAOA,EACPC,OAAQA,EACR3E,IAAKgF,EACL9E,MAAO4E,EAAIJ,EACXzE,OAAQ+E,EAAIL,EACZxE,KAAM2E,EACNA,EAAGA,EACHE,EAAGA,EAEP,CCrCe,SAASE,GAAcjgB,GACpC,IAAIqf,EAAa1E,GAAsB3a,GAGnCyf,EAAQzf,EAAQwf,YAChBE,EAAS1f,EAAQiE,aAUrB,OARIkD,KAAKsM,IAAI4L,EAAWI,MAAQA,IAAU,IACxCA,EAAQJ,EAAWI,OAGjBtY,KAAKsM,IAAI4L,EAAWK,OAASA,IAAW,IAC1CA,EAASL,EAAWK,QAGf,CACLG,EAAG7f,EAAQ8f,WACXC,EAAG/f,EAAQggB,UACXP,MAAOA,EACPC,OAAQA,EAEZ,CCvBe,SAASrc,GAASiW,EAAQnJ,GACvC,IAAI+P,EAAW/P,EAAMvM,aAAeuM,EAAMvM,cAE1C,GAAI0V,EAAOjW,SAAS8M,GAClB,OAAO,EAEJ,GAAI+P,GAAYhD,GAAagD,GAAW,CACzC,IAAIxP,EAAOP,EAEX,EAAG,CACD,GAAIO,GAAQ4I,EAAO6G,WAAWzP,GAC5B,OAAO,EAITA,EAAOA,EAAK1N,YAAc0N,EAAK0P,IACjC,OAAS1P,EACX,CAGF,OAAO,CACT,CCrBe,SAAS/N,GAAiB3C,GACvC,OAAO4c,GAAU5c,GAAS2C,iBAAiB3C,EAC7C,CCFe,SAASqgB,GAAergB,GACrC,MAAO,CAAC,QAAS,KAAM,MAAMkH,QAAQwV,GAAY1c,KAAa,CAChE,CCFe,SAASsgB,GAAmBtgB,GAEzC,QAASiC,GAAUjC,GAAWA,EAAQ8c,cACtC9c,EAAQsC,WAAarB,OAAOqB,UAAUoB,eACxC,CCFe,SAAS6c,GAAcvgB,GACpC,MAA6B,SAAzB0c,GAAY1c,GACPA,EAMPA,EAAQwgB,cACRxgB,EAAQgD,aACRka,GAAald,GAAWA,EAAQogB,KAAO,OAEvCE,GAAmBtgB,EAGvB,CCVA,SAASygB,GAAoBzgB,GAC3B,OAAKgd,GAAchd,IACoB,UAAvC2C,GAAiB3C,GAAS+d,SAInB/d,EAAQ0gB,aAHN,IAIX,CAwCe,SAASC,GAAgB3gB,GAItC,IAHA,IAAIiB,EAAS2b,GAAU5c,GACnB0gB,EAAeD,GAAoBzgB,GAEhC0gB,GAAgBL,GAAeK,IAA6D,WAA5C/d,GAAiB+d,GAAc3C,UACpF2C,EAAeD,GAAoBC,GAGrC,OAAIA,IAA+C,SAA9BhE,GAAYgE,IAA0D,SAA9BhE,GAAYgE,IAAwE,WAA5C/d,GAAiB+d,GAAc3C,UAC3H9c,EAGFyf,GAhDT,SAA4B1gB,GAC1B,IAAI4gB,EAAY,WAAWtS,KAAKmQ,MAGhC,GAFW,WAAWnQ,KAAKmQ,OAEfzB,GAAchd,IAII,UAFX2C,GAAiB3C,GAEnB+d,SACb,OAAO,KAIX,IAAI8C,EAAcN,GAAcvgB,GAMhC,IAJIkd,GAAa2D,KACfA,EAAcA,EAAYT,MAGrBpD,GAAc6D,IAAgB,CAAC,OAAQ,QAAQ3Z,QAAQwV,GAAYmE,IAAgB,GAAG,CAC3F,IAAIC,EAAMne,GAAiBke,GAI3B,GAAsB,SAAlBC,EAAIC,WAA4C,SAApBD,EAAIE,aAA0C,UAAhBF,EAAIG,UAAgF,IAAzD,CAAC,YAAa,eAAe/Z,QAAQ4Z,EAAII,aAAsBN,GAAgC,WAAnBE,EAAII,YAA2BN,GAAaE,EAAI5T,QAAyB,SAAf4T,EAAI5T,OACjO,OAAO2T,EAEPA,EAAcA,EAAY7d,UAE9B,CAEA,OAAO,IACT,CAgByBme,CAAmBnhB,IAAYiB,CACxD,CCpEe,SAASmgB,GAAyBtF,GAC/C,MAAO,CAAC,MAAO,UAAU5U,QAAQ4U,IAAc,EAAI,IAAM,GAC3D,CCDO,SAASuF,GAAOha,EAAK0E,EAAO3E,GACjC,OAAOka,GAAQja,EAAKka,GAAQxV,EAAO3E,GACrC,CCFe,SAASoa,GAAmBC,GACzC,OAAOhgB,OAAOkc,OAAO,GCDd,CACL5C,IAAK,EACLE,MAAO,EACPD,OAAQ,EACRE,KAAM,GDHuCuG,EACjD,CEHe,SAASC,GAAgB3V,EAAOpL,GAC7C,OAAOA,EAAKib,OAAO,SAAU+F,EAAS1hB,GAEpC,OADA0hB,EAAQ1hB,GAAO8L,EACR4V,CACT,EAAG,GACL,CC4EA,MAAAC,GAAe,CACbhd,KAAM,QACNwY,SAAS,EACTC,MAAO,OACPtY,GApEF,SAAeuY,GACb,IAAIuE,EAEAtE,EAAQD,EAAKC,MACb3Y,EAAO0Y,EAAK1Y,KACZoZ,EAAUV,EAAKU,QACf8D,EAAevE,EAAMC,SAASW,MAC9B4D,EAAgBxE,EAAMyE,cAAcD,cACpCE,EAAgB1D,GAAiBhB,EAAMzB,WACvCoG,EAAOd,GAAyBa,GAEhCE,EADa,CAACjH,GAAMD,IAAO/T,QAAQ+a,IAAkB,EAClC,SAAW,QAElC,GAAKH,GAAiBC,EAAtB,CAIA,IAAIN,EAxBgB,SAAyBW,EAAS7E,GAItD,OAAOiE,GAAsC,iBAH7CY,EAA6B,mBAAZA,EAAyBA,EAAQ3gB,OAAOkc,OAAO,GAAIJ,EAAM8E,MAAO,CAC/EvG,UAAWyB,EAAMzB,aACbsG,GACkDA,EAAUV,GAAgBU,EAAShH,IAC7F,CAmBsBkH,CAAgBtE,EAAQoE,QAAS7E,GACjDgF,EAAYtC,GAAc6B,GAC1BU,EAAmB,MAATN,EAAenH,GAAMG,GAC/BuH,EAAmB,MAATP,EAAelH,GAASC,GAClCyH,EAAUnF,EAAM8E,MAAM3G,UAAUyG,GAAO5E,EAAM8E,MAAM3G,UAAUwG,GAAQH,EAAcG,GAAQ3E,EAAM8E,MAAM5G,OAAO0G,GAC9GQ,EAAYZ,EAAcG,GAAQ3E,EAAM8E,MAAM3G,UAAUwG,GACxDU,EAAoBjC,GAAgBmB,GACpCe,EAAaD,EAA6B,MAATV,EAAeU,EAAkBE,cAAgB,EAAIF,EAAkBG,aAAe,EAAI,EAC3HC,EAAoBN,EAAU,EAAIC,EAAY,EAG9Ctb,EAAMoa,EAAce,GACpBpb,EAAMyb,EAAaN,EAAUJ,GAAOV,EAAcgB,GAClDQ,EAASJ,EAAa,EAAIN,EAAUJ,GAAO,EAAIa,EAC/CE,EAAS7B,GAAOha,EAAK4b,EAAQ7b,GAE7B+b,EAAWjB,EACf3E,EAAMyE,cAAcpd,KAASid,EAAwB,IAA0BsB,GAAYD,EAAQrB,EAAsBuB,aAAeF,EAASD,EAAQpB,EAnBzJ,CAoBF,EAkCEjE,OAhCF,SAAgBC,GACd,IAAIN,EAAQM,EAAMN,MAEd8F,EADUxF,EAAMG,QACWhe,QAC3B8hB,OAAoC,IAArBuB,EAA8B,sBAAwBA,EAErD,MAAhBvB,IAKwB,iBAAjBA,IACTA,EAAevE,EAAMC,SAAS/B,OAAOlZ,cAAcuf,MAOhDze,GAASka,EAAMC,SAAS/B,OAAQqG,KAIrCvE,EAAMC,SAASW,MAAQ2D,EACzB,EASExD,SAAU,CAAC,iBACXgF,iBAAkB,CAAC,oBCxFN,SAASC,GAAazH,GACnC,OAAOA,EAAU1V,MAAM,KAAK,EAC9B,CCOA,IAAIod,GAAa,CACfzI,IAAK,OACLE,MAAO,OACPD,OAAQ,OACRE,KAAM,QAeD,SAASuI,GAAY5F,GAC1B,IAAI6F,EAEAjI,EAASoC,EAAMpC,OACfkI,EAAa9F,EAAM8F,WACnB7H,EAAY+B,EAAM/B,UAClB8H,EAAY/F,EAAM+F,UAClBC,EAAUhG,EAAMgG,QAChB9F,EAAWF,EAAME,SACjB+F,EAAkBjG,EAAMiG,gBACxBC,EAAWlG,EAAMkG,SACjBC,EAAenG,EAAMmG,aACrBC,EAAUpG,EAAMoG,QAChBC,EAAaL,EAAQhE,EACrBA,OAAmB,IAAfqE,EAAwB,EAAIA,EAChCC,EAAaN,EAAQ9D,EACrBA,OAAmB,IAAfoE,EAAwB,EAAIA,EAEhCC,EAAgC,mBAAjBJ,EAA8BA,EAAa,CAC5DnE,EAAGA,EACHE,EAAGA,IACA,CACHF,EAAGA,EACHE,EAAGA,GAGLF,EAAIuE,EAAMvE,EACVE,EAAIqE,EAAMrE,EACV,IAAIsE,EAAOR,EAAQzF,eAAe,KAC9BkG,EAAOT,EAAQzF,eAAe,KAC9BmG,EAAQrJ,GACRsJ,EAAQzJ,GACR0J,EAAMxjB,OAEV,GAAI8iB,EAAU,CACZ,IAAIrD,EAAeC,GAAgBlF,GAC/BiJ,EAAa,eACbC,EAAY,cAEZjE,IAAiB9D,GAAUnB,IAGmB,WAA5C9Y,GAFJ+d,EAAeJ,GAAmB7E,IAECsC,UAAsC,aAAbA,IAC1D2G,EAAa,eACbC,EAAY,gBAOZ7I,IAAcf,KAAQe,IAAcZ,IAAQY,IAAcb,KAAU2I,IAActI,MACpFkJ,EAAQxJ,GAGR+E,IAFckE,GAAWvD,IAAiB+D,GAAOA,EAAI9E,eAAiB8E,EAAI9E,eAAeD,OACzFgB,EAAagE,IACEf,EAAWjE,OAC1BK,GAAK+D,EAAkB,GAAI,GAGzBhI,IAAcZ,KAASY,IAAcf,IAAOe,IAAcd,IAAW4I,IAActI,MACrFiJ,EAAQtJ,GAGR4E,IAFcoE,GAAWvD,IAAiB+D,GAAOA,EAAI9E,eAAiB8E,EAAI9E,eAAeF,MACzFiB,EAAaiE,IACEhB,EAAWlE,MAC1BI,GAAKiE,EAAkB,GAAI,EAE/B,CAEA,IAgBMc,EAhBFC,EAAepjB,OAAOkc,OAAO,CAC/BI,SAAUA,GACTgG,GAAYP,IAEXsB,GAAyB,IAAjBd,EAlFd,SAA2B1G,EAAMmH,GAC/B,IAAI5E,EAAIvC,EAAKuC,EACTE,EAAIzC,EAAKyC,EACTgF,EAAMN,EAAIO,kBAAoB,EAClC,MAAO,CACLnF,EAAGrB,GAAMqB,EAAIkF,GAAOA,GAAO,EAC3BhF,EAAGvB,GAAMuB,EAAIgF,GAAOA,GAAO,EAE/B,CA0EsCE,CAAkB,CACpDpF,EAAGA,EACHE,EAAGA,GACFnD,GAAUnB,IAAW,CACtBoE,EAAGA,EACHE,EAAGA,GAML,OAHAF,EAAIiF,EAAMjF,EACVE,EAAI+E,EAAM/E,EAEN+D,EAGKriB,OAAOkc,OAAO,GAAIkH,IAAeD,EAAiB,IAAmBJ,GAASF,EAAO,IAAM,GAAIM,EAAeL,GAASF,EAAO,IAAM,GAAIO,EAAe7D,WAAa0D,EAAIO,kBAAoB,IAAM,EAAI,aAAenF,EAAI,OAASE,EAAI,MAAQ,eAAiBF,EAAI,OAASE,EAAI,SAAU6E,IAG5RnjB,OAAOkc,OAAO,GAAIkH,IAAenB,EAAkB,IAAoBc,GAASF,EAAOvE,EAAI,KAAO,GAAI2D,EAAgBa,GAASF,EAAOxE,EAAI,KAAO,GAAI6D,EAAgB3C,UAAY,GAAI2C,GAC9L,CA4CA,MAAAwB,GAAe,CACbtgB,KAAM,gBACNwY,SAAS,EACTC,MAAO,cACPtY,GA9CF,SAAuBogB,GACrB,IAAI5H,EAAQ4H,EAAM5H,MACdS,EAAUmH,EAAMnH,QAChBoH,EAAwBpH,EAAQ8F,gBAChCA,OAA4C,IAA1BsB,GAA0CA,EAC5DC,EAAoBrH,EAAQ+F,SAC5BA,OAAiC,IAAtBsB,GAAsCA,EACjDC,EAAwBtH,EAAQgG,aAChCA,OAAyC,IAA1BsB,GAA0CA,EACzDT,EAAe,CACjB/I,UAAWyC,GAAiBhB,EAAMzB,WAClC8H,UAAWL,GAAahG,EAAMzB,WAC9BL,OAAQ8B,EAAMC,SAAS/B,OACvBkI,WAAYpG,EAAM8E,MAAM5G,OACxBqI,gBAAiBA,EACjBG,QAAoC,UAA3B1G,EAAMS,QAAQC,UAGgB,MAArCV,EAAMyE,cAAcD,gBACtBxE,EAAMG,OAAOjC,OAASha,OAAOkc,OAAO,GAAIJ,EAAMG,OAAOjC,OAAQgI,GAAYhiB,OAAOkc,OAAO,GAAIkH,EAAc,CACvGhB,QAAStG,EAAMyE,cAAcD,cAC7BhE,SAAUR,EAAMS,QAAQC,SACxB8F,SAAUA,EACVC,aAAcA,OAIe,MAA7BzG,EAAMyE,cAAc7D,QACtBZ,EAAMG,OAAOS,MAAQ1c,OAAOkc,OAAO,GAAIJ,EAAMG,OAAOS,MAAOsF,GAAYhiB,OAAOkc,OAAO,GAAIkH,EAAc,CACrGhB,QAAStG,EAAMyE,cAAc7D,MAC7BJ,SAAU,WACVgG,UAAU,EACVC,aAAcA,OAIlBzG,EAAMxQ,WAAW0O,OAASha,OAAOkc,OAAO,GAAIJ,EAAMxQ,WAAW0O,OAAQ,CACnE,wBAAyB8B,EAAMzB,WAEnC,EAQEjK,KAAM,ICrKR,IAAI0T,GAAU,CACZA,SAAS,GAsCX,MAAAC,GAAe,CACb5gB,KAAM,iBACNwY,SAAS,EACTC,MAAO,QACPtY,GAAI,WAAe,EACnB6Y,OAxCF,SAAgBN,GACd,IAAIC,EAAQD,EAAKC,MACbrd,EAAWod,EAAKpd,SAChB8d,EAAUV,EAAKU,QACfyH,EAAkBzH,EAAQ0H,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAkB3H,EAAQ4H,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7C1kB,EAAS2b,GAAUW,EAAMC,SAAS/B,QAClCoK,EAAgB,GAAG9V,OAAOwN,EAAMsI,cAAcnK,UAAW6B,EAAMsI,cAAcpK,QAYjF,OAVIiK,GACFG,EAAcpI,QAAQ,SAAUqI,GAC9BA,EAAa1gB,iBAAiB,SAAUlF,EAAS6lB,OAAQR,GAC3D,GAGEK,GACF3kB,EAAOmE,iBAAiB,SAAUlF,EAAS6lB,OAAQR,IAG9C,WACDG,GACFG,EAAcpI,QAAQ,SAAUqI,GAC9BA,EAAarf,oBAAoB,SAAUvG,EAAS6lB,OAAQR,GAC9D,GAGEK,GACF3kB,EAAOwF,oBAAoB,SAAUvG,EAAS6lB,OAAQR,GAE1D,CACF,EASE1T,KAAM,IC/CR,IAAImU,GAAO,CACT9K,KAAM,QACND,MAAO,OACPD,OAAQ,MACRD,IAAK,UAEQ,SAASkL,GAAqBnK,GAC3C,OAAOA,EAAU1a,QAAQ,yBAA0B,SAAU8kB,GAC3D,OAAOF,GAAKE,EACd,EACF,CCVA,IAAIF,GAAO,CACT3K,MAAO,MACPC,IAAK,SAEQ,SAAS6K,GAA8BrK,GACpD,OAAOA,EAAU1a,QAAQ,aAAc,SAAU8kB,GAC/C,OAAOF,GAAKE,EACd,EACF,CCPe,SAASE,GAAgBvJ,GACtC,IAAI4H,EAAM7H,GAAUC,GAGpB,MAAO,CACLwJ,WAHe5B,EAAI6B,YAInBC,UAHc9B,EAAI+B,YAKtB,CCNe,SAASC,GAAoBzmB,GAQ1C,OAAO2a,GAAsB2F,GAAmBtgB,IAAUkb,KAAOkL,GAAgBpmB,GAASqmB,UAC5F,CCXe,SAASK,GAAe1mB,GAErC,IAAI2mB,EAAoBhkB,GAAiB3C,GACrC4mB,EAAWD,EAAkBC,SAC7BC,EAAYF,EAAkBE,UAC9BC,EAAYH,EAAkBG,UAElC,MAAO,6BAA6BxY,KAAKsY,EAAWE,EAAYD,EAClE,CCLe,SAASE,GAAgBlK,GACtC,MAAI,CAAC,OAAQ,OAAQ,aAAa3V,QAAQwV,GAAYG,KAAU,EAEvDA,EAAKC,cAAc1Y,KAGxB4Y,GAAcH,IAAS6J,GAAe7J,GACjCA,EAGFkK,GAAgBxG,GAAc1D,GACvC,CCJe,SAASmK,GAAkBhnB,EAAS4G,GACjD,IAAIqgB,OAES,IAATrgB,IACFA,EAAO,IAGT,IAAIkf,EAAeiB,GAAgB/mB,GAC/BknB,EAASpB,KAAqE,OAAlDmB,EAAwBjnB,EAAQ8c,oBAAyB,EAASmK,EAAsB7iB,MACpHqgB,EAAM7H,GAAUkJ,GAChBtf,EAAS0gB,EAAS,CAACzC,GAAK1U,OAAO0U,EAAI9E,gBAAkB,GAAI+G,GAAeZ,GAAgBA,EAAe,IAAMA,EAC7GqB,EAAcvgB,EAAKmJ,OAAOvJ,GAC9B,OAAO0gB,EAASC,EAChBA,EAAYpX,OAAOiX,GAAkBzG,GAAc/Z,IACrD,CCzBe,SAAS4gB,GAAiBC,GACvC,OAAO5lB,OAAOkc,OAAO,GAAI0J,EAAM,CAC7BnM,KAAMmM,EAAKxH,EACX9E,IAAKsM,EAAKtH,EACV9E,MAAOoM,EAAKxH,EAAIwH,EAAK5H,MACrBzE,OAAQqM,EAAKtH,EAAIsH,EAAK3H,QAE1B,CCqBA,SAAS4H,GAA2BtnB,EAASunB,EAAgBtJ,GAC3D,OAAOsJ,IAAmB/L,GAAW4L,GCzBxB,SAAyBpnB,EAASie,GAC/C,IAAIwG,EAAM7H,GAAU5c,GAChBwnB,EAAOlH,GAAmBtgB,GAC1B2f,EAAiB8E,EAAI9E,eACrBF,EAAQ+H,EAAKzE,YACbrD,EAAS8H,EAAK1E,aACdjD,EAAI,EACJE,EAAI,EAER,GAAIJ,EAAgB,CAClBF,EAAQE,EAAeF,MACvBC,EAASC,EAAeD,OACxB,IAAI+H,EAAiBvI,MAEjBuI,IAAmBA,GAA+B,UAAbxJ,KACvC4B,EAAIF,EAAeG,WACnBC,EAAIJ,EAAeK,UAEvB,CAEA,MAAO,CACLP,MAAOA,EACPC,OAAQA,EACRG,EAAGA,EAAI4G,GAAoBzmB,GAC3B+f,EAAGA,EAEP,CDDwD2H,CAAgB1nB,EAASie,IAAahc,GAAUslB,GAdxG,SAAoCvnB,EAASie,GAC3C,IAAIoJ,EAAO1M,GAAsB3a,GAAS,EAAoB,UAAbie,GASjD,OARAoJ,EAAKtM,IAAMsM,EAAKtM,IAAM/a,EAAQ2nB,UAC9BN,EAAKnM,KAAOmM,EAAKnM,KAAOlb,EAAQ4nB,WAChCP,EAAKrM,OAASqM,EAAKtM,IAAM/a,EAAQ8iB,aACjCuE,EAAKpM,MAAQoM,EAAKnM,KAAOlb,EAAQ+iB,YACjCsE,EAAK5H,MAAQzf,EAAQ+iB,YACrBsE,EAAK3H,OAAS1f,EAAQ8iB,aACtBuE,EAAKxH,EAAIwH,EAAKnM,KACdmM,EAAKtH,EAAIsH,EAAKtM,IACPsM,CACT,CAG0HQ,CAA2BN,EAAgBtJ,GAAYmJ,GEtBlK,SAAyBpnB,GACtC,IAAIinB,EAEAO,EAAOlH,GAAmBtgB,GAC1B8nB,EAAY1B,GAAgBpmB,GAC5BoE,EAA0D,OAAlD6iB,EAAwBjnB,EAAQ8c,oBAAyB,EAASmK,EAAsB7iB,KAChGqb,EAAQrY,GAAIogB,EAAKO,YAAaP,EAAKzE,YAAa3e,EAAOA,EAAK2jB,YAAc,EAAG3jB,EAAOA,EAAK2e,YAAc,GACvGrD,EAAStY,GAAIogB,EAAKQ,aAAcR,EAAK1E,aAAc1e,EAAOA,EAAK4jB,aAAe,EAAG5jB,EAAOA,EAAK0e,aAAe,GAC5GjD,GAAKiI,EAAUzB,WAAaI,GAAoBzmB,GAChD+f,GAAK+H,EAAUvB,UAMnB,MAJiD,QAA7C5jB,GAAiByB,GAAQojB,GAAM9T,YACjCmM,GAAKzY,GAAIogB,EAAKzE,YAAa3e,EAAOA,EAAK2e,YAAc,GAAKtD,GAGrD,CACLA,MAAOA,EACPC,OAAQA,EACRG,EAAGA,EACHE,EAAGA,EAEP,CFCkMkI,CAAgB3H,GAAmBtgB,IACrO,CG1Be,SAASkoB,GAAe5K,GACrC,IAOIuG,EAPAnI,EAAY4B,EAAK5B,UACjB1b,EAAUsd,EAAKtd,QACf8b,EAAYwB,EAAKxB,UACjBmG,EAAgBnG,EAAYyC,GAAiBzC,GAAa,KAC1D8H,EAAY9H,EAAYyH,GAAazH,GAAa,KAClDqM,EAAUzM,EAAUmE,EAAInE,EAAU+D,MAAQ,EAAIzf,EAAQyf,MAAQ,EAC9D2I,EAAU1M,EAAUqE,EAAIrE,EAAUgE,OAAS,EAAI1f,EAAQ0f,OAAS,EAGpE,OAAQuC,GACN,KAAKlH,GACH8I,EAAU,CACRhE,EAAGsI,EACHpI,EAAGrE,EAAUqE,EAAI/f,EAAQ0f,QAE3B,MAEF,KAAK1E,GACH6I,EAAU,CACRhE,EAAGsI,EACHpI,EAAGrE,EAAUqE,EAAIrE,EAAUgE,QAE7B,MAEF,KAAKzE,GACH4I,EAAU,CACRhE,EAAGnE,EAAUmE,EAAInE,EAAU+D,MAC3BM,EAAGqI,GAEL,MAEF,KAAKlN,GACH2I,EAAU,CACRhE,EAAGnE,EAAUmE,EAAI7f,EAAQyf,MACzBM,EAAGqI,GAEL,MAEF,QACEvE,EAAU,CACRhE,EAAGnE,EAAUmE,EACbE,EAAGrE,EAAUqE,GAInB,IAAIsI,EAAWpG,EAAgBb,GAAyBa,GAAiB,KAEzE,GAAgB,MAAZoG,EAAkB,CACpB,IAAIlG,EAAmB,MAAbkG,EAAmB,SAAW,QAExC,OAAQzE,GACN,KAAKvI,GACHwI,EAAQwE,GAAYxE,EAAQwE,IAAa3M,EAAUyG,GAAO,EAAIniB,EAAQmiB,GAAO,GAC7E,MAEF,KAAK7G,GACHuI,EAAQwE,GAAYxE,EAAQwE,IAAa3M,EAAUyG,GAAO,EAAIniB,EAAQmiB,GAAO,GAKnF,CAEA,OAAO0B,CACT,CC3De,SAASyE,GAAe/K,EAAOS,QAC5B,IAAZA,IACFA,EAAU,IAGZ,IAAIuK,EAAWvK,EACXwK,EAAqBD,EAASzM,UAC9BA,OAAmC,IAAvB0M,EAAgCjL,EAAMzB,UAAY0M,EAC9DC,EAAoBF,EAAStK,SAC7BA,OAAiC,IAAtBwK,EAA+BlL,EAAMU,SAAWwK,EAC3DC,EAAoBH,EAASI,SAC7BA,OAAiC,IAAtBD,EAA+BnN,GAAkBmN,EAC5DE,EAAwBL,EAASM,aACjCA,OAAyC,IAA1BD,EAAmCpN,GAAWoN,EAC7DE,EAAwBP,EAASQ,eACjCA,OAA2C,IAA1BD,EAAmCrN,GAASqN,EAC7DE,EAAuBT,EAASU,YAChCA,OAAuC,IAAzBD,GAA0CA,EACxDE,EAAmBX,EAASnG,QAC5BA,OAA+B,IAArB8G,EAA8B,EAAIA,EAC5CzH,EAAgBD,GAAsC,iBAAZY,EAAuBA,EAAUV,GAAgBU,EAAShH,KACpG+N,EAAaJ,IAAmBtN,GAASC,GAAYD,GACrDkI,EAAapG,EAAM8E,MAAM5G,OACzBzb,EAAUud,EAAMC,SAASyL,EAAcE,EAAaJ,GACpDK,EJkBS,SAAyBppB,EAAS2oB,EAAUE,EAAc5K,GACvE,IAAIoL,EAAmC,oBAAbV,EAlB5B,SAA4B3oB,GAC1B,IAAIub,EAAkByL,GAAkBzG,GAAcvgB,IAElDspB,EADoB,CAAC,WAAY,SAASpiB,QAAQvE,GAAiB3C,GAAS+d,WAAa,GACnDf,GAAchd,GAAW2gB,GAAgB3gB,GAAWA,EAE9F,OAAKiC,GAAUqnB,GAKR/N,EAAgBrO,OAAO,SAAUqa,GACtC,OAAOtlB,GAAUslB,IAAmBlkB,GAASkkB,EAAgB+B,IAAmD,SAAhC5M,GAAY6K,EAC9F,GANS,EAOX,CAK6DgC,CAAmBvpB,GAAW,GAAG+P,OAAO4Y,GAC/FpN,EAAkB,GAAGxL,OAAOsZ,EAAqB,CAACR,IAClDW,EAAsBjO,EAAgB,GACtCkO,EAAelO,EAAgBK,OAAO,SAAU8N,EAASnC,GAC3D,IAAIF,EAAOC,GAA2BtnB,EAASunB,EAAgBtJ,GAK/D,OAJAyL,EAAQ3O,IAAM3T,GAAIigB,EAAKtM,IAAK2O,EAAQ3O,KACpC2O,EAAQzO,MAAQ5T,GAAIggB,EAAKpM,MAAOyO,EAAQzO,OACxCyO,EAAQ1O,OAAS3T,GAAIggB,EAAKrM,OAAQ0O,EAAQ1O,QAC1C0O,EAAQxO,KAAO9T,GAAIigB,EAAKnM,KAAMwO,EAAQxO,MAC/BwO,CACT,EAAGpC,GAA2BtnB,EAASwpB,EAAqBvL,IAK5D,OAJAwL,EAAahK,MAAQgK,EAAaxO,MAAQwO,EAAavO,KACvDuO,EAAa/J,OAAS+J,EAAazO,OAASyO,EAAa1O,IACzD0O,EAAa5J,EAAI4J,EAAavO,KAC9BuO,EAAa1J,EAAI0J,EAAa1O,IACvB0O,CACT,CInC2BE,CAAgB1nB,GAAUjC,GAAWA,EAAUA,EAAQ4pB,gBAAkBtJ,GAAmB/C,EAAMC,SAAS/B,QAASkN,EAAUE,EAAc5K,GACjK4L,EAAsBlP,GAAsB4C,EAAMC,SAAS9B,WAC3DqG,EAAgBmG,GAAe,CACjCxM,UAAWmO,EACX7pB,QAAS2jB,EAET7H,UAAWA,IAETgO,EAAmB1C,GAAiB3lB,OAAOkc,OAAO,GAAIgG,EAAY5B,IAClEgI,EAAoBhB,IAAmBtN,GAASqO,EAAmBD,EAGnEG,EAAkB,CACpBjP,IAAKqO,EAAmBrO,IAAMgP,EAAkBhP,IAAM0G,EAAc1G,IACpEC,OAAQ+O,EAAkB/O,OAASoO,EAAmBpO,OAASyG,EAAczG,OAC7EE,KAAMkO,EAAmBlO,KAAO6O,EAAkB7O,KAAOuG,EAAcvG,KACvED,MAAO8O,EAAkB9O,MAAQmO,EAAmBnO,MAAQwG,EAAcxG,OAExEgP,EAAa1M,EAAMyE,cAAckB,OAErC,GAAI6F,IAAmBtN,IAAUwO,EAAY,CAC3C,IAAI/G,EAAS+G,EAAWnO,GACxBra,OAAOd,KAAKqpB,GAAiBvM,QAAQ,SAAUxd,GAC7C,IAAIiqB,EAAW,CAACjP,GAAOD,IAAQ9T,QAAQjH,IAAQ,EAAI,GAAI,EACnDiiB,EAAO,CAACnH,GAAKC,IAAQ9T,QAAQjH,IAAQ,EAAI,IAAM,IACnD+pB,EAAgB/pB,IAAQijB,EAAOhB,GAAQgI,CACzC,EACF,CAEA,OAAOF,CACT,CC5De,SAASG,GAAqB5M,EAAOS,QAClC,IAAZA,IACFA,EAAU,IAGZ,IAAIuK,EAAWvK,EACXlC,EAAYyM,EAASzM,UACrB6M,EAAWJ,EAASI,SACpBE,EAAeN,EAASM,aACxBzG,EAAUmG,EAASnG,QACnBgI,EAAiB7B,EAAS6B,eAC1BC,EAAwB9B,EAAS+B,sBACjCA,OAAkD,IAA1BD,EAAmCE,GAAgBF,EAC3EzG,EAAYL,GAAazH,GACzBC,EAAa6H,EAAYwG,EAAiBzO,GAAsBA,GAAoBzO,OAAO,SAAU4O,GACvG,OAAOyH,GAAazH,KAAe8H,CACrC,GAAKxI,GACDoP,EAAoBzO,EAAW7O,OAAO,SAAU4O,GAClD,OAAOwO,EAAsBpjB,QAAQ4U,IAAc,CACrD,GAEiC,IAA7B0O,EAAkBnoB,SACpBmoB,EAAoBzO,GAItB,IAAI0O,EAAYD,EAAkB5O,OAAO,SAAUC,EAAKC,GAOtD,OANAD,EAAIC,GAAawM,GAAe/K,EAAO,CACrCzB,UAAWA,EACX6M,SAAUA,EACVE,aAAcA,EACdzG,QAASA,IACR7D,GAAiBzC,IACbD,CACT,EAAG,IACH,OAAOpa,OAAOd,KAAK8pB,GAAWC,KAAK,SAAUC,EAAGC,GAC9C,OAAOH,EAAUE,GAAKF,EAAUG,EAClC,EACF,CC+FA,MAAAC,GAAe,CACbjmB,KAAM,OACNwY,SAAS,EACTC,MAAO,OACPtY,GA5HF,SAAcuY,GACZ,IAAIC,EAAQD,EAAKC,MACbS,EAAUV,EAAKU,QACfpZ,EAAO0Y,EAAK1Y,KAEhB,IAAI2Y,EAAMyE,cAAcpd,GAAMkmB,MAA9B,CAoCA,IAhCA,IAAIC,EAAoB/M,EAAQqK,SAC5B2C,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBjN,EAAQkN,QAC3BC,OAAoC,IAArBF,GAAqCA,EACpDG,EAA8BpN,EAAQqN,mBACtCjJ,EAAUpE,EAAQoE,QAClBuG,EAAW3K,EAAQ2K,SACnBE,EAAe7K,EAAQ6K,aACvBI,EAAcjL,EAAQiL,YACtBqC,EAAwBtN,EAAQoM,eAChCA,OAA2C,IAA1BkB,GAA0CA,EAC3DhB,EAAwBtM,EAAQsM,sBAChCiB,EAAqBhO,EAAMS,QAAQlC,UACnCmG,EAAgB1D,GAAiBgN,GAEjCF,EAAqBD,IADHnJ,IAAkBsJ,GACqCnB,EAjC/E,SAAuCtO,GACrC,GAAIyC,GAAiBzC,KAAeX,GAClC,MAAO,GAGT,IAAIqQ,EAAoBvF,GAAqBnK,GAC7C,MAAO,CAACqK,GAA8BrK,GAAY0P,EAAmBrF,GAA8BqF,GACrG,CA0B6IC,CAA8BF,GAA3E,CAACtF,GAAqBsF,KAChHxP,EAAa,CAACwP,GAAoBxb,OAAOsb,GAAoBzP,OAAO,SAAUC,EAAKC,GACrF,OAAOD,EAAI9L,OAAOwO,GAAiBzC,KAAeX,GAAOgP,GAAqB5M,EAAO,CACnFzB,UAAWA,EACX6M,SAAUA,EACVE,aAAcA,EACdzG,QAASA,EACTgI,eAAgBA,EAChBE,sBAAuBA,IACpBxO,EACP,EAAG,IACC4P,EAAgBnO,EAAM8E,MAAM3G,UAC5BiI,EAAapG,EAAM8E,MAAM5G,OACzBkQ,EAAY,IAAI9rB,IAChB+rB,GAAqB,EACrBC,EAAwB9P,EAAW,GAE9B+P,EAAI,EAAGA,EAAI/P,EAAW1Z,OAAQypB,IAAK,CAC1C,IAAIhQ,EAAYC,EAAW+P,GAEvBC,EAAiBxN,GAAiBzC,GAElCkQ,EAAmBzI,GAAazH,KAAeT,GAC/C4Q,EAAa,CAAClR,GAAKC,IAAQ9T,QAAQ6kB,IAAmB,EACtD5J,EAAM8J,EAAa,QAAU,SAC7BrF,EAAW0B,GAAe/K,EAAO,CACnCzB,UAAWA,EACX6M,SAAUA,EACVE,aAAcA,EACdI,YAAaA,EACb7G,QAASA,IAEP8J,EAAoBD,EAAaD,EAAmB/Q,GAAQC,GAAO8Q,EAAmBhR,GAASD,GAE/F2Q,EAAcvJ,GAAOwB,EAAWxB,KAClC+J,EAAoBjG,GAAqBiG,IAG3C,IAAIC,EAAmBlG,GAAqBiG,GACxCE,EAAS,GAUb,GARIpB,GACFoB,EAAO/mB,KAAKuhB,EAASmF,IAAmB,GAGtCZ,GACFiB,EAAO/mB,KAAKuhB,EAASsF,IAAsB,EAAGtF,EAASuF,IAAqB,GAG1EC,EAAOC,MAAM,SAAUC,GACzB,OAAOA,CACT,GAAI,CACFT,EAAwB/P,EACxB8P,GAAqB,EACrB,KACF,CAEAD,EAAU5rB,IAAI+b,EAAWsQ,EAC3B,CAEA,GAAIR,EAqBF,IAnBA,IAEIW,EAAQ,SAAeC,GACzB,IAAIC,EAAmB1Q,EAAWvT,KAAK,SAAUsT,GAC/C,IAAIsQ,EAAST,EAAUtrB,IAAIyb,GAE3B,GAAIsQ,EACF,OAAOA,EAAOphB,MAAM,EAAGwhB,GAAIH,MAAM,SAAUC,GACzC,OAAOA,CACT,EAEJ,GAEA,GAAIG,EAEF,OADAZ,EAAwBY,EACjB,OAEX,EAESD,EAnBYpC,EAAiB,EAAI,EAmBZoC,EAAK,GAGpB,UAFFD,EAAMC,GADmBA,KAOpCjP,EAAMzB,YAAc+P,IACtBtO,EAAMyE,cAAcpd,GAAMkmB,OAAQ,EAClCvN,EAAMzB,UAAY+P,EAClBtO,EAAMmP,OAAQ,EA5GhB,CA8GF,EAQEpJ,iBAAkB,CAAC,UACnBzR,KAAM,CACJiZ,OAAO,IC7IX,SAAS6B,GAAe/F,EAAUS,EAAMuF,GAQtC,YAPyB,IAArBA,IACFA,EAAmB,CACjB/M,EAAG,EACHE,EAAG,IAIA,CACLhF,IAAK6L,EAAS7L,IAAMsM,EAAK3H,OAASkN,EAAiB7M,EACnD9E,MAAO2L,EAAS3L,MAAQoM,EAAK5H,MAAQmN,EAAiB/M,EACtD7E,OAAQ4L,EAAS5L,OAASqM,EAAK3H,OAASkN,EAAiB7M,EACzD7E,KAAM0L,EAAS1L,KAAOmM,EAAK5H,MAAQmN,EAAiB/M,EAExD,CAEA,SAASgN,GAAsBjG,GAC7B,MAAO,CAAC7L,GAAKE,GAAOD,GAAQE,IAAM4R,KAAK,SAAUC,GAC/C,OAAOnG,EAASmG,IAAS,CAC3B,EACF,CA+BA,MAAAC,GAAe,CACbpoB,KAAM,OACNwY,SAAS,EACTC,MAAO,OACPiG,iBAAkB,CAAC,mBACnBve,GAlCF,SAAcuY,GACZ,IAAIC,EAAQD,EAAKC,MACb3Y,EAAO0Y,EAAK1Y,KACZ8mB,EAAgBnO,EAAM8E,MAAM3G,UAC5BiI,EAAapG,EAAM8E,MAAM5G,OACzBmR,EAAmBrP,EAAMyE,cAAciL,gBACvCC,EAAoB5E,GAAe/K,EAAO,CAC5CwL,eAAgB,cAEdoE,EAAoB7E,GAAe/K,EAAO,CAC5C0L,aAAa,IAEXmE,EAA2BT,GAAeO,EAAmBxB,GAC7D2B,EAAsBV,GAAeQ,EAAmBxJ,EAAYiJ,GACpEU,EAAoBT,GAAsBO,GAC1CG,EAAmBV,GAAsBQ,GAC7C9P,EAAMyE,cAAcpd,GAAQ,CAC1BwoB,yBAA0BA,EAC1BC,oBAAqBA,EACrBC,kBAAmBA,EACnBC,iBAAkBA,GAEpBhQ,EAAMxQ,WAAW0O,OAASha,OAAOkc,OAAO,GAAIJ,EAAMxQ,WAAW0O,OAAQ,CACnE,+BAAgC6R,EAChC,sBAAuBC,GAE3B,GCJAC,GAAe,CACb5oB,KAAM,SACNwY,SAAS,EACTC,MAAO,OACPiB,SAAU,CAAC,iBACXvZ,GA5BF,SAAgB8Y,GACd,IAAIN,EAAQM,EAAMN,MACdS,EAAUH,EAAMG,QAChBpZ,EAAOiZ,EAAMjZ,KACb6oB,EAAkBzP,EAAQkF,OAC1BA,OAA6B,IAApBuK,EAA6B,CAAC,EAAG,GAAKA,EAC/C5b,EAAOkK,GAAWH,OAAO,SAAUC,EAAKC,GAE1C,OADAD,EAAIC,GA5BD,SAAiCA,EAAWuG,EAAOa,GACxD,IAAIjB,EAAgB1D,GAAiBzC,GACjC4R,EAAiB,CAACxS,GAAMH,IAAK7T,QAAQ+a,IAAkB,GAAI,EAAK,EAEhE3E,EAAyB,mBAAX4F,EAAwBA,EAAOzhB,OAAOkc,OAAO,GAAI0E,EAAO,CACxEvG,UAAWA,KACPoH,EACFyK,EAAWrQ,EAAK,GAChBsQ,EAAWtQ,EAAK,GAIpB,OAFAqQ,EAAWA,GAAY,EACvBC,GAAYA,GAAY,GAAKF,EACtB,CAACxS,GAAMD,IAAO/T,QAAQ+a,IAAkB,EAAI,CACjDpC,EAAG+N,EACH7N,EAAG4N,GACD,CACF9N,EAAG8N,EACH5N,EAAG6N,EAEP,CASqBC,CAAwB/R,EAAWyB,EAAM8E,MAAOa,GAC1DrH,CACT,EAAG,IACCiS,EAAwBjc,EAAK0L,EAAMzB,WACnC+D,EAAIiO,EAAsBjO,EAC1BE,EAAI+N,EAAsB/N,EAEW,MAArCxC,EAAMyE,cAAcD,gBACtBxE,EAAMyE,cAAcD,cAAclC,GAAKA,EACvCtC,EAAMyE,cAAcD,cAAchC,GAAKA,GAGzCxC,EAAMyE,cAAcpd,GAAQiN,CAC9B,GC1BAkc,GAAe,CACbnpB,KAAM,gBACNwY,SAAS,EACTC,MAAO,OACPtY,GApBF,SAAuBuY,GACrB,IAAIC,EAAQD,EAAKC,MACb3Y,EAAO0Y,EAAK1Y,KAKhB2Y,EAAMyE,cAAcpd,GAAQsjB,GAAe,CACzCxM,UAAW6B,EAAM8E,MAAM3G,UACvB1b,QAASud,EAAM8E,MAAM5G,OAErBK,UAAWyB,EAAMzB,WAErB,EAQEjK,KAAM,ICgHRmc,GAAe,CACbppB,KAAM,kBACNwY,SAAS,EACTC,MAAO,OACPtY,GA/HF,SAAyBuY,GACvB,IAAIC,EAAQD,EAAKC,MACbS,EAAUV,EAAKU,QACfpZ,EAAO0Y,EAAK1Y,KACZmmB,EAAoB/M,EAAQqK,SAC5B2C,OAAsC,IAAtBD,GAAsCA,EACtDE,EAAmBjN,EAAQkN,QAC3BC,OAAoC,IAArBF,GAAsCA,EACrDtC,EAAW3K,EAAQ2K,SACnBE,EAAe7K,EAAQ6K,aACvBI,EAAcjL,EAAQiL,YACtB7G,EAAUpE,EAAQoE,QAClB6L,EAAkBjQ,EAAQkQ,OAC1BA,OAA6B,IAApBD,GAAoCA,EAC7CE,EAAwBnQ,EAAQoQ,aAChCA,OAAyC,IAA1BD,EAAmC,EAAIA,EACtDvH,EAAW0B,GAAe/K,EAAO,CACnCoL,SAAUA,EACVE,aAAcA,EACdzG,QAASA,EACT6G,YAAaA,IAEXhH,EAAgB1D,GAAiBhB,EAAMzB,WACvC8H,EAAYL,GAAahG,EAAMzB,WAC/BuS,GAAmBzK,EACnByE,EAAWjH,GAAyBa,GACpCiJ,ECrCY,MDqCS7C,ECrCH,IAAM,IDsCxBtG,EAAgBxE,EAAMyE,cAAcD,cACpC2J,EAAgBnO,EAAM8E,MAAM3G,UAC5BiI,EAAapG,EAAM8E,MAAM5G,OACzB6S,EAA4C,mBAAjBF,EAA8BA,EAAa3sB,OAAOkc,OAAO,GAAIJ,EAAM8E,MAAO,CACvGvG,UAAWyB,EAAMzB,aACbsS,EACFG,EAA2D,iBAAtBD,EAAiC,CACxEjG,SAAUiG,EACVpD,QAASoD,GACP7sB,OAAOkc,OAAO,CAChB0K,SAAU,EACV6C,QAAS,GACRoD,GACCE,EAAsBjR,EAAMyE,cAAckB,OAAS3F,EAAMyE,cAAckB,OAAO3F,EAAMzB,WAAa,KACjGjK,EAAO,CACTgO,EAAG,EACHE,EAAG,GAGL,GAAKgC,EAAL,CAIA,GAAIiJ,EAAe,CACjB,IAAIyD,EAEAC,EAAwB,MAAbrG,EAAmBtN,GAAMG,GACpCyT,EAAuB,MAAbtG,EAAmBrN,GAASC,GACtCkH,EAAmB,MAAbkG,EAAmB,SAAW,QACpCnF,EAASnB,EAAcsG,GACvBhhB,EAAM6b,EAAS0D,EAAS8H,GACxBtnB,EAAM8b,EAAS0D,EAAS+H,GACxBC,EAAWV,GAAUvK,EAAWxB,GAAO,EAAI,EAC3C0M,EAASjL,IAAcvI,GAAQqQ,EAAcvJ,GAAOwB,EAAWxB,GAC/D2M,EAASlL,IAAcvI,IAASsI,EAAWxB,IAAQuJ,EAAcvJ,GAGjEL,EAAevE,EAAMC,SAASW,MAC9BoE,EAAY2L,GAAUpM,EAAe7B,GAAc6B,GAAgB,CACrErC,MAAO,EACPC,OAAQ,GAENqP,EAAqBxR,EAAMyE,cAAc,oBAAsBzE,EAAMyE,cAAc,oBAAoBI,QxBhFtG,CACLrH,IAAK,EACLE,MAAO,EACPD,OAAQ,EACRE,KAAM,GwB6EF8T,EAAkBD,EAAmBL,GACrCO,EAAkBF,EAAmBJ,GAMrCO,EAAW7N,GAAO,EAAGqK,EAAcvJ,GAAMI,EAAUJ,IACnDgN,EAAYd,EAAkB3C,EAAcvJ,GAAO,EAAIyM,EAAWM,EAAWF,EAAkBT,EAA4BlG,SAAWwG,EAASK,EAAWF,EAAkBT,EAA4BlG,SACxM+G,EAAYf,GAAmB3C,EAAcvJ,GAAO,EAAIyM,EAAWM,EAAWD,EAAkBV,EAA4BlG,SAAWyG,EAASI,EAAWD,EAAkBV,EAA4BlG,SACzMzF,EAAoBrF,EAAMC,SAASW,OAASwC,GAAgBpD,EAAMC,SAASW,OAC3EkR,EAAezM,EAAiC,MAAbyF,EAAmBzF,EAAkB+E,WAAa,EAAI/E,EAAkBgF,YAAc,EAAI,EAC7H0H,EAAwH,OAAjGb,EAA+C,MAAvBD,OAA8B,EAASA,EAAoBnG,IAAqBoG,EAAwB,EAEvJc,EAAYrM,EAASkM,EAAYE,EACjCE,EAAkBnO,GAAO6M,EAAS3M,GAAQla,EAF9B6b,EAASiM,EAAYG,EAAsBD,GAEKhoB,EAAK6b,EAAQgL,EAAS5M,GAAQla,EAAKmoB,GAAanoB,GAChH2a,EAAcsG,GAAYmH,EAC1B3d,EAAKwW,GAAYmH,EAAkBtM,CACrC,CAEA,GAAIiI,EAAc,CAChB,IAAIsE,EAEAC,EAAyB,MAAbrH,EAAmBtN,GAAMG,GAErCyU,GAAwB,MAAbtH,EAAmBrN,GAASC,GAEvC2U,GAAU7N,EAAcmJ,GAExB2E,GAAmB,MAAZ3E,EAAkB,SAAW,QAEpC4E,GAAOF,GAAUhJ,EAAS8I,GAE1BK,GAAOH,GAAUhJ,EAAS+I,IAE1BK,IAAsD,IAAvC,CAACjV,GAAKG,IAAMhU,QAAQ+a,GAEnCgO,GAAyH,OAAjGR,EAAgD,MAAvBjB,OAA8B,EAASA,EAAoBtD,IAAoBuE,EAAyB,EAEzJS,GAAaF,GAAeF,GAAOF,GAAUlE,EAAcmE,IAAQlM,EAAWkM,IAAQI,GAAuB1B,EAA4BrD,QAEzIiF,GAAaH,GAAeJ,GAAUlE,EAAcmE,IAAQlM,EAAWkM,IAAQI,GAAuB1B,EAA4BrD,QAAU6E,GAE5IK,GAAmBlC,GAAU8B,G1BzH9B,SAAwB3oB,EAAK0E,EAAO3E,GACzC,IAAIipB,EAAIhP,GAAOha,EAAK0E,EAAO3E,GAC3B,OAAOipB,EAAIjpB,EAAMA,EAAMipB,CACzB,C0BsHoDC,CAAeJ,GAAYN,GAASO,IAAc9O,GAAO6M,EAASgC,GAAaJ,GAAMF,GAAS1B,EAASiC,GAAaJ,IAEpKhO,EAAcmJ,GAAWkF,GACzBve,EAAKqZ,GAAWkF,GAAmBR,EACrC,CAEArS,EAAMyE,cAAcpd,GAAQiN,CAvE5B,CAwEF,EAQEyR,iBAAkB,CAAC,WE1HN,SAASiN,GAAiBC,EAAyB9P,EAAcuD,QAC9D,IAAZA,IACFA,GAAU,GAGZ,ICnBoCpH,ECJO7c,EFuBvCywB,EAA0BzT,GAAc0D,GACxCgQ,EAAuB1T,GAAc0D,IAf3C,SAAyB1gB,GACvB,IAAIqnB,EAAOrnB,EAAQ2a,wBACf2E,EAASd,GAAM6I,EAAK5H,OAASzf,EAAQwf,aAAe,EACpDD,EAASf,GAAM6I,EAAK3H,QAAU1f,EAAQiE,cAAgB,EAC1D,OAAkB,IAAXqb,GAA2B,IAAXC,CACzB,CAU4DoR,CAAgBjQ,GACtEhd,EAAkB4c,GAAmBI,GACrC2G,EAAO1M,GAAsB6V,EAAyBE,EAAsBzM,GAC5EyB,EAAS,CACXW,WAAY,EACZE,UAAW,GAET1C,EAAU,CACZhE,EAAG,EACHE,EAAG,GAkBL,OAfI0Q,IAA4BA,IAA4BxM,MACxB,SAA9BvH,GAAYgE,IAChBgG,GAAehjB,MACbgiB,GCnCgC7I,EDmCT6D,KClCd9D,GAAUC,IAAUG,GAAcH,GCJxC,CACLwJ,YAFyCrmB,EDQb6c,GCNRwJ,WACpBE,UAAWvmB,EAAQumB,WDGZH,GAAgBvJ,IDoCnBG,GAAc0D,KAChBmD,EAAUlJ,GAAsB+F,GAAc,IACtCb,GAAKa,EAAakH,WAC1B/D,EAAQ9D,GAAKW,EAAaiH,WACjBjkB,IACTmgB,EAAQhE,EAAI4G,GAAoB/iB,KAI7B,CACLmc,EAAGwH,EAAKnM,KAAOwK,EAAOW,WAAaxC,EAAQhE,EAC3CE,EAAGsH,EAAKtM,IAAM2K,EAAOa,UAAY1C,EAAQ9D,EACzCN,MAAO4H,EAAK5H,MACZC,OAAQ2H,EAAK3H,OAEjB,CGvDA,SAASxI,GAAM0Z,GACb,IAAIjhB,EAAM,IAAI9P,IACVgxB,EAAU,IAAI9oB,IACd+oB,EAAS,GAKb,SAASpG,EAAKqG,GACZF,EAAQld,IAAIod,EAASnsB,MACN,GAAGmL,OAAOghB,EAASzS,UAAY,GAAIyS,EAASzN,kBAAoB,IACtE7F,QAAQ,SAAUuT,GACzB,IAAKH,EAAQ1wB,IAAI6wB,GAAM,CACrB,IAAIC,EAActhB,EAAItP,IAAI2wB,GAEtBC,GACFvG,EAAKuG,EAET,CACF,GACAH,EAAOzrB,KAAK0rB,EACd,CAQA,OAzBAH,EAAUnT,QAAQ,SAAUsT,GAC1BphB,EAAI5P,IAAIgxB,EAASnsB,KAAMmsB,EACzB,GAiBAH,EAAUnT,QAAQ,SAAUsT,GACrBF,EAAQ1wB,IAAI4wB,EAASnsB,OAExB8lB,EAAKqG,EAET,GACOD,CACT,CCvBA,IAAII,GAAkB,CACpBpV,UAAW,SACX8U,UAAW,GACX3S,SAAU,YAGZ,SAASkT,KACP,IAAK,IAAItB,EAAOuB,UAAU/uB,OAAQmD,EAAO,IAAI/E,MAAMovB,GAAOwB,EAAO,EAAGA,EAAOxB,EAAMwB,IAC/E7rB,EAAK6rB,GAAQD,UAAUC,GAGzB,OAAQ7rB,EAAKsnB,KAAK,SAAU9sB,GAC1B,QAASA,GAAoD,mBAAlCA,EAAQ2a,sBACrC,EACF,CAEO,SAAS2W,GAAgBC,QACL,IAArBA,IACFA,EAAmB,IAGrB,IAAIC,EAAoBD,EACpBE,EAAwBD,EAAkBE,iBAC1CA,OAA6C,IAA1BD,EAAmC,GAAKA,EAC3DE,EAAyBH,EAAkBI,eAC3CA,OAA4C,IAA3BD,EAAoCT,GAAkBS,EAC3E,OAAO,SAAsBjW,EAAWD,EAAQuC,QAC9B,IAAZA,IACFA,EAAU4T,GAGZ,ICxC6B7sB,EAC3B8sB,EDuCEtU,EAAQ,CACVzB,UAAW,SACXgW,iBAAkB,GAClB9T,QAASvc,OAAOkc,OAAO,GAAIuT,GAAiBU,GAC5C5P,cAAe,GACfxE,SAAU,CACR9B,UAAWA,EACXD,OAAQA,GAEV1O,WAAY,GACZ2Q,OAAQ,IAENqU,EAAmB,GACnBC,GAAc,EACd9xB,EAAW,CACbqd,MAAOA,EACP0U,WAAY,SAAoBC,GAC9B,IAAIlU,EAAsC,mBAArBkU,EAAkCA,EAAiB3U,EAAMS,SAAWkU,EACzFC,IACA5U,EAAMS,QAAUvc,OAAOkc,OAAO,GAAIiU,EAAgBrU,EAAMS,QAASA,GACjET,EAAMsI,cAAgB,CACpBnK,UAAWzZ,GAAUyZ,GAAasL,GAAkBtL,GAAaA,EAAUkO,eAAiB5C,GAAkBtL,EAAUkO,gBAAkB,GAC1InO,OAAQuL,GAAkBvL,IAI5B,IElE4BmV,EAC9BwB,EFiEMN,EDhCG,SAAwBlB,GAErC,IAAIkB,EAAmB5a,GAAM0Z,GAE7B,OAAOnU,GAAeb,OAAO,SAAUC,EAAKwB,GAC1C,OAAOxB,EAAI9L,OAAO+hB,EAAiB5kB,OAAO,SAAU6jB,GAClD,OAAOA,EAAS1T,QAAUA,CAC5B,GACF,EAAG,GACL,CCuB+BgV,EElEKzB,EFkEsB,GAAG7gB,OAAO2hB,EAAkBnU,EAAMS,QAAQ4S,WEjE9FwB,EAASxB,EAAUhV,OAAO,SAAUwW,EAAQE,GAC9C,IAAIC,EAAWH,EAAOE,EAAQ1tB,MAK9B,OAJAwtB,EAAOE,EAAQ1tB,MAAQ2tB,EAAW9wB,OAAOkc,OAAO,GAAI4U,EAAUD,EAAS,CACrEtU,QAASvc,OAAOkc,OAAO,GAAI4U,EAASvU,QAASsU,EAAQtU,SACrDnM,KAAMpQ,OAAOkc,OAAO,GAAI4U,EAAS1gB,KAAMygB,EAAQzgB,QAC5CygB,EACEF,CACT,EAAG,IAEI3wB,OAAOd,KAAKyxB,GAAQziB,IAAI,SAAU1P,GACvC,OAAOmyB,EAAOnyB,EAChB,KF4DM,OAJAsd,EAAMuU,iBAAmBA,EAAiB5kB,OAAO,SAAUslB,GACzD,OAAOA,EAAEpV,OACX,GA+FFG,EAAMuU,iBAAiBrU,QAAQ,SAAUH,GACvC,IAAI1Y,EAAO0Y,EAAK1Y,KACZ6tB,EAAenV,EAAKU,QACpBA,OAA2B,IAAjByU,EAA0B,GAAKA,EACzC7U,EAASN,EAAKM,OAElB,GAAsB,mBAAXA,EAAuB,CAChC,IAAI8U,EAAY9U,EAAO,CACrBL,MAAOA,EACP3Y,KAAMA,EACN1E,SAAUA,EACV8d,QAASA,IAKX+T,EAAiB1sB,KAAKqtB,GAFT,WAAmB,EAGlC,CACF,GA/GSxyB,EAAS6lB,QAClB,EAMA4M,YAAa,WACX,IAAIX,EAAJ,CAIA,IAAIY,EAAkBrV,EAAMC,SACxB9B,EAAYkX,EAAgBlX,UAC5BD,EAASmX,EAAgBnX,OAG7B,GAAK0V,GAAiBzV,EAAWD,GAAjC,CAKA8B,EAAM8E,MAAQ,CACZ3G,UAAW6U,GAAiB7U,EAAWiF,GAAgBlF,GAAoC,UAA3B8B,EAAMS,QAAQC,UAC9ExC,OAAQwE,GAAcxE,IAOxB8B,EAAMmP,OAAQ,EACdnP,EAAMzB,UAAYyB,EAAMS,QAAQlC,UAKhCyB,EAAMuU,iBAAiBrU,QAAQ,SAAUsT,GACvC,OAAOxT,EAAMyE,cAAc+O,EAASnsB,MAAQnD,OAAOkc,OAAO,GAAIoT,EAASlf,KACzE,GAEA,IAAK,IAAI5K,EAAQ,EAAGA,EAAQsW,EAAMuU,iBAAiBzvB,OAAQ4E,IACzD,IAAoB,IAAhBsW,EAAMmP,MAAV,CAMA,IAAImG,EAAwBtV,EAAMuU,iBAAiB7qB,GAC/ClC,EAAK8tB,EAAsB9tB,GAC3B+tB,EAAyBD,EAAsB7U,QAC/CuK,OAAsC,IAA3BuK,EAAoC,GAAKA,EACpDluB,EAAOiuB,EAAsBjuB,KAEf,mBAAPG,IACTwY,EAAQxY,EAAG,CACTwY,MAAOA,EACPS,QAASuK,EACT3jB,KAAMA,EACN1E,SAAUA,KACNqd,EAdR,MAHEA,EAAMmP,OAAQ,EACdzlB,GAAQ,CAzBZ,CATA,CAqDF,EAGA8e,QC1I2BhhB,ED0IV,WACf,OAAO,IAAIguB,QAAQ,SAAUC,GAC3B9yB,EAASyyB,cACTK,EAAQzV,EACV,EACF,EC7IG,WAUL,OATKsU,IACHA,EAAU,IAAIkB,QAAQ,SAAUC,GAC9BD,QAAQC,UAAUC,KAAK,WACrBpB,OAAU/f,EACVkhB,EAAQjuB,IACV,EACF,IAGK8sB,CACT,GDmIIqB,QAAS,WACPf,IACAH,GAAc,CAChB,GAGF,IAAKb,GAAiBzV,EAAWD,GAC/B,OAAOvb,EAmCT,SAASiyB,IACPJ,EAAiBtU,QAAQ,SAAU1Y,GACjC,OAAOA,GACT,GACAgtB,EAAmB,EACrB,CAEA,OAvCA7xB,EAAS+xB,WAAWjU,GAASiV,KAAK,SAAU1V,IACrCyU,GAAehU,EAAQmV,eAC1BnV,EAAQmV,cAAc5V,EAE1B,GAmCOrd,CACT,CACF,CACO,IAAIkzB,GAA4B9B,KG9LnC8B,GAA4B9B,GAAgB,CAC9CI,iBAFqB,CAAClM,GAAgBzD,GAAesR,GAAeC,MCMlEF,GAA4B9B,GAAgB,CAC9CI,iBAFqB,CAAClM,GAAgBzD,GAAesR,GAAeC,GAAapQ,GAAQqQ,GAAMtG,GAAiB9O,GAAOlE,M,+lBCkBnHpV,GAAO,WAEPkK,GAAY,eACZgF,GAAe,YAIfyf,GAAe,UACfC,GAAiB,YAGjBza,GAAa,OAAOjK,KACpBkK,GAAe,SAASlK,KACxB+J,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KACtB8F,GAAuB,QAAQ9F,KAAYgF,KAC3C2f,GAAyB,UAAU3kB,KAAYgF,KAC/C4f,GAAuB,QAAQ5kB,KAAYgF,KAE3CmF,GAAkB,OAOlBnH,GAAuB,4DACvB6hB,GAA6B,GAAG7hB,MAAwBmH,KACxD2a,GAAgB,iBAKhBC,GAAgBxvB,IAAU,UAAY,YACtCyvB,GAAmBzvB,IAAU,YAAc,UAC3C0vB,GAAmB1vB,IAAU,aAAe,eAC5C2vB,GAAsB3vB,IAAU,eAAiB,aACjD4vB,GAAkB5vB,IAAU,aAAe,cAC3C6vB,GAAiB7vB,IAAU,cAAgB,aAI3CiJ,GAAU,CACd6mB,WAAW,EACXzL,SAAU,kBACV0L,QAAS,UACTnR,OAAQ,CAAC,EAAG,GACZoR,aAAc,KACd5Y,UAAW,UAGPlO,GAAc,CAClB4mB,UAAW,mBACXzL,SAAU,mBACV0L,QAAS,SACTnR,OAAQ,0BACRoR,aAAc,yBACd5Y,UAAW,2BAOb,MAAM6Y,WAAiB9lB,EACrBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAKmrB,QAAU,KACfnrB,KAAKorB,QAAUprB,KAAKsF,SAAS3L,WAE7BqG,KAAKqrB,MAAQ5kB,EAAeY,KAAKrH,KAAKsF,SAAUklB,IAAe,IAC7D/jB,EAAeS,KAAKlH,KAAKsF,SAAUklB,IAAe,IAClD/jB,EAAeG,QAAQ4jB,GAAexqB,KAAKorB,SAC7CprB,KAAKsrB,UAAYtrB,KAAKurB,eACxB,CAGA,kBAAWrnB,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,OAAOA,EACT,CAGAoN,SACE,OAAO5I,KAAK2Q,WAAa3Q,KAAK4Q,OAAS5Q,KAAK6Q,MAC9C,CAEAA,OACE,GAAIjX,EAAWoG,KAAKsF,WAAatF,KAAK2Q,WACpC,OAGF,MAAM7Q,EAAgB,CACpBA,cAAeE,KAAKsF,UAKtB,IAFkB/E,EAAasB,QAAQ7B,KAAKsF,SAAUmK,GAAY3P,GAEpDmC,iBAAd,CAUA,GANAjC,KAAKwrB,gBAMD,iBAAkBvyB,SAASoB,kBAAoB2F,KAAKorB,QAAQ3xB,QAtFxC,eAuFtB,IAAK,MAAM9C,IAAW,GAAG+P,UAAUzN,SAAS8B,KAAK8L,UAC/CtG,EAAac,GAAG1K,EAAS,YAAa+D,GAI1CsF,KAAKsF,SAASmmB,QACdzrB,KAAKsF,SAAShC,aAAa,iBAAiB,GAE5CtD,KAAKqrB,MAAMtxB,UAAUuQ,IAAIuF,IACzB7P,KAAKsF,SAASvL,UAAUuQ,IAAIuF,IAC5BtP,EAAasB,QAAQ7B,KAAKsF,SAAUoK,GAAa5P,EAnBjD,CAoBF,CAEA8Q,OACE,GAAIhX,EAAWoG,KAAKsF,YAActF,KAAK2Q,WACrC,OAGF,MAAM7Q,EAAgB,CACpBA,cAAeE,KAAKsF,UAGtBtF,KAAK0rB,cAAc5rB,EACrB,CAEA2F,UACMzF,KAAKmrB,SACPnrB,KAAKmrB,QAAQtB,UAGfxkB,MAAMI,SACR,CAEAiX,SACE1c,KAAKsrB,UAAYtrB,KAAKurB,gBAClBvrB,KAAKmrB,SACPnrB,KAAKmrB,QAAQzO,QAEjB,CAGAgP,cAAc5rB,GAEZ,IADkBS,EAAasB,QAAQ7B,KAAKsF,SAAUqK,GAAY7P,GACpDmC,iBAAd,CAMA,GAAI,iBAAkBhJ,SAASoB,gBAC7B,IAAK,MAAM1D,IAAW,GAAG+P,UAAUzN,SAAS8B,KAAK8L,UAC/CtG,EAAaC,IAAI7J,EAAS,YAAa+D,GAIvCsF,KAAKmrB,SACPnrB,KAAKmrB,QAAQtB,UAGf7pB,KAAKqrB,MAAMtxB,UAAUxC,OAAOsY,IAC5B7P,KAAKsF,SAASvL,UAAUxC,OAAOsY,IAC/B7P,KAAKsF,SAAShC,aAAa,gBAAiB,SAC5CF,EAAYG,oBAAoBvD,KAAKqrB,MAAO,UAC5C9qB,EAAasB,QAAQ7B,KAAKsF,SAAUsK,GAAc9P,EAlBlD,CAmBF,CAEAuE,WAAWC,GAGT,GAAgC,iBAFhCA,EAASe,MAAMhB,WAAWC,IAER+N,YAA2BzZ,EAAU0L,EAAO+N,YACV,mBAA3C/N,EAAO+N,UAAUf,sBAGxB,MAAM,IAAIpM,UAAU,GAAG1J,GAAK2J,+GAG9B,OAAOb,CACT,CAEAknB,gBACE,QAAsB,IAAXG,GACT,MAAM,IAAIzmB,UAAU,yEAGtB,IAAI0mB,EAAmB5rB,KAAKsF,SAEG,WAA3BtF,KAAKuF,QAAQ8M,UACfuZ,EAAmB5rB,KAAKorB,QACfxyB,EAAUoH,KAAKuF,QAAQ8M,WAChCuZ,EAAmB7yB,EAAWiH,KAAKuF,QAAQ8M,WACA,iBAA3BrS,KAAKuF,QAAQ8M,YAC7BuZ,EAAmB5rB,KAAKuF,QAAQ8M,WAGlC,MAAM4Y,EAAejrB,KAAK6rB,mBAC1B7rB,KAAKmrB,QAAUQ,GAAoBC,EAAkB5rB,KAAKqrB,MAAOJ,EACnE,CAEAta,WACE,OAAO3Q,KAAKqrB,MAAMtxB,UAAUC,SAAS6V,GACvC,CAEAic,gBACE,MAAMC,EAAiB/rB,KAAKorB,QAE5B,GAAIW,EAAehyB,UAAUC,SAzMN,WA0MrB,OAAO6wB,GAGT,GAAIkB,EAAehyB,UAAUC,SA5MJ,aA6MvB,OAAO8wB,GAGT,GAAIiB,EAAehyB,UAAUC,SA/MA,iBAgN3B,MAhMsB,MAmMxB,GAAI+xB,EAAehyB,UAAUC,SAlNE,mBAmN7B,MAnMyB,SAuM3B,MAAMgyB,EAAkF,QAA1E1yB,iBAAiB0G,KAAKqrB,OAAO9xB,iBAAiB,iBAAiB8M,OAE7E,OAAI0lB,EAAehyB,UAAUC,SA7NP,UA8NbgyB,EAAQtB,GAAmBD,GAG7BuB,EAAQpB,GAAsBD,EACvC,CAEAY,gBACE,OAAkD,OAA3CvrB,KAAKsF,SAAS7L,QA5ND,UA6NtB,CAEAwyB,aACE,MAAMpS,OAAEA,GAAW7Z,KAAKuF,QAExB,MAAsB,iBAAXsU,EACFA,EAAO9c,MAAM,KAAKuJ,IAAI5D,GAAS9F,OAAO8R,SAAShM,EAAO,KAGzC,mBAAXmX,EACFqS,GAAcrS,EAAOqS,EAAYlsB,KAAKsF,UAGxCuU,CACT,CAEAgS,mBACE,MAAMM,EAAwB,CAC5B1Z,UAAWzS,KAAK8rB,gBAChBvE,UAAW,CAAC,CACVhsB,KAAM,kBACNoZ,QAAS,CACP2K,SAAUtf,KAAKuF,QAAQ+Z,WAG3B,CACE/jB,KAAM,SACNoZ,QAAS,CACPkF,OAAQ7Z,KAAKisB,iBAcnB,OARIjsB,KAAKsrB,WAAsC,WAAzBtrB,KAAKuF,QAAQylB,WACjC5nB,EAAYC,iBAAiBrD,KAAKqrB,MAAO,SAAU,UACnDc,EAAsB5E,UAAY,CAAC,CACjChsB,KAAM,cACNwY,SAAS,KAIN,IACFoY,KACAlwB,EAAQ+D,KAAKuF,QAAQ0lB,aAAc,MAACxiB,EAAW0jB,IAEtD,CAEAC,iBAAgBx1B,IAAEA,EAAGuG,OAAEA,IACrB,MAAMqQ,EAAQ/G,EAAetH,KA5QF,8DA4Q+Ba,KAAKqrB,OAAOxnB,OAAOlN,GAAWwC,EAAUxC,IAE7F6W,EAAMxU,QAMXsE,EAAqBkQ,EAAOrQ,EAAQvG,IAAQwzB,IAAiB5c,EAAMpM,SAASjE,IAASsuB,OACvF,CAGA,sBAAO9vB,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAO0iB,GAASllB,oBAAoBhG,KAAMsE,GAEhD,GAAsB,iBAAXA,EAAX,CAIA,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IANL,CAOF,EACF,CAEA,iBAAO+nB,CAAWjtB,GAChB,GA/TuB,IA+TnBA,EAAMyJ,QAAiD,UAAfzJ,EAAMqB,MAlUtC,QAkU0DrB,EAAMxI,IAC1E,OAGF,MAAM01B,EAAc7lB,EAAetH,KAAKorB,IAExC,IAAK,MAAM3hB,KAAU0jB,EAAa,CAChC,MAAMC,EAAUrB,GAASnlB,YAAY6C,GACrC,IAAK2jB,IAAyC,IAA9BA,EAAQhnB,QAAQwlB,UAC9B,SAGF,MAAMyB,EAAeptB,EAAMotB,eACrBC,EAAeD,EAAaprB,SAASmrB,EAAQlB,OACnD,GACEmB,EAAaprB,SAASmrB,EAAQjnB,WACC,WAA9BinB,EAAQhnB,QAAQwlB,YAA2B0B,GACb,YAA9BF,EAAQhnB,QAAQwlB,WAA2B0B,EAE5C,SAIF,GAAIF,EAAQlB,MAAMrxB,SAASoF,EAAMjC,UAA4B,UAAfiC,EAAMqB,MAzV1C,QAyV8DrB,EAAMxI,KAAoB,qCAAqCqO,KAAK7F,EAAMjC,OAAO8K,UACvJ,SAGF,MAAMnI,EAAgB,CAAEA,cAAeysB,EAAQjnB,UAE5B,UAAflG,EAAMqB,OACRX,EAAckI,WAAa5I,GAG7BmtB,EAAQb,cAAc5rB,EACxB,CACF,CAEA,4BAAO4sB,CAAsBttB,GAI3B,MAAMutB,EAAU,kBAAkB1nB,KAAK7F,EAAMjC,OAAO8K,SAC9C2kB,EA7WS,WA6WOxtB,EAAMxI,IACtBi2B,EAAkB,CAAC1C,GAAcC,IAAgBhpB,SAAShC,EAAMxI,KAEtE,IAAKi2B,IAAoBD,EACvB,OAGF,GAAID,IAAYC,EACd,OAGFxtB,EAAMmD,iBAGN,MAAMuqB,EAAkB9sB,KAAK+G,QAAQ2B,IACnC1I,KACCyG,EAAeS,KAAKlH,KAAM0I,IAAsB,IAC/CjC,EAAeY,KAAKrH,KAAM0I,IAAsB,IAChDjC,EAAeG,QAAQ8B,GAAsBtJ,EAAMW,eAAepG,YAEhE9C,EAAWq0B,GAASllB,oBAAoB8mB,GAE9C,GAAID,EAIF,OAHAztB,EAAM2tB,kBACNl2B,EAASga,YACTha,EAASu1B,gBAAgBhtB,GAIvBvI,EAAS8Z,aACXvR,EAAM2tB,kBACNl2B,EAAS+Z,OACTkc,EAAgBrB,QAEpB,EAOFlrB,EAAac,GAAGpI,SAAUoxB,GAAwB3hB,GAAsBwiB,GAASwB,uBACjFnsB,EAAac,GAAGpI,SAAUoxB,GAAwBG,GAAeU,GAASwB,uBAC1EnsB,EAAac,GAAGpI,SAAUuS,GAAsB0f,GAASmB,YACzD9rB,EAAac,GAAGpI,SAAUqxB,GAAsBY,GAASmB,YACzD9rB,EAAac,GAAGpI,SAAUuS,GAAsB9C,GAAsB,SAAUtJ,GAC9EA,EAAMmD,iBACN2oB,GAASllB,oBAAoBhG,MAAM4I,QACrC,GAMAzN,EAAmB+vB,ICnbnB,MAAM1vB,GAAO,WAEPqU,GAAkB,OAClBmd,GAAkB,gBAAgBxxB,KAElC0I,GAAU,CACd+oB,UAAW,iBACXC,cAAe,KACfpnB,YAAY,EACZ3M,WAAW,EACXg0B,YAAa,QAGThpB,GAAc,CAClB8oB,UAAW,SACXC,cAAe,kBACfpnB,WAAY,UACZ3M,UAAW,UACXg0B,YAAa,oBAOf,MAAMC,WAAiBnpB,EACrBU,YAAYL,GACVe,QACArF,KAAKuF,QAAUvF,KAAKqE,WAAWC,GAC/BtE,KAAKqtB,aAAc,EACnBrtB,KAAKsF,SAAW,IAClB,CAGA,kBAAWpB,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,OAAOA,EACT,CAGAqV,KAAKxV,GACH,IAAK2E,KAAKuF,QAAQpM,UAEhB,YADA8C,EAAQZ,GAIV2E,KAAKstB,UAEL,MAAM32B,EAAUqJ,KAAKutB,cACjBvtB,KAAKuF,QAAQO,YACfnL,EAAOhE,GAGTA,EAAQoD,UAAUuQ,IAAIuF,IAEtB7P,KAAKwtB,kBAAkB,KACrBvxB,EAAQZ,IAEZ,CAEAuV,KAAKvV,GACE2E,KAAKuF,QAAQpM,WAKlB6G,KAAKutB,cAAcxzB,UAAUxC,OAAOsY,IAEpC7P,KAAKwtB,kBAAkB,KACrBxtB,KAAKyF,UACLxJ,EAAQZ,MARRY,EAAQZ,EAUZ,CAEAoK,UACOzF,KAAKqtB,cAIV9sB,EAAaC,IAAIR,KAAKsF,SAAU0nB,IAEhChtB,KAAKsF,SAAS/N,SACdyI,KAAKqtB,aAAc,EACrB,CAGAE,cACE,IAAKvtB,KAAKsF,SAAU,CAClB,MAAMmoB,EAAWx0B,SAASy0B,cAAc,OACxCD,EAASR,UAAYjtB,KAAKuF,QAAQ0nB,UAC9BjtB,KAAKuF,QAAQO,YACf2nB,EAAS1zB,UAAUuQ,IAjGH,QAoGlBtK,KAAKsF,SAAWmoB,CAClB,CAEA,OAAOztB,KAAKsF,QACd,CAEAd,kBAAkBF,GAGhB,OADAA,EAAO6oB,YAAcp0B,EAAWuL,EAAO6oB,aAChC7oB,CACT,CAEAgpB,UACE,GAAIttB,KAAKqtB,YACP,OAGF,MAAM12B,EAAUqJ,KAAKutB,cACrBvtB,KAAKuF,QAAQ4nB,YAAYQ,OAAOh3B,GAEhC4J,EAAac,GAAG1K,EAASq2B,GAAiB,KACxC/wB,EAAQ+D,KAAKuF,QAAQ2nB,iBAGvBltB,KAAKqtB,aAAc,CACrB,CAEAG,kBAAkBnyB,GAChBgB,EAAuBhB,EAAU2E,KAAKutB,cAAevtB,KAAKuF,QAAQO,WACpE,ECpIF,MAEMJ,GAAY,gBACZkoB,GAAgB,UAAUloB,KAC1BmoB,GAAoB,cAAcnoB,KAIlCooB,GAAmB,WAEnB5pB,GAAU,CACd6pB,WAAW,EACXC,YAAa,MAGT7pB,GAAc,CAClB4pB,UAAW,UACXC,YAAa,WAOf,MAAMC,WAAkBhqB,EACtBU,YAAYL,GACVe,QACArF,KAAKuF,QAAUvF,KAAKqE,WAAWC,GAC/BtE,KAAKkuB,WAAY,EACjBluB,KAAKmuB,qBAAuB,IAC9B,CAGA,kBAAWjqB,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MA1CS,WA2CX,CAGA4yB,WACMpuB,KAAKkuB,YAILluB,KAAKuF,QAAQwoB,WACf/tB,KAAKuF,QAAQyoB,YAAYvC,QAG3BlrB,EAAaC,IAAIvH,SAAUyM,IAC3BnF,EAAac,GAAGpI,SAAU20B,GAAexuB,GAASY,KAAKquB,eAAejvB,IACtEmB,EAAac,GAAGpI,SAAU40B,GAAmBzuB,GAASY,KAAKsuB,eAAelvB,IAE1EY,KAAKkuB,WAAY,EACnB,CAEAK,aACOvuB,KAAKkuB,YAIVluB,KAAKkuB,WAAY,EACjB3tB,EAAaC,IAAIvH,SAAUyM,IAC7B,CAGA2oB,eAAejvB,GACb,MAAM4uB,YAAEA,GAAgBhuB,KAAKuF,QAE7B,GAAInG,EAAMjC,SAAWlE,UAAYmG,EAAMjC,SAAW6wB,GAAeA,EAAYh0B,SAASoF,EAAMjC,QAC1F,OAGF,MAAMgX,EAAW1N,EAAec,kBAAkBymB,GAE1B,IAApB7Z,EAASnb,OACXg1B,EAAYvC,QACHzrB,KAAKmuB,uBAAyBL,GACvC3Z,EAASA,EAASnb,OAAS,GAAGyyB,QAE9BtX,EAAS,GAAGsX,OAEhB,CAEA6C,eAAelvB,GApFD,QAqFRA,EAAMxI,MAIVoJ,KAAKmuB,qBAAuB/uB,EAAMovB,SAAWV,GAxFzB,UAyFtB,EChGF,MAAMW,GAAyB,oDACzBC,GAA0B,cAC1BC,GAAmB,gBACnBC,GAAkB,eAMxB,MAAMC,GACJlqB,cACE3E,KAAKsF,SAAWrM,SAAS8B,IAC3B,CAGA+zB,WAEE,MAAMC,EAAgB91B,SAASoB,gBAAgBqf,YAC/C,OAAO5b,KAAKsM,IAAIxS,OAAOo3B,WAAaD,EACtC,CAEAne,OACE,MAAMwF,EAAQpW,KAAK8uB,WACnB9uB,KAAKivB,mBAELjvB,KAAKkvB,sBAAsBlvB,KAAKsF,SAAUqpB,GAAkBQ,GAAmBA,EAAkB/Y,GAEjGpW,KAAKkvB,sBAAsBT,GAAwBE,GAAkBQ,GAAmBA,EAAkB/Y,GAC1GpW,KAAKkvB,sBAAsBR,GAAyBE,GAAiBO,GAAmBA,EAAkB/Y,EAC5G,CAEAiN,QACErjB,KAAKovB,wBAAwBpvB,KAAKsF,SAAU,YAC5CtF,KAAKovB,wBAAwBpvB,KAAKsF,SAAUqpB,IAC5C3uB,KAAKovB,wBAAwBX,GAAwBE,IACrD3uB,KAAKovB,wBAAwBV,GAAyBE,GACxD,CAEAS,gBACE,OAAOrvB,KAAK8uB,WAAa,CAC3B,CAGAG,mBACEjvB,KAAKsvB,sBAAsBtvB,KAAKsF,SAAU,YAC1CtF,KAAKsF,SAAS6L,MAAMoM,SAAW,QACjC,CAEA2R,sBAAsBv3B,EAAU43B,EAAel0B,GAC7C,MAAMm0B,EAAiBxvB,KAAK8uB,WAW5B9uB,KAAKyvB,2BAA2B93B,EAVHhB,IAC3B,GAAIA,IAAYqJ,KAAKsF,UAAY1N,OAAOo3B,WAAar4B,EAAQ+iB,YAAc8V,EACzE,OAGFxvB,KAAKsvB,sBAAsB34B,EAAS44B,GACpC,MAAMJ,EAAkBv3B,OAAO0B,iBAAiB3C,GAAS4C,iBAAiBg2B,GAC1E54B,EAAQwa,MAAMue,YAAYH,EAAe,GAAGl0B,EAASuB,OAAOC,WAAWsyB,UAI3E,CAEAG,sBAAsB34B,EAAS44B,GAC7B,MAAMI,EAAch5B,EAAQwa,MAAM5X,iBAAiBg2B,GAC/CI,GACFvsB,EAAYC,iBAAiB1M,EAAS44B,EAAeI,EAEzD,CAEAP,wBAAwBz3B,EAAU43B,GAahCvvB,KAAKyvB,2BAA2B93B,EAZHhB,IAC3B,MAAM+L,EAAQU,EAAYY,iBAAiBrN,EAAS44B,GAEtC,OAAV7sB,GAKJU,EAAYG,oBAAoB5M,EAAS44B,GACzC54B,EAAQwa,MAAMue,YAAYH,EAAe7sB,IALvC/L,EAAQwa,MAAMye,eAAeL,IASnC,CAEAE,2BAA2B93B,EAAUk4B,GACnC,GAAIj3B,EAAUjB,GACZk4B,EAASl4B,QAIX,IAAK,MAAM4O,KAAOE,EAAetH,KAAKxH,EAAUqI,KAAKsF,UACnDuqB,EAAStpB,EAEb,ECxFF,MAEMb,GAAY,YAIZiK,GAAa,OAAOjK,KACpBoqB,GAAuB,gBAAgBpqB,KACvCkK,GAAe,SAASlK,KACxB+J,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KACtBqqB,GAAe,SAASrqB,KACxBsqB,GAAsB,gBAAgBtqB,KACtCuqB,GAA0B,oBAAoBvqB,KAC9CwqB,GAAwB,kBAAkBxqB,KAC1C8F,GAAuB,QAAQ9F,cAE/ByqB,GAAkB,aAElBtgB,GAAkB,OAClBugB,GAAoB,eAOpBlsB,GAAU,CACdupB,UAAU,EACVhC,OAAO,EACPvf,UAAU,GAGN/H,GAAc,CAClBspB,SAAU,mBACVhC,MAAO,UACPvf,SAAU,WAOZ,MAAMmkB,WAAcjrB,EAClBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAKswB,QAAU7pB,EAAeG,QAxBV,gBAwBmC5G,KAAKsF,UAC5DtF,KAAKuwB,UAAYvwB,KAAKwwB,sBACtBxwB,KAAKywB,WAAazwB,KAAK0wB,uBACvB1wB,KAAK2Q,UAAW,EAChB3Q,KAAKmQ,kBAAmB,EACxBnQ,KAAK2wB,WAAa,IAAI9B,GAEtB7uB,KAAK8M,oBACP,CAGA,kBAAW5I,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MAnES,OAoEX,CAGAoN,OAAO9I,GACL,OAAOE,KAAK2Q,SAAW3Q,KAAK4Q,OAAS5Q,KAAK6Q,KAAK/Q,EACjD,CAEA+Q,KAAK/Q,GACCE,KAAK2Q,UAAY3Q,KAAKmQ,kBAIR5P,EAAasB,QAAQ7B,KAAKsF,SAAUmK,GAAY,CAChE3P,kBAGYmC,mBAIdjC,KAAK2Q,UAAW,EAChB3Q,KAAKmQ,kBAAmB,EAExBnQ,KAAK2wB,WAAW/f,OAEhB3X,SAAS8B,KAAKhB,UAAUuQ,IAAI6lB,IAE5BnwB,KAAK4wB,gBAEL5wB,KAAKuwB,UAAU1f,KAAK,IAAM7Q,KAAK6wB,aAAa/wB,IAC9C,CAEA8Q,OACO5Q,KAAK2Q,WAAY3Q,KAAKmQ,mBAIT5P,EAAasB,QAAQ7B,KAAKsF,SAAUqK,IAExC1N,mBAIdjC,KAAK2Q,UAAW,EAChB3Q,KAAKmQ,kBAAmB,EACxBnQ,KAAKywB,WAAWlC,aAEhBvuB,KAAKsF,SAASvL,UAAUxC,OAAOsY,IAE/B7P,KAAK6F,eAAe,IAAM7F,KAAK8wB,aAAc9wB,KAAKsF,SAAUtF,KAAKoP,gBACnE,CAEA3J,UACElF,EAAaC,IAAI5I,OAAQ8N,IACzBnF,EAAaC,IAAIR,KAAKswB,QAAS5qB,IAE/B1F,KAAKuwB,UAAU9qB,UACfzF,KAAKywB,WAAWlC,aAEhBlpB,MAAMI,SACR,CAEAsrB,eACE/wB,KAAK4wB,eACP,CAGAJ,sBACE,OAAO,IAAIpD,GAAS,CAClBj0B,UAAW2H,QAAQd,KAAKuF,QAAQkoB,UAChC3nB,WAAY9F,KAAKoP,eAErB,CAEAshB,uBACE,OAAO,IAAIzC,GAAU,CACnBD,YAAahuB,KAAKsF,UAEtB,CAEAurB,aAAa/wB,GAEN7G,SAAS8B,KAAKf,SAASgG,KAAKsF,WAC/BrM,SAAS8B,KAAK4yB,OAAO3tB,KAAKsF,UAG5BtF,KAAKsF,SAAS6L,MAAM6Z,QAAU,QAC9BhrB,KAAKsF,SAAS9B,gBAAgB,eAC9BxD,KAAKsF,SAAShC,aAAa,cAAc,GACzCtD,KAAKsF,SAAShC,aAAa,OAAQ,UACnCtD,KAAKsF,SAAS4X,UAAY,EAE1B,MAAM8T,EAAYvqB,EAAeG,QAxIT,cAwIsC5G,KAAKswB,SAC/DU,IACFA,EAAU9T,UAAY,GAGxBviB,EAAOqF,KAAKsF,UAEZtF,KAAKsF,SAASvL,UAAUuQ,IAAIuF,IAa5B7P,KAAK6F,eAXsBorB,KACrBjxB,KAAKuF,QAAQkmB,OACfzrB,KAAKywB,WAAWrC,WAGlBpuB,KAAKmQ,kBAAmB,EACxB5P,EAAasB,QAAQ7B,KAAKsF,SAAUoK,GAAa,CAC/C5P,mBAIoCE,KAAKswB,QAAStwB,KAAKoP,cAC7D,CAEAtC,qBACEvM,EAAac,GAAGrB,KAAKsF,SAAU4qB,GAAuB9wB,IApLvC,WAqLTA,EAAMxI,MAINoJ,KAAKuF,QAAQ2G,SACflM,KAAK4Q,OAIP5Q,KAAKkxB,gCAGP3wB,EAAac,GAAGzJ,OAAQm4B,GAAc,KAChC/vB,KAAK2Q,WAAa3Q,KAAKmQ,kBACzBnQ,KAAK4wB,kBAITrwB,EAAac,GAAGrB,KAAKsF,SAAU2qB,GAAyB7wB,IAEtDmB,EAAae,IAAItB,KAAKsF,SAAU0qB,GAAqBmB,IAC/CnxB,KAAKsF,WAAalG,EAAMjC,QAAU6C,KAAKsF,WAAa6rB,EAAOh0B,SAIjC,WAA1B6C,KAAKuF,QAAQkoB,SAKbztB,KAAKuF,QAAQkoB,UACfztB,KAAK4Q,OALL5Q,KAAKkxB,iCASb,CAEAJ,aACE9wB,KAAKsF,SAAS6L,MAAM6Z,QAAU,OAC9BhrB,KAAKsF,SAAShC,aAAa,eAAe,GAC1CtD,KAAKsF,SAAS9B,gBAAgB,cAC9BxD,KAAKsF,SAAS9B,gBAAgB,QAC9BxD,KAAKmQ,kBAAmB,EAExBnQ,KAAKuwB,UAAU3f,KAAK,KAClB3X,SAAS8B,KAAKhB,UAAUxC,OAAO44B,IAC/BnwB,KAAKoxB,oBACLpxB,KAAK2wB,WAAWtN,QAChB9iB,EAAasB,QAAQ7B,KAAKsF,SAAUsK,KAExC,CAEAR,cACE,OAAOpP,KAAKsF,SAASvL,UAAUC,SA5NX,OA6NtB,CAEAk3B,6BAEE,GADkB3wB,EAAasB,QAAQ7B,KAAKsF,SAAUwqB,IACxC7tB,iBACZ,OAGF,MAAMovB,EAAqBrxB,KAAKsF,SAASqZ,aAAe1lB,SAASoB,gBAAgBof,aAC3E6X,EAAmBtxB,KAAKsF,SAAS6L,MAAMsM,UAEpB,WAArB6T,GAAiCtxB,KAAKsF,SAASvL,UAAUC,SAASo2B,MAIjEiB,IACHrxB,KAAKsF,SAAS6L,MAAMsM,UAAY,UAGlCzd,KAAKsF,SAASvL,UAAUuQ,IAAI8lB,IAC5BpwB,KAAK6F,eAAe,KAClB7F,KAAKsF,SAASvL,UAAUxC,OAAO64B,IAC/BpwB,KAAK6F,eAAe,KAClB7F,KAAKsF,SAAS6L,MAAMsM,UAAY6T,GAC/BtxB,KAAKswB,UACPtwB,KAAKswB,SAERtwB,KAAKsF,SAASmmB,QAChB,CAMAmF,gBACE,MAAMS,EAAqBrxB,KAAKsF,SAASqZ,aAAe1lB,SAASoB,gBAAgBof,aAC3E+V,EAAiBxvB,KAAK2wB,WAAW7B,WACjCyC,EAAoB/B,EAAiB,EAE3C,GAAI+B,IAAsBF,EAAoB,CAC5C,MAAMxsB,EAAW5J,IAAU,cAAgB,eAC3C+E,KAAKsF,SAAS6L,MAAMtM,GAAY,GAAG2qB,KACrC,CAEA,IAAK+B,GAAqBF,EAAoB,CAC5C,MAAMxsB,EAAW5J,IAAU,eAAiB,cAC5C+E,KAAKsF,SAAS6L,MAAMtM,GAAY,GAAG2qB,KACrC,CACF,CAEA4B,oBACEpxB,KAAKsF,SAAS6L,MAAMqgB,YAAc,GAClCxxB,KAAKsF,SAAS6L,MAAMsgB,aAAe,EACrC,CAGA,sBAAO91B,CAAgB2I,EAAQxE,GAC7B,OAAOE,KAAKuI,KAAK,WACf,MAAMC,EAAO6nB,GAAMrqB,oBAAoBhG,KAAMsE,GAE7C,GAAsB,iBAAXA,EAAX,CAIA,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,GAAQxE,EANb,CAOF,EACF,EAOFS,EAAac,GAAGpI,SAAUuS,GAnSG,2BAmSyC,SAAUpM,GAC9E,MAAMjC,EAASsJ,EAAekB,uBAAuB3H,MAEjD,CAAC,IAAK,QAAQoB,SAASpB,KAAKiI,UAC9B7I,EAAMmD,iBAGRhC,EAAae,IAAInE,EAAQsS,GAAYiiB,IAC/BA,EAAUzvB,kBAKd1B,EAAae,IAAInE,EAAQyS,GAAc,KACjCzW,EAAU6G,OACZA,KAAKyrB,YAMX,MAAMkG,EAAclrB,EAAeG,QA3Tf,eA4ThB+qB,GACFtB,GAAMtqB,YAAY4rB,GAAa/gB,OAGpByf,GAAMrqB,oBAAoB7I,GAElCyL,OAAO5I,KACd,GAEA6H,EAAqBwoB,IAMrBl1B,EAAmBk1B,IC/VnB,MAEM3qB,GAAY,gBACZgF,GAAe,YACfa,GAAsB,OAAO7F,KAAYgF,KAGzCmF,GAAkB,OAClB+hB,GAAqB,UACrBC,GAAoB,SAEpBC,GAAgB,kBAEhBriB,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KACtBiK,GAAa,OAAOjK,KACpBoqB,GAAuB,gBAAgBpqB,KACvCkK,GAAe,SAASlK,KACxBqqB,GAAe,SAASrqB,KACxB8F,GAAuB,QAAQ9F,KAAYgF,KAC3CwlB,GAAwB,kBAAkBxqB,KAI1CxB,GAAU,CACdupB,UAAU,EACVvhB,UAAU,EACVmQ,QAAQ,GAGJlY,GAAc,CAClBspB,SAAU,mBACVvhB,SAAU,UACVmQ,OAAQ,WAOV,MAAM0V,WAAkB3sB,EACtBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAK2Q,UAAW,EAChB3Q,KAAKuwB,UAAYvwB,KAAKwwB,sBACtBxwB,KAAKywB,WAAazwB,KAAK0wB,uBACvB1wB,KAAK8M,oBACP,CAGA,kBAAW5I,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MA5DS,WA6DX,CAGAoN,OAAO9I,GACL,OAAOE,KAAK2Q,SAAW3Q,KAAK4Q,OAAS5Q,KAAK6Q,KAAK/Q,EACjD,CAEA+Q,KAAK/Q,GACCE,KAAK2Q,UAISpQ,EAAasB,QAAQ7B,KAAKsF,SAAUmK,GAAY,CAAE3P,kBAEtDmC,mBAIdjC,KAAK2Q,UAAW,EAChB3Q,KAAKuwB,UAAU1f,OAEV7Q,KAAKuF,QAAQ8W,SAChB,IAAIwS,IAAkBje,OAGxB5Q,KAAKsF,SAAShC,aAAa,cAAc,GACzCtD,KAAKsF,SAAShC,aAAa,OAAQ,UACnCtD,KAAKsF,SAASvL,UAAUuQ,IAAIsnB,IAY5B5xB,KAAK6F,eAVoBsJ,KAClBnP,KAAKuF,QAAQ8W,SAAUrc,KAAKuF,QAAQkoB,UACvCztB,KAAKywB,WAAWrC,WAGlBpuB,KAAKsF,SAASvL,UAAUuQ,IAAIuF,IAC5B7P,KAAKsF,SAASvL,UAAUxC,OAAOq6B,IAC/BrxB,EAAasB,QAAQ7B,KAAKsF,SAAUoK,GAAa,CAAE5P,mBAGfE,KAAKsF,UAAU,GACvD,CAEAsL,OACO5Q,KAAK2Q,WAIQpQ,EAAasB,QAAQ7B,KAAKsF,SAAUqK,IAExC1N,mBAIdjC,KAAKywB,WAAWlC,aAChBvuB,KAAKsF,SAAS0sB,OACdhyB,KAAK2Q,UAAW,EAChB3Q,KAAKsF,SAASvL,UAAUuQ,IAAIunB,IAC5B7xB,KAAKuwB,UAAU3f,OAcf5Q,KAAK6F,eAZoBosB,KACvBjyB,KAAKsF,SAASvL,UAAUxC,OAAOsY,GAAiBgiB,IAChD7xB,KAAKsF,SAAS9B,gBAAgB,cAC9BxD,KAAKsF,SAAS9B,gBAAgB,QAEzBxD,KAAKuF,QAAQ8W,SAChB,IAAIwS,IAAkBxL,QAGxB9iB,EAAasB,QAAQ7B,KAAKsF,SAAUsK,KAGA5P,KAAKsF,UAAU,IACvD,CAEAG,UACEzF,KAAKuwB,UAAU9qB,UACfzF,KAAKywB,WAAWlC,aAChBlpB,MAAMI,SACR,CAGA+qB,sBACE,MAUMr3B,EAAY2H,QAAQd,KAAKuF,QAAQkoB,UAEvC,OAAO,IAAIL,GAAS,CAClBH,UAlJsB,qBAmJtB9zB,YACA2M,YAAY,EACZqnB,YAAantB,KAAKsF,SAAS3L,WAC3BuzB,cAAe/zB,EAjBK+zB,KACU,WAA1BltB,KAAKuF,QAAQkoB,SAKjBztB,KAAK4Q,OAJHrQ,EAAasB,QAAQ7B,KAAKsF,SAAUwqB,KAeK,MAE/C,CAEAY,uBACE,OAAO,IAAIzC,GAAU,CACnBD,YAAahuB,KAAKsF,UAEtB,CAEAwH,qBACEvM,EAAac,GAAGrB,KAAKsF,SAAU4qB,GAAuB9wB,IAtKvC,WAuKTA,EAAMxI,MAINoJ,KAAKuF,QAAQ2G,SACflM,KAAK4Q,OAIPrQ,EAAasB,QAAQ7B,KAAKsF,SAAUwqB,MAExC,CAGA,sBAAOn0B,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOupB,GAAU/rB,oBAAoBhG,KAAMsE,GAEjD,GAAsB,iBAAXA,EAAX,CAIA,QAAqBmE,IAAjBD,EAAKlE,IAAyBA,EAAO7C,WAAW,MAAmB,gBAAX6C,EAC1D,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,GAAQtE,KANb,CAOF,EACF,EAOFO,EAAac,GAAGpI,SAAUuS,GAzLG,+BAyLyC,SAAUpM,GAC9E,MAAMjC,EAASsJ,EAAekB,uBAAuB3H,MAMrD,GAJI,CAAC,IAAK,QAAQoB,SAASpB,KAAKiI,UAC9B7I,EAAMmD,iBAGJ3I,EAAWoG,MACb,OAGFO,EAAae,IAAInE,EAAQyS,GAAc,KAEjCzW,EAAU6G,OACZA,KAAKyrB,UAKT,MAAMkG,EAAclrB,EAAeG,QAAQkrB,IACvCH,GAAeA,IAAgBx0B,GACjC40B,GAAUhsB,YAAY4rB,GAAa/gB,OAGxBmhB,GAAU/rB,oBAAoB7I,GACtCyL,OAAO5I,KACd,GAEAO,EAAac,GAAGzJ,OAAQ2T,GAAqB,KAC3C,IAAK,MAAM5T,KAAY8O,EAAetH,KAAK2yB,IACzCC,GAAU/rB,oBAAoBrO,GAAUkZ,SAI5CtQ,EAAac,GAAGzJ,OAAQm4B,GAAc,KACpC,IAAK,MAAMp5B,KAAW8P,EAAetH,KAAK,gDACG,UAAvC7F,iBAAiB3C,GAAS+d,UAC5Bqd,GAAU/rB,oBAAoBrP,GAASia,SAK7C/I,EAAqBkqB,IAMrB52B,EAAmB42B,IC/QnB,MAEaG,GAAmB,CAE9B,IAAK,CAAC,QAAS,MAAO,KAAM,OAAQ,OAJP,kBAK7B5Q,EAAG,CAAC,SAAU,OAAQ,QAAS,OAC/B6Q,KAAM,GACN5Q,EAAG,GACH6Q,GAAI,GACJC,IAAK,GACLC,KAAM,GACNC,GAAI,GACJC,IAAK,GACLC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJC,GAAI,GACJzQ,EAAG,GACHxU,IAAK,CAAC,MAAO,SAAU,MAAO,QAAS,QAAS,UAChDklB,GAAI,GACJC,GAAI,GACJC,EAAG,GACHC,IAAK,GACLC,EAAG,GACHC,MAAO,GACPC,KAAM,GACNC,IAAK,GACLC,IAAK,GACLC,OAAQ,GACRC,EAAG,GACHC,GAAI,IAIAC,GAAgB,IAAIr1B,IAAI,CAC5B,aACA,OACA,OACA,WACA,WACA,SACA,MACA,eASIs1B,GAAmB,0DAEnBC,GAAmBA,CAACjf,EAAWkf,KACnC,MAAMC,EAAgBnf,EAAU1B,SAAS9a,cAEzC,OAAI07B,EAAqB9yB,SAAS+yB,IAC5BJ,GAAcj9B,IAAIq9B,IACbrzB,QAAQkzB,GAAiB/uB,KAAK+P,EAAUof,YAO5CF,EAAqBrwB,OAAOwwB,GAAkBA,aAA0BrvB,QAC5Eye,KAAK6Q,GAASA,EAAMrvB,KAAKkvB,KC9DxBjwB,GAAU,CACdqwB,UAAWrC,GACXsC,QAAS,GACTC,WAAY,GACZtW,MAAM,EACNuW,UAAU,EACVC,WAAY,KACZC,SAAU,eAGNzwB,GAAc,CAClBowB,UAAW,SACXC,QAAS,SACTC,WAAY,oBACZtW,KAAM,UACNuW,SAAU,UACVC,WAAY,kBACZC,SAAU,UAGNC,GAAqB,CACzBC,MAAO,iCACPn9B,SAAU,oBAOZ,MAAMo9B,WAAwB9wB,EAC5BU,YAAYL,GACVe,QACArF,KAAKuF,QAAUvF,KAAKqE,WAAWC,EACjC,CAGA,kBAAWJ,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MA/CS,iBAgDX,CAGAw5B,aACE,OAAO58B,OAAO8G,OAAOc,KAAKuF,QAAQivB,SAC/BluB,IAAIhC,GAAUtE,KAAKi1B,yBAAyB3wB,IAC5CT,OAAO/C,QACZ,CAEAo0B,aACE,OAAOl1B,KAAKg1B,aAAah8B,OAAS,CACpC,CAEAm8B,cAAcX,GAGZ,OAFAx0B,KAAKo1B,cAAcZ,GACnBx0B,KAAKuF,QAAQivB,QAAU,IAAKx0B,KAAKuF,QAAQivB,WAAYA,GAC9Cx0B,IACT,CAEAq1B,SACE,MAAMC,EAAkBr8B,SAASy0B,cAAc,OAC/C4H,EAAgBC,UAAYv1B,KAAKw1B,eAAex1B,KAAKuF,QAAQqvB,UAE7D,IAAK,MAAOj9B,EAAU89B,KAASr9B,OAAO+I,QAAQnB,KAAKuF,QAAQivB,SACzDx0B,KAAK01B,YAAYJ,EAAiBG,EAAM99B,GAG1C,MAAMi9B,EAAWU,EAAgBzuB,SAAS,GACpC4tB,EAAaz0B,KAAKi1B,yBAAyBj1B,KAAKuF,QAAQkvB,YAM9D,OAJIA,GACFG,EAAS76B,UAAUuQ,OAAOmqB,EAAW13B,MAAM,MAGtC63B,CACT,CAGAnwB,iBAAiBH,GACfe,MAAMZ,iBAAiBH,GACvBtE,KAAKo1B,cAAc9wB,EAAOkwB,QAC5B,CAEAY,cAAcO,GACZ,IAAK,MAAOh+B,EAAU68B,KAAYp8B,OAAO+I,QAAQw0B,GAC/CtwB,MAAMZ,iBAAiB,CAAE9M,WAAUm9B,MAAON,GAAWK,GAEzD,CAEAa,YAAYd,EAAUJ,EAAS78B,GAC7B,MAAMi+B,EAAkBnvB,EAAeG,QAAQjP,EAAUi9B,GAEpDgB,KAILpB,EAAUx0B,KAAKi1B,yBAAyBT,IAOpC57B,EAAU47B,GACZx0B,KAAK61B,sBAAsB98B,EAAWy7B,GAAUoB,GAI9C51B,KAAKuF,QAAQ4Y,KACfyX,EAAgBL,UAAYv1B,KAAKw1B,eAAehB,GAIlDoB,EAAgBE,YAActB,EAd5BoB,EAAgBr+B,SAepB,CAEAi+B,eAAeG,GACb,OAAO31B,KAAKuF,QAAQmvB,SD1DjB,SAAsBqB,EAAYxB,EAAWyB,GAClD,IAAKD,EAAW/8B,OACd,OAAO+8B,EAGT,GAAIC,GAAgD,mBAArBA,EAC7B,OAAOA,EAAiBD,GAG1B,MACME,GADY,IAAIr+B,OAAOs+B,WACKC,gBAAgBJ,EAAY,aACxD5hB,EAAW,GAAGzN,UAAUuvB,EAAgBl7B,KAAKqF,iBAAiB,MAEpE,IAAK,MAAMzJ,KAAWwd,EAAU,CAC9B,MAAMiiB,EAAcz/B,EAAQ2c,SAAS9a,cAErC,IAAKJ,OAAOd,KAAKi9B,GAAWnzB,SAASg1B,GAAc,CACjDz/B,EAAQY,SACR,QACF,CAEA,MAAM8+B,EAAgB,GAAG3vB,UAAU/P,EAAQ+M,YACrC4yB,EAAoB,GAAG5vB,OAAO6tB,EAAU,MAAQ,GAAIA,EAAU6B,IAAgB,IAEpF,IAAK,MAAMphB,KAAaqhB,EACjBpC,GAAiBjf,EAAWshB,IAC/B3/B,EAAQ6M,gBAAgBwR,EAAU1B,SAGxC,CAEA,OAAO2iB,EAAgBl7B,KAAKw6B,SAC9B,CC0BmCgB,CAAaZ,EAAK31B,KAAKuF,QAAQgvB,UAAWv0B,KAAKuF,QAAQovB,YAAcgB,CACtG,CAEAV,yBAAyBU,GACvB,OAAO15B,EAAQ05B,EAAK,MAACltB,EAAWzI,MAClC,CAEA61B,sBAAsBl/B,EAASi/B,GAC7B,GAAI51B,KAAKuF,QAAQ4Y,KAGf,OAFAyX,EAAgBL,UAAY,QAC5BK,EAAgBjI,OAAOh3B,GAIzBi/B,EAAgBE,YAAcn/B,EAAQm/B,WACxC,ECvIF,MACMU,GAAwB,IAAI93B,IAAI,CAAC,WAAY,YAAa,eAE1D+3B,GAAkB,OAElB5mB,GAAkB,OAElB6mB,GAAyB,iBACzBC,GAAiB,SAEjBC,GAAmB,gBAEnBC,GAAgB,QAChBC,GAAgB,QAChBC,GAAgB,QAchBC,GAAgB,CACpBC,KAAM,OACNC,IAAK,MACLC,MAAOl8B,IAAU,OAAS,QAC1Bm8B,OAAQ,SACRC,KAAMp8B,IAAU,QAAU,QAGtBiJ,GAAU,CACdqwB,UAAWrC,GACXoF,WAAW,EACXhY,SAAU,kBACViY,WAAW,EACXC,YAAa,GACbC,MAAO,EACPzV,mBAAoB,CAAC,MAAO,QAAS,SAAU,QAC/C7D,MAAM,EACNtE,OAAQ,CAAC,EAAG,GACZpH,UAAW,MACXwY,aAAc,KACdyJ,UAAU,EACVC,WAAY,KACZh9B,UAAU,EACVi9B,SAAU,+GAIV8C,MAAO,GACP71B,QAAS,eAGLsC,GAAc,CAClBowB,UAAW,SACX+C,UAAW,UACXhY,SAAU,mBACViY,UAAW,2BACXC,YAAa,oBACbC,MAAO,kBACPzV,mBAAoB,QACpB7D,KAAM,UACNtE,OAAQ,0BACRpH,UAAW,oBACXwY,aAAc,yBACdyJ,SAAU,UACVC,WAAY,kBACZh9B,SAAU,mBACVi9B,SAAU,SACV8C,MAAO,4BACP71B,QAAS,UAOX,MAAM81B,WAAgBvyB,EACpBT,YAAYhO,EAAS2N,GACnB,QAAsB,IAAXqnB,GACT,MAAM,IAAIzmB,UAAU,wEAGtBG,MAAM1O,EAAS2N,GAGftE,KAAK43B,YAAa,EAClB53B,KAAK63B,SAAW,EAChB73B,KAAK83B,WAAa,KAClB93B,KAAK+3B,eAAiB,GACtB/3B,KAAKmrB,QAAU,KACfnrB,KAAKg4B,iBAAmB,KACxBh4B,KAAKi4B,YAAc,KAGnBj4B,KAAKk4B,IAAM,KAEXl4B,KAAKm4B,gBAEAn4B,KAAKuF,QAAQ5N,UAChBqI,KAAKo4B,WAET,CAGA,kBAAWl0B,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MAxHS,SAyHX,CAGA68B,SACEr4B,KAAK43B,YAAa,CACpB,CAEAU,UACEt4B,KAAK43B,YAAa,CACpB,CAEAW,gBACEv4B,KAAK43B,YAAc53B,KAAK43B,UAC1B,CAEAhvB,SACO5I,KAAK43B,aAIN53B,KAAK2Q,WACP3Q,KAAKw4B,SAIPx4B,KAAKy4B,SACP,CAEAhzB,UACE4I,aAAarO,KAAK63B,UAElBt3B,EAAaC,IAAIR,KAAKsF,SAAS7L,QAAQk9B,IAAiBC,GAAkB52B,KAAK04B,mBAE3E14B,KAAKsF,SAASnL,aAAa,2BAC7B6F,KAAKsF,SAAShC,aAAa,QAAStD,KAAKsF,SAASnL,aAAa,2BAGjE6F,KAAK24B,iBACLtzB,MAAMI,SACR,CAEAoL,OACE,GAAoC,SAAhC7Q,KAAKsF,SAAS6L,MAAM6Z,QACtB,MAAM,IAAI5mB,MAAM,uCAGlB,IAAMpE,KAAK44B,mBAAoB54B,KAAK43B,WAClC,OAGF,MAAMlG,EAAYnxB,EAAasB,QAAQ7B,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UAxJxD,SA0JT2yB,GADaz+B,EAAe4F,KAAKsF,WACLtF,KAAKsF,SAASmO,cAAcpZ,iBAAiBL,SAASgG,KAAKsF,UAE7F,GAAIosB,EAAUzvB,mBAAqB42B,EACjC,OAIF74B,KAAK24B,iBAEL,MAAMT,EAAMl4B,KAAK84B,iBAEjB94B,KAAKsF,SAAShC,aAAa,mBAAoB40B,EAAI/9B,aAAa,OAEhE,MAAMo9B,UAAEA,GAAcv3B,KAAKuF,QAe3B,GAbKvF,KAAKsF,SAASmO,cAAcpZ,gBAAgBL,SAASgG,KAAKk4B,OAC7DX,EAAU5J,OAAOuK,GACjB33B,EAAasB,QAAQ7B,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UAzKpC,cA4KnBlG,KAAKmrB,QAAUnrB,KAAKwrB,cAAc0M,GAElCA,EAAIn+B,UAAUuQ,IAAIuF,IAMd,iBAAkB5W,SAASoB,gBAC7B,IAAK,MAAM1D,IAAW,GAAG+P,UAAUzN,SAAS8B,KAAK8L,UAC/CtG,EAAac,GAAG1K,EAAS,YAAa+D,GAc1CsF,KAAK6F,eAVYwL,KACf9Q,EAAasB,QAAQ7B,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UA5LvC,WA8LU,IAApBlG,KAAK83B,YACP93B,KAAKw4B,SAGPx4B,KAAK83B,YAAa,GAGU93B,KAAKk4B,IAAKl4B,KAAKoP,cAC/C,CAEAwB,OACE,GAAK5Q,KAAK2Q,aAIQpQ,EAAasB,QAAQ7B,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UAhNxD,SAiNDjE,iBAAd,CASA,GALYjC,KAAK84B,iBACb/+B,UAAUxC,OAAOsY,IAIjB,iBAAkB5W,SAASoB,gBAC7B,IAAK,MAAM1D,IAAW,GAAG+P,UAAUzN,SAAS8B,KAAK8L,UAC/CtG,EAAaC,IAAI7J,EAAS,YAAa+D,GAI3CsF,KAAK+3B,eAAehB,KAAiB,EACrC/2B,KAAK+3B,eAAejB,KAAiB,EACrC92B,KAAK+3B,eAAelB,KAAiB,EACrC72B,KAAK83B,WAAa,KAelB93B,KAAK6F,eAbYwL,KACXrR,KAAK+4B,yBAIJ/4B,KAAK83B,YACR93B,KAAK24B,iBAGP34B,KAAKsF,SAAS9B,gBAAgB,oBAC9BjD,EAAasB,QAAQ7B,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UA9OtC,aAiPalG,KAAKk4B,IAAKl4B,KAAKoP,cA/B7C,CAgCF,CAEAsN,SACM1c,KAAKmrB,SACPnrB,KAAKmrB,QAAQzO,QAEjB,CAGAkc,iBACE,OAAO93B,QAAQd,KAAKg5B,YACtB,CAEAF,iBAKE,OAJK94B,KAAKk4B,MACRl4B,KAAKk4B,IAAMl4B,KAAKi5B,kBAAkBj5B,KAAKi4B,aAAej4B,KAAKk5B,2BAGtDl5B,KAAKk4B,GACd,CAEAe,kBAAkBzE,GAChB,MAAM0D,EAAMl4B,KAAKm5B,oBAAoB3E,GAASa,SAG9C,IAAK6C,EACH,OAAO,KAGTA,EAAIn+B,UAAUxC,OAAOk/B,GAAiB5mB,IAEtCqoB,EAAIn+B,UAAUuQ,IAAI,MAAMtK,KAAK2E,YAAYnJ,aAEzC,MAAM49B,E3EpRKC,KACb,GACEA,GAAUv7B,KAAKw7B,MAjCH,IAiCSx7B,KAAKy7B,gBACnBtgC,SAASugC,eAAeH,IAEjC,OAAOA,G2E+QSI,CAAOz5B,KAAK2E,YAAYnJ,MAAMlD,WAQ5C,OANA4/B,EAAI50B,aAAa,KAAM81B,GAEnBp5B,KAAKoP,eACP8oB,EAAIn+B,UAAUuQ,IAAImsB,IAGbyB,CACT,CAEAwB,WAAWlF,GACTx0B,KAAKi4B,YAAczD,EACfx0B,KAAK2Q,aACP3Q,KAAK24B,iBACL34B,KAAK6Q,OAET,CAEAsoB,oBAAoB3E,GAalB,OAZIx0B,KAAKg4B,iBACPh4B,KAAKg4B,iBAAiB7C,cAAcX,GAEpCx0B,KAAKg4B,iBAAmB,IAAIjD,GAAgB,IACvC/0B,KAAKuF,QAGRivB,UACAC,WAAYz0B,KAAKi1B,yBAAyBj1B,KAAKuF,QAAQiyB,eAIpDx3B,KAAKg4B,gBACd,CAEAkB,yBACE,MAAO,CACLxC,CAACA,IAAyB12B,KAAKg5B,YAEnC,CAEAA,YACE,OAAOh5B,KAAKi1B,yBAAyBj1B,KAAKuF,QAAQmyB,QAAU13B,KAAKsF,SAASnL,aAAa,yBACzF,CAGAw/B,6BAA6Bv6B,GAC3B,OAAOY,KAAK2E,YAAYqB,oBAAoB5G,EAAMW,eAAgBC,KAAK45B,qBACzE,CAEAxqB,cACE,OAAOpP,KAAKuF,QAAQ+xB,WAAct3B,KAAKk4B,KAAOl4B,KAAKk4B,IAAIn+B,UAAUC,SAASy8B,GAC5E,CAEA9lB,WACE,OAAO3Q,KAAKk4B,KAAOl4B,KAAKk4B,IAAIn+B,UAAUC,SAAS6V,GACjD,CAEA2b,cAAc0M,GACZ,MAAMzlB,EAAYxW,EAAQ+D,KAAKuF,QAAQkN,UAAW,CAACzS,KAAMk4B,EAAKl4B,KAAKsF,WAC7Du0B,EAAa7C,GAAcvkB,EAAUtN,eAC3C,OAAOwmB,GAAoB3rB,KAAKsF,SAAU4yB,EAAKl4B,KAAK6rB,iBAAiBgO,GACvE,CAEA5N,aACE,MAAMpS,OAAEA,GAAW7Z,KAAKuF,QAExB,MAAsB,iBAAXsU,EACFA,EAAO9c,MAAM,KAAKuJ,IAAI5D,GAAS9F,OAAO8R,SAAShM,EAAO,KAGzC,mBAAXmX,EACFqS,GAAcrS,EAAOqS,EAAYlsB,KAAKsF,UAGxCuU,CACT,CAEAob,yBAAyBU,GACvB,OAAO15B,EAAQ05B,EAAK,CAAC31B,KAAKsF,SAAUtF,KAAKsF,UAC3C,CAEAumB,iBAAiBgO,GACf,MAAM1N,EAAwB,CAC5B1Z,UAAWonB,EACXtS,UAAW,CACT,CACEhsB,KAAM,OACNoZ,QAAS,CACPqN,mBAAoBhiB,KAAKuF,QAAQyc,qBAGrC,CACEzmB,KAAM,SACNoZ,QAAS,CACPkF,OAAQ7Z,KAAKisB,eAGjB,CACE1wB,KAAM,kBACNoZ,QAAS,CACP2K,SAAUtf,KAAKuF,QAAQ+Z,WAG3B,CACE/jB,KAAM,QACNoZ,QAAS,CACPhe,QAAS,IAAIqJ,KAAK2E,YAAYnJ,eAGlC,CACED,KAAM,kBACNwY,SAAS,EACTC,MAAO,aACPtY,GAAI8M,IAGFxI,KAAK84B,iBAAiBx1B,aAAa,wBAAyBkF,EAAK0L,MAAMzB,eAM/E,MAAO,IACF0Z,KACAlwB,EAAQ+D,KAAKuF,QAAQ0lB,aAAc,MAACxiB,EAAW0jB,IAEtD,CAEAgM,gBACE,MAAM2B,EAAW95B,KAAKuF,QAAQ1D,QAAQ9E,MAAM,KAE5C,IAAK,MAAM8E,KAAWi4B,EACpB,GAAgB,UAAZj4B,EACFtB,EAAac,GAAGrB,KAAKsF,SAAUtF,KAAK2E,YAAYuB,UArZpC,SAqZ4DlG,KAAKuF,QAAQ5N,SAAUyH,IAC7F,MAAMmtB,EAAUvsB,KAAK25B,6BAA6Bv6B,GAClDmtB,EAAQwL,eAAehB,MAAmBxK,EAAQ5b,YAAc4b,EAAQwL,eAAehB,KACvFxK,EAAQ3jB,gBAEL,GAjaU,WAiaN/G,EAA4B,CACrC,MAAMk4B,EAAUl4B,IAAYg1B,GAC1B72B,KAAK2E,YAAYuB,UAzZF,cA0ZflG,KAAK2E,YAAYuB,UA5ZL,WA6ZR8zB,EAAWn4B,IAAYg1B,GAC3B72B,KAAK2E,YAAYuB,UA3ZF,cA4ZflG,KAAK2E,YAAYuB,UA9ZJ,YAgaf3F,EAAac,GAAGrB,KAAKsF,SAAUy0B,EAAS/5B,KAAKuF,QAAQ5N,SAAUyH,IAC7D,MAAMmtB,EAAUvsB,KAAK25B,6BAA6Bv6B,GAClDmtB,EAAQwL,eAA8B,YAAf34B,EAAMqB,KAAqBq2B,GAAgBD,KAAiB,EACnFtK,EAAQkM,WAEVl4B,EAAac,GAAGrB,KAAKsF,SAAU00B,EAAUh6B,KAAKuF,QAAQ5N,SAAUyH,IAC9D,MAAMmtB,EAAUvsB,KAAK25B,6BAA6Bv6B,GAClDmtB,EAAQwL,eAA8B,aAAf34B,EAAMqB,KAAsBq2B,GAAgBD,IACjEtK,EAAQjnB,SAAStL,SAASoF,EAAMU,eAElCysB,EAAQiM,UAEZ,CAGFx4B,KAAK04B,kBAAoB,KACnB14B,KAAKsF,UACPtF,KAAK4Q,QAITrQ,EAAac,GAAGrB,KAAKsF,SAAS7L,QAAQk9B,IAAiBC,GAAkB52B,KAAK04B,kBAChF,CAEAN,YACE,MAAMV,EAAQ13B,KAAKsF,SAASnL,aAAa,SAEpCu9B,IAIA13B,KAAKsF,SAASnL,aAAa,eAAkB6F,KAAKsF,SAASwwB,YAAYzvB,QAC1ErG,KAAKsF,SAAShC,aAAa,aAAco0B,GAG3C13B,KAAKsF,SAAShC,aAAa,yBAA0Bo0B,GACrD13B,KAAKsF,SAAS9B,gBAAgB,SAChC,CAEAi1B,SACMz4B,KAAK2Q,YAAc3Q,KAAK83B,WAC1B93B,KAAK83B,YAAa,GAIpB93B,KAAK83B,YAAa,EAElB93B,KAAKi6B,YAAY,KACXj6B,KAAK83B,YACP93B,KAAK6Q,QAEN7Q,KAAKuF,QAAQkyB,MAAM5mB,MACxB,CAEA2nB,SACMx4B,KAAK+4B,yBAIT/4B,KAAK83B,YAAa,EAElB93B,KAAKi6B,YAAY,KACVj6B,KAAK83B,YACR93B,KAAK4Q,QAEN5Q,KAAKuF,QAAQkyB,MAAM7mB,MACxB,CAEAqpB,YAAY/8B,EAASg9B,GACnB7rB,aAAarO,KAAK63B,UAClB73B,KAAK63B,SAAWx6B,WAAWH,EAASg9B,EACtC,CAEAnB,uBACE,OAAO3gC,OAAO8G,OAAOc,KAAK+3B,gBAAgB32B,UAAS,EACrD,CAEAiD,WAAWC,GACT,MAAM61B,EAAiB/2B,EAAYK,kBAAkBzD,KAAKsF,UAE1D,IAAK,MAAM80B,KAAiBhiC,OAAOd,KAAK6iC,GAClC3D,GAAsB1/B,IAAIsjC,WACrBD,EAAeC,GAW1B,OAPA91B,EAAS,IACJ61B,KACmB,iBAAX71B,GAAuBA,EAASA,EAAS,IAEtDA,EAAStE,KAAKuE,gBAAgBD,GAC9BA,EAAStE,KAAKwE,kBAAkBF,GAChCtE,KAAKyE,iBAAiBH,GACfA,CACT,CAEAE,kBAAkBF,GAkBhB,OAjBAA,EAAOizB,WAAiC,IAArBjzB,EAAOizB,UAAsBt+B,SAAS8B,KAAOhC,EAAWuL,EAAOizB,WAEtD,iBAAjBjzB,EAAOmzB,QAChBnzB,EAAOmzB,MAAQ,CACb5mB,KAAMvM,EAAOmzB,MACb7mB,KAAMtM,EAAOmzB,QAIW,iBAAjBnzB,EAAOozB,QAChBpzB,EAAOozB,MAAQpzB,EAAOozB,MAAMp/B,YAGA,iBAAnBgM,EAAOkwB,UAChBlwB,EAAOkwB,QAAUlwB,EAAOkwB,QAAQl8B,YAG3BgM,CACT,CAEAs1B,qBACE,MAAMt1B,EAAS,GAEf,IAAK,MAAO1N,EAAK8L,KAAUtK,OAAO+I,QAAQnB,KAAKuF,SACzCvF,KAAK2E,YAAYT,QAAQtN,KAAS8L,IACpC4B,EAAO1N,GAAO8L,GAUlB,OANA4B,EAAO3M,UAAW,EAClB2M,EAAOzC,QAAU,SAKVyC,CACT,CAEAq0B,iBACM34B,KAAKmrB,UACPnrB,KAAKmrB,QAAQtB,UACb7pB,KAAKmrB,QAAU,MAGbnrB,KAAKk4B,MACPl4B,KAAKk4B,IAAI3gC,SACTyI,KAAKk4B,IAAM,KAEf,CAGA,sBAAOv8B,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOmvB,GAAQ3xB,oBAAoBhG,KAAMsE,GAE/C,GAAsB,iBAAXA,EAAX,CAIA,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IANL,CAOF,EACF,EAOFnJ,EAAmBw8B,ICxmBnB,MAEM0C,GAAiB,kBACjBC,GAAmB,gBAEnBp2B,GAAU,IACXyzB,GAAQzzB,QACXswB,QAAS,GACT3a,OAAQ,CAAC,EAAG,GACZpH,UAAW,QACXmiB,SAAU,8IAKV/yB,QAAS,SAGLsC,GAAc,IACfwzB,GAAQxzB,YACXqwB,QAAS,kCAOX,MAAM+F,WAAgB5C,GAEpB,kBAAWzzB,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MAtCS,SAuCX,CAGAo9B,iBACE,OAAO54B,KAAKg5B,aAAeh5B,KAAKw6B,aAClC,CAGAtB,yBACE,MAAO,CACLmB,CAACA,IAAiBr6B,KAAKg5B,YACvBsB,CAACA,IAAmBt6B,KAAKw6B,cAE7B,CAEAA,cACE,OAAOx6B,KAAKi1B,yBAAyBj1B,KAAKuF,QAAQivB,QACpD,CAGA,sBAAO74B,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAO+xB,GAAQv0B,oBAAoBhG,KAAMsE,GAE/C,GAAsB,iBAAXA,EAAX,CAIA,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IANL,CAOF,EACF,EAOFnJ,EAAmBo/B,IC5EnB,MAEM70B,GAAY,gBAGZ+0B,GAAiB,WAAW/0B,KAC5Bg1B,GAAc,QAAQh1B,KACtB6F,GAAsB,OAAO7F,cAG7BgG,GAAoB,SAGpBivB,GAAwB,SAExBC,GAAqB,YAGrBC,GAAsB,GAAGD,mBAA+CA,uBAIxE12B,GAAU,CACd2V,OAAQ,KACRihB,WAAY,eACZC,cAAc,EACd59B,OAAQ,KACR69B,UAAW,CAAC,GAAK,GAAK,IAGlB72B,GAAc,CAClB0V,OAAQ,gBACRihB,WAAY,SACZC,aAAc,UACd59B,OAAQ,UACR69B,UAAW,SAOb,MAAMC,WAAkB71B,EACtBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAGftE,KAAKk7B,aAAe,IAAI1kC,IACxBwJ,KAAKm7B,oBAAsB,IAAI3kC,IAC/BwJ,KAAKo7B,aAA6D,YAA9C9hC,iBAAiB0G,KAAKsF,UAAUmY,UAA0B,KAAOzd,KAAKsF,SAC1FtF,KAAKq7B,cAAgB,KACrBr7B,KAAKs7B,UAAY,KACjBt7B,KAAKu7B,oBAAsB,CACzBC,gBAAiB,EACjBC,gBAAiB,GAEnBz7B,KAAK07B,SACP,CAGA,kBAAWx3B,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MArES,WAsEX,CAGAkgC,UACE17B,KAAK27B,mCACL37B,KAAK47B,2BAED57B,KAAKs7B,UACPt7B,KAAKs7B,UAAUO,aAEf77B,KAAKs7B,UAAYt7B,KAAK87B,kBAGxB,IAAK,MAAMC,KAAW/7B,KAAKm7B,oBAAoBj8B,SAC7Cc,KAAKs7B,UAAUU,QAAQD,EAE3B,CAEAt2B,UACEzF,KAAKs7B,UAAUO,aACfx2B,MAAMI,SACR,CAGAjB,kBAAkBF,GAWhB,OATAA,EAAOnH,OAASpE,EAAWuL,EAAOnH,SAAWlE,SAAS8B,KAGtDuJ,EAAOw2B,WAAax2B,EAAOuV,OAAS,GAAGvV,EAAOuV,oBAAsBvV,EAAOw2B,WAE3C,iBAArBx2B,EAAO02B,YAChB12B,EAAO02B,UAAY12B,EAAO02B,UAAUj+B,MAAM,KAAKuJ,IAAI5D,GAAS9F,OAAOC,WAAW6F,KAGzE4B,CACT,CAEAs3B,2BACO57B,KAAKuF,QAAQw1B,eAKlBx6B,EAAaC,IAAIR,KAAKuF,QAAQpI,OAAQu9B,IAEtCn6B,EAAac,GAAGrB,KAAKuF,QAAQpI,OAAQu9B,GAAaC,GAAuBv7B,IACvE,MAAM68B,EAAoBj8B,KAAKm7B,oBAAoBnkC,IAAIoI,EAAMjC,OAAOwf,MACpE,GAAIsf,EAAmB,CACrB78B,EAAMmD,iBACN,MAAM/H,EAAOwF,KAAKo7B,cAAgBxjC,OAC5Bye,EAAS4lB,EAAkBtlB,UAAY3W,KAAKsF,SAASqR,UAC3D,GAAInc,EAAK0hC,SAEP,YADA1hC,EAAK0hC,SAAS,CAAExqB,IAAK2E,EAAQ8lB,SAAU,WAKzC3hC,EAAK0iB,UAAY7G,CACnB,IAEJ,CAEAylB,kBACE,MAAMnnB,EAAU,CACdna,KAAMwF,KAAKo7B,aACXJ,UAAWh7B,KAAKuF,QAAQy1B,UACxBF,WAAY96B,KAAKuF,QAAQu1B,YAG3B,OAAO,IAAIsB,qBAAqBj7B,GAAWnB,KAAKq8B,kBAAkBl7B,GAAUwT,EAC9E,CAGA0nB,kBAAkBl7B,GAChB,MAAMm7B,EAAgBxH,GAAS90B,KAAKk7B,aAAalkC,IAAI,IAAI89B,EAAM33B,OAAOlF,MAChEm2B,EAAW0G,IACf90B,KAAKu7B,oBAAoBC,gBAAkB1G,EAAM33B,OAAOwZ,UACxD3W,KAAKu8B,SAASD,EAAcxH,KAGxB2G,GAAmBz7B,KAAKo7B,cAAgBniC,SAASoB,iBAAiB6iB,UAClEsf,EAAkBf,GAAmBz7B,KAAKu7B,oBAAoBE,gBACpEz7B,KAAKu7B,oBAAoBE,gBAAkBA,EAE3C,IAAK,MAAM3G,KAAS3zB,EAAS,CAC3B,IAAK2zB,EAAM2H,eAAgB,CACzBz8B,KAAKq7B,cAAgB,KACrBr7B,KAAK08B,kBAAkBJ,EAAcxH,IAErC,QACF,CAEA,MAAM6H,EAA2B7H,EAAM33B,OAAOwZ,WAAa3W,KAAKu7B,oBAAoBC,gBAEpF,GAAIgB,GAAmBG,GAGrB,GAFAvO,EAAS0G,IAEJ2G,EACH,YAOCe,GAAoBG,GACvBvO,EAAS0G,EAEb,CACF,CAEA6G,mCACE37B,KAAKk7B,aAAe,IAAI1kC,IACxBwJ,KAAKm7B,oBAAsB,IAAI3kC,IAE/B,MAAMomC,EAAcn2B,EAAetH,KAAKw7B,GAAuB36B,KAAKuF,QAAQpI,QAE5E,IAAK,MAAM0/B,KAAUD,EAAa,CAEhC,IAAKC,EAAOlgB,MAAQ/iB,EAAWijC,GAC7B,SAGF,MAAMZ,EAAoBx1B,EAAeG,QAAQk2B,UAAUD,EAAOlgB,MAAO3c,KAAKsF,UAG1EnM,EAAU8iC,KACZj8B,KAAKk7B,aAAaxkC,IAAIomC,UAAUD,EAAOlgB,MAAOkgB,GAC9C78B,KAAKm7B,oBAAoBzkC,IAAImmC,EAAOlgB,KAAMsf,GAE9C,CACF,CAEAM,SAASp/B,GACH6C,KAAKq7B,gBAAkBl+B,IAI3B6C,KAAK08B,kBAAkB18B,KAAKuF,QAAQpI,QACpC6C,KAAKq7B,cAAgBl+B,EACrBA,EAAOpD,UAAUuQ,IAAIoB,IACrB1L,KAAK+8B,iBAAiB5/B,GAEtBoD,EAAasB,QAAQ7B,KAAKsF,SAAUm1B,GAAgB,CAAE36B,cAAe3C,IACvE,CAEA4/B,iBAAiB5/B,GAEf,GAAIA,EAAOpD,UAAUC,SAlNQ,iBAmN3ByM,EAAeG,QAxMY,mBAwMsBzJ,EAAO1D,QAzMpC,cA0MjBM,UAAUuQ,IAAIoB,SAInB,IAAK,MAAMsxB,KAAav2B,EAAeO,QAAQ7J,EAnNnB,qBAsN1B,IAAK,MAAMsY,KAAQhP,EAAeS,KAAK81B,EAAWnC,IAChDplB,EAAK1b,UAAUuQ,IAAIoB,GAGzB,CAEAgxB,kBAAkBzsB,GAChBA,EAAOlW,UAAUxC,OAAOmU,IAExB,MAAMuxB,EAAcx2B,EAAetH,KAAK,GAAGw7B,MAAyBjvB,KAAqBuE,GACzF,IAAK,MAAMuD,KAAQypB,EACjBzpB,EAAKzZ,UAAUxC,OAAOmU,GAE1B,CAGA,sBAAO/P,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOyyB,GAAUj1B,oBAAoBhG,KAAMsE,GAEjD,GAAsB,iBAAXA,EAAX,CAIA,QAAqBmE,IAAjBD,EAAKlE,IAAyBA,EAAO7C,WAAW,MAAmB,gBAAX6C,EAC1D,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IANL,CAOF,EACF,EAOF/D,EAAac,GAAGzJ,OAAQ2T,GAAqB,KAC3C,IAAK,MAAM2xB,KAAOz2B,EAAetH,KA9PT,0BA+PtB87B,GAAUj1B,oBAAoBk3B,KAQlC/hC,EAAmB8/B,ICrRnB,MAEMv1B,GAAY,UAEZiK,GAAa,OAAOjK,KACpBkK,GAAe,SAASlK,KACxB+J,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KACtB8F,GAAuB,QAAQ9F,KAC/ByF,GAAgB,UAAUzF,KAC1B6F,GAAsB,OAAO7F,KAE7BiF,GAAiB,YACjBC,GAAkB,aAClBuf,GAAe,UACfC,GAAiB,YACjB+S,GAAW,OACXC,GAAU,MAEV1xB,GAAoB,SACpB+qB,GAAkB,OAClB5mB,GAAkB,OAGlBwtB,GAA2B,mBAE3BC,GAA+B,QAAQD,MAKvC30B,GAAuB,2EACvB60B,GAAsB,YAFOD,uBAAiDA,mBAA6CA,OAE/E50B,KAE5C80B,GAA8B,IAAI9xB,8BAA6CA,+BAA8CA,4BAMnI,MAAM+xB,WAAYr4B,EAChBT,YAAYhO,GACV0O,MAAM1O,GACNqJ,KAAKorB,QAAUprB,KAAKsF,SAAS7L,QAfN,uCAiBlBuG,KAAKorB,UAOVprB,KAAK09B,sBAAsB19B,KAAKorB,QAASprB,KAAK29B,gBAE9Cp9B,EAAac,GAAGrB,KAAKsF,SAAU6F,GAAe/L,GAASY,KAAK+N,SAAS3O,IACvE,CAGA,eAAW5D,GACT,MA3DS,KA4DX,CAGAqV,OACE,MAAM+sB,EAAY59B,KAAKsF,SACvB,GAAItF,KAAK69B,cAAcD,GACrB,OAIF,MAAME,EAAS99B,KAAK+9B,iBAEdC,EAAYF,EAChBv9B,EAAasB,QAAQi8B,EAAQnuB,GAAY,CAAE7P,cAAe89B,IAC1D,KAEgBr9B,EAAasB,QAAQ+7B,EAAWnuB,GAAY,CAAE3P,cAAeg+B,IAEjE77B,kBAAqB+7B,GAAaA,EAAU/7B,mBAI1DjC,KAAKi+B,YAAYH,EAAQF,GACzB59B,KAAKk+B,UAAUN,EAAWE,GAC5B,CAGAI,UAAUvnC,EAASwnC,GACZxnC,IAILA,EAAQoD,UAAUuQ,IAAIoB,IAEtB1L,KAAKk+B,UAAUz3B,EAAekB,uBAAuBhR,IAgBrDqJ,KAAK6F,eAdYwL,KACsB,QAAjC1a,EAAQwD,aAAa,SAKzBxD,EAAQ6M,gBAAgB,YACxB7M,EAAQ2M,aAAa,iBAAiB,GACtCtD,KAAKo+B,gBAAgBznC,GAAS,GAC9B4J,EAAasB,QAAQlL,EAAS+Y,GAAa,CACzC5P,cAAeq+B,KARfxnC,EAAQoD,UAAUuQ,IAAIuF,KAYIlZ,EAASA,EAAQoD,UAAUC,SAASy8B,KACpE,CAEAwH,YAAYtnC,EAASwnC,GACdxnC,IAILA,EAAQoD,UAAUxC,OAAOmU,IACzB/U,EAAQq7B,OAERhyB,KAAKi+B,YAAYx3B,EAAekB,uBAAuBhR,IAcvDqJ,KAAK6F,eAZYwL,KACsB,QAAjC1a,EAAQwD,aAAa,SAKzBxD,EAAQ2M,aAAa,iBAAiB,GACtC3M,EAAQ2M,aAAa,WAAY,MACjCtD,KAAKo+B,gBAAgBznC,GAAS,GAC9B4J,EAAasB,QAAQlL,EAASiZ,GAAc,CAAE9P,cAAeq+B,KAP3DxnC,EAAQoD,UAAUxC,OAAOsY,KAUClZ,EAASA,EAAQoD,UAAUC,SAASy8B,KACpE,CAEA1oB,SAAS3O,GACP,IAAM,CAACuL,GAAgBC,GAAiBuf,GAAcC,GAAgB+S,GAAUC,IAASh8B,SAAShC,EAAMxI,KACtG,OAGFwI,EAAM2tB,kBACN3tB,EAAMmD,iBAEN,MAAMsE,EAAW7G,KAAK29B,eAAe95B,OAAOlN,IAAYiD,EAAWjD,IACnE,IAAI0nC,EAEJ,GAAI,CAAClB,GAAUC,IAASh8B,SAAShC,EAAMxI,KACrCynC,EAAoBx3B,EAASzH,EAAMxI,MAAQumC,GAAW,EAAIt2B,EAAS7N,OAAS,OACvE,CACL,MAAM2V,EAAS,CAAC/D,GAAiBwf,IAAgBhpB,SAAShC,EAAMxI,KAChEynC,EAAoB/gC,EAAqBuJ,EAAUzH,EAAMjC,OAAQwR,GAAQ,EAC3E,CAEI0vB,IACFA,EAAkB5S,MAAM,CAAE6S,eAAe,IACzCb,GAAIz3B,oBAAoBq4B,GAAmBxtB,OAE/C,CAEA8sB,eACE,OAAOl3B,EAAetH,KAAKo+B,GAAqBv9B,KAAKorB,QACvD,CAEA2S,iBACE,OAAO/9B,KAAK29B,eAAex+B,KAAK2H,GAAS9G,KAAK69B,cAAc/2B,KAAW,IACzE,CAEA42B,sBAAsBztB,EAAQpJ,GAC5B7G,KAAKu+B,yBAAyBtuB,EAAQ,OAAQ,WAE9C,IAAK,MAAMnJ,KAASD,EAClB7G,KAAKw+B,6BAA6B13B,EAEtC,CAEA03B,6BAA6B13B,GAC3BA,EAAQ9G,KAAKy+B,iBAAiB33B,GAC9B,MAAM43B,EAAW1+B,KAAK69B,cAAc/2B,GAC9B63B,EAAY3+B,KAAK4+B,iBAAiB93B,GACxCA,EAAMxD,aAAa,gBAAiBo7B,GAEhCC,IAAc73B,GAChB9G,KAAKu+B,yBAAyBI,EAAW,OAAQ,gBAG9CD,GACH53B,EAAMxD,aAAa,WAAY,MAGjCtD,KAAKu+B,yBAAyBz3B,EAAO,OAAQ,OAG7C9G,KAAK6+B,mCAAmC/3B,EAC1C,CAEA+3B,mCAAmC/3B,GACjC,MAAM3J,EAASsJ,EAAekB,uBAAuBb,GAEhD3J,IAIL6C,KAAKu+B,yBAAyBphC,EAAQ,OAAQ,YAE1C2J,EAAM7O,IACR+H,KAAKu+B,yBAAyBphC,EAAQ,kBAAmB,GAAG2J,EAAM7O,MAEtE,CAEAmmC,gBAAgBznC,EAASmoC,GACvB,MAAMH,EAAY3+B,KAAK4+B,iBAAiBjoC,GACxC,IAAKgoC,EAAU5kC,UAAUC,SAhMN,YAiMjB,OAGF,MAAM4O,EAASA,CAACjR,EAAUs1B,KACxB,MAAMt2B,EAAU8P,EAAeG,QAAQjP,EAAUgnC,GAC7ChoC,GACFA,EAAQoD,UAAU6O,OAAOqkB,EAAW6R,IAIxCl2B,EAAOy0B,GAA0B3xB,IACjC9C,EAzM2B,iBAyMIiH,IAC/B8uB,EAAUr7B,aAAa,gBAAiBw7B,EAC1C,CAEAP,yBAAyB5nC,EAASqe,EAAWtS,GACtC/L,EAAQuD,aAAa8a,IACxBre,EAAQ2M,aAAa0R,EAAWtS,EAEpC,CAEAm7B,cAAcvtB,GACZ,OAAOA,EAAKvW,UAAUC,SAAS0R,GACjC,CAGA+yB,iBAAiBnuB,GACf,OAAOA,EAAKvJ,QAAQw2B,IAAuBjtB,EAAO7J,EAAeG,QAAQ22B,GAAqBjtB,EAChG,CAGAsuB,iBAAiBtuB,GACf,OAAOA,EAAK7W,QA1NO,gCA0NoB6W,CACzC,CAGA,sBAAO3U,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAOi1B,GAAIz3B,oBAAoBhG,MAErC,GAAsB,iBAAXsE,EAAX,CAIA,QAAqBmE,IAAjBD,EAAKlE,IAAyBA,EAAO7C,WAAW,MAAmB,gBAAX6C,EAC1D,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,IANL,CAOF,EACF,EAOF/D,EAAac,GAAGpI,SAAUuS,GAAsB9C,GAAsB,SAAUtJ,GAC1E,CAAC,IAAK,QAAQgC,SAASpB,KAAKiI,UAC9B7I,EAAMmD,iBAGJ3I,EAAWoG,OAIfy9B,GAAIz3B,oBAAoBhG,MAAM6Q,MAChC,GAKAtQ,EAAac,GAAGzJ,OAAQ2T,GAAqB,KAC3C,IAAK,MAAM5U,KAAW8P,EAAetH,KAAKq+B,IACxCC,GAAIz3B,oBAAoBrP,KAO5BwE,EAAmBsiC,ICxSnB,MAEM/3B,GAAY,YAEZq5B,GAAkB,YAAYr5B,KAC9Bs5B,GAAiB,WAAWt5B,KAC5BkoB,GAAgB,UAAUloB,KAC1Bu5B,GAAiB,WAAWv5B,KAC5BiK,GAAa,OAAOjK,KACpBkK,GAAe,SAASlK,KACxB+J,GAAa,OAAO/J,KACpBgK,GAAc,QAAQhK,KAGtBw5B,GAAkB,OAClBrvB,GAAkB,OAClB+hB,GAAqB,UAErBztB,GAAc,CAClBmzB,UAAW,UACX6H,SAAU,UACV1H,MAAO,UAGHvzB,GAAU,CACdozB,WAAW,EACX6H,UAAU,EACV1H,MAAO,KAOT,MAAM2H,WAAch6B,EAClBT,YAAYhO,EAAS2N,GACnBe,MAAM1O,EAAS2N,GAEftE,KAAK63B,SAAW,KAChB73B,KAAKq/B,sBAAuB,EAC5Br/B,KAAKs/B,yBAA0B,EAC/Bt/B,KAAKm4B,eACP,CAGA,kBAAWj0B,GACT,OAAOA,EACT,CAEA,sBAAWC,GACT,OAAOA,EACT,CAEA,eAAW3I,GACT,MAtDS,OAuDX,CAGAqV,OACoBtQ,EAAasB,QAAQ7B,KAAKsF,SAAUmK,IAExCxN,mBAIdjC,KAAKu/B,gBAEDv/B,KAAKuF,QAAQ+xB,WACft3B,KAAKsF,SAASvL,UAAUuQ,IAvDN,QAiEpBtK,KAAKsF,SAASvL,UAAUxC,OAAO2nC,IAC/BvkC,EAAOqF,KAAKsF,UACZtF,KAAKsF,SAASvL,UAAUuQ,IAAIuF,GAAiB+hB,IAE7C5xB,KAAK6F,eAXYwL,KACfrR,KAAKsF,SAASvL,UAAUxC,OAAOq6B,IAC/BrxB,EAAasB,QAAQ7B,KAAKsF,SAAUoK,IAEpC1P,KAAKw/B,sBAOuBx/B,KAAKsF,SAAUtF,KAAKuF,QAAQ+xB,WAC5D,CAEA1mB,OACO5Q,KAAKy/B,YAIQl/B,EAAasB,QAAQ7B,KAAKsF,SAAUqK,IAExC1N,mBAUdjC,KAAKsF,SAASvL,UAAUuQ,IAAIsnB,IAC5B5xB,KAAK6F,eAPYwL,KACfrR,KAAKsF,SAASvL,UAAUuQ,IAAI40B,IAC5Bl/B,KAAKsF,SAASvL,UAAUxC,OAAOq6B,GAAoB/hB,IACnDtP,EAAasB,QAAQ7B,KAAKsF,SAAUsK,KAIR5P,KAAKsF,SAAUtF,KAAKuF,QAAQ+xB,YAC5D,CAEA7xB,UACEzF,KAAKu/B,gBAEDv/B,KAAKy/B,WACPz/B,KAAKsF,SAASvL,UAAUxC,OAAOsY,IAGjCxK,MAAMI,SACR,CAEAg6B,UACE,OAAOz/B,KAAKsF,SAASvL,UAAUC,SAAS6V,GAC1C,CAGA2vB,qBACOx/B,KAAKuF,QAAQ45B,WAIdn/B,KAAKq/B,sBAAwBr/B,KAAKs/B,0BAItCt/B,KAAK63B,SAAWx6B,WAAW,KACzB2C,KAAK4Q,QACJ5Q,KAAKuF,QAAQkyB,QAClB,CAEAiI,eAAetgC,EAAOugC,GACpB,OAAQvgC,EAAMqB,MACZ,IAAK,YACL,IAAK,WACHT,KAAKq/B,qBAAuBM,EAC5B,MAGF,IAAK,UACL,IAAK,WACH3/B,KAAKs/B,wBAA0BK,EASnC,GAAIA,EAEF,YADA3/B,KAAKu/B,gBAIP,MAAM3wB,EAAcxP,EAAMU,cACtBE,KAAKsF,WAAasJ,GAAe5O,KAAKsF,SAAStL,SAAS4U,IAI5D5O,KAAKw/B,oBACP,CAEArH,gBACE53B,EAAac,GAAGrB,KAAKsF,SAAUy5B,GAAiB3/B,GAASY,KAAK0/B,eAAetgC,GAAO,IACpFmB,EAAac,GAAGrB,KAAKsF,SAAU05B,GAAgB5/B,GAASY,KAAK0/B,eAAetgC,GAAO,IACnFmB,EAAac,GAAGrB,KAAKsF,SAAUsoB,GAAexuB,GAASY,KAAK0/B,eAAetgC,GAAO,IAClFmB,EAAac,GAAGrB,KAAKsF,SAAU25B,GAAgB7/B,GAASY,KAAK0/B,eAAetgC,GAAO,GACrF,CAEAmgC,gBACElxB,aAAarO,KAAK63B,UAClB73B,KAAK63B,SAAW,IAClB,CAGA,sBAAOl8B,CAAgB2I,GACrB,OAAOtE,KAAKuI,KAAK,WACf,MAAMC,EAAO42B,GAAMp5B,oBAAoBhG,KAAMsE,GAE7C,GAAsB,iBAAXA,EAAqB,CAC9B,QAA4B,IAAjBkE,EAAKlE,GACd,MAAM,IAAIY,UAAU,oBAAoBZ,MAG1CkE,EAAKlE,GAAQtE,KACf,CACF,EACF,E,OAOF6H,EAAqBu3B,IAMrBjkC,EAAmBikC,ICzMJ,CACbh3B,QACAO,SACA4D,YACA2D,YACAgb,YACAmF,SACA0B,aACAwI,WACAU,aACAwC,OACA2B,SACAzH,W","ignoreList":[]} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js b/extensions/pagetop-bootsier/static/js/bootstrap.min.js deleted file mode 100644 index 7f2bc627..00000000 --- a/extensions/pagetop-bootsier/static/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.3.8 (https://getbootstrap.com/) - * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,function(t){"use strict";function e(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t)for(const i in t)if("default"!==i){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}return e.default=t,Object.freeze(e)}const i=e(t),s=new Map,n={set(t,e,i){s.has(t)||s.set(t,new Map);const n=s.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>s.has(t)&&s.get(t).get(e)||null,remove(t,e){if(!s.has(t))return;const i=s.get(t);i.delete(e),0===i.size&&s.delete(t)}},o="transitionend",r=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),a=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),l=t=>{t.dispatchEvent(new Event(o))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(r(t)):null,d=t=>{if(!c(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},u=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),_=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?_(t.parentNode):null},g=()=>{},f=t=>{t.offsetHeight},m=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],b=()=>"rtl"===document.documentElement.dir,v=t=>{var e;e=()=>{const e=m();if(e){const i=t.NAME,s=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=s,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},y=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,w=(t,e,i=!0)=>{if(!i)return void y(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let n=!1;const r=({target:i})=>{i===e&&(n=!0,e.removeEventListener(o,r),y(t))};e.addEventListener(o,r),setTimeout(()=>{n||l(e)},s)},A=(t,e,i,s)=>{const n=t.length;let o=t.indexOf(e);return-1===o?!i&&s?t[n-1]:t[0]:(o+=i?1:-1,s&&(o=(o+n)%n),t[Math.max(0,Math.min(o,n-1))])},E=/[^.]*(?=\..*)\.|.*/,C=/\..*/,T=/::\d+$/,k={};let $=1;const S={mouseenter:"mouseover",mouseleave:"mouseout"},L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${$++}`||t.uidEvent||$++}function I(t){const e=O(t);return t.uidEvent=e,k[e]=k[e]||{},k[e]}function D(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function N(t,e,i){const s="string"==typeof e,n=s?i:e||i;let o=j(t);return L.has(o)||(o=t),[s,n,o]}function P(t,e,i,s,n){if("string"!=typeof e||!t)return;let[o,r,a]=N(e,i,s);if(e in S){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=I(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=O(r,e.replace(E,"")),u=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return z(n,{delegateTarget:r}),s.oneOff&&F.off(t,n.type,e,i),i.apply(r,[n])}}(t,i,r):function(t,e){return function i(s){return z(s,{delegateTarget:t}),i.oneOff&&F.off(t,s.type,e),e.apply(t,[s])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function x(t,e,i,s,n){const o=D(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}function M(t,e,i,s){const n=e[i]||{};for(const[o,r]of Object.entries(n))o.includes(s)&&x(t,e,i,r.callable,r.delegationSelector)}function j(t){return t=t.replace(C,""),S[t]||t}const F={on(t,e,i,s){P(t,e,i,s,!1)},one(t,e,i,s){P(t,e,i,s,!0)},off(t,e,i,s){if("string"!=typeof e||!t)return;const[n,o,r]=N(e,i,s),a=r!==e,l=I(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))M(t,l,i,e.slice(1));for(const[i,s]of Object.entries(c)){const n=i.replace(T,"");a&&!e.includes(n)||x(t,l,r,s.callable,s.delegationSelector)}}else{if(!Object.keys(c).length)return;x(t,l,r,o,n?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const s=m();let n=null,o=!0,r=!0,a=!1;e!==j(e)&&s&&(n=s.Event(e,i),s(t).trigger(n),o=!n.isPropagationStopped(),r=!n.isImmediatePropagationStopped(),a=n.isDefaultPrevented());const l=z(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&n&&n.preventDefault(),l}};function z(t,e={}){for(const[i,s]of Object.entries(e))try{t[i]=s}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>s})}return t}function H(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function B(t){return t.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`)}const q={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${B(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${B(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter(t=>t.startsWith("bs")&&!t.startsWith("bsConfig"));for(const s of i){let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1),e[i]=H(t.dataset[s])}return e},getDataAttribute:(t,e)=>H(t.getAttribute(`data-bs-${B(e)}`))};class W{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=c(e)?q.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...c(e)?q.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[i,s]of Object.entries(e)){const e=t[i],n=c(e)?"element":a(e);if(!new RegExp(s).test(n))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${n}" but expected type "${s}".`)}}}class R extends W{constructor(t,e){super(),(t=h(t))&&(this._element=t,this._config=this._getConfig(e),n.set(this._element,this.constructor.DATA_KEY,this))}dispose(){n.remove(this._element,this.constructor.DATA_KEY),F.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){w(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return n.get(h(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.8"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const K=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map(t=>r(t)).join(","):null},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let s=t.parentNode.closest(e);for(;s;)i.push(s),s=s.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,t).filter(t=>!u(t)&&d(t))},getSelectorFromElement(t){const e=K(t);return e&&V.findOne(e)?e:null},getElementFromSelector(t){const e=K(t);return e?V.findOne(e):null},getMultipleElementsFromSelector(t){const e=K(t);return e?V.find(e):[]}},Q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;F.on(document,i,`[data-bs-dismiss="${s}"]`,function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),u(this))return;const n=V.getElementFromSelector(this)||this.closest(`.${s}`);t.getOrCreateInstance(n)[e]()})},X=".bs.alert",Y=`close${X}`,U=`closed${X}`;class G extends R{static get NAME(){return"alert"}close(){if(F.trigger(this._element,Y).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),F.trigger(this._element,U),this.dispose()}static jQueryInterface(t){return this.each(function(){const e=G.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}Q(G,"close"),v(G);const J='[data-bs-toggle="button"]';class Z extends R{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each(function(){const e=Z.getOrCreateInstance(this);"toggle"===t&&e[t]()})}}F.on(document,"click.bs.button.data-api",J,t=>{t.preventDefault();const e=t.target.closest(J);Z.getOrCreateInstance(e).toggle()}),v(Z);const tt=".bs.swipe",et=`touchstart${tt}`,it=`touchmove${tt}`,st=`touchend${tt}`,nt=`pointerdown${tt}`,ot=`pointerup${tt}`,rt={endCallback:null,leftCallback:null,rightCallback:null},at={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class lt extends W{constructor(t,e){super(),this._element=t,t&&lt.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return rt}static get DefaultType(){return at}static get NAME(){return"swipe"}dispose(){F.off(this._element,tt)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),y(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&y(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(F.on(this._element,nt,t=>this._start(t)),F.on(this._element,ot,t=>this._end(t)),this._element.classList.add("pointer-event")):(F.on(this._element,et,t=>this._start(t)),F.on(this._element,it,t=>this._move(t)),F.on(this._element,st,t=>this._end(t)))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ct=".bs.carousel",ht=".data-api",dt="ArrowLeft",ut="ArrowRight",_t="next",gt="prev",ft="left",mt="right",pt=`slide${ct}`,bt=`slid${ct}`,vt=`keydown${ct}`,yt=`mouseenter${ct}`,wt=`mouseleave${ct}`,At=`dragstart${ct}`,Et=`load${ct}${ht}`,Ct=`click${ct}${ht}`,Tt="carousel",kt="active",$t=".active",St=".carousel-item",Lt=$t+St,Ot={[dt]:mt,[ut]:ft},It={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Dt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class Nt extends R{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===Tt&&this.cycle()}static get Default(){return It}static get DefaultType(){return Dt}static get NAME(){return"carousel"}next(){this._slide(_t)}nextWhenVisible(){!document.hidden&&d(this._element)&&this.next()}prev(){this._slide(gt)}pause(){this._isSliding&&l(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?F.one(this._element,bt,()=>this.cycle()):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void F.one(this._element,bt,()=>this.to(t));const i=this._getItemIndex(this._getActive());if(i===t)return;const s=t>i?_t:gt;this._slide(s,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&F.on(this._element,vt,t=>this._keydown(t)),"hover"===this._config.pause&&(F.on(this._element,yt,()=>this.pause()),F.on(this._element,wt,()=>this._maybeEnableCycle())),this._config.touch&&lt.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of V.find(".carousel-item img",this._element))F.on(t,At,t=>t.preventDefault());const t={leftCallback:()=>this._slide(this._directionToOrder(ft)),rightCallback:()=>this._slide(this._directionToOrder(mt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new lt(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Ot[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=V.findOne($t,this._indicatorsElement);e.classList.remove(kt),e.removeAttribute("aria-current");const i=V.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(kt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),s=t===_t,n=e||A(this._getItems(),i,s,this._config.wrap);if(n===i)return;const o=this._getItemIndex(n),r=e=>F.trigger(this._element,e,{relatedTarget:n,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(pt).defaultPrevented)return;if(!i||!n)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=n;const l=s?"carousel-item-start":"carousel-item-end",c=s?"carousel-item-next":"carousel-item-prev";n.classList.add(c),f(n),i.classList.add(l),n.classList.add(l),this._queueCallback(()=>{n.classList.remove(l,c),n.classList.add(kt),i.classList.remove(kt,c,l),this._isSliding=!1,r(bt)},i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return V.findOne(Lt,this._element)}_getItems(){return V.find(St,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return b()?t===ft?gt:_t:t===ft?_t:gt}_orderToDirection(t){return b()?t===gt?ft:mt:t===gt?mt:ft}static jQueryInterface(t){return this.each(function(){const e=Nt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)})}}F.on(document,Ct,"[data-bs-slide], [data-bs-slide-to]",function(t){const e=V.getElementFromSelector(this);if(!e||!e.classList.contains(Tt))return;t.preventDefault();const i=Nt.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===q.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())}),F.on(window,Et,()=>{const t=V.find('[data-bs-ride="carousel"]');for(const e of t)Nt.getOrCreateInstance(e)}),v(Nt);const Pt=".bs.collapse",xt=`show${Pt}`,Mt=`shown${Pt}`,jt=`hide${Pt}`,Ft=`hidden${Pt}`,zt=`click${Pt}.data-api`,Ht="show",Bt="collapse",qt="collapsing",Wt=`:scope .${Bt} .${Bt}`,Rt='[data-bs-toggle="collapse"]',Kt={parent:null,toggle:!0},Vt={parent:"(null|element)",toggle:"boolean"};class Qt extends R{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=V.find(Rt);for(const t of i){const e=V.getSelectorFromElement(t),i=V.find(e).filter(t=>t===this._element);null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Kt}static get DefaultType(){return Vt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter(t=>t!==this._element).map(t=>Qt.getOrCreateInstance(t,{toggle:!1}))),t.length&&t[0]._isTransitioning)return;if(F.trigger(this._element,xt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Bt),this._element.classList.add(qt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(qt),this._element.classList.add(Bt,Ht),this._element.style[e]="",F.trigger(this._element,Mt)},this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(F.trigger(this._element,jt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,f(this._element),this._element.classList.add(qt),this._element.classList.remove(Bt,Ht);for(const t of this._triggerArray){const e=V.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(qt),this._element.classList.add(Bt),F.trigger(this._element,Ft)},this._element,!0)}_isShown(t=this._element){return t.classList.contains(Ht)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=h(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Rt);for(const e of t){const t=V.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=V.find(Wt,this._config.parent);return V.find(t,this._config.parent).filter(t=>!e.includes(t))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each(function(){const i=Qt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}})}}F.on(document,zt,Rt,function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of V.getMultipleElementsFromSelector(this))Qt.getOrCreateInstance(t,{toggle:!1}).toggle()}),v(Qt);const Xt="dropdown",Yt=".bs.dropdown",Ut=".data-api",Gt="ArrowUp",Jt="ArrowDown",Zt=`hide${Yt}`,te=`hidden${Yt}`,ee=`show${Yt}`,ie=`shown${Yt}`,se=`click${Yt}${Ut}`,ne=`keydown${Yt}${Ut}`,oe=`keyup${Yt}${Ut}`,re="show",ae='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',le=`${ae}.${re}`,ce=".dropdown-menu",he=b()?"top-end":"top-start",de=b()?"top-start":"top-end",ue=b()?"bottom-end":"bottom-start",_e=b()?"bottom-start":"bottom-end",ge=b()?"left-start":"right-start",fe=b()?"right-start":"left-start",me={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},pe={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class be extends R{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=V.next(this._element,ce)[0]||V.prev(this._element,ce)[0]||V.findOne(ce,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return me}static get DefaultType(){return pe}static get NAME(){return Xt}toggle(){return this._isShown()?this.hide():this.show()}show(){if(u(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!F.trigger(this._element,ee,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))F.on(t,"mouseover",g);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(re),this._element.classList.add(re),F.trigger(this._element,ie,t)}}hide(){if(u(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!F.trigger(this._element,Zt,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.off(t,"mouseover",g);this._popper&&this._popper.destroy(),this._menu.classList.remove(re),this._element.classList.remove(re),this._element.setAttribute("aria-expanded","false"),q.removeDataAttribute(this._menu,"popper"),F.trigger(this._element,te,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!c(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Xt.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){if(void 0===i)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org/docs/v2/)");let t=this._element;"parent"===this._config.reference?t=this._parent:c(this._config.reference)?t=h(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=i.createPopper(t,this._menu,e)}_isShown(){return this._menu.classList.contains(re)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return ge;if(t.classList.contains("dropstart"))return fe;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?de:he:e?_e:ue}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(q.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...y(this._config.popperConfig,[void 0,t])}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(t=>d(t));i.length&&A(i,e,t===Jt,!i.includes(e)).focus()}static jQueryInterface(t){return this.each(function(){const e=be.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=V.find(le);for(const i of e){const e=be.getInstance(i);if(!e||!1===e._config.autoClose)continue;const s=t.composedPath(),n=s.includes(e._menu);if(s.includes(e._element)||"inside"===e._config.autoClose&&!n||"outside"===e._config.autoClose&&n)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,s=[Gt,Jt].includes(t.key);if(!s&&!i)return;if(e&&!i)return;t.preventDefault();const n=this.matches(ae)?this:V.prev(this,ae)[0]||V.next(this,ae)[0]||V.findOne(ae,t.delegateTarget.parentNode),o=be.getOrCreateInstance(n);if(s)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),n.focus())}}F.on(document,ne,ae,be.dataApiKeydownHandler),F.on(document,ne,ce,be.dataApiKeydownHandler),F.on(document,se,be.clearMenus),F.on(document,oe,be.clearMenus),F.on(document,se,ae,function(t){t.preventDefault(),be.getOrCreateInstance(this).toggle()}),v(be);const ve="backdrop",ye="show",we=`mousedown.bs.${ve}`,Ae={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Ee={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ce extends W{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Ae}static get DefaultType(){return Ee}static get NAME(){return ve}show(t){if(!this._config.isVisible)return void y(t);this._append();const e=this._getElement();this._config.isAnimated&&f(e),e.classList.add(ye),this._emulateAnimation(()=>{y(t)})}hide(t){this._config.isVisible?(this._getElement().classList.remove(ye),this._emulateAnimation(()=>{this.dispose(),y(t)})):y(t)}dispose(){this._isAppended&&(F.off(this._element,we),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=h(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),F.on(t,we,()=>{y(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(t){w(t,this._getElement(),this._config.isAnimated)}}const Te=".bs.focustrap",ke=`focusin${Te}`,$e=`keydown.tab${Te}`,Se="backward",Le={autofocus:!0,trapElement:null},Oe={autofocus:"boolean",trapElement:"element"};class Ie extends W{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Le}static get DefaultType(){return Oe}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),F.off(document,Te),F.on(document,ke,t=>this._handleFocusin(t)),F.on(document,$e,t=>this._handleKeydown(t)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,F.off(document,Te))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=V.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Se?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Se:"forward")}}const De=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Ne=".sticky-top",Pe="padding-right",xe="margin-right";class Me{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Pe,e=>e+t),this._setElementAttributes(De,Pe,e=>e+t),this._setElementAttributes(Ne,xe,e=>e-t)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Pe),this._resetElementAttributes(De,Pe),this._resetElementAttributes(Ne,xe)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const s=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+s)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(n))}px`)})}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&q.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=q.getDataAttribute(t,e);null!==i?(q.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)})}_applyManipulationCallback(t,e){if(c(t))e(t);else for(const i of V.find(t,this._element))e(i)}}const je=".bs.modal",Fe=`hide${je}`,ze=`hidePrevented${je}`,He=`hidden${je}`,Be=`show${je}`,qe=`shown${je}`,We=`resize${je}`,Re=`click.dismiss${je}`,Ke=`mousedown.dismiss${je}`,Ve=`keydown.dismiss${je}`,Qe=`click${je}.data-api`,Xe="modal-open",Ye="show",Ue="modal-static",Ge={backdrop:!0,focus:!0,keyboard:!0},Je={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ze extends R{constructor(t,e){super(t,e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Me,this._addEventListeners()}static get Default(){return Ge}static get DefaultType(){return Je}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||F.trigger(this._element,Be,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Xe),this._adjustDialog(),this._backdrop.show(()=>this._showElement(t)))}hide(){this._isShown&&!this._isTransitioning&&(F.trigger(this._element,Fe).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Ye),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated())))}dispose(){F.off(window,je),F.off(this._dialog,je),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ce({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ie({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=V.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),f(this._element),this._element.classList.add(Ye),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,F.trigger(this._element,qe,{relatedTarget:t})},this._dialog,this._isAnimated())}_addEventListeners(){F.on(this._element,Ve,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())}),F.on(window,We,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),F.on(this._element,Ke,t=>{F.one(this._element,Re,e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Xe),this._resetAdjustments(),this._scrollBar.reset(),F.trigger(this._element,He)})}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(F.trigger(this._element,ze).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Ue)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Ue),this._queueCallback(()=>{this._element.classList.remove(Ue),this._queueCallback(()=>{this._element.style.overflowY=e},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=b()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=b()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each(function(){const i=Ze.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}})}}F.on(document,Qe,'[data-bs-toggle="modal"]',function(t){const e=V.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),F.one(e,Be,t=>{t.defaultPrevented||F.one(e,He,()=>{d(this)&&this.focus()})});const i=V.findOne(".modal.show");i&&Ze.getInstance(i).hide(),Ze.getOrCreateInstance(e).toggle(this)}),Q(Ze),v(Ze);const ti=".bs.offcanvas",ei=".data-api",ii=`load${ti}${ei}`,si="show",ni="showing",oi="hiding",ri=".offcanvas.show",ai=`show${ti}`,li=`shown${ti}`,ci=`hide${ti}`,hi=`hidePrevented${ti}`,di=`hidden${ti}`,ui=`resize${ti}`,_i=`click${ti}${ei}`,gi=`keydown.dismiss${ti}`,fi={backdrop:!0,keyboard:!0,scroll:!1},mi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class pi extends R{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return fi}static get DefaultType(){return mi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||F.trigger(this._element,ai,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Me).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ni),this._queueCallback(()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(si),this._element.classList.remove(ni),F.trigger(this._element,li,{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(F.trigger(this._element,ci).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(oi),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(si,oi),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Me).reset(),F.trigger(this._element,di)},this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ce({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():F.trigger(this._element,hi)}:null})}_initializeFocusTrap(){return new Ie({trapElement:this._element})}_addEventListeners(){F.on(this._element,gi,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():F.trigger(this._element,hi))})}static jQueryInterface(t){return this.each(function(){const e=pi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}F.on(document,_i,'[data-bs-toggle="offcanvas"]',function(t){const e=V.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),u(this))return;F.one(e,di,()=>{d(this)&&this.focus()});const i=V.findOne(ri);i&&i!==e&&pi.getInstance(i).hide(),pi.getOrCreateInstance(e).toggle(this)}),F.on(window,ii,()=>{for(const t of V.find(ri))pi.getOrCreateInstance(t).show()}),F.on(window,ui,()=>{for(const t of V.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&pi.getOrCreateInstance(t).hide()}),Q(pi),v(pi);const bi={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},vi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),yi=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,wi=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!vi.has(i)||Boolean(yi.test(t.nodeValue)):e.filter(t=>t instanceof RegExp).some(t=>t.test(i))},Ai={allowList:bi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"<div></div>"},Ei={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ci={entry:"(string|element|function|null)",selector:"(string|element)"};class Ti extends W{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Ai}static get DefaultType(){return Ei}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map(t=>this._resolvePossibleFunction(t)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Ci)}_setContent(t,e,i){const s=V.findOne(i,t);s&&((e=this._resolvePossibleFunction(e))?c(e)?this._putElementInTemplate(h(e),s):this._config.html?s.innerHTML=this._maybeSanitize(e):s.textContent=e:s.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const s=(new window.DOMParser).parseFromString(t,"text/html"),n=[].concat(...s.body.querySelectorAll("*"));for(const t of n){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const s=[].concat(...t.attributes),n=[].concat(e["*"]||[],e[i]||[]);for(const e of s)wi(e,n)||t.removeAttribute(e.nodeName)}return s.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return y(t,[void 0,this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const ki=new Set(["sanitize","allowList","sanitizeFn"]),$i="fade",Si="show",Li=".tooltip-inner",Oi=".modal",Ii="hide.bs.modal",Di="hover",Ni="focus",Pi="click",xi={AUTO:"auto",TOP:"top",RIGHT:b()?"left":"right",BOTTOM:"bottom",LEFT:b()?"right":"left"},Mi={allowList:bi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',title:"",trigger:"hover focus"},ji={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class Fi extends R{constructor(t,e){if(void 0===i)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return Mi}static get DefaultType(){return ji}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),F.off(this._element.closest(Oi),Ii,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=F.trigger(this._element,this.constructor.eventName("show")),e=(_(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:s}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(s.append(i),F.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(Si),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.on(t,"mouseover",g);this._queueCallback(()=>{F.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(this._isShown()&&!F.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(Si),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))F.off(t,"mouseover",g);this._activeTrigger[Pi]=!1,this._activeTrigger[Ni]=!1,this._activeTrigger[Di]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),F.trigger(this._element,this.constructor.eventName("hidden")))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove($i,Si),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add($i),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Ti({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[Li]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains($i)}_isShown(){return this.tip&&this.tip.classList.contains(Si)}_createPopper(t){const e=y(this._config.placement,[this,t,this._element]),s=xi[e.toUpperCase()];return i.createPopper(this._element,t,this._getPopperConfig(s))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return y(t,[this._element,this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...y(this._config.popperConfig,[void 0,e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)F.on(this._element,this.constructor.eventName("click"),this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger[Pi]=!(e._isShown()&&e._activeTrigger[Pi]),e.toggle()});else if("manual"!==e){const t=e===Di?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===Di?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");F.on(this._element,t,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?Ni:Di]=!0,e._enter()}),F.on(this._element,i,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?Ni:Di]=e._element.contains(t.relatedTarget),e._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},F.on(this._element.closest(Oi),Ii,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=q.getDataAttributes(this._element);for(const t of Object.keys(e))ki.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:h(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each(function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}v(Fi);const zi=".popover-header",Hi=".popover-body",Bi={...Fi.Default,content:"",offset:[0,8],placement:"right",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',trigger:"click"},qi={...Fi.DefaultType,content:"(null|string|element|function)"};class Wi extends Fi{static get Default(){return Bi}static get DefaultType(){return qi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[zi]:this._getTitle(),[Hi]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each(function(){const e=Wi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}v(Wi);const Ri=".bs.scrollspy",Ki=`activate${Ri}`,Vi=`click${Ri}`,Qi=`load${Ri}.data-api`,Xi="active",Yi="[href]",Ui=".nav-link",Gi=`${Ui}, .nav-item > ${Ui}, .list-group-item`,Ji={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Zi={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class ts extends R{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ji}static get DefaultType(){return Zi}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=h(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map(t=>Number.parseFloat(t))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(F.off(this._config.target,Vi),F.on(this._config.target,Vi,Yi,t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,s=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:s,behavior:"smooth"});i.scrollTop=s}}))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},s=(this._rootElement||document.documentElement).scrollTop,n=s>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=s;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(n&&t){if(i(o),!s)return}else n||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=V.find(Yi,this._config.target);for(const e of t){if(!e.hash||u(e))continue;const t=V.findOne(decodeURI(e.hash),this._element);d(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Xi),this._activateParents(t),F.trigger(this._element,Ki,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))V.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Xi);else for(const e of V.parents(t,".nav, .list-group"))for(const t of V.prev(e,Gi))t.classList.add(Xi)}_clearActiveClass(t){t.classList.remove(Xi);const e=V.find(`${Yi}.${Xi}`,t);for(const t of e)t.classList.remove(Xi)}static jQueryInterface(t){return this.each(function(){const e=ts.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}F.on(window,Qi,()=>{for(const t of V.find('[data-bs-spy="scroll"]'))ts.getOrCreateInstance(t)}),v(ts);const es=".bs.tab",is=`hide${es}`,ss=`hidden${es}`,ns=`show${es}`,os=`shown${es}`,rs=`click${es}`,as=`keydown${es}`,ls=`load${es}`,cs="ArrowLeft",hs="ArrowRight",ds="ArrowUp",us="ArrowDown",_s="Home",gs="End",fs="active",ms="fade",ps="show",bs=".dropdown-toggle",vs=`:not(${bs})`,ys='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',ws=`.nav-link${vs}, .list-group-item${vs}, [role="tab"]${vs}, ${ys}`,As=`.${fs}[data-bs-toggle="tab"], .${fs}[data-bs-toggle="pill"], .${fs}[data-bs-toggle="list"]`;class Es extends R{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),F.on(this._element,as,t=>this._keydown(t)))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?F.trigger(e,is,{relatedTarget:t}):null;F.trigger(t,ns,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(fs),this._activate(V.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),F.trigger(t,os,{relatedTarget:e})):t.classList.add(ps)},t,t.classList.contains(ms)))}_deactivate(t,e){t&&(t.classList.remove(fs),t.blur(),this._deactivate(V.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),F.trigger(t,ss,{relatedTarget:e})):t.classList.remove(ps)},t,t.classList.contains(ms)))}_keydown(t){if(![cs,hs,ds,us,_s,gs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter(t=>!u(t));let i;if([_s,gs].includes(t.key))i=e[t.key===_s?0:e.length-1];else{const s=[hs,us].includes(t.key);i=A(e,t.target,s,!0)}i&&(i.focus({preventScroll:!0}),Es.getOrCreateInstance(i).show())}_getChildren(){return V.find(ws,this._parent)}_getActiveElem(){return this._getChildren().find(t=>this._elemIsActive(t))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=V.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const s=(t,s)=>{const n=V.findOne(t,i);n&&n.classList.toggle(s,e)};s(bs,fs),s(".dropdown-menu",ps),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(fs)}_getInnerElement(t){return t.matches(ws)?t:V.findOne(ws,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each(function(){const e=Es.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}F.on(document,rs,ys,function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),u(this)||Es.getOrCreateInstance(this).show()}),F.on(window,ls,()=>{for(const t of V.find(As))Es.getOrCreateInstance(t)}),v(Es);const Cs=".bs.toast",Ts=`mouseover${Cs}`,ks=`mouseout${Cs}`,$s=`focusin${Cs}`,Ss=`focusout${Cs}`,Ls=`hide${Cs}`,Os=`hidden${Cs}`,Is=`show${Cs}`,Ds=`shown${Cs}`,Ns="hide",Ps="show",xs="showing",Ms={animation:"boolean",autohide:"boolean",delay:"number"},js={animation:!0,autohide:!0,delay:5e3};class Fs extends R{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return js}static get DefaultType(){return Ms}static get NAME(){return"toast"}show(){F.trigger(this._element,Is).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Ns),f(this._element),this._element.classList.add(Ps,xs),this._queueCallback(()=>{this._element.classList.remove(xs),F.trigger(this._element,Ds),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this.isShown()&&(F.trigger(this._element,Ls).defaultPrevented||(this._element.classList.add(xs),this._queueCallback(()=>{this._element.classList.add(Ns),this._element.classList.remove(xs,Ps),F.trigger(this._element,Os)},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Ps),super.dispose()}isShown(){return this._element.classList.contains(Ps)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){F.on(this._element,Ts,t=>this._onInteraction(t,!0)),F.on(this._element,ks,t=>this._onInteraction(t,!1)),F.on(this._element,$s,t=>this._onInteraction(t,!0)),F.on(this._element,Ss,t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each(function(){const e=Fs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}})}}return Q(Fs),v(Fs),{Alert:G,Button:Z,Carousel:Nt,Collapse:Qt,Dropdown:be,Modal:Ze,Offcanvas:pi,Popover:Wi,ScrollSpy:ts,Tab:Es,Toast:Fs,Tooltip:Fi}}); -//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map deleted file mode 100644 index df4b8491..00000000 --- a/extensions/pagetop-bootsier/static/js/bootstrap.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","toType","object","Object","prototype","toString","call","toLowerCase","triggerTransitionEnd","dispatchEvent","Event","isElement","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","JSON","parse","decodeURIComponent","normalizeDataKey","chr","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ARROW_LEFT_KEY$1","ARROW_RIGHT_KEY$1","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","endCallBack","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","_orderToDirection","isCycling","directionalClassName","orderClassName","completeCallBack","_isAnimated","clearInterval","carousel","slideIndex","carousels","EVENT_SHOW","EVENT_SHOWN","EVENT_HIDE","EVENT_HIDDEN","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","CLASS_NAME_DEEPER_CHILDREN","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","complete","getBoundingClientRect","selected","triggerArray","isOpen","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_KEYDOWN_DATA_API","EVENT_KEYUP_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","boundary","display","offset","popperConfig","reference","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","destroy","update","Popper","referenceElement","_getPopperConfig","createPopper","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","placement","modifiers","options","enabled","_selectMenuItem","clearMenus","openToggles","context","composedPath","isMenuTarget","dataApiKeydownHandler","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","EVENT_FOCUSIN","EVENT_KEYDOWN_TAB","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","elements","shiftKey","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","clientWidth","innerWidth","width","_disableOverFlow","_setElementAttributes","calculatedValue","reset","_resetElementAttributes","isOverflowing","_saveInitialAttribute","overflow","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","EVENT_HIDE_PREVENTED","EVENT_RESIZE","EVENT_CLICK_DISMISS","EVENT_MOUSEDOWN_DISMISS","EVENT_KEYDOWN_DISMISS","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","handleUpdate","scrollTop","modalBody","transitionComplete","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","scrollHeight","clientHeight","initialOverflowY","overflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","scroll","Offcanvas","blur","completeCallback","position","DefaultAllowlist","a","area","b","br","col","code","dd","div","dl","dt","em","hr","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","uriAttributes","SAFE_URL_PATTERN","allowedAttribute","attribute","allowedAttributeList","attributeName","nodeName","nodeValue","attributeRegex","some","regex","allowList","content","extraClass","html","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_TOOLTIP_INNER","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","TRIGGER_CLICK","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","fallbackPlacements","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","ownerDocument","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","phase","state","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","SELECTOR_TITLE","SELECTOR_CONTENT","Popover","_getContent","EVENT_ACTIVATE","EVENT_CLICK","SELECTOR_TARGET_LINKS","SELECTOR_NAV_LINKS","SELECTOR_LINK_ITEMS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","hash","height","offsetTop","scrollTo","top","behavior","IntersectionObserver","_observerCallback","targetElement","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","decodeURI","_activateParents","listGroup","item","activeNodes","node","spy","HOME_KEY","END_KEY","SELECTOR_DROPDOWN_TOGGLE","NOT_SELECTOR_DROPDOWN_TOGGLE","SELECTOR_INNER_ELEM","SELECTOR_DATA_TOGGLE_ACTIVE","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","EVENT_MOUSEOVER","EVENT_MOUSEOUT","EVENT_FOCUSOUT","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/dom/data.js","../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/dom/selector-engine.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../js/src/dropdown.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/util/scrollbar.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`)\n }\n\n return selector\n}\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object))\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getjQuery,\n getNextActiveElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n parseSelector,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index.js'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.8'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n // Private\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible, parseSelector } from '../util/index.js'\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null\n}\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n },\n\n getSelectorFromElement(element) {\n const selector = getSelector(element)\n\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null\n }\n\n return null\n },\n\n getElementFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.findOne(selector) : null\n },\n\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element)\n\n return selector ? SelectorEngine.find(selector) : []\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport { execute } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index.js'\nimport Swipe from './util/swipe.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n getElement,\n reflow\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n // Private\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for <a> elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin,\n execute,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org/docs/v2/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport Config from './config.js'\nimport {\n execute, executeAfterTransition, getElement, reflow\n} from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin, isRTL, isVisible, reflow\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n EventHandler.off(window, EVENT_KEY)\n EventHandler.off(this._dialog, EVENT_KEY)\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport Backdrop from './util/backdrop.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport FocusTrap from './util/focustrap.js'\nimport {\n defineJQueryPlugin,\n isDisabled,\n isVisible\n} from './util/index.js'\nimport ScrollBarHelper from './util/scrollbar.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n this.hide()\n return\n }\n\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n// js-docs-end allow-list\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine.js'\nimport Config from './config.js'\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'\nimport { execute, getElement, isElement } from './index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '<div></div>'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [undefined, this])\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport Manipulator from './dom/manipulator.js'\nimport {\n defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop\n} from './util/index.js'\nimport { DefaultAllowlist } from './util/sanitizer.js'\nimport TemplateFactory from './util/template-factory.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '<div class=\"tooltip\" role=\"tooltip\">' +\n '<div class=\"tooltip-arrow\"></div>' +\n '<div class=\"tooltip-inner\"></div>' +\n '</div>',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org/docs/v2/)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper()\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n this._popper = this._createPopper(tip)\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n this._disposePopper()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // TODO: remove this check in v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element])\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element, this._element])\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK])\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport {\n defineJQueryPlugin, getElement, isDisabled, isVisible\n} from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor\n for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {\n item.classList.add(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _clearActiveClass(parent) {\n parent.classList.remove(CLASS_NAME_ACTIVE)\n\n const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)\n for (const node of activeNodes) {\n node.classList.remove(CLASS_NAME_ACTIVE)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = ScrollSpy.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {\n ScrollSpy.getOrCreateInstance(spy)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(ScrollSpy)\n\nexport default ScrollSpy\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap tab.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport SelectorEngine from './dom/selector-engine.js'\nimport { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'tab'\nconst DATA_KEY = 'bs.tab'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}`\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst HOME_KEY = 'Home'\nconst END_KEY = 'End'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_DROPDOWN = 'dropdown'\n\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\nconst SELECTOR_DROPDOWN_MENU = '.dropdown-menu'\nconst NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`\n\nconst SELECTOR_TAB_PANEL = '.list-group, .nav, [role=\"tablist\"]'\nconst SELECTOR_OUTER = '.nav-item, .list-group-item'\nconst SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role=\"tab\"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"tab\"], [data-bs-toggle=\"pill\"], [data-bs-toggle=\"list\"]' // TODO: could only be `tab` in v6\nconst SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`\n\nconst SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle=\"tab\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"pill\"], .${CLASS_NAME_ACTIVE}[data-bs-toggle=\"list\"]`\n\n/**\n * Class definition\n */\n\nclass Tab extends BaseComponent {\n constructor(element) {\n super(element)\n this._parent = this._element.closest(SELECTOR_TAB_PANEL)\n\n if (!this._parent) {\n return\n // TODO: should throw exception in v6\n // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)\n }\n\n // Set up initial aria attributes\n this._setInitialAttributes(this._parent, this._getChildren())\n\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n show() { // Shows this elem and deactivate the active sibling if exists\n const innerElem = this._element\n if (this._elemIsActive(innerElem)) {\n return\n }\n\n // Search for active tab on same parent to deactivate it\n const active = this._getActiveElem()\n\n const hideEvent = active ?\n EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :\n null\n\n const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })\n\n if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {\n return\n }\n\n this._deactivate(active, innerElem)\n this._activate(innerElem, active)\n }\n\n // Private\n _activate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.add(CLASS_NAME_ACTIVE)\n\n this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.add(CLASS_NAME_SHOW)\n return\n }\n\n element.removeAttribute('tabindex')\n element.setAttribute('aria-selected', true)\n this._toggleDropDown(element, true)\n EventHandler.trigger(element, EVENT_SHOWN, {\n relatedTarget: relatedElem\n })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _deactivate(element, relatedElem) {\n if (!element) {\n return\n }\n\n element.classList.remove(CLASS_NAME_ACTIVE)\n element.blur()\n\n this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too\n\n const complete = () => {\n if (element.getAttribute('role') !== 'tab') {\n element.classList.remove(CLASS_NAME_SHOW)\n return\n }\n\n element.setAttribute('aria-selected', false)\n element.setAttribute('tabindex', '-1')\n this._toggleDropDown(element, false)\n EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })\n }\n\n this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))\n }\n\n _keydown(event) {\n if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {\n return\n }\n\n event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page\n event.preventDefault()\n\n const children = this._getChildren().filter(element => !isDisabled(element))\n let nextActiveElement\n\n if ([HOME_KEY, END_KEY].includes(event.key)) {\n nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]\n } else {\n const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)\n nextActiveElement = getNextActiveElement(children, event.target, isNext, true)\n }\n\n if (nextActiveElement) {\n nextActiveElement.focus({ preventScroll: true })\n Tab.getOrCreateInstance(nextActiveElement).show()\n }\n }\n\n _getChildren() { // collection of inner elements\n return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)\n }\n\n _getActiveElem() {\n return this._getChildren().find(child => this._elemIsActive(child)) || null\n }\n\n _setInitialAttributes(parent, children) {\n this._setAttributeIfNotExists(parent, 'role', 'tablist')\n\n for (const child of children) {\n this._setInitialAttributesOnChild(child)\n }\n }\n\n _setInitialAttributesOnChild(child) {\n child = this._getInnerElement(child)\n const isActive = this._elemIsActive(child)\n const outerElem = this._getOuterElement(child)\n child.setAttribute('aria-selected', isActive)\n\n if (outerElem !== child) {\n this._setAttributeIfNotExists(outerElem, 'role', 'presentation')\n }\n\n if (!isActive) {\n child.setAttribute('tabindex', '-1')\n }\n\n this._setAttributeIfNotExists(child, 'role', 'tab')\n\n // set attributes to the related panel too\n this._setInitialAttributesOnTargetPanel(child)\n }\n\n _setInitialAttributesOnTargetPanel(child) {\n const target = SelectorEngine.getElementFromSelector(child)\n\n if (!target) {\n return\n }\n\n this._setAttributeIfNotExists(target, 'role', 'tabpanel')\n\n if (child.id) {\n this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)\n }\n }\n\n _toggleDropDown(element, open) {\n const outerElem = this._getOuterElement(element)\n if (!outerElem.classList.contains(CLASS_DROPDOWN)) {\n return\n }\n\n const toggle = (selector, className) => {\n const element = SelectorEngine.findOne(selector, outerElem)\n if (element) {\n element.classList.toggle(className, open)\n }\n }\n\n toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)\n toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)\n outerElem.setAttribute('aria-expanded', open)\n }\n\n _setAttributeIfNotExists(element, attribute, value) {\n if (!element.hasAttribute(attribute)) {\n element.setAttribute(attribute, value)\n }\n }\n\n _elemIsActive(elem) {\n return elem.classList.contains(CLASS_NAME_ACTIVE)\n }\n\n // Try to get the inner element (usually the .nav-link)\n _getInnerElement(elem) {\n return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)\n }\n\n // Try to get the outer element (usually the .nav-item)\n _getOuterElement(elem) {\n return elem.closest(SELECTOR_OUTER) || elem\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tab.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n Tab.getOrCreateInstance(this).show()\n})\n\n/**\n * Initialize on focus\n */\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {\n Tab.getOrCreateInstance(element)\n }\n})\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tab)\n\nexport default Tab\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap toast.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin, reflow } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toast'\nconst DATA_KEY = 'bs.toast'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`\nconst EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_FOCUSOUT = `focusout${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\n\nconst DefaultType = {\n animation: 'boolean',\n autohide: 'boolean',\n delay: 'number'\n}\n\nconst Default = {\n animation: true,\n autohide: true,\n delay: 5000\n}\n\n/**\n * Class definition\n */\n\nclass Toast extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._timeout = null\n this._hasMouseInteraction = false\n this._hasKeyboardInteraction = false\n this._setListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show() {\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._clearTimeout()\n\n if (this._config.animation) {\n this._element.classList.add(CLASS_NAME_FADE)\n }\n\n const complete = () => {\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN)\n\n this._maybeScheduleHide()\n }\n\n this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated\n reflow(this._element)\n this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)\n\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n hide() {\n if (!this.isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const complete = () => {\n this._element.classList.add(CLASS_NAME_HIDE) // @deprecated\n this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.classList.add(CLASS_NAME_SHOWING)\n this._queueCallback(complete, this._element, this._config.animation)\n }\n\n dispose() {\n this._clearTimeout()\n\n if (this.isShown()) {\n this._element.classList.remove(CLASS_NAME_SHOW)\n }\n\n super.dispose()\n }\n\n isShown() {\n return this._element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _maybeScheduleHide() {\n if (!this._config.autohide) {\n return\n }\n\n if (this._hasMouseInteraction || this._hasKeyboardInteraction) {\n return\n }\n\n this._timeout = setTimeout(() => {\n this.hide()\n }, this._config.delay)\n }\n\n _onInteraction(event, isInteracting) {\n switch (event.type) {\n case 'mouseover':\n case 'mouseout': {\n this._hasMouseInteraction = isInteracting\n break\n }\n\n case 'focusin':\n case 'focusout': {\n this._hasKeyboardInteraction = isInteracting\n break\n }\n\n default: {\n break\n }\n }\n\n if (isInteracting) {\n this._clearTimeout()\n return\n }\n\n const nextElement = event.relatedTarget\n if (this._element === nextElement || this._element.contains(nextElement)) {\n return\n }\n\n this._maybeScheduleHide()\n }\n\n _setListeners() {\n EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false))\n EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true))\n EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false))\n }\n\n _clearTimeout() {\n clearTimeout(this._timeout)\n this._timeout = null\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Toast.getOrCreateInstance(this, config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Toast)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Toast)\n\nexport default Toast\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap index.umd.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Alert from './src/alert.js'\nimport Button from './src/button.js'\nimport Carousel from './src/carousel.js'\nimport Collapse from './src/collapse.js'\nimport Dropdown from './src/dropdown.js'\nimport Modal from './src/modal.js'\nimport Offcanvas from './src/offcanvas.js'\nimport Popover from './src/popover.js'\nimport ScrollSpy from './src/scrollspy.js'\nimport Tab from './src/tab.js'\nimport Toast from './src/toast.js'\nimport Tooltip from './src/tooltip.js'\n\nexport default {\n Alert,\n Button,\n Carousel,\n Collapse,\n Dropdown,\n Modal,\n Offcanvas,\n Popover,\n ScrollSpy,\n Tab,\n Toast,\n Tooltip\n}\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file From 175fc90775a512b05ab6043b38914da8b558434c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 2 Nov 2025 12:42:36 +0100 Subject: [PATCH 177/224] =?UTF-8?q?=F0=9F=93=9D=20Repasa=20doc=20de=20`Dro?= =?UTF-8?q?pdown`,=20`Nav`=20y=20`Offcanvas`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/dropdown.rs | 17 +++ .../src/theme/dropdown/component.rs | 18 +-- .../src/theme/dropdown/item.rs | 4 +- extensions/pagetop-bootsier/src/theme/nav.rs | 20 +++ .../src/theme/nav/component.rs | 21 +-- .../pagetop-bootsier/src/theme/nav/item.rs | 6 +- .../pagetop-bootsier/src/theme/offcanvas.rs | 20 +++ .../src/theme/offcanvas/component.rs | 126 ++++++++---------- 8 files changed, 124 insertions(+), 108 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index eb00e4c8..ed4cbec0 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -6,6 +6,23 @@ //! //! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y //! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). +//! +//! # Ejemplo +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let dd = Dropdown::new() +//! .with_title(L10n::n("Menu")) +//! .with_button_color(ButtonColor::Background(Color::Secondary)) +//! .with_auto_close(dropdown::AutoClose::ClickableInside) +//! .with_direction(dropdown::Direction::Dropend) +//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +//! .add_item(dropdown::Item::divider()) +//! .add_item(dropdown::Item::header(L10n::n("User session"))) +//! .add_item(dropdown::Item::button(L10n::n("Sign out"))); +//! ``` mod props; pub use props::{AutoClose, Direction, MenuAlign, MenuPosition}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 984a3e06..c29ac145 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -17,22 +17,8 @@ use crate::LOCALES_BOOTSIER; /// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el /// resto de propiedades no afectarán a su representación en [`Nav`]. /// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; -/// let dd = Dropdown::new() -/// .with_title(L10n::n("Menu")) -/// .with_button_color(ButtonColor::Background(Color::Secondary)) -/// .with_auto_close(dropdown::AutoClose::ClickableInside) -/// .with_direction(dropdown::Direction::Dropend) -/// .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) -/// .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) -/// .add_item(dropdown::Item::divider()) -/// .add_item(dropdown::Item::header(L10n::n("User session"))) -/// .add_item(dropdown::Item::button(L10n::n("Sign out"))); -/// ``` +/// Ver ejemplo en el módulo [`dropdown`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Dropdown { diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 1aed83b4..a13058dd 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -79,7 +79,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && current_path.map_or(false, |p| p == path); + let is_current = !*disabled && (current_path == Some(path)); let mut classes = "dropdown-item".to_string(); if is_current { @@ -274,7 +274,7 @@ impl Item { &self.classes } - /// Devuelve el tipo de elemento representado por este elemento. + /// Devuelve el tipo de elemento representado. pub fn item_kind(&self) -> &ItemKind { &self.item_kind } diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index b540c323..c74ab3b5 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -6,6 +6,26 @@ //! //! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y //! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). +//! +//! # Ejemplo +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let nav = Nav::tabs() +//! .with_layout(nav::Layout::End) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +//! .add_item(nav::Item::dropdown( +//! Dropdown::new() +//! .with_title(L10n::n("Options")) +//! .with_items(TypedOp::AddMany(vec![ +//! Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")), +//! Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")), +//! ])), +//! )) +//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")); +//! ``` mod props; pub use props::{Kind, Layout}; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index 34c33b94..d4cf2c8c 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -8,25 +8,8 @@ use crate::prelude::*; /// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite /// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)). /// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; -/// let nav = Nav::tabs() -/// .with_layout(nav::Layout::End) -/// .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) -/// .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) -/// .add_item(nav::Item::dropdown( -/// Dropdown::new() -/// .with_title(L10n::n("Options")) -/// .with_items(TypedOp::AddMany(vec![ -/// Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")), -/// Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")), -/// ])), -/// )) -/// .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")); -/// ``` +/// Ver ejemplo en el módulo [`nav`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Nav { diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 63248f8d..bc097e0c 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -72,7 +72,7 @@ impl Component for Item { ItemKind::Label(label) => PrepareMarkup::With(html! { li id=[self.id()] class=[self.classes().get()] { - span { + span class="nav-link disabled" aria-disabled="true" { (label.using(cx)) } } @@ -86,7 +86,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && current_path.map_or(false, |p| p == path); + let is_current = !*disabled && (current_path == Some(path)); let mut classes = "nav-link".to_string(); if is_current { @@ -250,7 +250,7 @@ impl Item { &self.classes } - /// Devuelve el tipo de elemento representado por este elemento. + /// Devuelve el tipo de elemento representado. pub fn item_kind(&self) -> &ItemKind { &self.item_kind } diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index 560bd30a..dc905348 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -1,4 +1,24 @@ //! Definiciones para crear paneles laterales deslizantes [`Offcanvas`]. +//! +//! # Ejemplo +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let panel = Offcanvas::new() +//! .with_id("offcanvas_example") +//! .with_title(L10n::n("Offcanvas title")) +//! .with_placement(offcanvas::Placement::End) +//! .with_backdrop(offcanvas::Backdrop::Enabled) +//! .with_body_scroll(offcanvas::BodyScroll::Enabled) +//! .with_visibility(offcanvas::Visibility::Default) +//! .add_child(Dropdown::new() +//! .with_button_title(L10n::n("Menu")) +//! .add_item(dropdown::Item::label(L10n::n("Label"))) +//! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es")) +//! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout")) +//! ); +//! ``` mod props; pub use props::{Backdrop, BodyScroll, Placement, Visibility}; diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 7cd7dffe..61e6fae7 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -18,27 +18,9 @@ use crate::LOCALES_BOOTSIER; /// ([`with_breakpoint()`](Self::with_breakpoint)). /// - Asocia título y controles de accesibilidad a un identificador único y expone atributos /// adecuados para lectores de pantalla y navegación por teclado. -/// - **No se renderiza** si no tiene contenido. /// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; -/// let panel = Offcanvas::new() -/// .with_id("offcanvas_example") -/// .with_title(L10n::n("Offcanvas title")) -/// .with_placement(offcanvas::Placement::End) -/// .with_backdrop(offcanvas::Backdrop::Enabled) -/// .with_body_scroll(offcanvas::BodyScroll::Enabled) -/// .with_visibility(offcanvas::Visibility::Default) -/// .add_child(Dropdown::new() -/// .with_button_title(L10n::n("Menu")) -/// .add_item(dropdown::Item::label(L10n::n("Label"))) -/// .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es")) -/// .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout")) -/// ); -/// ``` +/// Ver ejemplo en el módulo [`offcanvas`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Offcanvas { @@ -84,54 +66,7 @@ impl Component for Offcanvas { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let body = self.children().render(cx); - if body.is_empty() { - return PrepareMarkup::None; - } - - let id = cx.required_id::<Self>(self.id()); - let id_label = join!(id, "-label"); - let id_target = join!("#", id); - - let body_scroll = match self.body_scroll() { - offcanvas::BodyScroll::Disabled => None, - offcanvas::BodyScroll::Enabled => Some("true"), - }; - - let backdrop = match self.backdrop() { - offcanvas::Backdrop::Disabled => Some("false"), - offcanvas::Backdrop::Enabled => None, - offcanvas::Backdrop::Static => Some("static"), - }; - - let title = self.title().using(cx); - - PrepareMarkup::With(html! { - div - id=(id) - class=[self.classes().get()] - tabindex="-1" - data-bs-scroll=[body_scroll] - data-bs-backdrop=[backdrop] - aria-labelledby=(id_label) - { - div class="offcanvas-header" { - @if !title.is_empty() { - h5 class="offcanvas-title" id=(id_label) { (title) } - } - button - type="button" - class="btn-close" - data-bs-dismiss="offcanvas" - data-bs-target=(id_target) - aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)] - {} - } - div class="offcanvas-body" { - (body) - } - } - }) + PrepareMarkup::With(self.render_offcanvas(cx, None)) } } @@ -258,4 +193,59 @@ impl Offcanvas { pub fn children(&self) -> &Children { &self.children } + + // **< Offcanvas HELPERS >********************************************************************** + + pub(crate) fn render_offcanvas(&self, cx: &mut Context, extra: Option<&Children>) -> Markup { + let body = self.children().render(cx); + let body_extra = extra.map(|c| c.render(cx)).unwrap_or_else(|| html! {}); + if body.is_empty() && body_extra.is_empty() { + return html! {}; + } + + let id = cx.required_id::<Self>(self.id()); + let id_label = join!(id, "-label"); + let id_target = join!("#", id); + + let body_scroll = match self.body_scroll() { + offcanvas::BodyScroll::Disabled => None, + offcanvas::BodyScroll::Enabled => Some("true"), + }; + + let backdrop = match self.backdrop() { + offcanvas::Backdrop::Disabled => Some("false"), + offcanvas::Backdrop::Enabled => None, + offcanvas::Backdrop::Static => Some("static"), + }; + + let title = self.title().using(cx); + + html! { + div + id=(id) + class=[self.classes().get()] + tabindex="-1" + data-bs-scroll=[body_scroll] + data-bs-backdrop=[backdrop] + aria-labelledby=(id_label) + { + div class="offcanvas-header" { + @if !title.is_empty() { + h5 class="offcanvas-title" id=(id_label) { (title) } + } + button + type="button" + class="btn-close" + data-bs-dismiss="offcanvas" + data-bs-target=(id_target) + aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)] + {} + } + div class="offcanvas-body" { + (body) + (body_extra) + } + } + } + } } From 1ed5ab86fe4b1b14da87e1d7697bbbe82339a47b Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 2 Nov 2025 20:46:43 +0100 Subject: [PATCH 178/224] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20(menu):=20Elimina?= =?UTF-8?q?=20implementaci=C3=B3n=20base=20de=20men=C3=BAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component.rs | 2 - src/base/component/menu.rs | 17 -- src/base/component/menu/element.rs | 56 ---- src/base/component/menu/group.rs | 58 ---- src/base/component/menu/item.rs | 185 ------------- src/base/component/menu/megamenu.rs | 57 ---- src/base/component/menu/menu_menu.rs | 108 -------- src/base/component/menu/submenu.rs | 73 ----- static/css/menu.css | 384 --------------------------- static/js/menu.js | 95 ------- 10 files changed, 1035 deletions(-) delete mode 100644 src/base/component/menu.rs delete mode 100644 src/base/component/menu/element.rs delete mode 100644 src/base/component/menu/group.rs delete mode 100644 src/base/component/menu/item.rs delete mode 100644 src/base/component/menu/megamenu.rs delete mode 100644 src/base/component/menu/menu_menu.rs delete mode 100644 src/base/component/menu/submenu.rs delete mode 100644 static/css/menu.css delete mode 100644 static/js/menu.js diff --git a/src/base/component.rs b/src/base/component.rs index 8991d721..508a28e1 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -62,5 +62,3 @@ pub use poweredby::PoweredBy; mod icon; pub use icon::{Icon, IconKind}; - -pub mod menu; diff --git a/src/base/component/menu.rs b/src/base/component/menu.rs deleted file mode 100644 index 14f75898..00000000 --- a/src/base/component/menu.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod menu_menu; -pub use menu_menu::Menu; - -mod item; -pub use item::{Item, ItemKind}; - -mod submenu; -pub use submenu::Submenu; - -mod megamenu; -pub use megamenu::Megamenu; - -mod group; -pub use group::Group; - -mod element; -pub use element::{Element, ElementType}; diff --git a/src/base/component/menu/element.rs b/src/base/component/menu/element.rs deleted file mode 100644 index 6d142048..00000000 --- a/src/base/component/menu/element.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::prelude::*; - -type Content = Typed<Html>; -type SubmenuItems = Typed<menu::Submenu>; - -#[derive(AutoDefault)] -pub enum ElementType { - #[default] - Void, - Html(Content), - Submenu(SubmenuItems), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Element { - element_type: ElementType, -} - -impl Component for Element { - fn new() -> Self { - Element::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - match self.element_type() { - ElementType::Void => PrepareMarkup::None, - ElementType::Html(content) => PrepareMarkup::With(html! { - (content.render(cx)) - }), - ElementType::Submenu(submenu) => PrepareMarkup::With(html! { - (submenu.render(cx)) - }), - } - } -} - -impl Element { - pub fn html(content: Html) -> Self { - Element { - element_type: ElementType::Html(Content::with(content)), - } - } - - pub fn submenu(submenu: menu::Submenu) -> Self { - Element { - element_type: ElementType::Submenu(SubmenuItems::with(submenu)), - } - } - - // **< Element GETTERS >************************************************************************ - - pub fn element_type(&self) -> &ElementType { - &self.element_type - } -} diff --git a/src/base/component/menu/group.rs b/src/base/component/menu/group.rs deleted file mode 100644 index ba188934..00000000 --- a/src/base/component/menu/group.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Group { - id : AttrId, - elements: Children, -} - -impl Component for Group { - fn new() -> Self { - Group::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__group" { - (self.elements().render(cx)) - } - }) - } -} - -impl Group { - // **< Group BUILDER >************************************************************************** - - /// Establece el identificador único (`id`) del grupo. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - /// Añade un nuevo elemento al menú. - pub fn add_element(mut self, element: menu::Element) -> Self { - self.elements - .alter_typed(TypedOp::Add(Typed::with(element))); - self - } - - /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_elements(mut self, op: TypedOp<menu::Element>) -> Self { - self.elements.alter_typed(op); - self - } - - // **< Group GETTERS >************************************************************************** - - /// Devuelve la lista de elementos (`children`) del grupo. - pub fn elements(&self) -> &Children { - &self.elements - } -} diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs deleted file mode 100644 index c6342186..00000000 --- a/src/base/component/menu/item.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::prelude::*; - -type Label = L10n; -type Content = Typed<Html>; -type SubmenuItems = Typed<menu::Submenu>; -type MegamenuGroups = Typed<menu::Megamenu>; - -#[derive(AutoDefault)] -pub enum ItemKind { - #[default] - Void, - Label(Label), - Link(Label, FnPathByContext), - LinkBlank(Label, FnPathByContext), - Html(Content), - Submenu(Label, SubmenuItems), - Megamenu(Label, MegamenuGroups), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Item { - item_kind : ItemKind, - description: AttrL10n, - left_icon : Typed<Icon>, - right_icon : Typed<Icon>, -} - -impl Component for Item { - fn new() -> Self { - Item::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let description = self.description().lookup(cx); - let left_icon = self.left_icon().render(cx); - let right_icon = self.right_icon().render(cx); - - match self.item_kind() { - ItemKind::Void => PrepareMarkup::None, - ItemKind::Label(label) => PrepareMarkup::With(html! { - li class="menu__item menu__item--label" { - span title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::Link(label, path) => PrepareMarkup::With(html! { - li class="menu__item menu__item--link" { - a class="menu__link" href=(path(cx)) title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! { - li class="menu__item menu__item--link" { - a class="menu__link" href=(path(cx)) title=[description] target="_blank" { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::Html(content) => PrepareMarkup::With(html! { - li class="menu__item menu__item--html" { - (content.render(cx)) - } - }), - ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! { - li class="menu__item menu__item--children" { - button type="button" class="menu__link" title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (Icon::svg(html! { - path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} - }).render(cx)) - } - div class="menu__children menu__children--submenu" { - (submenu.render(cx)) - } - } - }), - ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! { - li class="menu__item menu__item--children" { - button type="button" class="menu__link" title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (Icon::svg(html! { - path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} - }).render(cx)) - } - div class="menu__children menu__children--mega" { - (megamenu.render(cx)) - } - } - }), - } - } -} - -impl Item { - pub fn label(label: L10n) -> Self { - Item { - item_kind: ItemKind::Label(label), - ..Default::default() - } - } - - pub fn link(label: L10n, path: FnPathByContext) -> Self { - Item { - item_kind: ItemKind::Link(label, path), - ..Default::default() - } - } - - pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { - Item { - item_kind: ItemKind::LinkBlank(label, path), - ..Default::default() - } - } - - pub fn html(content: Html) -> Self { - Item { - item_kind: ItemKind::Html(Content::with(content)), - ..Default::default() - } - } - - pub fn submenu(label: L10n, submenu: menu::Submenu) -> Self { - Item { - item_kind: ItemKind::Submenu(label, SubmenuItems::with(submenu)), - ..Default::default() - } - } - - pub fn megamenu(label: L10n, megamenu: menu::Megamenu) -> Self { - Item { - item_kind: ItemKind::Megamenu(label, MegamenuGroups::with(megamenu)), - ..Default::default() - } - } - - // **< Item BUILDER >*************************************************************************** - - #[builder_fn] - pub fn with_description(mut self, text: L10n) -> Self { - self.description.alter_value(text); - self - } - - #[builder_fn] - pub fn with_left_icon<I: Into<Icon>>(mut self, icon: Option<I>) -> Self { - self.left_icon.alter_component(icon.map(Into::into)); - self - } - - #[builder_fn] - pub fn with_right_icon<I: Into<Icon>>(mut self, icon: Option<I>) -> Self { - self.right_icon.alter_component(icon.map(Into::into)); - self - } - - // **< Item GETTERS >*************************************************************************** - - pub fn item_kind(&self) -> &ItemKind { - &self.item_kind - } - - pub fn description(&self) -> &AttrL10n { - &self.description - } - - pub fn left_icon(&self) -> &Typed<Icon> { - &self.left_icon - } - - pub fn right_icon(&self) -> &Typed<Icon> { - &self.right_icon - } -} diff --git a/src/base/component/menu/megamenu.rs b/src/base/component/menu/megamenu.rs deleted file mode 100644 index e435e753..00000000 --- a/src/base/component/menu/megamenu.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Megamenu { - id : AttrId, - groups: Children, -} - -impl Component for Megamenu { - fn new() -> Self { - Megamenu::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__mega" { - (self.groups().render(cx)) - } - }) - } -} - -impl Megamenu { - // **< Megamenu BUILDER >*********************************************************************** - - /// Establece el identificador único (`id`) del megamenú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - /// Añade un nuevo grupo al menú. - pub fn add_group(mut self, group: menu::Group) -> Self { - self.groups.alter_typed(TypedOp::Add(Typed::with(group))); - self - } - - /// Modifica la lista de grupos (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_groups(mut self, op: TypedOp<menu::Group>) -> Self { - self.groups.alter_typed(op); - self - } - - // **< Megamenu GETTERS >*********************************************************************** - - /// Devuelve la lista de grupos (`children`) del megamenú. - pub fn groups(&self) -> &Children { - &self.groups - } -} diff --git a/src/base/component/menu/menu_menu.rs b/src/base/component/menu/menu_menu.rs deleted file mode 100644 index 58a4c21d..00000000 --- a/src/base/component/menu/menu_menu.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Menu { - id : AttrId, - classes: AttrClasses, - items : Children, -} - -impl Component for Menu { - fn new() -> Self { - Menu::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes(ClassesOp::Prepend, "menu"); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - // cx.set_param::<bool>(PARAM_BASE_INCLUDE_MENU_ASSETS, &true); - // cx.set_param::<bool>(PARAM_BASE_INCLUDE_ICONS, &true); - - PrepareMarkup::With(html! { - div id=[self.id()] class=[self.classes().get()] { - div class="menu__wrapper" { - div class="menu__panel" { - div class="menu__overlay" {} - nav class="menu__nav" { - div class="menu__header" { - button type="button" class="menu__back" { - (Icon::svg(html! { - path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0" {} - }).render(cx)) - } - div class="menu__title" {} - button type="button" class="menu__close" { - (Icon::svg(html! { - path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" {} - }).render(cx)) - } - } - ul class="menu__list" { - (self.items().render(cx)) - } - } - } - button - type="button" - class="menu__trigger" - title=[L10n::l("menu_toggle").lookup(cx)] - { - (Icon::svg(html! { - path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5" {} - }).render(cx)) - } - } - } - }) - } -} - -impl Menu { - // **< Menu BUILDER >*************************************************************************** - - /// Establece el identificador único (`id`) del menú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - /// Modifica la lista de clases CSS aplicadas al menú. - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - /// Añade un nuevo ítem al menú. - pub fn add_item(mut self, item: menu::Item) -> Self { - self.items.alter_typed(TypedOp::Add(Typed::with(item))); - self - } - - /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_items(mut self, op: TypedOp<menu::Item>) -> Self { - self.items.alter_typed(op); - self - } - - // **< Menu GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al menú. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve la lista de ítems (`children`) del menú. - pub fn items(&self) -> &Children { - &self.items - } -} diff --git a/src/base/component/menu/submenu.rs b/src/base/component/menu/submenu.rs deleted file mode 100644 index a5957ef9..00000000 --- a/src/base/component/menu/submenu.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Submenu { - id : AttrId, - title: AttrL10n, - items: Children, -} - -impl Component for Submenu { - fn new() -> Self { - Submenu::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__submenu" { - @if let Some(title) = self.title().lookup(cx) { - h4 class="menu__submenu-title" { (title) } - } - ul { - (self.items().render(cx)) - } - } - }) - } -} - -impl Submenu { - // **< Submenu BUILDER >************************************************************************ - - /// Establece el identificador único (`id`) del submenú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - #[builder_fn] - pub fn with_title(mut self, title: L10n) -> Self { - self.title.alter_value(title); - self - } - - /// Añade un nuevo ítem al submenú. - pub fn add_item(mut self, item: menu::Item) -> Self { - self.items.alter_typed(TypedOp::Add(Typed::with(item))); - self - } - - /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_items(mut self, op: TypedOp<menu::Item>) -> Self { - self.items.alter_typed(op); - self - } - - // **< Submenu GETTERS >************************************************************************ - - pub fn title(&self) -> &AttrL10n { - &self.title - } - - /// Devuelve la lista de ítems (`children`) del submenú. - pub fn items(&self) -> &Children { - &self.items - } -} diff --git a/static/css/menu.css b/static/css/menu.css deleted file mode 100644 index 428ba157..00000000 --- a/static/css/menu.css +++ /dev/null @@ -1,384 +0,0 @@ -/* Aislamiento & normalización */ - -.menu { - isolation: isolate; -} -@supports (all: revert) { - .menu { - all: revert; - display: block; } -} -.menu { - box-sizing: border-box; - line-height: var(--val-menu--line-height, 1.5); - color: var(--val-color--text); - text-align: left; - text-transform: none; - letter-spacing: normal; - word-spacing: normal; - white-space: normal; - cursor: default; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - width: 100%; - height: auto; - margin: 0; - padding: 0; - z-index: 9999; - border: 0; - background: var(--val-menu--color-bg); -} -.menu *, -.menu *::before, -.menu *::after { - box-sizing: inherit; -} -.menu :where(a, button) { - appearance: none; - background: none; - border: 0; - font: inherit; - color: inherit; - text-decoration: none; - cursor: pointer; - -webkit-tap-highlight-color: transparent; -} -.menu :where(a, button):focus-visible { - outline: 2px solid var(--val-menu--color-highlight); - outline-offset: 2px; -} -.menu :where(ul, ol) { - list-style: none; - margin: 0; - padding: 0; -} -.menu svg { - fill: currentColor; -} - -/* Estructura */ - -.menu__wrapper { - padding-right: var(--val-gap); -} - -.menu__nav li { - display: inline-block; - margin: 0; - margin-inline-start: 1.5rem; - padding: 0; - line-height: var(--val-menu--item-height); - list-style: none; -} - -.menu__item--label, -.menu__nav li > .menu__link { - position: relative; - font-weight: normal; - text-rendering: optimizeLegibility; - font-size: 1.45rem; -} -.menu__nav li > .menu__link { - transition: color 0.3s ease-in-out; -} -.menu__nav li:hover > .menu__link, -.menu__nav li > .menu__link:focus { - color: var(--val-menu--color-highlight); -} -.menu__nav li > .menu__link > svg.icon { - margin-left: 0.25rem; -} - -.menu__children { - position: absolute; - max-width: 100%; - height: auto; - padding: var(--val-gap-0-5) var(--val-gap-1-5); - border: 0; - background: var(--val-menu--color-bg); - border-top: 3px solid var(--val-menu--color-highlight); - z-index: 500; - opacity: 0; - visibility: hidden; - box-shadow: 0 4px 6px -1px var(--val-menu--color-border), 0 2px 4px -1px var(--val-menu--color-shadow); - transition: all 0.3s ease-in-out; -} - -.menu__item--children:hover > .menu__children, -.menu__item--children > .menu__link:focus + .menu__children, -.menu__item--children .menu__children:focus-within { - margin-top: 0.4rem; - opacity: 1; - visibility: visible; -} - -.menu__submenu { - min-width: var(--val-menu--item-width-min); - max-width: var(--val-menu--item-width-max); -} -.menu__submenu-title { - font-size: 1rem; - font-weight: normal; - margin: 0; - padding: var(--val-menu--line-padding) 0; - line-height: var(--val-menu--line-height); - border: 0; - color: var(--val-menu--color-highlight); - text-transform: uppercase; - text-rendering: optimizeLegibility; -} -.menu__submenu li { - display: block; - margin: 0; -} - -.menu__children--mega { - left: 50%; - transform: translateX(-50%); -} - -.menu__mega { - display: flex; - flex-wrap: nowrap; -} - -.menu__header, -.menu__trigger { - display: none; -} - -/* Responsive <= 62rem (992px) */ - -@media (max-width: 62rem) { - .menu__wrapper { - padding-right: var(--val-gap-0-5); - } - .menu__trigger { - width: var(--val-menu--trigger-width); - height: var(--val-menu--item-height); - display: flex; - flex-direction: column; - justify-content: center; - } - .menu__trigger svg.icon { - width: 2rem; - height: 2rem; - } - - .menu__nav, - .menu__children { - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - - .menu__nav { - position: fixed; - top: 0; - left: 0; - width: var(--val-menu--side-width); - height: 100%; - z-index: 9099; - overflow: hidden; - background: var(--val-menu--color-bg); - transform: translate(-100%); - transition: transform .5s ease-in-out, opacity .5s ease-in-out; - will-change: transform; - backface-visibility: hidden; - visibility: hidden; - pointer-events: none; - } - .menu__nav.active { - transform: translate(0%); - visibility: visible; - pointer-events: auto; - } - - .menu__nav li { - display: block; - margin: 0; - line-height: var(--val-menu--line-height); - } - - .menu__item--label, - .menu__nav li > .menu__link { - display: block; - text-align: inherit; - width: 100%; - padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); - border-bottom: 1px solid var(--val-menu--color-border); - } - .menu__nav li ul li.menu__item--label, - .menu__nav li ul li > .menu__link { - border-bottom: 0; - } - .menu__nav li > .menu__link > svg.icon { - position: absolute; - top: var(--val-menu--line-padding); - right: var(--val-menu--line-padding); - height: var(--val-menu--line-height); - font-size: 1.25rem; - transform: rotate(-90deg); - } - - .menu__children { - position: absolute; - display: none; - top: 0; - left: 0; - max-width: none; - min-width: auto; - width: 100%; - height: 100%; - margin: 0 !important; - padding: 0; - border-top: 0; - opacity: 1; - overflow-y: auto; - visibility: visible; - transform: translateX(0%); - box-shadow: none; - transition: opacity .5s ease-in-out, transform .5s ease-in-out, margin-top .5s ease-in-out; - } - .menu__children.active { - display: block; - } - .menu__children > :first-child { - margin-top: 2.675rem; - } - - .menu__submenu-title { - padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); - } - - .menu__mega { - display: block; - } - - .menu__header { - position: sticky; - display: flex; - align-items: center; - justify-content: space-between; - top: 0; - height: var(--val-menu--item-height); - border-bottom: 1px solid var(--val-menu--color-border); - background: var(--val-menu--color-bg); - z-index: 501; - } - .menu__title { - padding: var(--val-menu--line-padding); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 1.45rem; - font-weight: normal; - opacity: 0; - transform: translateY(.25rem); - transition: opacity .5s ease-in-out, transform .5s ease-in-out; - will-change: opacity, transform; - } - .menu__header.active .menu__title { - opacity: 1; - transform: translateY(0); - } - .menu__close, - .menu__back { - width: var(--val-menu--item-height); - min-width: var(--val-menu--item-height); - height: var(--val-menu--item-height); - line-height: var(--val-menu--item-height); - color: var(--val-color--text); - display: flex; - align-items: center; - justify-content: center; - background: var(--val-menu--color-bg); - } - .menu__close { - font-size: 2.25rem; - border: 1px solid var(--val-menu--color-border) !important; - border-width: 0 0 1px 1px !important; - } - .menu__back { - font-size: 1.25rem; - border: 1px solid var(--val-menu--color-border) !important; - border-width: 0 1px 1px 0 !important; - display: none; - } - .menu__header.active .menu__back { - display: flex; - } - - .menu__list { - height: 100%; - overflow-y: auto; - overflow-x: hidden; - padding: 0; - margin: 0; - } - - .menu__overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 9098; - opacity: 0; - visibility: hidden; - background: rgba(0, 0, 0, 0.55); - transition: opacity .5s ease-in-out, visibility 0s linear .5s; - } - .menu__overlay.active { - opacity: 1; - visibility: visible; - transition-delay: 0s, 0s; - } -} - -@media (hover: hover) and (pointer: fine) { - .menu__item--children:hover > .menu__children { - margin-top: 0.4rem; - opacity: 1; - visibility: visible; - } - .menu.menu--closing .menu__children { - margin-top: 0 !important; - opacity: 0 !important; - visibility: hidden !important; - } -} - -@media (prefers-reduced-motion: reduce) { - .menu__nav, - .menu__children, - .menu__title, - .menu__overlay { - transition: none !important; - animation: none !important; - } -} - -/* Animaciones */ - -@keyframes slideLeft { - 0% { - opacity: 0; - transform: translateX(100%); - } - 100% { - opacity: 1; - transform: translateX(0%); - } -} - -@keyframes slideRight { - 0% { - opacity: 1; - transform: translateX(0%); - } - 100% { - opacity: 0; - transform: translateX(100%); - } -} diff --git a/static/js/menu.js b/static/js/menu.js deleted file mode 100644 index dca8e4df..00000000 --- a/static/js/menu.js +++ /dev/null @@ -1,95 +0,0 @@ -const getTitle = (li) => li.querySelector('.menu__label')?.textContent.trim() ?? ''; - -function menu__showChildren(nav, children) { - const li = children[0]; - const submenu = li.querySelector('.menu__children'); - submenu.classList.add('active'); - submenu.style.animation = 'slideLeft 0.5s ease forwards'; - - nav.querySelector('.menu__title').textContent = getTitle(li);; - nav.querySelector('.menu__header').classList.add('active'); -} - -function menu__hideChildren(nav, children) { - const submenu = children[0].querySelector('.menu__children'); - submenu.style.animation = 'slideRight 0.5s ease forwards'; - setTimeout(() => { - submenu.classList.remove('active'); - submenu.style.removeProperty('animation'); - }, 300); - - children.shift(); - if (children.length > 0) { - nav.querySelector('.menu__title').textContent = getTitle(children[0]); - } else { - nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').textContent = ''; - } -} - -function menu__toggle(nav, overlay) { - nav.classList.toggle('active'); - overlay.classList.toggle('active'); -} - -function menu__reset(menu, nav, overlay) { - menu__toggle(nav, overlay); - setTimeout(() => { - nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').textContent = ''; - menu.querySelectorAll('.menu__children').forEach(submenu => { - submenu.classList.remove('active'); - submenu.style.removeProperty('animation'); - }); - }, 300); - return []; -} - -document.querySelectorAll('.menu').forEach(menu => { - let menuChildren = []; - const menuNav = menu.querySelector('.menu__nav'); - const menuOverlay = menu.querySelector('.menu__overlay'); - - menu.querySelector('.menu__list').addEventListener('click', (e) => { - if (menuNav.classList.contains('active')) { - let target = e.target.closest('.menu__item--children'); - if (target && target != menuChildren[0]) { - menuChildren.unshift(target); - menu__showChildren(menuNav, menuChildren); - } - } - }); - - menu.querySelector('.menu__back').addEventListener('click', () => { - menu__hideChildren(menuNav, menuChildren); - }); - - menu.querySelector('.menu__close').addEventListener('click', () => { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - }); - - menu.querySelectorAll('.menu__item--link > a[target="_blank"]').forEach(link => { - link.addEventListener('click', (e) => { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - e.target.blur(); - }); - }); - - menu.querySelector('.menu__trigger').addEventListener('click', () => { - menu__toggle(menuNav, menuOverlay); - }); - - menuOverlay.addEventListener('click', () => { - menu__toggle(menuNav, menuOverlay); - }); - - let resizeTimeout; - window.addEventListener('resize', () => { - if (menuNav.classList.contains('active')) { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - }, 150); - } - }); -}); From 0d6f975649edae10c6cf54705586a00cea710d1b Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 2 Nov 2025 20:47:26 +0100 Subject: [PATCH 179/224] =?UTF-8?q?=F0=9F=93=9D=20Corrige=20ejemplo=20de?= =?UTF-8?q?=20documentaci=C3=B3n=20de`Offcanvas`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme/offcanvas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index dc905348..18cc253a 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -13,7 +13,7 @@ //! .with_body_scroll(offcanvas::BodyScroll::Enabled) //! .with_visibility(offcanvas::Visibility::Default) //! .add_child(Dropdown::new() -//! .with_button_title(L10n::n("Menu")) +//! .with_title(L10n::n("Menu")) //! .add_item(dropdown::Item::label(L10n::n("Label"))) //! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es")) //! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout")) From 9a26d579ed022a479c39cb2687407c100f649869 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 2 Nov 2025 20:47:50 +0100 Subject: [PATCH 180/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20ejemplo=20de?= =?UTF-8?q?=20barra=20de=20men=C3=BA=20de=20navegaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/navbar-menus.rs | 101 ++++++++++++++++++++++++++++++++++++ src/locale/en-US/sample.ftl | 24 +++++++++ src/locale/es-ES/sample.ftl | 24 +++++++++ 3 files changed, 149 insertions(+) create mode 100644 examples/navbar-menus.rs create mode 100644 src/locale/en-US/sample.ftl create mode 100644 src/locale/es-ES/sample.ftl diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs new file mode 100644 index 00000000..22ef336c --- /dev/null +++ b/examples/navbar-menus.rs @@ -0,0 +1,101 @@ +use pagetop::prelude::*; + +use pagetop_bootsier::prelude::*; + +struct SuperMenu; + +impl Extension for SuperMenu { + fn dependencies(&self) -> Vec<ExtensionRef> { + vec![&pagetop_aliner::Aliner, &pagetop_bootsier::Bootsier] + } + + fn initialize(&self) { + let home_path = |cx: &Context| match cx.langid().language.as_str() { + "en" => "/en", + _ => "/", + }; + + let navbar_menu = Navbar::brand_left(navbar::Brand::new().with_path(Some(home_path))) + .with_expand(BreakPoint::LG) + .add_item(navbar::Item::nav( + Nav::new() + .add_item(nav::Item::link( + L10n::l("sample_menus_item_link"), + home_path, + )) + .add_item(nav::Item::link_blank( + L10n::l("sample_menus_item_blank"), + |_| "https://docs.rs/pagetop", + )) + .add_item(nav::Item::dropdown( + Dropdown::new() + .with_title(L10n::l("sample_menus_test_title")) + .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_dev_getting_started"), + |_| "/dev/getting-started", + )) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_dev_guides"), + |_| "/dev/guides", + )) + .add_item(dropdown::Item::link_blank( + L10n::l("sample_menus_dev_forum"), + |_| "https://forum.example.dev", + )) + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_sdk_rust"), + |_| "/dev/sdks/rust", + )) + .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |_| { + "/dev/sdks/js" + })) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_sdk_python"), + |_| "/dev/sdks/python", + )) + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::header(L10n::l( + "sample_menus_plugin_header", + ))) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_plugin_auth"), + |_| "/dev/sdks/rust/plugins/auth", + )) + .add_item(dropdown::Item::link( + L10n::l("sample_menus_plugin_cache"), + |_| "/dev/sdks/rust/plugins/cache", + )) + .add_item(dropdown::Item::divider()) + .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) + .add_item(dropdown::Item::link_disabled( + L10n::l("sample_menus_item_disabled"), + |_| "#", + )), + )) + .add_item(nav::Item::link_disabled( + L10n::l("sample_menus_item_disabled"), + |_| "#", + )), + )) + .add_item(navbar::Item::nav( + Nav::new() + .add_item(nav::Item::link( + L10n::l("sample_menus_item_sign_up"), + |_| "/auth/sign-up", + )) + .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |_| { + "/auth/login" + })), + )); + + InRegion::Key("header").add(Child::with(navbar_menu)); + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&SuperMenu).run()?.await +} diff --git a/src/locale/en-US/sample.ftl b/src/locale/en-US/sample.ftl new file mode 100644 index 00000000..ae5b9a7f --- /dev/null +++ b/src/locale/en-US/sample.ftl @@ -0,0 +1,24 @@ +# menus.rs +sample_menus_item_label = Label +sample_menus_item_link = Link +sample_menus_item_blank = External link +sample_menus_item_disabled = Disabled link + +sample_menus_test_title = Dropdown + +sample_menus_dev_header = Intro +sample_menus_dev_getting_started = Getting started +sample_menus_dev_guides = Development guides +sample_menus_dev_forum = Developers forum + +sample_menus_sdk_header = Software Development Kits +sample_menus_sdk_rust = SDKs Rust +sample_menus_sdk_js = SDKs JavaScript +sample_menus_sdk_python = SDKs Python + +sample_menus_plugin_header = Plugins +sample_menus_plugin_auth = Rust Plugin Auth +sample_menus_plugin_cache = Rust Plugin Cache + +sample_menus_item_sign_up = Sign up +sample_menus_item_login = Login diff --git a/src/locale/es-ES/sample.ftl b/src/locale/es-ES/sample.ftl new file mode 100644 index 00000000..65785972 --- /dev/null +++ b/src/locale/es-ES/sample.ftl @@ -0,0 +1,24 @@ +# menus.rs +sample_menus_item_label = Etiqueta +sample_menus_item_link = Enlace +sample_menus_item_blank = Enlace externo +sample_menus_item_disabled = Enlace deshabilitado + +sample_menus_test_title = Desplegable + +sample_menus_dev_header = Introducción +sample_menus_dev_getting_started = Primeros pasos +sample_menus_dev_guides = Guías de desarrollo +sample_menus_dev_forum = Foro de desarrolladores + +sample_menus_sdk_header = Kits de Desarrollo Software +sample_menus_sdk_rust = SDKs de Rust +sample_menus_sdk_js = SDKs de JavaScript +sample_menus_sdk_python = SDKs de Python + +sample_menus_plugin_header = Plugins +sample_menus_plugin_auth = Plugin Rust de autenticación +sample_menus_plugin_cache = Plugin Rust de caché + +sample_menus_item_sign_up = Registrarse +sample_menus_item_login = Iniciar sesión From d5f0d83939f31ffcd20121e32b34b6a7346dadca Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 3 Nov 2025 22:43:31 +0100 Subject: [PATCH 181/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(bootsier):=20Refa?= =?UTF-8?q?ctoriza=20y=20renombra=20estilos=20`aux`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme/aux.rs | 9 +- .../pagetop-bootsier/src/theme/aux/border.rs | 39 ++- .../pagetop-bootsier/src/theme/aux/color.rs | 226 ++++++++++++++---- .../pagetop-bootsier/src/theme/aux/opacity.rs | 109 --------- .../pagetop-bootsier/src/theme/aux/rounded.rs | 2 +- .../pagetop-bootsier/src/theme/aux/size.rs | 9 +- .../pagetop-bootsier/src/theme/container.rs | 40 ++-- .../pagetop-bootsier/src/theme/dropdown.rs | 2 +- .../src/theme/dropdown/component.rs | 6 +- .../src/theme/navbar/component.rs | 48 +++- 10 files changed, 267 insertions(+), 223 deletions(-) delete mode 100644 extensions/pagetop-bootsier/src/theme/aux/opacity.rs diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs index f3cc127d..a56b14e9 100644 --- a/extensions/pagetop-bootsier/src/theme/aux.rs +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -4,12 +4,9 @@ mod breakpoint; pub use breakpoint::BreakPoint; mod color; -pub use color::Color; -pub use color::{BgColor, BorderColor, ButtonColor, TextColor}; - -mod opacity; -pub use opacity::Opacity; -pub use opacity::{BgOpacity, BorderOpacity, TextOpacity}; +pub use color::{Color, Opacity}; +pub use color::{ColorBg, ColorBorder, ColorButton, ColorText}; +pub use color::{StyleBg, StyleBorder, StyleText}; mod border; pub use border::{Border, BorderSize}; diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index 840f03c7..de3df81e 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -6,7 +6,7 @@ use std::fmt; // **< BorderSize >********************************************************************************* -/// Tamaño (**ancho**) para los bordes ([`Border`]). +/// Tamaño para el ancho de los bordes ([`Border`]). /// /// Mapea a `border`, `border-0` y `border-{1..5}`: /// @@ -58,7 +58,7 @@ impl fmt::Display for BorderSize { /// - Definir un tamaño **global** para todo el borde (`size`). /// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, /// respetando LTR/RTL). -/// - Aplicar un **color** al borde (`BorderColor`). +/// - Aplicar un **color** al borde (`ColorBorder`). /// - Aplicar un nivel de **opacidad** (`BorderOpacity`). /// /// # Comportamiento aditivo / sustractivo @@ -107,21 +107,19 @@ impl fmt::Display for BorderSize { /// let b = Border::with(BorderSize::Default) // Borde global por defecto. /// .with_top(BorderSize::Zero) // Quita borde superior. /// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. -/// .with_color(BorderColor::Theme(Color::Primary)) -/// .with_opacity(BorderOpacity::Theme(Opacity::Half)); +/// .with_style(StyleBorder::Both(ColorBorder::Theme(Color::Primary), Opacity::Half)); /// /// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); /// ``` #[rustfmt::skip] #[derive(AutoDefault)] pub struct Border { - size : BorderSize, - top : BorderSize, - end : BorderSize, - bottom : BorderSize, - start : BorderSize, - color : BorderColor, - opacity: BorderOpacity, + size : BorderSize, + top : BorderSize, + end : BorderSize, + bottom: BorderSize, + start : BorderSize, + style : StyleBorder, } impl Border { @@ -167,22 +165,16 @@ impl Border { self } - /// Establece el **color** del borde. - pub fn with_color(mut self, color: BorderColor) -> Self { - self.color = color; - self - } - - /// Establece la **opacidad** del borde. - pub fn with_opacity(mut self, opacity: BorderOpacity) -> Self { - self.opacity = opacity; + /// Establece el estilo de color/opacidad del borde. + pub fn with_style(mut self, style: StyleBorder) -> Self { + self.style = style; self } } impl fmt::Display for Border { - /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, *color* y - /// *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. + /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start` y + /// *color*/*opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -193,8 +185,7 @@ impl fmt::Display for Border { self.end.to_class("border-end"), self.bottom.to_class("border-bottom"), self.start.to_class("border-start"), - self.color.to_string(), - self.opacity.to_string(), + self.style.to_string(), ]; " ") .unwrap_or_default() ) diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index c071e407..17d192dd 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -4,11 +4,11 @@ use std::fmt; // **< Color >************************************************************************************** -/// Paleta de colores **temáticos**. +/// Paleta de colores temáticos. /// -/// Equivalente a los nombres estándar de Bootstrap (`primary`, `secondary`, `success`, etc.). Sirve -/// como base para componer clases de fondo ([`BgColor`]), borde ([`BorderColor`]) y texto -/// ([`TextColor`]). +/// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`, +/// etc.). Este tipo enumerado sirve de base para componer clases de color para el fondo +/// ([`ColorBg`]), bordes ([`ColorBorder`]) y texto ([`ColorText`]). #[derive(AutoDefault)] pub enum Color { #[default] @@ -38,32 +38,34 @@ impl fmt::Display for Color { } } -// **< BgColor >************************************************************************************ +// **< ColorBg >************************************************************************************ -/// Colores de fondo (`bg-*`). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Body*` usa fondos predefinidos del tema (`bg-body`, `bg-body-secondary`, `bg-body-tertiary`). -/// - `Theme(Color)` genera `bg-{color}` (p. ej., `bg-primary`). -/// - `Subtle(Color)` genera `bg-{color}-subtle` (tono suave). -/// - `Black` y `White` son colores explícitos. -/// - `Transparent` no aplica color de fondo (`bg-transparent`). +/// Colores `bg-*` para el fondo. #[derive(AutoDefault)] -pub enum BgColor { +pub enum ColorBg { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). #[default] Default, + /// Fondo predefinido del tema (`bg-body`). Body, + /// Fondo predefinido del tema (`bg-body-secondary`). BodySecondary, + /// Fondo predefinido del tema (`bg-body-tertiary`). BodyTertiary, + /// Genera internamente clases `bg-{color}` (p. ej., `bg-primary`). Theme(Color), + /// Genera internamente clases `bg-{color}-subtle` (un tono suavizado del color). Subtle(Color), + /// Color negro. Black, + /// Color blanco. White, + /// No aplica ningún color de fondo (`bg-transparent`). Transparent, } #[rustfmt::skip] -impl fmt::Display for BgColor { +impl fmt::Display for ColorBg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Default => Ok(()), @@ -79,26 +81,26 @@ impl fmt::Display for BgColor { } } -// **< BorderColor >******************************************************************************** +// **< ColorBorder >******************************************************************************** -/// Colores (`border-*`) para los bordes ([`Border`](crate::theme::aux::Border)). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Theme(Color)` genera `border-{color}`. -/// - `Subtle(Color)` genera `border-{color}-subtle` (versión suavizada). -/// - `Black` y `White` son colores explícitos. +/// Colores `border-*` para los bordes ([`Border`](crate::theme::aux::Border)). #[derive(AutoDefault)] -pub enum BorderColor { +pub enum ColorBorder { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). #[default] Default, + /// Genera internamente clases `border-{color}`. Theme(Color), + /// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color). Subtle(Color), + /// Color negro. Black, + /// Color blanco. White, } #[rustfmt::skip] -impl fmt::Display for BorderColor { +impl fmt::Display for ColorBorder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Default => Ok(()), @@ -110,25 +112,24 @@ impl fmt::Display for BorderColor { } } -// **< ButtonColor >******************************************************************************** +// **< ColorButton >******************************************************************************** -/// Variantes de color (`btn-*`) para **botones**. -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Background(Color)` genera `btn-{color}` (botón relleno). -/// - `Outline(Color)` genera `btn-outline-{color}` (contorno: texto y borde, fondo transparente). -/// - `Link` aplica estilo de enlace (`btn-link`), sin caja ni fondo, heredando el color de texto. +/// Variantes de color `btn-*` para botones. #[derive(AutoDefault)] -pub enum ButtonColor { +pub enum ColorButton { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). #[default] Default, + /// Genera internamente clases `btn-{color}` (botón relleno). Background(Color), + /// Genera `btn-outline-{color}` (fondo transparente y contorno con borde). Outline(Color), + /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. Link, } #[rustfmt::skip] -impl fmt::Display for ButtonColor { +impl fmt::Display for ColorButton { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Default => Ok(()), @@ -139,34 +140,34 @@ impl fmt::Display for ButtonColor { } } -// **< TextColor >********************************************************************************** +// **< ColorText >********************************************************************************** -/// Colores de texto y fondos de texto (`text-*`). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Body*` aplica colores predefinidos del tema (`text-body`, `text-body-emphasis`, -/// `text-body-secondary`, `text-body-tertiary`). -/// - `Theme(Color)` genera `text-{color}`. -/// - `Emphasis(Color)` genera `text-{color}-emphasis` (contraste mayor acorde al tema). -/// - `Background(Color)` genera `text-bg-{color}` (para color de fondo del texto). -/// - `Black` y `White` son colores explícitos. +/// Colores `text-*` para el texto. #[derive(AutoDefault)] -pub enum TextColor { +pub enum ColorText { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). #[default] Default, + /// Color predefinido del tema (`text-body`). Body, + /// Color predefinido del tema (`text-body-emphasis`). BodyEmphasis, + /// Color predefinido del tema (`text-body-secondary`). BodySecondary, + /// Color predefinido del tema (`text-body-tertiary`). BodyTertiary, + /// Genera internamente clases `text-{color}`. Theme(Color), + /// Genera internamente clases `text-{color}-emphasis` (mayor contraste acorde al tema). Emphasis(Color), - Background(Color), + /// Color negro. Black, + /// Color blanco. White, } #[rustfmt::skip] -impl fmt::Display for TextColor { +impl fmt::Display for ColorText { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Default => Ok(()), @@ -176,9 +177,140 @@ impl fmt::Display for TextColor { Self::BodyTertiary => f.write_str("text-body-tertiary"), Self::Theme(c) => write!(f, "text-{c}"), Self::Emphasis(c) => write!(f, "text-{c}-emphasis"), - Self::Background(c) => write!(f, "text-bg-{c}"), Self::Black => f.write_str("text-black"), Self::White => f.write_str("text-white"), } } } + +// **< Opacity >************************************************************************************ + +/// Niveles de opacidad (`opacity-*`). +/// +/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` +/// ([`StyleBg`]), de los bordes `border-opacity-*` ([`StyleBorder`]) o del texto `text-opacity-*` +/// ([`StyleText`]). +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum Opacity { + /// Genera internamente clases `opacity-100` (100% de opacidad). + #[default] + Opaque, + /// Genera internamente clases `opacity-75` (75%). + SemiOpaque, + /// Genera internamente clases `opacity-50` (50%). + Half, + /// Genera internamente clases `opacity-25` (25%). + SemiTransparent, + /// Genera internamente clases `opacity-10` (10%). + AlmostTransparent, + /// Genera internamente clases `opacity-0` (0%, totalmente transparente). + Transparent, +} + +#[rustfmt::skip] +impl fmt::Display for Opacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Opaque => f.write_str("opacity-100"), + Self::SemiOpaque => f.write_str("opacity-75"), + Self::Half => f.write_str("opacity-50"), + Self::SemiTransparent => f.write_str("opacity-25"), + Self::AlmostTransparent => f.write_str("opacity-10"), + Self::Transparent => f.write_str("opacity-0"), + } + } +} + +// **< StyleBg >*********************************************************************************** + +/// Estilos de color/opacidad para el fondo. +#[derive(AutoDefault)] +pub enum StyleBg { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + #[default] + Default, + /// Genera internamente clases `bg-*`. + Color(ColorBg), + /// Genera internamente clases `bg-opacity-*`. + Opacity(Opacity), + /// Genera internamente clases `bg-* bg-opacity-*`. + Both(ColorBg, Opacity), +} + +#[rustfmt::skip] +impl fmt::Display for StyleBg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Color(c) => write!(f, "{c}"), + Self::Opacity(o) => write!(f, "bg-{o}"), + Self::Both(c, o) => write!(f, "{c} bg-{o}"), + } + } +} + +// **< StyleBorder >******************************************************************************* + +/// Estilos de color/opacidad para los bordes ([`Border`](crate::theme::aux::Border)). +#[derive(AutoDefault)] +pub enum StyleBorder { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + #[default] + Default, + /// Genera internamente clases `border-*`. + Color(ColorBorder), + /// Genera internamente clases `border-opacity-*`. + Opacity(Opacity), + /// Genera internamente clases `border-* border-opacity-*`. + Both(ColorBorder, Opacity), +} + +#[rustfmt::skip] +impl fmt::Display for StyleBorder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Color(c) => write!(f, "{c}"), + Self::Opacity(o) => write!(f, "border-{o}"), + Self::Both(c, o) => write!(f, "{c} border-{o}"), + } + } +} + +// **< StyleText >********************************************************************************* + +/// Estilos de color/opacidad para texto y fondo del texto. +#[derive(AutoDefault)] +pub enum StyleText { + /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + #[default] + Default, + /// Genera internamente clases `text-*`. + Color(ColorText), + /// Genera internamente clases `text-opacity-*`. + Opacity(Opacity), + /// Genera internamente clases `text-* text-opacity-*`. + Both(ColorText, Opacity), + /// Genera internamente clases `text-bg-*` (para el color de fondo del texto). + Bg(Color), + /// Genera internamente clases `text-bg-* text-*`. + BgAndColor(Color, ColorText), + /// Genera internamente clases `text-bg-* text-* text-opacity-*`. + All(Color, ColorText, Opacity), +} + +#[rustfmt::skip] +impl fmt::Display for StyleText { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Color(c) => write!(f, "{c}"), + Self::Opacity(o) => write!(f, "text-{o}"), + Self::Both(c, o) => write!(f, "{c} text-{o}"), + Self::Bg(bg) => write!(f, "text-bg-{bg}"), + Self::BgAndColor(bg, c) => write!(f, "text-bg-{bg} {c}"), + Self::All(bg, c, o) => write!(f, "text-bg-{bg} {c} text-{o}"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/opacity.rs b/extensions/pagetop-bootsier/src/theme/aux/opacity.rs deleted file mode 100644 index 96724d91..00000000 --- a/extensions/pagetop-bootsier/src/theme/aux/opacity.rs +++ /dev/null @@ -1,109 +0,0 @@ -use pagetop::prelude::*; - -use std::fmt; - -// **< Opacity >************************************************************************************ - -/// Niveles de **opacidad** (`opacity-*`). -/// -/// Se usa para modular la transparencia del color de fondo `bg-opacity-*` ([`BgOpacity`]), borde -/// `border-opacity-*` ([`BorderOpacity`]) o texto `text-opacity-*` ([`TextOpacity`]), según las -/// siguientes equivalencias: -/// -/// - `Opaque` => `opacity-100` (100% de opacidad). -/// - `SemiOpaque` => `opacity-75` (75%). -/// - `Half` => `opacity-50` (50%). -/// - `SemiTransparent` => `opacity-25` (25%). -/// - `AlmostTransparent` => `opacity-10` (10%). -/// - `Transparent` => `opacity-0` (0%, totalmente transparente). -#[rustfmt::skip] -#[derive(AutoDefault)] -pub enum Opacity { - #[default] - Opaque, // 100% - SemiOpaque, // 75% - Half, // 50% - SemiTransparent, // 25% - AlmostTransparent, // 10% - Transparent, // 0% -} - -#[rustfmt::skip] -impl fmt::Display for Opacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Opaque => f.write_str("opacity-100"), - Self::SemiOpaque => f.write_str("opacity-75"), - Self::Half => f.write_str("opacity-50"), - Self::SemiTransparent => f.write_str("opacity-25"), - Self::AlmostTransparent => f.write_str("opacity-10"), - Self::Transparent => f.write_str("opacity-0"), - } - } -} - -// **< BgOpacity >********************************************************************************** - -/// Opacidad para el fondo (`bg-opacity-*`). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Theme(Opacity)` genera `bg-{opacity}` (p. ej., `bg-opacity-50`). -#[derive(AutoDefault)] -pub enum BgOpacity { - #[default] - Default, - Theme(Opacity), -} - -impl fmt::Display for BgOpacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(o) => write!(f, "bg-{o}"), - } - } -} - -// **< BorderOpacity >****************************************************************************** - -/// Opacidad (`border-opacity-*`) para los bordes ([`Border`](crate::theme::aux::Border)). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Theme(Opacity)` genera `border-{opacity}` (p. ej., `border-opacity-25`). -#[derive(AutoDefault)] -pub enum BorderOpacity { - #[default] - Default, - Theme(Opacity), -} - -impl fmt::Display for BorderOpacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(o) => write!(f, "border-{o}"), - } - } -} - -// **< TextOpacity >******************************************************************************** - -/// Opacidad para el texto (`text-opacity-*`). -/// -/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases). -/// - `Theme(Opacity)` genera `text-{opacity}` (p. ej., `text-opacity-100`). -#[derive(AutoDefault)] -pub enum TextOpacity { - #[default] - Default, - Theme(Opacity), -} - -impl fmt::Display for TextOpacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(o) => write!(f, "text-{o}"), - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index f7f6e3fe..470a48a3 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -4,7 +4,7 @@ use std::fmt; // **< RoundedRadius >****************************************************************************** -/// Radio (**redondeo**) para las esquinas ([`Rounded`]). +/// Radio para el redondeo de esquinas ([`Rounded`]). /// /// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`. /// diff --git a/extensions/pagetop-bootsier/src/theme/aux/size.rs b/extensions/pagetop-bootsier/src/theme/aux/size.rs index e5cff63c..d0abdb51 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/size.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/size.rs @@ -5,17 +5,14 @@ use std::fmt; // **< ButtonSize >********************************************************************************* /// Tamaño visual de un botón. -/// -/// Controla la escala del botón según el diseño del tema: -/// -/// - `Default`, tamaño por defecto del tema (no añade clase). -/// - `Small`, botón compacto. -/// - `Large`, botón destacado/grande. #[derive(AutoDefault)] pub enum ButtonSize { + /// Tamaño por defecto del tema (no añade clase). #[default] Default, + /// Botón compacto. Small, + /// Botón destacado/grande. Large, } diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index 17512505..411eb0aa 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -56,8 +56,8 @@ pub struct Container { classes : AttrClasses, container_type : ContainerType, container_width: ContainerWidth, - bg_color : BgColor, - text_color : TextColor, + style_bg : StyleBg, + style_text : StyleText, border : Border, rounded : Rounded, children : Children, @@ -86,8 +86,8 @@ impl Component for Container { ContainerWidth::FluidMax(_) => "fluid".to_string(), } ), - self.bg_color().to_string(), - self.text_color().to_string(), + self.style_bg().to_string(), + self.style_text().to_string(), self.border().to_string(), self.rounded().to_string(), ] @@ -205,25 +205,25 @@ impl Container { self } - /// Establece el color de fondo ([`BgColor`]). + /// Establece el estilo del fondo ([`StyleBg`]). #[builder_fn] - pub fn with_bg_color(mut self, color: BgColor) -> Self { - self.bg_color = color; + pub fn with_style_bg(mut self, style: StyleBg) -> Self { + self.style_bg = style; self } - /// Establece el color del texto ([`TextColor`]). + /// Establece el estilo del texto ([`StyleText`]). #[builder_fn] - pub fn with_text_color(mut self, color: TextColor) -> Self { - self.text_color = color; + pub fn with_style_text(mut self, style: StyleText) -> Self { + self.style_text = style; self } - /// Atajo para definir los colores de fondo y texto a la vez. + /// Atajo para definir los estilos de fondo y texto a la vez. #[builder_fn] - pub fn with_colors(mut self, bg_color: BgColor, text_color: TextColor) -> Self { - self.bg_color = bg_color; - self.text_color = text_color; + pub fn with_styles(mut self, style_bg: StyleBg, style_text: StyleText) -> Self { + self.style_bg = style_bg; + self.style_text = style_text; self } @@ -272,14 +272,14 @@ impl Container { &self.container_width } - /// Devuelve el color de fondo del contenedor. - pub fn bg_color(&self) -> &BgColor { - &self.bg_color + /// Devuelve el estilo del fondo del contenedor. + pub fn style_bg(&self) -> &StyleBg { + &self.style_bg } - /// Devuelve el color del texto del contenedor. - pub fn text_color(&self) -> &TextColor { - &self.text_color + /// Devuelve el estilo del texto del contenedor. + pub fn style_text(&self) -> &StyleText { + &self.style_text } /// Devuelve el borde configurado del contenedor. diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index ed4cbec0..bbad2d3f 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -14,7 +14,7 @@ //! # use pagetop_bootsier::prelude::*; //! let dd = Dropdown::new() //! .with_title(L10n::n("Menu")) -//! .with_button_color(ButtonColor::Background(Color::Secondary)) +//! .with_button_color(ColorButton::Background(Color::Secondary)) //! .with_auto_close(dropdown::AutoClose::ClickableInside) //! .with_direction(dropdown::Direction::Dropend) //! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index c29ac145..57e30713 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -26,7 +26,7 @@ pub struct Dropdown { classes : AttrClasses, title : L10n, button_size : ButtonSize, - button_color : ButtonColor, + button_color : ColorButton, button_split : bool, button_grouped: bool, auto_close : dropdown::AutoClose, @@ -212,7 +212,7 @@ impl Dropdown { /// Define el color/estilo del botón. #[builder_fn] - pub fn with_button_color(mut self, color: ButtonColor) -> Self { + pub fn with_button_color(mut self, color: ColorButton) -> Self { self.button_color = color; self } @@ -291,7 +291,7 @@ impl Dropdown { } /// Devuelve el color/estilo configurado del botón. - pub fn button_color(&self) -> &ButtonColor { + pub fn button_color(&self) -> &ColorButton { &self.button_color } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index fc46175a..6362b9d6 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -17,12 +17,14 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; #[rustfmt::skip] #[derive(AutoDefault)] pub struct Navbar { - id : AttrId, - classes : AttrClasses, - expand : BreakPoint, - layout : navbar::Layout, - position: navbar::Position, - items : Children, + id : AttrId, + classes : AttrClasses, + expand : BreakPoint, + layout : navbar::Layout, + position : navbar::Position, + style_bg : StyleBg, + style_text: StyleText, + items : Children, } impl Component for Navbar { @@ -48,6 +50,8 @@ impl Component for Navbar { navbar::Position::StickyBottom => "sticky-bottom", } .to_string(), + self.style_bg().to_string(), + self.style_text().to_string(), ] .join(" "), ); @@ -255,6 +259,28 @@ impl Navbar { self } + /// Establece el estilo del fondo ([`StyleBg`]). + #[builder_fn] + pub fn with_style_bg(mut self, style: StyleBg) -> Self { + self.style_bg = style; + self + } + + /// Establece el estilo del texto ([`StyleText`]). + #[builder_fn] + pub fn with_style_text(mut self, style: StyleText) -> Self { + self.style_text = style; + self + } + + /// Atajo para definir los estilos de fondo y texto a la vez. + #[builder_fn] + pub fn with_styles(mut self, style_bg: StyleBg, style_text: StyleText) -> Self { + self.style_bg = style_bg; + self.style_text = style_text; + self + } + /// Añade un nuevo contenido hijo. #[inline] pub fn add_item(mut self, item: navbar::Item) -> Self { @@ -291,6 +317,16 @@ impl Navbar { &self.position } + /// Devuelve el estilo del fondo del contenedor. + pub fn style_bg(&self) -> &StyleBg { + &self.style_bg + } + + /// Devuelve el estilo del texto del contenedor. + pub fn style_text(&self) -> &StyleText { + &self.style_text + } + /// Devuelve la lista de contenidos (`children`). pub fn items(&self) -> &Children { &self.items From 5ec69345b3db07afa46cfba2f169e8b1e29a7d5d Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 8 Nov 2025 08:07:59 +0100 Subject: [PATCH 182/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20trait=20`JoinC?= =?UTF-8?q?lasses`=20para=20unir=20clases=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit También elimina macros sin uso `join_op!` y `join_strict!` (KISS). --- src/html.rs | 3 + src/html/assets/javascript.rs | 6 +- src/html/assets/stylesheet.rs | 2 +- src/html/join_classes.rs | 67 ++++++++++++++++++++++ src/prelude.rs | 6 +- src/util.rs | 103 +++++----------------------------- 6 files changed, 90 insertions(+), 97 deletions(-) create mode 100644 src/html/join_classes.rs diff --git a/src/html.rs b/src/html.rs index 5f5b833a..f8709dc5 100644 --- a/src/html.rs +++ b/src/html.rs @@ -87,6 +87,9 @@ use crate::{core, AutoDefault}; #[allow(type_alias_bounds)] pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>; +mod join_classes; +pub use join_classes::JoinClasses; + mod unit; pub use unit::UnitValue; diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 0e86f0d4..6394842a 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -215,13 +215,13 @@ impl Asset for JavaScript { fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { - script src=(join_pair!(path, "?v=", self.version.as_str())) {}; + script src=(join_pair!(path, "?v=", &self.version)) {}; }, Source::Defer(path) => html! { - script src=(join_pair!(path, "?v=", self.version.as_str())) defer {}; + script src=(join_pair!(path, "?v=", &self.version)) defer {}; }, Source::Async(path) => html! { - script src=(join_pair!(path, "?v=", self.version.as_str())) async {}; + script src=(join_pair!(path, "?v=", &self.version)) async {}; }, Source::Inline(_, f) => html! { script { (PreEscaped((f)(cx))) }; diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 5f0eaaa0..68a13da6 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -170,7 +170,7 @@ impl Asset for StyleSheet { Source::From(path) => html! { link rel="stylesheet" - href=(join_pair!(path, "?v=", self.version.as_str())) + href=(join_pair!(path, "?v=", &self.version)) media=[self.media.as_str_opt()]; }, Source::Inline(_, f) => html! { diff --git a/src/html/join_classes.rs b/src/html/join_classes.rs new file mode 100644 index 00000000..3f7d7e70 --- /dev/null +++ b/src/html/join_classes.rs @@ -0,0 +1,67 @@ +/// Añade a los *slices* de elementos [`AsRef<str>`] un método para unir clases CSS. +/// +/// El método es [`join_classes()`](JoinClasses::join_classes), que une las cadenas **no vacías** +/// del *slice* usando un espacio como separador. +pub trait JoinClasses { + /// Une las cadenas **no vacías** de un *slice* usando un espacio como separador. + /// + /// Son cadenas vacías únicamente los elementos del *slice* cuya longitud es `0` (p. ej., `""`); + /// no se realiza recorte ni normalización, por lo que elementos como `" "` no se consideran + /// vacíos. + /// + /// Si todas las cadenas están vacías, devuelve una cadena vacía. Acepta elementos que + /// implementen [`AsRef<str>`] como `&str`, [`String`] o `Cow<'_, str>`. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let classes = ["btn", "", "btn-primary"]; + /// assert_eq!(classes.join_classes(), "btn btn-primary"); + /// + /// let empty: [&str; 3] = ["", "", ""]; + /// assert_eq!(empty.join_classes(), ""); + /// + /// let border = String::from("border"); + /// let border_top = String::from("border-top-0"); + /// let v = vec![&border, "", "", "", &border_top]; + /// assert_eq!(v.as_slice().join_classes(), "border border-top-0"); + /// + /// // Elementos con espacios afectan al resultado. + /// let spaced = ["btn", " ", "primary "]; + /// assert_eq!(spaced.join_classes(), "btn primary "); + /// ``` + fn join_classes(&self) -> String; +} + +impl<T> JoinClasses for [T] +where + T: AsRef<str>, +{ + #[inline] + fn join_classes(&self) -> String { + let mut count = 0usize; + let mut total = 0usize; + for s in self.iter().map(T::as_ref).filter(|s| !s.is_empty()) { + count += 1; + total += s.len(); + } + if count == 0 { + return String::new(); + } + let separator = " "; + let mut result = String::with_capacity(total + separator.len() * count.saturating_sub(1)); + for (i, s) in self + .iter() + .map(T::as_ref) + .filter(|s| !s.is_empty()) + .enumerate() + { + if i > 0 { + result.push_str(separator); + } + result.push_str(s); + } + result + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 377dcdfd..0919e991 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,7 @@ pub use crate::{AutoDefault, StaticResources, UniqueId, Weight}; // MACROS. // crate::util -pub use crate::{hm, join, join_opt, join_pair, join_strict}; +pub use crate::{hm, join, join_pair}; // crate::config pub use crate::include_config; // crate::locale @@ -33,8 +33,8 @@ pub use crate::trace; // alias obsoletos se volverá a declarar como `pub use crate::html::*;`. pub use crate::html::{ display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, - ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup, - StyleSheet, TargetMedia, UnitValue, DOCTYPE, + ClassesOp, Escaper, Favicon, JavaScript, JoinClasses, Markup, PageTopSvg, PreEscaped, + PrepareMarkup, StyleSheet, TargetMedia, UnitValue, DOCTYPE, }; pub use crate::locale::*; diff --git a/src/util.rs b/src/util.rs index 3d07361a..30b994f2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -65,53 +65,11 @@ macro_rules! hm { /// ``` #[macro_export] macro_rules! join { - ($($arg:tt)*) => { - $crate::util::concat_string!($($arg)*) + ($($arg:expr),+) => { + $crate::util::concat_string!($($arg),+) }; } -/// Concatena los fragmentos **no vacíos** en un [`Option<String>`] con un separador opcional. -/// -/// Acepta cualquier número de fragmentos que implementen [`AsRef<str>`]. Si todos los fragmentos -/// están vacíos, devuelve `None`. -/// -/// # Ejemplos -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Concatena los fragmentos no vacíos con un espacio como separador. -/// let result_with_separator = join_opt!(["Hello", "", "World"]; " "); -/// assert_eq!(result_with_separator, Some("Hello World".to_string())); -/// -/// // Concatena los fragmentos no vacíos sin un separador. -/// let result_without_separator = join_opt!(["Hello", "", "World"]); -/// assert_eq!(result_without_separator, Some("HelloWorld".to_string())); -/// -/// // Devuelve `None` si todos los fragmentos están vacíos. -/// let result_empty = join_opt!(["", "", ""]; ","); -/// assert_eq!(result_empty, None); -/// ``` -#[macro_export] -macro_rules! join_opt { - ([$($arg:expr),* $(,)?]) => {{ - let s = $crate::util::concat_string!($($arg),*); - (!s.is_empty()).then_some(s) - }}; - ([$($arg:expr),* $(,)?]; $separator:expr) => {{ - let sep = ($separator).as_ref(); - let mut s = String::new(); - for part in [ $( ($arg).as_ref() ),* ] { - if !(part as &str).is_empty() { - if !s.is_empty() { - s.push_str(sep); - } - s.push_str(part); - } - } - (!s.is_empty()).then_some(s) - }}; -} - /// Concatena dos fragmentos en un [`String`] usando un separador. /// /// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado. @@ -145,54 +103,19 @@ macro_rules! join_opt { #[macro_export] macro_rules! join_pair { ($first:expr, $separator:expr, $second:expr) => {{ - if $first.is_empty() { - String::from($second) - } else if $second.is_empty() { - String::from($first) - } else { - $crate::util::concat_string!($first, $separator, $second) - } - }}; -} + let first_val = $first; + let second_val = $second; + let separator_val = $separator; -/// Concatena varios fragmentos en un [`Option<String>`] **si ninguno está vacío**. -/// -/// Si alguno de los fragmentos, que deben implementar [`AsRef<str>`], está vacío, devuelve -/// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Concatena los fragmentos. -/// let result = join_strict!(["Hello", "World"]); -/// assert_eq!(result, Some("HelloWorld".to_string())); -/// -/// // Concatena los fragmentos con un separador. -/// let result_with_separator = join_strict!(["Hello", "World"]; " "); -/// assert_eq!(result_with_separator, Some("Hello World".to_string())); -/// -/// // Devuelve `None` si alguno de los fragmentos está vacío. -/// let result_with_empty = join_strict!(["Hello", "", "World"]); -/// assert_eq!(result_with_empty, None); -/// ``` -#[macro_export] -macro_rules! join_strict { - ([$($arg:expr),* $(,)?]) => {{ - let fragments = [$($arg),*]; - if fragments.iter().any(|&item| item.is_empty()) { - None + let first = AsRef::<str>::as_ref(&first_val); + let second = AsRef::<str>::as_ref(&second_val); + let separator = if first.is_empty() || second.is_empty() { + "" } else { - Some(fragments.concat()) - } - }}; - ([$($arg:expr),* $(,)?]; $separator:expr) => {{ - let fragments = [$($arg),*]; - if fragments.iter().any(|&item| item.is_empty()) { - None - } else { - Some(fragments.join($separator)) - } + AsRef::<str>::as_ref(&separator_val) + }; + + $crate::util::concat_string!(first, separator, second) }}; } From 748bd81bf14f17d012f36b45d01b87a32da788b1 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 10 Nov 2025 07:45:05 +0100 Subject: [PATCH 183/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20clases=20de=20?= =?UTF-8?q?fondo,=20texto,=20bordes=20y=20esquinas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactoriza el componente contenedor `Container` para usar estas clases y aplicar los nuevos enums `Kind` y `Width` para mejorar el comportamiento semántico y *responsive*. - Actualiza los componentes `Dropdown`, `Image`, `Nav`, `Navbar` y `Offcanvas` para usar los nuevos métodos de unión de clases. - Elimina propiedades de estilo redundantes de los componentes `Navbar` e `Image`, simplificando sus interfaces. --- extensions/pagetop-bootsier/src/lib.rs | 1 - extensions/pagetop-bootsier/src/theme.rs | 14 +- extensions/pagetop-bootsier/src/theme/aux.rs | 11 +- .../pagetop-bootsier/src/theme/aux/border.rs | 182 ++-------- .../src/theme/aux/breakpoint.rs | 77 ++--- .../pagetop-bootsier/src/theme/aux/button.rs | 58 ++++ .../pagetop-bootsier/src/theme/aux/color.rs | 258 ++++---------- .../pagetop-bootsier/src/theme/aux/rounded.rs | 186 +--------- .../pagetop-bootsier/src/theme/aux/size.rs | 28 -- .../pagetop-bootsier/src/theme/classes.rs | 10 + .../src/theme/classes/border.rs | 155 +++++++++ .../src/theme/classes/color.rs | 207 ++++++++++++ .../src/theme/classes/rounded.rs | 163 +++++++++ .../pagetop-bootsier/src/theme/container.rs | 319 ++---------------- .../src/theme/container/component.rs | 197 +++++++++++ .../src/theme/container/props.rs | 44 +++ .../pagetop-bootsier/src/theme/dropdown.rs | 2 +- .../src/theme/dropdown/component.rs | 10 +- .../src/theme/image/component.rs | 52 +-- .../src/theme/nav/component.rs | 2 +- .../src/theme/navbar/component.rs | 55 +-- .../pagetop-bootsier/src/theme/navbar/item.rs | 2 +- .../src/theme/offcanvas/component.rs | 2 +- 23 files changed, 1041 insertions(+), 994 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/aux/button.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/aux/size.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/border.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/color.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/rounded.rs create mode 100644 extensions/pagetop-bootsier/src/theme/container/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/container/props.rs diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 6df5341c..4cb35a27 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -98,7 +98,6 @@ pub mod theme; /// *Prelude* del tema. pub mod prelude { pub use crate::config::*; - pub use crate::theme::aux::*; pub use crate::theme::*; } diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index 4c547f6a..2c6b5757 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -1,10 +1,18 @@ //! Definiciones y componentes del tema. +//! +//! En esta página, el apartado **Modules** incluye las definiciones necesarias para los componentes +//! que se muestran en el apartado **Structs**, mientras que en **Enums** se listan los elementos +//! auxiliares del tema utilizados en clases y componentes. -pub mod aux; +mod aux; +pub use aux::*; + +pub mod classes; // Container. -mod container; -pub use container::{Container, ContainerType, ContainerWidth}; +pub mod container; +#[doc(inline)] +pub use container::Container; // Dropdown. pub mod dropdown; diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs index a56b14e9..528126de 100644 --- a/extensions/pagetop-bootsier/src/theme/aux.rs +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -5,14 +5,13 @@ pub use breakpoint::BreakPoint; mod color; pub use color::{Color, Opacity}; -pub use color::{ColorBg, ColorBorder, ColorButton, ColorText}; -pub use color::{StyleBg, StyleBorder, StyleText}; +pub use color::{ColorBg, ColorText}; mod border; -pub use border::{Border, BorderSize}; +pub use border::{BorderColor, BorderSize}; mod rounded; -pub use rounded::{Rounded, RoundedRadius}; +pub use rounded::RoundedRadius; -mod size; -pub use size::ButtonSize; +mod button; +pub use button::{ButtonColor, ButtonSize}; diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index de3df81e..47547c30 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -1,16 +1,47 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::aux::Color; use std::fmt; +// **< BorderColor >******************************************************************************** + +/// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)). +#[derive(AutoDefault)] +pub enum BorderColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera internamente clases `border-{color}`. + Theme(Color), + /// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color). + Subtle(Color), + /// Color negro. + Black, + /// Color blanco. + White, +} + +#[rustfmt::skip] +impl fmt::Display for BorderColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(c) => write!(f, "border-{c}"), + Self::Subtle(c) => write!(f, "border-{c}-subtle"), + Self::Black => f.write_str("border-black"), + Self::White => f.write_str("border-white"), + } + } +} + // **< BorderSize >********************************************************************************* -/// Tamaño para el ancho de los bordes ([`Border`]). +/// Tamaño para el ancho de los bordes ([`classes::Border`](crate::theme::classes::Border)). /// /// Mapea a `border`, `border-0` y `border-{1..5}`: /// -/// - `None` no añade clase (devuelve `""`). +/// - `None` no añade ninguna clase. /// - `Default` genera `border` (borde por defecto del tema). /// - `Zero` genera `border-0` (sin borde). /// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente). @@ -29,7 +60,7 @@ pub enum BorderSize { impl BorderSize { #[rustfmt::skip] - fn to_class(&self, prefix: impl AsRef<str>) -> String { + pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { match self { Self::None => String::new(), Self::Default => String::from(prefix.as_ref()), @@ -48,146 +79,3 @@ impl fmt::Display for BorderSize { write!(f, "{}", self.to_class("border")) } } - -// **< Border >************************************************************************************* - -/// Agrupa propiedades para crear **bordes**. -/// -/// Permite: -/// -/// - Definir un tamaño **global** para todo el borde (`size`). -/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, -/// respetando LTR/RTL). -/// - Aplicar un **color** al borde (`ColorBorder`). -/// - Aplicar un nivel de **opacidad** (`BorderOpacity`). -/// -/// # Comportamiento aditivo / sustractivo -/// -/// - **Aditivo**: basta con crear un borde sin tamaño con `Border::new()` para ir añadiendo cada -/// lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. -/// -/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando -/// `Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. -/// -/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. -/// -/// # Ejemplos -/// -/// **Borde global:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Scale2); -/// assert_eq!(b.to_string(), "border-2"); -/// ``` -/// -/// **Aditivo (solo borde superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::new().with_top(BorderSize::Scale1); -/// assert_eq!(b.to_string(), "border-top-1"); -/// ``` -/// -/// **Sustractivo (borde global menos el superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Default).with_top(BorderSize::Zero); -/// assert_eq!(b.to_string(), "border border-top-0"); -/// ``` -/// -/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); -/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); -/// ``` -/// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Default) // Borde global por defecto. -/// .with_top(BorderSize::Zero) // Quita borde superior. -/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. -/// .with_style(StyleBorder::Both(ColorBorder::Theme(Color::Primary), Opacity::Half)); -/// -/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); -/// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Border { - size : BorderSize, - top : BorderSize, - end : BorderSize, - bottom: BorderSize, - start : BorderSize, - style : StyleBorder, -} - -impl Border { - /// Prepara un borde **sin tamaño global** de partida. - pub fn new() -> Self { - Self::default() - } - - /// Crea un borde **con tamaño global** (`size`). - pub fn with(size: BorderSize) -> Self { - Self::default().with_size(size) - } - - // **< Border BUILDER >************************************************************************* - - /// Establece el tamaño global del borde (`border*`). - pub fn with_size(mut self, size: BorderSize) -> Self { - self.size = size; - self - } - - /// Establece el tamaño del borde superior (`border-top-*`). - pub fn with_top(mut self, size: BorderSize) -> Self { - self.top = size; - self - } - - /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. - pub fn with_end(mut self, size: BorderSize) -> Self { - self.end = size; - self - } - - /// Establece el tamaño del borde inferior (`border-bottom-*`). - pub fn with_bottom(mut self, size: BorderSize) -> Self { - self.bottom = size; - self - } - - /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. - pub fn with_start(mut self, size: BorderSize) -> Self { - self.start = size; - self - } - - /// Establece el estilo de color/opacidad del borde. - pub fn with_style(mut self, style: StyleBorder) -> Self { - self.style = style; - self - } -} - -impl fmt::Display for Border { - /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start` y - /// *color*/*opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - join_opt!([ - self.size.to_string(), - self.top.to_class("border-top"), - self.end.to_class("border-end"), - self.bottom.to_class("border-bottom"), - self.start.to_class("border-start"), - self.style.to_string(), - ]; " ") - .unwrap_or_default() - ) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index 601aa547..27d7c29f 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -3,25 +3,6 @@ use pagetop::prelude::*; use std::fmt; /// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. -/// -/// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o -/// `XXL`, respectivamente. -/// - `""` (cadena vacía) para `None`. -/// -/// # Ejemplos -/// -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// assert_eq!(BreakPoint::MD.to_string(), "md"); -/// assert_eq!(BreakPoint::None.to_string(), ""); -/// -/// // Forma correcta para clases con prefijo: -/// assert_eq!(BreakPoint::MD.to_class("col"), "col-md"); -/// assert_eq!(BreakPoint::None.to_class("offcanvas"), "offcanvas"); -/// -/// assert_eq!(BreakPoint::XXL.try_class("col"), Some("col-xxl".to_string())); -/// assert_eq!(BreakPoint::None.try_class("offcanvas"), None); -/// ``` #[derive(AutoDefault)] pub enum BreakPoint { /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. @@ -40,46 +21,43 @@ pub enum BreakPoint { } impl BreakPoint { - /// Comprueba si es un punto de ruptura efectivo. - /// - /// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso. + #[rustfmt::skip] #[inline] - pub const fn is_breakpoint(&self) -> bool { - !matches!(self, Self::None) + const fn suffix(&self) -> Option<&'static str> { + match self { + Self::None => None, + Self::SM => Some("sm"), + Self::MD => Some("md"), + Self::LG => Some("lg"), + Self::XL => Some("xl"), + Self::XXL => Some("xxl"), + } } /// Genera un nombre de clase CSS basado en el punto de ruptura. /// - /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), concatena - /// el prefijo, un guion (`-`) y el sufijo asociado. En otro caso devuelve únicamente el - /// prefijo. + /// Si es un punto de ruptura efectivo concatena el prefijo, un guion (`-`) y el sufijo + /// asociado. Para `None` devuelve sólo el prefijo. /// /// # Ejemplo /// /// ```rust /// # use pagetop_bootsier::prelude::*; /// let breakpoint = BreakPoint::MD; - /// let class = breakpoint.to_class("col"); - /// assert_eq!(class, "col-md".to_string()); + /// assert_eq!(breakpoint.to_class("col"), "col-md"); /// /// let breakpoint = BreakPoint::None; - /// let class = breakpoint.to_class("offcanvas"); - /// assert_eq!(class, "offcanvas".to_string()); + /// assert_eq!(breakpoint.to_class("offcanvas"), "offcanvas"); /// ``` #[inline] pub fn to_class(&self, prefix: impl AsRef<str>) -> String { - if self.is_breakpoint() { - join!(prefix, "-", self.to_string()) - } else { - String::from(prefix.as_ref()) - } + join_pair!(prefix, "-", self.suffix().unwrap_or_default()) } /// Intenta generar un nombre de clase CSS basado en el punto de ruptura. /// - /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), devuelve - /// `Some(String)` concatenando el prefijo, un guion (`-`) y el sufijo asociado. En otro caso - /// devuelve `None`. + /// Si es un punto de ruptura efectivo devuelve `Some(String)` concatenando el prefijo, un guion + /// (`-`) y el sufijo asociado. En otro caso devuelve `None`. /// /// # Ejemplo /// @@ -95,24 +73,19 @@ impl BreakPoint { /// ``` #[inline] pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> { - if self.is_breakpoint() { - Some(join!(prefix, "-", self.to_string())) - } else { - None - } + self.suffix().map(|suffix| join_pair!(prefix, "-", suffix)) } } -#[rustfmt::skip] impl fmt::Display for BreakPoint { + /// Implementa [`Display`](std::fmt::Display) para asociar `"sm"`, `"md"`, `"lg"`, `"xl"` o + /// `"xxl"` a los puntos de ruptura `BreakPoint::SM`, `MD`, `LG`, `XL` o `XXL`, respectivamente. + /// Y `""` (cadena vacía) a `BreakPoint::None`. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::None => Ok(()), - Self::SM => f.write_str("sm"), - Self::MD => f.write_str("md"), - Self::LG => f.write_str("lg"), - Self::XL => f.write_str("xl"), - Self::XXL => f.write_str("xxl"), + if let Some(suffix) = self.suffix() { + f.write_str(suffix) + } else { + Ok(()) } } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/button.rs b/extensions/pagetop-bootsier/src/theme/aux/button.rs new file mode 100644 index 00000000..a2f2efb9 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/button.rs @@ -0,0 +1,58 @@ +use pagetop::prelude::*; + +use crate::theme::aux::Color; + +use std::fmt; + +// **< ButtonColor >******************************************************************************** + +/// Variantes de color `btn-*` para botones. +#[derive(AutoDefault)] +pub enum ButtonColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera internamente clases `btn-{color}` (botón relleno). + Background(Color), + /// Genera `btn-outline-{color}` (fondo transparente y contorno con borde). + Outline(Color), + /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. + Link, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Background(c) => write!(f, "btn-{c}"), + Self::Outline(c) => write!(f, "btn-outline-{c}"), + Self::Link => f.write_str("btn-link"), + } + } +} + +// **< ButtonSize >********************************************************************************* + +/// Tamaño visual de un botón. +#[derive(AutoDefault)] +pub enum ButtonSize { + /// Tamaño por defecto del tema (no añade clase). + #[default] + Default, + /// Botón compacto. + Small, + /// Botón destacado/grande. + Large, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Small => f.write_str("btn-sm"), + Self::Large => f.write_str("btn-lg"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index 17d192dd..d0f1da00 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -7,8 +7,10 @@ use std::fmt; /// Paleta de colores temáticos. /// /// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`, -/// etc.). Este tipo enumerado sirve de base para componer clases de color para el fondo -/// ([`ColorBg`]), bordes ([`ColorBorder`]) y texto ([`ColorText`]). +/// etc.). Este tipo enumerado sirve de base para componer las clases de color para fondo +/// ([`classes::Background`](crate::theme::classes::Background)), bordes +/// ([`classes::Border`](crate::theme::classes::Border)) y texto +/// ([`classes::Text`](crate::theme::classes::Text)). #[derive(AutoDefault)] pub enum Color { #[default] @@ -38,12 +40,69 @@ impl fmt::Display for Color { } } +// **< Opacity >************************************************************************************ + +/// Niveles de opacidad (`opacity-*`). +/// +/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` +/// ([`classes::Background`](crate::theme::classes::Background)), de los bordes `border-opacity-*` +/// ([`classes::Border`](crate::theme::classes::Border)) o del texto `text-opacity-*` +/// ([`classes::Text`](crate::theme::classes::Text)). +#[derive(AutoDefault)] +pub enum Opacity { + /// No define ninguna clase. + #[default] + Default, + /// Permite generar clases `*-opacity-100` (100% de opacidad). + Opaque, + /// Permite generar clases `*-opacity-75` (75%). + SemiOpaque, + /// Permite generar clases `*-opacity-50` (50%). + Half, + /// Permite generar clases `*-opacity-25` (25%). + SemiTransparent, + /// Permite generar clases `*-opacity-10` (10%). + AlmostTransparent, + /// Permite generar clases `*-opacity-0` (0%, totalmente transparente). + Transparent, +} + +impl Opacity { + #[rustfmt::skip] + #[inline] + const fn suffix(&self) -> &'static str { + match self { + Self::Default => "", + Self::Opaque => "opacity-100", + Self::SemiOpaque => "opacity-75", + Self::Half => "opacity-50", + Self::SemiTransparent => "opacity-25", + Self::AlmostTransparent => "opacity-10", + Self::Transparent => "opacity-0", + } + } + + #[inline] + pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { + match self { + Self::Default => String::new(), + _ => join_pair!(prefix, "-", self.suffix()), + } + } +} + +impl fmt::Display for Opacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.suffix()) + } +} + // **< ColorBg >************************************************************************************ /// Colores `bg-*` para el fondo. #[derive(AutoDefault)] pub enum ColorBg { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + /// No define ninguna clase. #[default] Default, /// Fondo predefinido del tema (`bg-body`). @@ -81,71 +140,12 @@ impl fmt::Display for ColorBg { } } -// **< ColorBorder >******************************************************************************** - -/// Colores `border-*` para los bordes ([`Border`](crate::theme::aux::Border)). -#[derive(AutoDefault)] -pub enum ColorBorder { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `border-{color}`. - Theme(Color), - /// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color). - Subtle(Color), - /// Color negro. - Black, - /// Color blanco. - White, -} - -#[rustfmt::skip] -impl fmt::Display for ColorBorder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(c) => write!(f, "border-{c}"), - Self::Subtle(c) => write!(f, "border-{c}-subtle"), - Self::Black => f.write_str("border-black"), - Self::White => f.write_str("border-white"), - } - } -} - -// **< ColorButton >******************************************************************************** - -/// Variantes de color `btn-*` para botones. -#[derive(AutoDefault)] -pub enum ColorButton { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `btn-{color}` (botón relleno). - Background(Color), - /// Genera `btn-outline-{color}` (fondo transparente y contorno con borde). - Outline(Color), - /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. - Link, -} - -#[rustfmt::skip] -impl fmt::Display for ColorButton { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Background(c) => write!(f, "btn-{c}"), - Self::Outline(c) => write!(f, "btn-outline-{c}"), - Self::Link => f.write_str("btn-link"), - } - } -} - // **< ColorText >********************************************************************************** /// Colores `text-*` para el texto. #[derive(AutoDefault)] pub enum ColorText { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + /// No define ninguna clase. #[default] Default, /// Color predefinido del tema (`text-body`). @@ -182,135 +182,3 @@ impl fmt::Display for ColorText { } } } - -// **< Opacity >************************************************************************************ - -/// Niveles de opacidad (`opacity-*`). -/// -/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` -/// ([`StyleBg`]), de los bordes `border-opacity-*` ([`StyleBorder`]) o del texto `text-opacity-*` -/// ([`StyleText`]). -#[rustfmt::skip] -#[derive(AutoDefault)] -pub enum Opacity { - /// Genera internamente clases `opacity-100` (100% de opacidad). - #[default] - Opaque, - /// Genera internamente clases `opacity-75` (75%). - SemiOpaque, - /// Genera internamente clases `opacity-50` (50%). - Half, - /// Genera internamente clases `opacity-25` (25%). - SemiTransparent, - /// Genera internamente clases `opacity-10` (10%). - AlmostTransparent, - /// Genera internamente clases `opacity-0` (0%, totalmente transparente). - Transparent, -} - -#[rustfmt::skip] -impl fmt::Display for Opacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Opaque => f.write_str("opacity-100"), - Self::SemiOpaque => f.write_str("opacity-75"), - Self::Half => f.write_str("opacity-50"), - Self::SemiTransparent => f.write_str("opacity-25"), - Self::AlmostTransparent => f.write_str("opacity-10"), - Self::Transparent => f.write_str("opacity-0"), - } - } -} - -// **< StyleBg >*********************************************************************************** - -/// Estilos de color/opacidad para el fondo. -#[derive(AutoDefault)] -pub enum StyleBg { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `bg-*`. - Color(ColorBg), - /// Genera internamente clases `bg-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `bg-* bg-opacity-*`. - Both(ColorBg, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleBg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "bg-{o}"), - Self::Both(c, o) => write!(f, "{c} bg-{o}"), - } - } -} - -// **< StyleBorder >******************************************************************************* - -/// Estilos de color/opacidad para los bordes ([`Border`](crate::theme::aux::Border)). -#[derive(AutoDefault)] -pub enum StyleBorder { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `border-*`. - Color(ColorBorder), - /// Genera internamente clases `border-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `border-* border-opacity-*`. - Both(ColorBorder, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleBorder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "border-{o}"), - Self::Both(c, o) => write!(f, "{c} border-{o}"), - } - } -} - -// **< StyleText >********************************************************************************* - -/// Estilos de color/opacidad para texto y fondo del texto. -#[derive(AutoDefault)] -pub enum StyleText { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `text-*`. - Color(ColorText), - /// Genera internamente clases `text-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `text-* text-opacity-*`. - Both(ColorText, Opacity), - /// Genera internamente clases `text-bg-*` (para el color de fondo del texto). - Bg(Color), - /// Genera internamente clases `text-bg-* text-*`. - BgAndColor(Color, ColorText), - /// Genera internamente clases `text-bg-* text-* text-opacity-*`. - All(Color, ColorText, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleText { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "text-{o}"), - Self::Both(c, o) => write!(f, "{c} text-{o}"), - Self::Bg(bg) => write!(f, "text-bg-{bg}"), - Self::BgAndColor(bg, c) => write!(f, "text-bg-{bg} {c}"), - Self::All(bg, c, o) => write!(f, "text-bg-{bg} {c} text-{o}"), - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index 470a48a3..8eac7c53 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -2,13 +2,11 @@ use pagetop::prelude::*; use std::fmt; -// **< RoundedRadius >****************************************************************************** - -/// Radio para el redondeo de esquinas ([`Rounded`]). +/// Radio para el redondeo de esquinas ([`classes::Rounded`](crate::theme::classes::Rounded)). /// /// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`. /// -/// - `None` no añade clase (devuelve `""`). +/// - `None` no añade ninguna clase. /// - `Default` genera `rounded` (radio por defecto del tema). /// - `Zero` genera `rounded-0` (sin redondeo). /// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente). @@ -31,18 +29,18 @@ pub enum RoundedRadius { impl RoundedRadius { #[rustfmt::skip] - fn to_class(&self, base: impl AsRef<str>) -> String { + pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { match self { RoundedRadius::None => String::new(), - RoundedRadius::Default => String::from(base.as_ref()), - RoundedRadius::Zero => join!(base, "-0"), - RoundedRadius::Scale1 => join!(base, "-1"), - RoundedRadius::Scale2 => join!(base, "-2"), - RoundedRadius::Scale3 => join!(base, "-3"), - RoundedRadius::Scale4 => join!(base, "-4"), - RoundedRadius::Scale5 => join!(base, "-5"), - RoundedRadius::Circle => join!(base, "-circle"), - RoundedRadius::Pill => join!(base, "-pill"), + RoundedRadius::Default => String::from(prefix.as_ref()), + RoundedRadius::Zero => join!(prefix, "-0"), + RoundedRadius::Scale1 => join!(prefix, "-1"), + RoundedRadius::Scale2 => join!(prefix, "-2"), + RoundedRadius::Scale3 => join!(prefix, "-3"), + RoundedRadius::Scale4 => join!(prefix, "-4"), + RoundedRadius::Scale5 => join!(prefix, "-5"), + RoundedRadius::Circle => join!(prefix, "-circle"), + RoundedRadius::Pill => join!(prefix, "-pill"), } } } @@ -52,163 +50,3 @@ impl fmt::Display for RoundedRadius { write!(f, "{}", self.to_class("rounded")) } } - -// **< Rounded >************************************************************************************ - -/// Agrupa propiedades para crear **esquinas redondeadas**. -/// -/// Permite: -/// -/// - Definir un radio **global para todas las esquinas** (`radius`). -/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`, -/// `start`, **en este orden**, respetando LTR/RTL). -/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`, -/// `bottom-end`, **en este orden**, respetando LTR/RTL). -/// -/// # Ejemplos -/// -/// **Radio global:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::with(RoundedRadius::Default); -/// assert_eq!(r.to_string(), "rounded"); -/// ``` -/// -/// **Sin redondeo:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new(); -/// assert_eq!(r.to_string(), ""); -/// ``` -/// -/// **Radio en las esquinas de un lado lógico:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new().with_end(RoundedRadius::Scale2); -/// assert_eq!(r.to_string(), "rounded-end-2"); -/// ``` -/// -/// **Radio en una esquina concreta:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new().with_top_start(RoundedRadius::Scale3); -/// assert_eq!(r.to_string(), "rounded-top-start-3"); -/// ``` -/// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new() -/// .with_top(RoundedRadius::Default) // Añade redondeo arriba. -/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. -/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. -/// -/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); -/// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Rounded { - radius : RoundedRadius, - top : RoundedRadius, - end : RoundedRadius, - bottom : RoundedRadius, - start : RoundedRadius, - top_start : RoundedRadius, - top_end : RoundedRadius, - bottom_start: RoundedRadius, - bottom_end : RoundedRadius, -} - -impl Rounded { - /// Prepara las esquinas **sin redondeo global** de partida. - pub fn new() -> Self { - Self::default() - } - - /// Crea las esquinas **con redondeo global** (`radius`). - pub fn with(radius: RoundedRadius) -> Self { - Self::default().with_radius(radius) - } - - // **< Rounded BUILDER >************************************************************************ - - /// Establece el radio global de las esquinas (`rounded*`). - pub fn with_radius(mut self, radius: RoundedRadius) -> Self { - self.radius = radius; - self - } - - /// Establece el radio en las esquinas del lado superior (`rounded-top-*`). - pub fn with_top(mut self, radius: RoundedRadius) -> Self { - self.top = radius; - self - } - - /// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL. - pub fn with_end(mut self, radius: RoundedRadius) -> Self { - self.end = radius; - self - } - - /// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`). - pub fn with_bottom(mut self, radius: RoundedRadius) -> Self { - self.bottom = radius; - self - } - - /// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta - /// LTR/RTL. - pub fn with_start(mut self, radius: RoundedRadius) -> Self { - self.start = radius; - self - } - - /// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL. - pub fn with_top_start(mut self, radius: RoundedRadius) -> Self { - self.top_start = radius; - self - } - - /// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL. - pub fn with_top_end(mut self, radius: RoundedRadius) -> Self { - self.top_end = radius; - self - } - - /// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta - /// LTR/RTL. - pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self { - self.bottom_start = radius; - self - } - - /// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL. - pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self { - self.bottom_end = radius; - self - } -} - -impl fmt::Display for Rounded { - /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, - /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las - /// definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - join_opt!([ - self.radius.to_string(), - self.top.to_class("rounded-top"), - self.end.to_class("rounded-end"), - self.bottom.to_class("rounded-bottom"), - self.start.to_class("rounded-start"), - self.top_start.to_class("rounded-top-start"), - self.top_end.to_class("rounded-top-end"), - self.bottom_start.to_class("rounded-bottom-start"), - self.bottom_end.to_class("rounded-bottom-end"), - ]; " ") - .unwrap_or_default() - ) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/size.rs b/extensions/pagetop-bootsier/src/theme/aux/size.rs deleted file mode 100644 index d0abdb51..00000000 --- a/extensions/pagetop-bootsier/src/theme/aux/size.rs +++ /dev/null @@ -1,28 +0,0 @@ -use pagetop::prelude::*; - -use std::fmt; - -// **< ButtonSize >********************************************************************************* - -/// Tamaño visual de un botón. -#[derive(AutoDefault)] -pub enum ButtonSize { - /// Tamaño por defecto del tema (no añade clase). - #[default] - Default, - /// Botón compacto. - Small, - /// Botón destacado/grande. - Large, -} - -#[rustfmt::skip] -impl fmt::Display for ButtonSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Small => f.write_str("btn-sm"), - Self::Large => f.write_str("btn-lg"), - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/classes.rs b/extensions/pagetop-bootsier/src/theme/classes.rs new file mode 100644 index 00000000..4e586e11 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes.rs @@ -0,0 +1,10 @@ +//! Conjunto de clases para aplicar en componentes del tema. + +mod color; +pub use color::{Background, Text}; + +mod border; +pub use border::Border; + +mod rounded; +pub use rounded::Rounded; diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs new file mode 100644 index 00000000..f49c75c6 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -0,0 +1,155 @@ +use pagetop::prelude::*; + +use crate::theme::aux::{BorderColor, BorderSize, Opacity}; + +use std::fmt; + +/// Clases para crear **bordes**. +/// +/// Permite: +/// +/// - Definir un tamaño **global** para todo el borde (`size`). +/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, +/// respetando LTR/RTL). +/// - Aplicar un **color** al borde (`BorderColor`). +/// - Aplicar un nivel de **opacidad** (`Opacity`). +/// +/// # Comportamiento aditivo / sustractivo +/// +/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::new()` para ir +/// añadiendo cada lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. +/// +/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando +/// `classes::Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. +/// +/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. +/// +/// # Ejemplos +/// +/// **Borde global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Scale2); +/// assert_eq!(b.to_string(), "border-2"); +/// ``` +/// +/// **Aditivo (solo borde superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::new().with_top(BorderSize::Scale1); +/// assert_eq!(b.to_string(), "border-top-1"); +/// ``` +/// +/// **Sustractivo (borde global menos el superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Default).with_top(BorderSize::Zero); +/// assert_eq!(b.to_string(), "border border-top-0"); +/// ``` +/// +/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); +/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Default) // Borde global por defecto. +/// .with_top(BorderSize::Zero) // Quita borde superior. +/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. +/// .with_color(BorderColor::Theme(Color::Primary)) +/// .with_opacity(Opacity::Half); +/// +/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Border { + size : BorderSize, + top : BorderSize, + end : BorderSize, + bottom : BorderSize, + start : BorderSize, + color : BorderColor, + opacity: Opacity, +} + +impl Border { + /// Prepara un borde **sin tamaño global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea un borde **con tamaño global** (`size`). + pub fn with(size: BorderSize) -> Self { + Self::default().with_size(size) + } + + // **< Border BUILDER >************************************************************************* + + /// Establece el tamaño global del borde (`border*`). + pub fn with_size(mut self, size: BorderSize) -> Self { + self.size = size; + self + } + + /// Establece el tamaño del borde superior (`border-top-*`). + pub fn with_top(mut self, size: BorderSize) -> Self { + self.top = size; + self + } + + /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, size: BorderSize) -> Self { + self.end = size; + self + } + + /// Establece el tamaño del borde inferior (`border-bottom-*`). + pub fn with_bottom(mut self, size: BorderSize) -> Self { + self.bottom = size; + self + } + + /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. + pub fn with_start(mut self, size: BorderSize) -> Self { + self.start = size; + self + } + + /// Establece el color del borde. + pub fn with_color(mut self, color: BorderColor) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del borde. + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Border { + /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, *color* + /// y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + [ + self.size.to_string(), + self.top.to_class("border-top"), + self.end.to_class("border-end"), + self.bottom.to_class("border-bottom"), + self.start.to_class("border-start"), + self.color.to_string(), + self.opacity.to_class("border"), + ] + .join_classes() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes/color.rs b/extensions/pagetop-bootsier/src/theme/classes/color.rs new file mode 100644 index 00000000..6579d21c --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/color.rs @@ -0,0 +1,207 @@ +use pagetop::prelude::*; + +use crate::theme::aux::{ColorBg, ColorText, Opacity}; + +use std::fmt; + +// **< Bg >***************************************************************************************** + +/// Clases para establecer **color/opacidad del fondo**. +/// +/// # Ejemplos +/// +/// ``` +/// # use pagetop_bootsier::prelude::*; +/// // Sin clases. +/// let s = classes::Background::new(); +/// assert_eq!(s.to_string(), ""); +/// +/// // Sólo color de fondo. +/// let s = classes::Background::with(ColorBg::Theme(Color::Primary)); +/// assert_eq!(s.to_string(), "bg-primary"); +/// +/// // Color más opacidad. +/// let s = classes::Background::with(ColorBg::BodySecondary).with_opacity(Opacity::Half); +/// assert_eq!(s.to_string(), "bg-body-secondary bg-opacity-50"); +/// +/// // Usando `From<ColorBg>`. +/// let s: classes::Background = ColorBg::Black.into(); +/// assert_eq!(s.to_string(), "bg-black"); +/// +/// // Usando `From<(ColorBg, Opacity)>`. +/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); +/// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Background { + color : ColorBg, + opacity: Opacity, +} + +impl Background { + /// Prepara un nuevo estilo para aplicar al fondo. + pub fn new() -> Self { + Self::default() + } + + /// Crea un estilo fijando el color de fondo (`bg-*`). + pub fn with(color: ColorBg) -> Self { + Self::default().with_color(color) + } + + // **< Bg BUILDER >***************************************************************************** + + /// Establece el color de fondo (`bg-*`). + pub fn with_color(mut self, color: ColorBg) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del fondo (`bg-opacity-*`). + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Background { + /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo + /// las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let classes = [self.color.to_string(), self.opacity.to_class("bg")].join_classes(); + write!(f, "{classes}") + } +} + +impl From<(ColorBg, Opacity)> for Background { + /// Atajo para crear un [`classes::Background`](crate::theme::classes::Background) a partir del color de fondo y + /// la opacidad. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); + /// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); + /// ``` + fn from((color, opacity): (ColorBg, Opacity)) -> Self { + Background::with(color).with_opacity(opacity) + } +} + +impl From<ColorBg> for Background { + /// Atajo para crear un [`classes::Background`](crate::theme::classes::Background) a partir del color de fondo. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Background = ColorBg::Black.into(); + /// assert_eq!(s.to_string(), "bg-black"); + /// ``` + fn from(color: ColorBg) -> Self { + Background::with(color) + } +} + +// **< Text >*************************************************************************************** + +/// Clases para establecer **color/opacidad del texto**. +/// +/// # Ejemplos +/// +/// ``` +/// # use pagetop_bootsier::prelude::*; +/// // Sin clases. +/// let s = classes::Text::new(); +/// assert_eq!(s.to_string(), ""); +/// +/// // Sólo color del texto. +/// let s = classes::Text::with(ColorText::Theme(Color::Primary)); +/// assert_eq!(s.to_string(), "text-primary"); +/// +/// // Color del texto y opacidad. +/// let s = classes::Text::new().with_color(ColorText::White).with_opacity(Opacity::SemiTransparent); +/// assert_eq!(s.to_string(), "text-white text-opacity-25"); +/// +/// // Usando `From<ColorText>`. +/// let s: classes::Text = ColorText::Black.into(); +/// assert_eq!(s.to_string(), "text-black"); +/// +/// // Usando `From<(ColorText, Opacity)>`. +/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); +/// assert_eq!(s.to_string(), "text-danger text-opacity-100"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Text { + color : ColorText, + opacity: Opacity, +} + +impl Text { + /// Prepara un nuevo estilo para aplicar al texto. + pub fn new() -> Self { + Self::default() + } + + /// Crea un estilo fijando el color del texto (`text-*`). + pub fn with(color: ColorText) -> Self { + Self::default().with_color(color) + } + + // **< Text BUILDER >*************************************************************************** + + /// Establece el color del texto (`text-*`). + pub fn with_color(mut self, color: ColorText) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del texto (`text-opacity-*`). + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Text { + /// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let classes = [self.color.to_string(), self.opacity.to_class("text")].join_classes(); + write!(f, "{classes}") + } +} + +impl From<(ColorText, Opacity)> for Text { + /// Atajo para crear un [`classes::Text`](crate::theme::classes::Text) a partir del color del + /// texto y su opacidad. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); + /// assert_eq!(s.to_string(), "text-danger text-opacity-100"); + /// ``` + fn from((color, opacity): (ColorText, Opacity)) -> Self { + Text::with(color).with_opacity(opacity) + } +} + +impl From<ColorText> for Text { + /// Atajo para crear un [`classes::Text`](crate::theme::classes::Text) a partir del color del + /// texto. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Text = ColorText::Black.into(); + /// assert_eq!(s.to_string(), "text-black"); + /// ``` + fn from(color: ColorText) -> Self { + Text::with(color) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs new file mode 100644 index 00000000..b7510c14 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -0,0 +1,163 @@ +use pagetop::prelude::*; + +use crate::theme::aux::RoundedRadius; + +use std::fmt; + +/// Clases para definir **esquinas redondeadas**. +/// +/// Permite: +/// +/// - Definir un radio **global para todas las esquinas** (`radius`). +/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`, +/// `start`, **en este orden**, respetando LTR/RTL). +/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`, +/// `bottom-end`, **en este orden**, respetando LTR/RTL). +/// +/// # Ejemplos +/// +/// **Radio global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::with(RoundedRadius::Default); +/// assert_eq!(r.to_string(), "rounded"); +/// ``` +/// +/// **Sin redondeo:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new(); +/// assert_eq!(r.to_string(), ""); +/// ``` +/// +/// **Radio en las esquinas de un lado lógico:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2); +/// assert_eq!(r.to_string(), "rounded-end-2"); +/// ``` +/// +/// **Radio en una esquina concreta:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3); +/// assert_eq!(r.to_string(), "rounded-top-start-3"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new() +/// .with_top(RoundedRadius::Default) // Añade redondeo arriba. +/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. +/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. +/// +/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Rounded { + radius : RoundedRadius, + top : RoundedRadius, + end : RoundedRadius, + bottom : RoundedRadius, + start : RoundedRadius, + top_start : RoundedRadius, + top_end : RoundedRadius, + bottom_start: RoundedRadius, + bottom_end : RoundedRadius, +} + +impl Rounded { + /// Prepara las esquinas **sin redondeo global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea las esquinas **con redondeo global** (`radius`). + pub fn with(radius: RoundedRadius) -> Self { + Self::default().with_radius(radius) + } + + // **< Rounded BUILDER >************************************************************************ + + /// Establece el radio global de las esquinas (`rounded*`). + pub fn with_radius(mut self, radius: RoundedRadius) -> Self { + self.radius = radius; + self + } + + /// Establece el radio en las esquinas del lado superior (`rounded-top-*`). + pub fn with_top(mut self, radius: RoundedRadius) -> Self { + self.top = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, radius: RoundedRadius) -> Self { + self.end = radius; + self + } + + /// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`). + pub fn with_bottom(mut self, radius: RoundedRadius) -> Self { + self.bottom = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta + /// LTR/RTL. + pub fn with_start(mut self, radius: RoundedRadius) -> Self { + self.start = radius; + self + } + + /// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL. + pub fn with_top_start(mut self, radius: RoundedRadius) -> Self { + self.top_start = radius; + self + } + + /// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL. + pub fn with_top_end(mut self, radius: RoundedRadius) -> Self { + self.top_end = radius; + self + } + + /// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta + /// LTR/RTL. + pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self { + self.bottom_start = radius; + self + } + + /// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL. + pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self { + self.bottom_end = radius; + self + } +} + +impl fmt::Display for Rounded { + /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, + /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las + /// definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + [ + self.radius.to_string(), + self.top.to_class("rounded-top"), + self.end.to_class("rounded-end"), + self.bottom.to_class("rounded-bottom"), + self.start.to_class("rounded-start"), + self.top_start.to_class("rounded-top-start"), + self.top_end.to_class("rounded-top-end"), + self.bottom_start.to_class("rounded-bottom-start"), + self.bottom_end.to_class("rounded-bottom-end"), + ] + .join_classes() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index 411eb0aa..a860110f 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -1,299 +1,24 @@ -use pagetop::prelude::*; +//! Definiciones para crear contenedores de componentes ([`Container`]). +//! +//! Cada contenedor envuelve contenido usando la etiqueta semántica indicada por +//! [`container::Kind`](crate::theme::container::Kind). +//! +//! Con [`container::Width`](crate::theme::container::Width) se puede definir el ancho y el +//! comportamiento *responsive* del contenedor. También permite aplicar utilidades de estilo para el +//! fondo, texto, borde o esquinas redondeadas. +//! +//! # Ejemplo +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let main = Container::main() +//! .with_id("main-page") +//! .with_width(container::Width::From(BreakPoint::LG)); +//! ``` -use crate::prelude::*; +mod props; +pub use props::{Kind, Width}; -// **< ContainerType >****************************************************************************** - -/// Tipo de contenedor ([`Container`]). -/// -/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API -/// común a todos los contenedores. -#[derive(AutoDefault)] -pub enum ContainerType { - /// Contenedor genérico (`<div>`). - #[default] - Default, - /// Contenido principal de la página (`<main>`). - Main, - /// Encabezado de la página o de sección (`<header>`). - Header, - /// Pie de la página o de sección (`<footer>`). - Footer, - /// Sección de contenido (`<section>`). - Section, - /// Artículo de contenido (`<article>`). - Article, -} - -// **< ContainerWidth >***************************************************************************** - -/// Define el comportamiento para ajustar el ancho de un contenedor ([`Container`]). -#[derive(AutoDefault)] -pub enum ContainerWidth { - /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de - /// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible. - #[default] - Default, - /// Aplica los anchos máximos predefinidos a partir del punto de ruptura indicado. Por debajo de - /// ese punto de ruptura ocupa el 100% del ancho disponible. - From(BreakPoint), - /// Ocupa el 100% del ancho disponible siempre. - Fluid, - /// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito. - FluidMax(UnitValue), -} - -// **< Container >********************************************************************************** - -/// Componente para crear un **contenedor de componentes**. -/// -/// Envuelve el contenido con la etiqueta HTML indicada por [`ContainerType`]. Sólo se renderiza si -/// existen componentes hijos (*children*). -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Container { - id : AttrId, - classes : AttrClasses, - container_type : ContainerType, - container_width: ContainerWidth, - style_bg : StyleBg, - style_text : StyleText, - border : Border, - rounded : Rounded, - children : Children, -} - -impl Component for Container { - fn new() -> Self { - Container::default() - } - - fn id(&self) -> Option<String> { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - join_pair!( - "container", - "-", - match self.width() { - ContainerWidth::Default => String::new(), - ContainerWidth::From(bp) => bp.to_string(), - ContainerWidth::Fluid => "fluid".to_string(), - ContainerWidth::FluidMax(_) => "fluid".to_string(), - } - ), - self.style_bg().to_string(), - self.style_text().to_string(), - self.border().to_string(), - self.rounded().to_string(), - ] - .join(" "), - ); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let output = self.children().render(cx); - if output.is_empty() { - return PrepareMarkup::None; - } - let style = match self.width() { - ContainerWidth::FluidMax(w) if w.is_measurable() => { - Some(join!("max-width: ", w.to_string(), ";")) - } - _ => None, - }; - match self.container_type() { - ContainerType::Default => PrepareMarkup::With(html! { - div id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - ContainerType::Main => PrepareMarkup::With(html! { - main id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - ContainerType::Header => PrepareMarkup::With(html! { - header id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - ContainerType::Footer => PrepareMarkup::With(html! { - footer id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - ContainerType::Section => PrepareMarkup::With(html! { - section id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - ContainerType::Article => PrepareMarkup::With(html! { - article id=[self.id()] class=[self.classes().get()] style=[style] { - (output) - } - }), - } - } -} - -impl Container { - /// Crea un contenedor de tipo `Main` (`<main>`). - pub fn main() -> Self { - Container { - container_type: ContainerType::Main, - ..Default::default() - } - } - - /// Crea un contenedor de tipo `Header` (`<header>`). - pub fn header() -> Self { - Container { - container_type: ContainerType::Header, - ..Default::default() - } - } - - /// Crea un contenedor de tipo `Footer` (`<footer>`). - pub fn footer() -> Self { - Container { - container_type: ContainerType::Footer, - ..Default::default() - } - } - - /// Crea un contenedor de tipo `Section` (`<section>`). - pub fn section() -> Self { - Container { - container_type: ContainerType::Section, - ..Default::default() - } - } - - /// Crea un contenedor de tipo `Article` (`<article>`). - pub fn article() -> Self { - Container { - container_type: ContainerType::Article, - ..Default::default() - } - } - - // **< Container BUILDER >********************************************************************** - - /// Establece el identificador único (`id`) del contenedor. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef<str>) -> Self { - self.id.alter_value(id); - self - } - - /// Modifica la lista de clases CSS aplicadas al contenedor. - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { - self.classes.alter_value(op, classes); - self - } - - /// Establece el comportamiento del ancho para el contenedor. - #[builder_fn] - pub fn with_width(mut self, width: ContainerWidth) -> Self { - self.container_width = width; - self - } - - /// Establece el estilo del fondo ([`StyleBg`]). - #[builder_fn] - pub fn with_style_bg(mut self, style: StyleBg) -> Self { - self.style_bg = style; - self - } - - /// Establece el estilo del texto ([`StyleText`]). - #[builder_fn] - pub fn with_style_text(mut self, style: StyleText) -> Self { - self.style_text = style; - self - } - - /// Atajo para definir los estilos de fondo y texto a la vez. - #[builder_fn] - pub fn with_styles(mut self, style_bg: StyleBg, style_text: StyleText) -> Self { - self.style_bg = style_bg; - self.style_text = style_text; - self - } - - /// Establece el borde del contenedor ([`Border`]). - #[builder_fn] - pub fn with_border(mut self, border: Border) -> Self { - self.border = border; - self - } - - /// Establece esquinas redondeadas para el contenedor ([`Rounded`]). - #[builder_fn] - pub fn with_rounded(mut self, rounded: Rounded) -> Self { - self.rounded = rounded; - self - } - - /// Añade un nuevo componente hijo al contenedor. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. - #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); - self - } - - // **< Container GETTERS >********************************************************************** - - /// Devuelve las clases CSS asociadas al contenedor. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el tipo semántico del contenedor. - pub fn container_type(&self) -> &ContainerType { - &self.container_type - } - - /// Devuelve el comportamiento para el ancho del contenedor. - pub fn width(&self) -> &ContainerWidth { - &self.container_width - } - - /// Devuelve el estilo del fondo del contenedor. - pub fn style_bg(&self) -> &StyleBg { - &self.style_bg - } - - /// Devuelve el estilo del texto del contenedor. - pub fn style_text(&self) -> &StyleText { - &self.style_text - } - - /// Devuelve el borde configurado del contenedor. - pub fn border(&self) -> &Border { - &self.border - } - - /// Devuelve las esquinas redondeadas configuradas para el contenedor. - pub fn rounded(&self) -> &Rounded { - &self.rounded - } - - /// Devuelve la lista de componentes (`children`) del contenedor. - pub fn children(&self) -> &Children { - &self.children - } -} +mod component; +pub use component::Container; diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs new file mode 100644 index 00000000..1bcf1006 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -0,0 +1,197 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +/// Componente para crear un **contenedor de componentes**. +/// +/// Envuelve un contenido con la etiqueta HTML indicada por [`container::Kind`]. Sólo se renderiza +/// si existen componentes hijos (*children*). +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Container { + id : AttrId, + classes : AttrClasses, + container_kind : container::Kind, + container_width: container::Width, + children : Children, +} + +impl Component for Container { + fn new() -> Self { + Container::default() + } + + fn id(&self) -> Option<String> { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [join_pair!( + "container", + "-", + match self.width() { + container::Width::Default => String::new(), + container::Width::From(bp) => bp.to_string(), + container::Width::Fluid => "fluid".to_string(), + container::Width::FluidMax(_) => "fluid".to_string(), + } + )] + .join_classes(), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let output = self.children().render(cx); + if output.is_empty() { + return PrepareMarkup::None; + } + let style = match self.width() { + container::Width::FluidMax(w) if w.is_measurable() => { + Some(join!("max-width: ", w.to_string(), ";")) + } + _ => None, + }; + match self.container_kind() { + container::Kind::Default => PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + container::Kind::Main => PrepareMarkup::With(html! { + main id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + container::Kind::Header => PrepareMarkup::With(html! { + header id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + container::Kind::Footer => PrepareMarkup::With(html! { + footer id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + container::Kind::Section => PrepareMarkup::With(html! { + section id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + container::Kind::Article => PrepareMarkup::With(html! { + article id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }), + } + } +} + +impl Container { + /// Crea un contenedor de tipo `Main` (`<main>`). + pub fn main() -> Self { + Container { + container_kind: container::Kind::Main, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Header` (`<header>`). + pub fn header() -> Self { + Container { + container_kind: container::Kind::Header, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Footer` (`<footer>`). + pub fn footer() -> Self { + Container { + container_kind: container::Kind::Footer, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Section` (`<section>`). + pub fn section() -> Self { + Container { + container_kind: container::Kind::Section, + ..Default::default() + } + } + + /// Crea un contenedor de tipo `Article` (`<article>`). + pub fn article() -> Self { + Container { + container_kind: container::Kind::Article, + ..Default::default() + } + } + + // **< Container BUILDER >********************************************************************** + + /// Establece el identificador único (`id`) del contenedor. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef<str>) -> Self { + self.id.alter_value(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al contenedor. + /// + /// También acepta clases predefinidas para: + /// + /// - Modificar el color de fondo ([`classes::Background`]). + /// - Definir la apariencia del texto ([`classes::Text`]). + /// - Establecer bordes ([`classes::Border`]). + /// - Redondear las esquinas ([`classes::Rounded`]). + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { + self.classes.alter_value(op, classes); + self + } + + /// Establece el comportamiento del ancho para el contenedor. + #[builder_fn] + pub fn with_width(mut self, width: container::Width) -> Self { + self.container_width = width; + self + } + + /// Añade un nuevo componente hijo al contenedor. + #[inline] + pub fn add_child(mut self, component: impl Component) -> Self { + self.children.add(Child::with(component)); + self + } + + /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + #[builder_fn] + pub fn with_child(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // **< Container GETTERS >********************************************************************** + + /// Devuelve las clases CSS asociadas al contenedor. + pub fn classes(&self) -> &AttrClasses { + &self.classes + } + + /// Devuelve el tipo semántico del contenedor. + pub fn container_kind(&self) -> &container::Kind { + &self.container_kind + } + + /// Devuelve el comportamiento para el ancho del contenedor. + pub fn width(&self) -> &container::Width { + &self.container_width + } + + /// Devuelve la lista de componentes (`children`) del contenedor. + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/extensions/pagetop-bootsier/src/theme/container/props.rs b/extensions/pagetop-bootsier/src/theme/container/props.rs new file mode 100644 index 00000000..796e32f4 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/container/props.rs @@ -0,0 +1,44 @@ +use pagetop::prelude::*; + +use crate::prelude::*; + +// **< Kind >*************************************************************************************** + +/// Tipo de contenedor ([`Container`]). +/// +/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API +/// común a todos los contenedores. +#[derive(AutoDefault)] +pub enum Kind { + /// Contenedor genérico (`<div>`). + #[default] + Default, + /// Contenido principal de la página (`<main>`). + Main, + /// Encabezado de la página o de sección (`<header>`). + Header, + /// Pie de la página o de sección (`<footer>`). + Footer, + /// Sección de contenido (`<section>`). + Section, + /// Artículo de contenido (`<article>`). + Article, +} + +// **< Width >************************************************************************************** + +/// Define el comportamiento para ajustar el ancho de un contenedor ([`Container`]). +#[derive(AutoDefault)] +pub enum Width { + /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de + /// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible. + #[default] + Default, + /// Aplica los anchos máximos predefinidos a partir del punto de ruptura indicado. Por debajo de + /// ese punto de ruptura ocupa el 100% del ancho disponible. + From(BreakPoint), + /// Ocupa el 100% del ancho disponible siempre. + Fluid, + /// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito. + FluidMax(UnitValue), +} diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index bbad2d3f..ed4cbec0 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -14,7 +14,7 @@ //! # use pagetop_bootsier::prelude::*; //! let dd = Dropdown::new() //! .with_title(L10n::n("Menu")) -//! .with_button_color(ColorButton::Background(Color::Secondary)) +//! .with_button_color(ButtonColor::Background(Color::Secondary)) //! .with_auto_close(dropdown::AutoClose::ClickableInside) //! .with_direction(dropdown::Direction::Dropend) //! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 57e30713..450edff2 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -26,7 +26,7 @@ pub struct Dropdown { classes : AttrClasses, title : L10n, button_size : ButtonSize, - button_color : ColorButton, + button_color : ButtonColor, button_split : bool, button_grouped: bool, auto_close : dropdown::AutoClose, @@ -59,7 +59,7 @@ impl Component for Dropdown { dropdown::Direction::Dropend => "dropend", dropdown::Direction::Dropstart => "dropstart", } - ].join(" ")); + ].join_classes()); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { @@ -79,7 +79,7 @@ impl Component for Dropdown { "btn", &self.button_size().to_string(), &self.button_color().to_string(), - ].join(" ")); + ].join_classes()); @let (offset, reference) = match self.menu_position() { dropdown::MenuPosition::Default => (None, None), dropdown::MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None), @@ -212,7 +212,7 @@ impl Dropdown { /// Define el color/estilo del botón. #[builder_fn] - pub fn with_button_color(mut self, color: ColorButton) -> Self { + pub fn with_button_color(mut self, color: ButtonColor) -> Self { self.button_color = color; self } @@ -291,7 +291,7 @@ impl Dropdown { } /// Devuelve el color/estilo configurado del botón. - pub fn button_color(&self) -> &ColorButton { + pub fn button_color(&self) -> &ButtonColor { &self.button_color } diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index 0345adb2..2eda4302 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -6,8 +6,8 @@ use crate::prelude::*; /// /// - Ajusta su disposición según el origen definido en [`image::Source`]. /// - Permite configurar **dimensiones** ([`with_size()`](Self::with_size)), **borde** -/// ([`with_border()`](Self::with_border)) y **redondeo de esquinas** -/// ([`with_rounded()`](Self::with_rounded)). +/// ([`classes::Border`](crate::theme::classes::Border)) y **redondeo de esquinas** +/// ([`classes::Rounded`](crate::theme::classes::Rounded)). /// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`]. #[rustfmt::skip] #[derive(AutoDefault)] @@ -17,8 +17,6 @@ pub struct Image { size : image::Size, source : image::Source, alt : AttrL10n, - border : Border, - rounded: Rounded, } impl Component for Image { @@ -33,17 +31,12 @@ impl Component for Image { fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes( ClassesOp::Prepend, - [ - String::from(match self.source() { - image::Source::Logo(_) => "img-fluid", - image::Source::Responsive(_) => "img-fluid", - image::Source::Thumbnail(_) => "img-thumbnail", - image::Source::Plain(_) => "", - }), - self.border().to_string(), - self.rounded().to_string(), - ] - .join(" "), + match self.source() { + image::Source::Logo(_) => "img-fluid", + image::Source::Responsive(_) => "img-fluid", + image::Source::Thumbnail(_) => "img-thumbnail", + image::Source::Plain(_) => "", + }, ); } @@ -116,6 +109,11 @@ impl Image { } /// Modifica la lista de clases CSS aplicadas a la imagen. + /// + /// También acepta clases predefinidas para: + /// + /// - Establecer bordes ([`classes::Border`]). + /// - Redondear las esquinas ([`classes::Rounded`]). #[builder_fn] pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { self.classes.alter_value(op, classes); @@ -146,20 +144,6 @@ impl Image { self } - /// Establece el borde de la imagen ([`Border`]). - #[builder_fn] - pub fn with_border(mut self, border: Border) -> Self { - self.border = border; - self - } - - /// Establece esquinas redondeadas para la imagen ([`Rounded`]). - #[builder_fn] - pub fn with_rounded(mut self, rounded: Rounded) -> Self { - self.rounded = rounded; - self - } - // **< Image GETTERS >************************************************************************** /// Devuelve las clases CSS asociadas a la imagen. @@ -181,14 +165,4 @@ impl Image { pub fn alternative(&self) -> &AttrL10n { &self.alt } - - /// Devuelve el borde configurado de la imagen. - pub fn border(&self) -> &Border { - &self.border - } - - /// Devuelve las esquinas redondeadas configuradas para la imagen. - pub fn rounded(&self) -> &Rounded { - &self.rounded - } } diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index d4cf2c8c..fe886bdd 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -50,7 +50,7 @@ impl Component for Nav { nav::Layout::Justified => "nav-justified", }, ] - .join(" "), + .join_classes(), ); } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 6362b9d6..ac592439 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -17,14 +17,12 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; #[rustfmt::skip] #[derive(AutoDefault)] pub struct Navbar { - id : AttrId, - classes : AttrClasses, - expand : BreakPoint, - layout : navbar::Layout, - position : navbar::Position, - style_bg : StyleBg, - style_text: StyleText, - items : Children, + id : AttrId, + classes : AttrClasses, + expand : BreakPoint, + layout : navbar::Layout, + position : navbar::Position, + items : Children, } impl Component for Navbar { @@ -50,10 +48,8 @@ impl Component for Navbar { navbar::Position::StickyBottom => "sticky-bottom", } .to_string(), - self.style_bg().to_string(), - self.style_text().to_string(), ] - .join(" "), + .join_classes(), ); } @@ -232,6 +228,11 @@ impl Navbar { } /// Modifica la lista de clases CSS aplicadas a la barra de navegación. + /// + /// También acepta clases predefinidas para: + /// + /// - Modificar el color de fondo ([`classes::Background`]). + /// - Definir la apariencia del texto ([`classes::Text`]). #[builder_fn] pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { self.classes.alter_value(op, classes); @@ -259,28 +260,6 @@ impl Navbar { self } - /// Establece el estilo del fondo ([`StyleBg`]). - #[builder_fn] - pub fn with_style_bg(mut self, style: StyleBg) -> Self { - self.style_bg = style; - self - } - - /// Establece el estilo del texto ([`StyleText`]). - #[builder_fn] - pub fn with_style_text(mut self, style: StyleText) -> Self { - self.style_text = style; - self - } - - /// Atajo para definir los estilos de fondo y texto a la vez. - #[builder_fn] - pub fn with_styles(mut self, style_bg: StyleBg, style_text: StyleText) -> Self { - self.style_bg = style_bg; - self.style_text = style_text; - self - } - /// Añade un nuevo contenido hijo. #[inline] pub fn add_item(mut self, item: navbar::Item) -> Self { @@ -317,16 +296,6 @@ impl Navbar { &self.position } - /// Devuelve el estilo del fondo del contenedor. - pub fn style_bg(&self) -> &StyleBg { - &self.style_bg - } - - /// Devuelve el estilo del texto del contenedor. - pub fn style_text(&self) -> &StyleText { - &self.style_text - } - /// Devuelve la lista de contenidos (`children`). pub fn items(&self) -> &Children { &self.items diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index f0646406..07f52be6 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -67,7 +67,7 @@ impl Component for Item { nav::Layout::Justified => "nav-justified", }, ] - .join(" "), + .join_classes(), ); PrepareMarkup::With(html! { ul id=[nav.id()] class=[classes.get()] { diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 61e6fae7..7516238b 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -61,7 +61,7 @@ impl Component for Offcanvas { offcanvas::Visibility::Show => "show", }.to_string(), ] - .join(" "), + .join_classes(), ); } From 2e39af0856a57e8c6e2bd77aaab72e55169eb703 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 15 Nov 2025 13:16:15 +0100 Subject: [PATCH 184/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(bootsier):=20Refa?= =?UTF-8?q?ctoriza=20la=20gesti=C3=B3n=20de=20clases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mejora la legibilidad del código. - Simplifica las alteraciones de clases en los componentes `Container`, `Dropdown`, `Image`, `Nav`, `Navbar` y `Offcanvas` usando métodos dedicados para generar clases en función de sus propiedades. - Mejora los enums añadiendo métodos que devuelven sus clases asociadas, reduciendo código repetitivo. - Elimina el trait `JoinClasses` y su implementación, integrando la lógica de unión de clases directamente en los componentes. --- extensions/pagetop-bootsier/src/theme/aux.rs | 5 +- .../pagetop-bootsier/src/theme/aux/border.rs | 114 +++---- .../src/theme/aux/breakpoint.rs | 145 +++++---- .../pagetop-bootsier/src/theme/aux/button.rs | 119 ++++++- .../pagetop-bootsier/src/theme/aux/color.rs | 305 ++++++++++++++---- .../pagetop-bootsier/src/theme/aux/layout.rs | 104 ++++++ .../pagetop-bootsier/src/theme/aux/rounded.rs | 119 +++++-- .../pagetop-bootsier/src/theme/classes.rs | 3 + .../src/theme/classes/border.rs | 178 +++++----- .../src/theme/classes/color.rs | 97 +++--- .../src/theme/classes/layout.rs | 205 ++++++++++++ .../src/theme/classes/rounded.rs | 60 ++-- .../src/theme/container/component.rs | 15 +- .../src/theme/container/props.rs | 38 ++- .../src/theme/dropdown/component.rs | 68 ++-- .../src/theme/dropdown/item.rs | 2 +- .../src/theme/dropdown/props.rs | 148 ++++++++- .../src/theme/image/component.rs | 31 +- .../pagetop-bootsier/src/theme/image/props.rs | 59 +++- .../src/theme/nav/component.rs | 28 +- .../pagetop-bootsier/src/theme/nav/item.rs | 51 ++- .../pagetop-bootsier/src/theme/nav/props.rs | 85 ++++- .../src/theme/navbar/component.rs | 22 +- .../pagetop-bootsier/src/theme/navbar/item.rs | 31 +- .../src/theme/navbar/props.rs | 36 ++- .../src/theme/offcanvas/component.rs | 25 +- .../src/theme/offcanvas/props.rs | 67 +++- src/base/component.rs | 12 +- src/html.rs | 3 - src/html/assets/stylesheet.rs | 6 +- src/html/join_classes.rs | 67 ---- src/html/logo.rs | 2 +- src/prelude.rs | 4 +- 33 files changed, 1607 insertions(+), 647 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/aux/layout.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/layout.rs delete mode 100644 src/html/join_classes.rs diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs index 528126de..99431fe3 100644 --- a/extensions/pagetop-bootsier/src/theme/aux.rs +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -7,8 +7,11 @@ mod color; pub use color::{Color, Opacity}; pub use color::{ColorBg, ColorText}; +mod layout; +pub use layout::{ScaleSize, Side}; + mod border; -pub use border::{BorderColor, BorderSize}; +pub use border::BorderColor; mod rounded; pub use rounded::RoundedRadius; diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index 47547c30..43882767 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -2,12 +2,8 @@ use pagetop::prelude::*; use crate::theme::aux::Color; -use std::fmt; - -// **< BorderColor >******************************************************************************** - /// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum BorderColor { /// No define ninguna clase. #[default] @@ -22,60 +18,70 @@ pub enum BorderColor { White, } -#[rustfmt::skip] -impl fmt::Display for BorderColor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(c) => write!(f, "border-{c}"), - Self::Subtle(c) => write!(f, "border-{c}-subtle"), - Self::Black => f.write_str("border-black"), - Self::White => f.write_str("border-white"), - } - } -} +impl BorderColor { + const BORDER: &str = "border"; + const BORDER_PREFIX: &str = "border-"; -// **< BorderSize >********************************************************************************* - -/// Tamaño para el ancho de los bordes ([`classes::Border`](crate::theme::classes::Border)). -/// -/// Mapea a `border`, `border-0` y `border-{1..5}`: -/// -/// - `None` no añade ninguna clase. -/// - `Default` genera `border` (borde por defecto del tema). -/// - `Zero` genera `border-0` (sin borde). -/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente). -#[derive(AutoDefault)] -pub enum BorderSize { - #[default] - None, - Default, - Zero, - Scale1, - Scale2, - Scale3, - Scale4, - Scale5, -} - -impl BorderSize { + // Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase. #[rustfmt::skip] - pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { + #[inline] + const fn suffix(self) -> Option<&'static str> { match self { - Self::None => String::new(), - Self::Default => String::from(prefix.as_ref()), - Self::Zero => join!(prefix, "-0"), - Self::Scale1 => join!(prefix, "-1"), - Self::Scale2 => join!(prefix, "-2"), - Self::Scale3 => join!(prefix, "-3"), - Self::Scale4 => join!(prefix, "-4"), - Self::Scale5 => join!(prefix, "-5"), + Self::Default => None, + Self::Theme(_) => Some(""), + Self::Subtle(_) => Some("-subtle"), + Self::Black => Some("-black"), + Self::White => Some("-white"), } } -} -impl fmt::Display for BorderSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_class("border")) + // Añade la clase `border-*` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Theme(c) | Self::Subtle(c) => { + classes.push_str(Self::BORDER_PREFIX); + classes.push_str(c.as_str()); + } + _ => classes.push_str(Self::BORDER), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `border-*` correspondiente al color de borde. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary"); + /// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle"); + /// assert_eq!(BorderColor::Black.to_class(), "border-black"); + /// assert_eq!(BorderColor::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + if let Some(suffix) = self.suffix() { + let base_len = match self { + Self::Theme(c) | Self::Subtle(c) => Self::BORDER_PREFIX.len() + c.as_str().len(), + _ => Self::BORDER.len(), + }; + let mut class = String::with_capacity(base_len + suffix.len()); + match self { + Self::Theme(c) | Self::Subtle(c) => { + class.push_str(Self::BORDER_PREFIX); + class.push_str(c.as_str()); + } + _ => class.push_str(Self::BORDER), + } + class.push_str(suffix); + return class; + } + String::new() } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index 27d7c29f..4d9a7626 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -1,9 +1,7 @@ use pagetop::prelude::*; -use std::fmt; - /// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum BreakPoint { /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. #[default] @@ -21,71 +19,96 @@ pub enum BreakPoint { } impl BreakPoint { + // Devuelve la identificación del punto de ruptura. #[rustfmt::skip] #[inline] - const fn suffix(&self) -> Option<&'static str> { + pub(crate) const fn as_str(self) -> &'static str { match self { - Self::None => None, - Self::SM => Some("sm"), - Self::MD => Some("md"), - Self::LG => Some("lg"), - Self::XL => Some("xl"), - Self::XXL => Some("xxl"), + Self::None => "", + Self::SM => "sm", + Self::MD => "md", + Self::LG => "lg", + Self::XL => "xl", + Self::XXL => "xxl", } } - /// Genera un nombre de clase CSS basado en el punto de ruptura. - /// - /// Si es un punto de ruptura efectivo concatena el prefijo, un guion (`-`) y el sufijo - /// asociado. Para `None` devuelve sólo el prefijo. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop_bootsier::prelude::*; - /// let breakpoint = BreakPoint::MD; - /// assert_eq!(breakpoint.to_class("col"), "col-md"); - /// - /// let breakpoint = BreakPoint::None; - /// assert_eq!(breakpoint.to_class("offcanvas"), "offcanvas"); - /// ``` + // Añade el punto de ruptura con un prefijo y un sufijo (opcional) separados por un guion `-` a + // la cadena de clases. + // + // - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). + // - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. #[inline] - pub fn to_class(&self, prefix: impl AsRef<str>) -> String { - join_pair!(prefix, "-", self.suffix().unwrap_or_default()) - } - - /// Intenta generar un nombre de clase CSS basado en el punto de ruptura. - /// - /// Si es un punto de ruptura efectivo devuelve `Some(String)` concatenando el prefijo, un guion - /// (`-`) y el sufijo asociado. En otro caso devuelve `None`. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop_bootsier::prelude::*; - /// let breakpoint = BreakPoint::MD; - /// let class = breakpoint.try_class("col"); - /// assert_eq!(class, Some("col-md".to_string())); - /// - /// let breakpoint = BreakPoint::None; - /// let class = breakpoint.try_class("navbar-expand"); - /// assert_eq!(class, None); - /// ``` - #[inline] - pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> { - self.suffix().map(|suffix| join_pair!(prefix, "-", suffix)) - } -} - -impl fmt::Display for BreakPoint { - /// Implementa [`Display`](std::fmt::Display) para asociar `"sm"`, `"md"`, `"lg"`, `"xl"` o - /// `"xxl"` a los puntos de ruptura `BreakPoint::SM`, `MD`, `LG`, `XL` o `XXL`, respectivamente. - /// Y `""` (cadena vacía) a `BreakPoint::None`. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(suffix) = self.suffix() { - f.write_str(suffix) - } else { - Ok(()) + pub(crate) fn push_class(self, classes: &mut String, prefix: &str, suffix: &str) { + if prefix.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::None => classes.push_str(prefix), + _ => { + classes.push_str(prefix); + classes.push('-'); + classes.push_str(self.as_str()); + } + } + if !suffix.is_empty() { + classes.push('-'); + classes.push_str(suffix); } } + + // Devuelve la clase para el punto de ruptura, con un prefijo y un sufijo opcional, separados + // por un guion `-`. + // + // - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). + // - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. + // - Si `prefix` está vacío devuelve `""`. + // + // # Ejemplos + // + // ```rust + // # use pagetop_bootsier::prelude::*; + // let bp = BreakPoint::MD; + // assert_eq!(bp.class_with("col", ""), "col-md"); + // assert_eq!(bp.class_with("col", "6"), "col-md-6"); + // + // let bp = BreakPoint::None; + // assert_eq!(bp.class_with("offcanvas", ""), "offcanvas"); + // assert_eq!(bp.class_with("col", "12"), "col-12"); + // + // let bp = BreakPoint::LG; + // assert_eq!(bp.class_with("", "3"), ""); + // ``` + #[inline] + pub(crate) fn class_with(self, prefix: &str, suffix: &str) -> String { + if prefix.is_empty() { + return String::new(); + } + + let bp = self.as_str(); + let has_bp = !bp.is_empty(); + let has_suffix = !suffix.is_empty(); + + let mut len = prefix.len(); + if has_bp { + len += 1 + bp.len(); + } + if has_suffix { + len += 1 + suffix.len(); + } + let mut class = String::with_capacity(len); + class.push_str(prefix); + if has_bp { + class.push('-'); + class.push_str(bp); + } + if has_suffix { + class.push('-'); + class.push_str(suffix); + } + class + } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/button.rs b/extensions/pagetop-bootsier/src/theme/aux/button.rs index a2f2efb9..0d1df87d 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/button.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/button.rs @@ -2,12 +2,10 @@ use pagetop::prelude::*; use crate::theme::aux::Color; -use std::fmt; - // **< ButtonColor >******************************************************************************** /// Variantes de color `btn-*` para botones. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum ButtonColor { /// No define ninguna clase. #[default] @@ -20,14 +18,72 @@ pub enum ButtonColor { Link, } -#[rustfmt::skip] -impl fmt::Display for ButtonColor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl ButtonColor { + const BTN_PREFIX: &str = "btn-"; + const BTN_OUTLINE_PREFIX: &str = "btn-outline-"; + const BTN_LINK: &str = "btn-link"; + + // Añade la clase `btn-*` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Self::Default = self { + return; + } + if !classes.is_empty() { + classes.push(' '); + } match self { - Self::Default => Ok(()), - Self::Background(c) => write!(f, "btn-{c}"), - Self::Outline(c) => write!(f, "btn-outline-{c}"), - Self::Link => f.write_str("btn-link"), + Self::Default => unreachable!(), + Self::Background(c) => { + classes.push_str(Self::BTN_PREFIX); + classes.push_str(c.as_str()); + } + Self::Outline(c) => { + classes.push_str(Self::BTN_OUTLINE_PREFIX); + classes.push_str(c.as_str()); + } + Self::Link => { + classes.push_str(Self::BTN_LINK); + } + } + } + + /// Devuelve la clase `btn-*` correspondiente al color del botón. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!( + /// ButtonColor::Background(Color::Primary).to_class(), + /// "btn-primary" + /// ); + /// assert_eq!( + /// ButtonColor::Outline(Color::Danger).to_class(), + /// "btn-outline-danger" + /// ); + /// assert_eq!(ButtonColor::Link.to_class(), "btn-link"); + /// assert_eq!(ButtonColor::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + match self { + Self::Default => String::new(), + Self::Background(c) => { + let color = c.as_str(); + let mut class = String::with_capacity(Self::BTN_PREFIX.len() + color.len()); + class.push_str(Self::BTN_PREFIX); + class.push_str(color); + class + } + Self::Outline(c) => { + let color = c.as_str(); + let mut class = String::with_capacity(Self::BTN_OUTLINE_PREFIX.len() + color.len()); + class.push_str(Self::BTN_OUTLINE_PREFIX); + class.push_str(color); + class + } + Self::Link => Self::BTN_LINK.to_string(), } } } @@ -35,7 +91,7 @@ impl fmt::Display for ButtonColor { // **< ButtonSize >********************************************************************************* /// Tamaño visual de un botón. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum ButtonSize { /// Tamaño por defecto del tema (no añade clase). #[default] @@ -46,13 +102,42 @@ pub enum ButtonSize { Large, } -#[rustfmt::skip] -impl fmt::Display for ButtonSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl ButtonSize { + const BTN_SM: &str = "btn-sm"; + const BTN_LG: &str = "btn-lg"; + + // Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Self::Default = self { + return; + } + if !classes.is_empty() { + classes.push(' '); + } match self { - Self::Default => Ok(()), - Self::Small => f.write_str("btn-sm"), - Self::Large => f.write_str("btn-lg"), + Self::Default => unreachable!(), + Self::Small => classes.push_str(Self::BTN_SM), + Self::Large => classes.push_str(Self::BTN_LG), + } + } + + /// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(ButtonSize::Small.to_class(), "btn-sm"); + /// assert_eq!(ButtonSize::Large.to_class(), "btn-lg"); + /// assert_eq!(ButtonSize::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + match self { + Self::Default => String::new(), + Self::Small => Self::BTN_SM.to_string(), + Self::Large => Self::BTN_LG.to_string(), } } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index d0f1da00..480ff3d8 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -1,7 +1,5 @@ use pagetop::prelude::*; -use std::fmt; - // **< Color >************************************************************************************** /// Paleta de colores temáticos. @@ -11,7 +9,7 @@ use std::fmt; /// ([`classes::Background`](crate::theme::classes::Background)), bordes /// ([`classes::Border`](crate::theme::classes::Border)) y texto /// ([`classes::Text`](crate::theme::classes::Text)). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Color { #[default] Primary, @@ -24,20 +22,45 @@ pub enum Color { Dark, } -#[rustfmt::skip] -impl fmt::Display for Color { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Color { + // Devuelve el nombre del color. + #[rustfmt::skip] + #[inline] + pub(crate) const fn as_str(self) -> &'static str { match self { - Self::Primary => f.write_str("primary"), - Self::Secondary => f.write_str("secondary"), - Self::Success => f.write_str("success"), - Self::Info => f.write_str("info"), - Self::Warning => f.write_str("warning"), - Self::Danger => f.write_str("danger"), - Self::Light => f.write_str("light"), - Self::Dark => f.write_str("dark"), + Self::Primary => "primary", + Self::Secondary => "secondary", + Self::Success => "success", + Self::Info => "info", + Self::Warning => "warning", + Self::Danger => "danger", + Self::Light => "light", + Self::Dark => "dark", } } + + /* Añade el nombre del color a la cadena de clases (reservado). + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(self.as_str()); + } */ + + /// Devuelve la clase correspondiente al color. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(Color::Primary.to_class(), "primary"); + /// assert_eq!(Color::Danger.to_class(), "danger"); + /// ``` + #[inline] + pub fn to_class(self) -> String { + self.as_str().to_owned() + } } // **< Opacity >************************************************************************************ @@ -48,7 +71,7 @@ impl fmt::Display for Color { /// ([`classes::Background`](crate::theme::classes::Background)), de los bordes `border-opacity-*` /// ([`classes::Border`](crate::theme::classes::Border)) o del texto `text-opacity-*` /// ([`classes::Text`](crate::theme::classes::Text)). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Opacity { /// No define ninguna clase. #[default] @@ -68,39 +91,95 @@ pub enum Opacity { } impl Opacity { + const OPACITY: &str = "opacity"; + const OPACITY_PREFIX: &str = "-opacity"; + + // Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] - const fn suffix(&self) -> &'static str { + const fn suffix(self) -> Option<&'static str> { match self { - Self::Default => "", - Self::Opaque => "opacity-100", - Self::SemiOpaque => "opacity-75", - Self::Half => "opacity-50", - Self::SemiTransparent => "opacity-25", - Self::AlmostTransparent => "opacity-10", - Self::Transparent => "opacity-0", + Self::Default => None, + Self::Opaque => Some("-100"), + Self::SemiOpaque => Some("-75"), + Self::Half => Some("-50"), + Self::SemiTransparent => Some("-25"), + Self::AlmostTransparent => Some("-10"), + Self::Transparent => Some("-0"), } } + // Añade la opacidad a la cadena de clases usando el prefijo dado (`bg`, `border`, `text`, o + // vacío para `opacity-*`). #[inline] - pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { - match self { - Self::Default => String::new(), - _ => join_pair!(prefix, "-", self.suffix()), + pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + if prefix.is_empty() { + classes.push_str(Self::OPACITY); + } else { + classes.push_str(prefix); + classes.push_str(Self::OPACITY_PREFIX); + } + classes.push_str(suffix); } } -} -impl fmt::Display for Opacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.suffix()) + // Devuelve la clase de opacidad con el prefijo dado (`bg`, `border`, `text`, o vacío para + // `opacity-*`). + // + // # Ejemplos + // + // ```rust + // # use pagetop_bootsier::prelude::*; + // assert_eq!(Opacity::Opaque.class_with(""), "opacity-100"); + // assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50"); + // assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25"); + // assert_eq!(Opacity::Default.class_with("bg"), ""); + // ``` + #[inline] + pub(crate) fn class_with(self, prefix: &str) -> String { + if let Some(suffix) = self.suffix() { + let base_len = if prefix.is_empty() { + Self::OPACITY.len() + } else { + prefix.len() + Self::OPACITY_PREFIX.len() + }; + let mut class = String::with_capacity(base_len + suffix.len()); + if prefix.is_empty() { + class.push_str(Self::OPACITY); + } else { + class.push_str(prefix); + class.push_str(Self::OPACITY_PREFIX); + } + class.push_str(suffix); + return class; + } + String::new() + } + + /// Devuelve la clase de opacidad `opacity-*`. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(Opacity::Opaque.to_class(), "opacity-100"); + /// assert_eq!(Opacity::Half.to_class(), "opacity-50"); + /// assert_eq!(Opacity::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + self.class_with("") } } // **< ColorBg >************************************************************************************ /// Colores `bg-*` para el fondo. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum ColorBg { /// No define ninguna clase. #[default] @@ -123,27 +202,83 @@ pub enum ColorBg { Transparent, } -#[rustfmt::skip] -impl fmt::Display for ColorBg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl ColorBg { + const BG: &str = "bg"; + const BG_PREFIX: &str = "bg-"; + + // Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { match self { - Self::Default => Ok(()), - Self::Body => f.write_str("bg-body"), - Self::BodySecondary => f.write_str("bg-body-secondary"), - Self::BodyTertiary => f.write_str("bg-body-tertiary"), - Self::Theme(c) => write!(f, "bg-{c}"), - Self::Subtle(c) => write!(f, "bg-{c}-subtle"), - Self::Black => f.write_str("bg-black"), - Self::White => f.write_str("bg-white"), - Self::Transparent => f.write_str("bg-transparent"), + Self::Default => None, + Self::Body => Some("-body"), + Self::BodySecondary => Some("-body-secondary"), + Self::BodyTertiary => Some("-body-tertiary"), + Self::Theme(_) => Some(""), + Self::Subtle(_) => Some("-subtle"), + Self::Black => Some("-black"), + Self::White => Some("-white"), + Self::Transparent => Some("-transparent"), } } + + // Añade la clase de fondo `bg-*` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Theme(c) | Self::Subtle(c) => { + classes.push_str(Self::BG_PREFIX); + classes.push_str(c.as_str()); + } + _ => classes.push_str(Self::BG), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `bg-*` correspondiente al fondo. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(ColorBg::Body.to_class(), "bg-body"); + /// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary"); + /// assert_eq!(ColorBg::Subtle(Color::Warning).to_class(), "bg-warning-subtle"); + /// assert_eq!(ColorBg::Transparent.to_class(), "bg-transparent"); + /// assert_eq!(ColorBg::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + if let Some(suffix) = self.suffix() { + let base_len = match self { + Self::Theme(c) | Self::Subtle(c) => Self::BG_PREFIX.len() + c.as_str().len(), + _ => Self::BG.len(), + }; + let mut class = String::with_capacity(base_len + suffix.len()); + match self { + Self::Theme(c) | Self::Subtle(c) => { + class.push_str(Self::BG_PREFIX); + class.push_str(c.as_str()); + } + _ => class.push_str(Self::BG), + } + class.push_str(suffix); + return class; + } + String::new() + } } // **< ColorText >********************************************************************************** /// Colores `text-*` para el texto. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum ColorText { /// No define ninguna clase. #[default] @@ -166,19 +301,75 @@ pub enum ColorText { White, } -#[rustfmt::skip] -impl fmt::Display for ColorText { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl ColorText { + const TEXT: &str = "text"; + const TEXT_PREFIX: &str = "text-"; + + // Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { match self { - Self::Default => Ok(()), - Self::Body => f.write_str("text-body"), - Self::BodyEmphasis => f.write_str("text-body-emphasis"), - Self::BodySecondary => f.write_str("text-body-secondary"), - Self::BodyTertiary => f.write_str("text-body-tertiary"), - Self::Theme(c) => write!(f, "text-{c}"), - Self::Emphasis(c) => write!(f, "text-{c}-emphasis"), - Self::Black => f.write_str("text-black"), - Self::White => f.write_str("text-white"), + Self::Default => None, + Self::Body => Some("-body"), + Self::BodyEmphasis => Some("-body-emphasis"), + Self::BodySecondary => Some("-body-secondary"), + Self::BodyTertiary => Some("-body-tertiary"), + Self::Theme(_) => Some(""), + Self::Emphasis(_) => Some("-emphasis"), + Self::Black => Some("-black"), + Self::White => Some("-white"), } } + + // Añade la clase de texto `text-*` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Theme(c) | Self::Emphasis(c) => { + classes.push_str(Self::TEXT_PREFIX); + classes.push_str(c.as_str()); + } + _ => classes.push_str(Self::TEXT), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `text-*` correspondiente al color del texto. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(ColorText::Body.to_class(), "text-body"); + /// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary"); + /// assert_eq!(ColorText::Emphasis(Color::Danger).to_class(), "text-danger-emphasis"); + /// assert_eq!(ColorText::Black.to_class(), "text-black"); + /// assert_eq!(ColorText::Default.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + if let Some(suffix) = self.suffix() { + let base_len = match self { + Self::Theme(c) | Self::Emphasis(c) => Self::TEXT_PREFIX.len() + c.as_str().len(), + _ => Self::TEXT.len(), + }; + let mut class = String::with_capacity(base_len + suffix.len()); + match self { + Self::Theme(c) | Self::Emphasis(c) => { + class.push_str(Self::TEXT_PREFIX); + class.push_str(c.as_str()); + } + _ => class.push_str(Self::TEXT), + } + class.push_str(suffix); + return class; + } + String::new() + } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/layout.rs b/extensions/pagetop-bootsier/src/theme/aux/layout.rs new file mode 100644 index 00000000..1d351582 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/layout.rs @@ -0,0 +1,104 @@ +use pagetop::prelude::*; + +// **< ScaleSize >********************************************************************************** + +/// Escala discreta de tamaños para definir clases utilitarias. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum ScaleSize { + /// Sin tamaño (no define ninguna clase). + #[default] + None, + /// Tamaño automático. + Auto, + /// Escala cero. + Zero, + /// Escala uno. + One, + /// Escala dos. + Two, + /// Escala tres. + Three, + /// Escala cuatro. + Four, + /// Escala cinco. + Five, +} + +impl ScaleSize { + // Devuelve el sufijo para el tamaño (`"-0"`, `"-1"`, etc.), o `None` si no define ninguna + // clase, o `""` para el tamaño automático. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { + match self { + Self::None => None, + Self::Auto => Some(""), + Self::Zero => Some("-0"), + Self::One => Some("-1"), + Self::Two => Some("-2"), + Self::Three => Some("-3"), + Self::Four => Some("-4"), + Self::Five => Some("-5"), + } + } + + // Añade el tamaño a la cadena de clases usando el prefijo dado. + #[inline] + pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { + if !prefix.is_empty() { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(prefix); + classes.push_str(suffix); + } + } + } + + /* Devuelve la clase del tamaño para el prefijo, o una cadena vacía si no aplica (reservado). + // + // # Ejemplo + // + // ```rust + // # use pagetop_bootsier::prelude::*; + // assert_eq!(ScaleSize::Auto.class_with("border"), "border"); + // assert_eq!(ScaleSize::Zero.class_with("m"), "m-0"); + // assert_eq!(ScaleSize::Three.class_with("p"), "p-3"); + // assert_eq!(ScaleSize::None.class_with("border"), ""); + // ``` + #[inline] + pub(crate) fn class_with(self, prefix: &str) -> String { + if !prefix.is_empty() { + if let Some(suffix) = self.suffix() { + let mut class = String::with_capacity(prefix.len() + suffix.len()); + class.push_str(prefix); + class.push_str(suffix); + return class; + } + } + String::new() + } */ +} + +// **< Side >*************************************************************************************** + +/// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL). +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum Side { + /// Todos los lados. + #[default] + All, + /// Lado superior. + Top, + /// Lado inferior. + Bottom, + /// Lado lógico de inicio (respetando RTL). + Start, + /// Lado lógico de fin (respetando RTL). + End, + /// Lados lógicos laterales (abreviatura *x*). + LeftAndRight, + /// Lados superior e inferior (abreviatura *y*). + TopAndBottom, +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index 8eac7c53..20e061d6 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -1,52 +1,117 @@ use pagetop::prelude::*; -use std::fmt; - /// Radio para el redondeo de esquinas ([`classes::Rounded`](crate::theme::classes::Rounded)). -/// -/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`. -/// -/// - `None` no añade ninguna clase. -/// - `Default` genera `rounded` (radio por defecto del tema). -/// - `Zero` genera `rounded-0` (sin redondeo). -/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente). -/// - `Circle` genera `rounded-circle`. -/// - `Pill` genera `rounded-pill`. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum RoundedRadius { + /// No define ninguna clase. #[default] None, + /// Genera `rounded` (radio por defecto del tema). Default, + /// Genera `rounded-0` (sin redondeo). Zero, + /// Genera `rounded-1`. Scale1, + /// Genera `rounded-2`. Scale2, + /// Genera `rounded-3`. Scale3, + /// Genera `rounded-4`. Scale4, + /// Genera `rounded-5`. Scale5, + /// Genera `rounded-circle`. Circle, + /// Genera `rounded-pill`. Pill, } impl RoundedRadius { + const ROUNDED: &str = "rounded"; + + // Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el + // redondeo por defecto. #[rustfmt::skip] - pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String { + #[inline] + const fn suffix(self) -> Option<&'static str> { match self { - RoundedRadius::None => String::new(), - RoundedRadius::Default => String::from(prefix.as_ref()), - RoundedRadius::Zero => join!(prefix, "-0"), - RoundedRadius::Scale1 => join!(prefix, "-1"), - RoundedRadius::Scale2 => join!(prefix, "-2"), - RoundedRadius::Scale3 => join!(prefix, "-3"), - RoundedRadius::Scale4 => join!(prefix, "-4"), - RoundedRadius::Scale5 => join!(prefix, "-5"), - RoundedRadius::Circle => join!(prefix, "-circle"), - RoundedRadius::Pill => join!(prefix, "-pill"), + Self::None => None, + Self::Default => Some(""), + Self::Zero => Some("-0"), + Self::Scale1 => Some("-1"), + Self::Scale2 => Some("-2"), + Self::Scale3 => Some("-3"), + Self::Scale4 => Some("-4"), + Self::Scale5 => Some("-5"), + Self::Circle => Some("-circle"), + Self::Pill => Some("-pill"), } } -} -impl fmt::Display for RoundedRadius { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_class("rounded")) + // Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`, + // `rounded-bottom-start`, o vacío para `rounded-*`). + #[inline] + pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + if prefix.is_empty() { + classes.push_str(Self::ROUNDED); + } else { + classes.push_str(prefix); + } + classes.push_str(suffix); + } + } + + // Devuelve la clase para el redondeo de esquinas con el prefijo dado (`rounded-top`, + // `rounded-bottom-start`, o vacío para `rounded-*`). + // + // # Ejemplos + // + // ```rust + // # use pagetop_bootsier::prelude::*; + // assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2"); + // assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0"); + // assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3"); + // assert_eq!(RoundedRadius::Circle.class_with(""), "rounded-circle"); + // assert_eq!(RoundedRadius::None.class_with("rounded-bottom-start"), ""); + // ``` + #[inline] + pub(crate) fn class_with(self, prefix: &str) -> String { + if let Some(suffix) = self.suffix() { + let base_len = if prefix.is_empty() { + Self::ROUNDED.len() + } else { + prefix.len() + }; + let mut class = String::with_capacity(base_len + suffix.len()); + if prefix.is_empty() { + class.push_str(Self::ROUNDED); + } else { + class.push_str(prefix); + } + class.push_str(suffix); + return class; + } + String::new() + } + + /// Devuelve la clase `rounded-*` para el redondeo de esquinas. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(RoundedRadius::Default.to_class(), "rounded"); + /// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0"); + /// assert_eq!(RoundedRadius::Scale3.to_class(), "rounded-3"); + /// assert_eq!(RoundedRadius::Circle.to_class(), "rounded-circle"); + /// assert_eq!(RoundedRadius::None.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + self.class_with("") } } diff --git a/extensions/pagetop-bootsier/src/theme/classes.rs b/extensions/pagetop-bootsier/src/theme/classes.rs index 4e586e11..9e6c234d 100644 --- a/extensions/pagetop-bootsier/src/theme/classes.rs +++ b/extensions/pagetop-bootsier/src/theme/classes.rs @@ -8,3 +8,6 @@ pub use border::Border; mod rounded; pub use rounded::Rounded; + +mod layout; +pub use layout::{Margin, Padding}; diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs index f49c75c6..3095498c 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/border.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -1,122 +1,113 @@ use pagetop::prelude::*; -use crate::theme::aux::{BorderColor, BorderSize, Opacity}; - -use std::fmt; +use crate::theme::aux::{BorderColor, Opacity, ScaleSize, Side}; /// Clases para crear **bordes**. /// /// Permite: /// +/// - Iniciar un borde sin tamaño inicial (`Border::default()`). +/// - Crear un borde con tamaño por defecto (`Border::new()`). +/// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL). /// - Definir un tamaño **global** para todo el borde (`size`). -/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, -/// respetando LTR/RTL). /// - Aplicar un **color** al borde (`BorderColor`). /// - Aplicar un nivel de **opacidad** (`Opacity`). /// /// # Comportamiento aditivo / sustractivo /// -/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::new()` para ir -/// añadiendo cada lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. +/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::default()` para ir +/// añadiendo cada lado lógico con el tamaño deseado usando `ScaleSize::{One..Five}`. /// -/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando -/// `classes::Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. +/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. usando +/// `classes::Border::new()` o `classes::Border::with(ScaleSize::Two)` y eliminar los lados +/// deseados con `ScaleSize::Zero`. /// -/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. +/// - **Anchos diferentes por lado**: usando `ScaleSize::{Zero..Five}` en cada lado deseado. /// /// # Ejemplos /// /// **Borde global:** /// ```rust /// # use pagetop_bootsier::prelude::*; -/// let b = classes::Border::with(BorderSize::Scale2); -/// assert_eq!(b.to_string(), "border-2"); +/// let b = classes::Border::with(ScaleSize::Two); +/// assert_eq!(b.to_class(), "border-2"); /// ``` /// /// **Aditivo (solo borde superior):** /// ```rust /// # use pagetop_bootsier::prelude::*; -/// let b = classes::Border::new().with_top(BorderSize::Scale1); -/// assert_eq!(b.to_string(), "border-top-1"); +/// let b = classes::Border::default().with_side(Side::Top, ScaleSize::One); +/// assert_eq!(b.to_class(), "border-top-1"); /// ``` /// /// **Sustractivo (borde global menos el superior):** /// ```rust /// # use pagetop_bootsier::prelude::*; -/// let b = classes::Border::with(BorderSize::Default).with_top(BorderSize::Zero); -/// assert_eq!(b.to_string(), "border border-top-0"); +/// let b = classes::Border::new().with_side(Side::Top, ScaleSize::Zero); +/// assert_eq!(b.to_class(), "border border-top-0"); /// ``` /// /// **Ancho por lado (lado lógico inicial a 2 y final a 4):** /// ```rust /// # use pagetop_bootsier::prelude::*; -/// let b = classes::Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); -/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); +/// let b = classes::Border::default() +/// .with_side(Side::Start, ScaleSize::Two) +/// .with_side(Side::End, ScaleSize::Four); +/// assert_eq!(b.to_class(), "border-end-4 border-start-2"); /// ``` /// /// **Combinado (ejemplo completo):** /// ```rust /// # use pagetop_bootsier::prelude::*; -/// let b = classes::Border::with(BorderSize::Default) // Borde global por defecto. -/// .with_top(BorderSize::Zero) // Quita borde superior. -/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. +/// let b = classes::Border::new() // Borde por defecto. +/// .with_side(Side::Top, ScaleSize::Zero) // Quita borde superior. +/// .with_side(Side::End, ScaleSize::Three) // Ancho 3 para el lado lógico final. /// .with_color(BorderColor::Theme(Color::Primary)) /// .with_opacity(Opacity::Half); /// -/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); +/// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50"); /// ``` #[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Border { - size : BorderSize, - top : BorderSize, - end : BorderSize, - bottom : BorderSize, - start : BorderSize, + all : ScaleSize, + top : ScaleSize, + end : ScaleSize, + bottom : ScaleSize, + start : ScaleSize, color : BorderColor, opacity: Opacity, } impl Border { - /// Prepara un borde **sin tamaño global** de partida. + /// Prepara un borde del tamaño predefinido. Equivale a `border` (ancho por defecto del tema). pub fn new() -> Self { - Self::default() + Self::with(ScaleSize::Auto) } - /// Crea un borde **con tamaño global** (`size`). - pub fn with(size: BorderSize) -> Self { - Self::default().with_size(size) + /// Crea un borde **con un tamaño global** (`size`). + pub fn with(size: ScaleSize) -> Self { + Self::default().with_side(Side::All, size) } // **< Border BUILDER >************************************************************************* - /// Establece el tamaño global del borde (`border*`). - pub fn with_size(mut self, size: BorderSize) -> Self { - self.size = size; - self - } - - /// Establece el tamaño del borde superior (`border-top-*`). - pub fn with_top(mut self, size: BorderSize) -> Self { - self.top = size; - self - } - - /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. - pub fn with_end(mut self, size: BorderSize) -> Self { - self.end = size; - self - } - - /// Establece el tamaño del borde inferior (`border-bottom-*`). - pub fn with_bottom(mut self, size: BorderSize) -> Self { - self.bottom = size; - self - } - - /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. - pub fn with_start(mut self, size: BorderSize) -> Self { - self.start = size; + pub fn with_side(mut self, side: Side, size: ScaleSize) -> Self { + match side { + Side::All => self.all = size, + Side::Top => self.top = size, + Side::Bottom => self.bottom = size, + Side::Start => self.start = size, + Side::End => self.end = size, + Side::LeftAndRight => { + self.start = size; + self.end = size; + } + Side::TopAndBottom => { + self.top = size; + self.bottom = size; + } + }; self } @@ -131,25 +122,54 @@ impl Border { self.opacity = opacity; self } -} -impl fmt::Display for Border { - /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, *color* - /// y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - [ - self.size.to_string(), - self.top.to_class("border-top"), - self.end.to_class("border-end"), - self.bottom.to_class("border-bottom"), - self.start.to_class("border-start"), - self.color.to_string(), - self.opacity.to_class("border"), - ] - .join_classes() - ) + // **< Border HELPERS >************************************************************************* + + /// Añade las clases de borde a la cadena de clases. + /// + /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, + /// *color* y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. + #[rustfmt::skip] + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + self.all .push_class(classes, "border"); + self.top .push_class(classes, "border-top"); + self.end .push_class(classes, "border-end"); + self.bottom .push_class(classes, "border-bottom"); + self.start .push_class(classes, "border-start"); + self.color .push_class(classes); + self.opacity.push_class(classes, "border"); + } + + /// Devuelve las clases de borde como cadena (`"border-2"`, + /// `"border border-top-0 border-end-3 border-primary border-opacity-50"`, etc.). + /// + /// Si no se define ningún tamaño, color ni opacidad, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let mut classes = String::new(); + self.push_class(&mut classes); + classes + } +} + +/// Atajo para crear un [`classes::Border`](crate::theme::classes::Border) a partir de un tamaño +/// [`ScaleSize`] aplicado a todo el borde. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// // Convertir explícitamente con `From::from`: +/// let b = classes::Border::from(ScaleSize::Two); +/// assert_eq!(b.to_class(), "border-2"); +/// +/// // Convertir implícitamente con `into()`: +/// let b: classes::Border = ScaleSize::Auto.into(); +/// assert_eq!(b.to_class(), "border"); +/// ``` +impl From<ScaleSize> for Border { + fn from(size: ScaleSize) -> Self { + Self::with(size) } } diff --git a/extensions/pagetop-bootsier/src/theme/classes/color.rs b/extensions/pagetop-bootsier/src/theme/classes/color.rs index 6579d21c..162b7849 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/color.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/color.rs @@ -2,9 +2,7 @@ use pagetop::prelude::*; use crate::theme::aux::{ColorBg, ColorText, Opacity}; -use std::fmt; - -// **< Bg >***************************************************************************************** +// **< Background >********************************************************************************* /// Clases para establecer **color/opacidad del fondo**. /// @@ -14,28 +12,27 @@ use std::fmt; /// # use pagetop_bootsier::prelude::*; /// // Sin clases. /// let s = classes::Background::new(); -/// assert_eq!(s.to_string(), ""); +/// assert_eq!(s.to_class(), ""); /// /// // Sólo color de fondo. /// let s = classes::Background::with(ColorBg::Theme(Color::Primary)); -/// assert_eq!(s.to_string(), "bg-primary"); +/// assert_eq!(s.to_class(), "bg-primary"); /// /// // Color más opacidad. /// let s = classes::Background::with(ColorBg::BodySecondary).with_opacity(Opacity::Half); -/// assert_eq!(s.to_string(), "bg-body-secondary bg-opacity-50"); +/// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50"); /// /// // Usando `From<ColorBg>`. /// let s: classes::Background = ColorBg::Black.into(); -/// assert_eq!(s.to_string(), "bg-black"); +/// assert_eq!(s.to_class(), "bg-black"); /// /// // Usando `From<(ColorBg, Opacity)>`. /// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); -/// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); +/// assert_eq!(s.to_class(), "bg-white bg-opacity-25"); /// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Background { - color : ColorBg, + color: ColorBg, opacity: Opacity, } @@ -50,7 +47,7 @@ impl Background { Self::default().with_color(color) } - // **< Bg BUILDER >***************************************************************************** + // **< Background BUILDER >********************************************************************* /// Establece el color de fondo (`bg-*`). pub fn with_color(mut self, color: ColorBg) -> Self { @@ -63,14 +60,27 @@ impl Background { self.opacity = opacity; self } -} -impl fmt::Display for Background { - /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo - /// las definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let classes = [self.color.to_string(), self.opacity.to_class("bg")].join_classes(); - write!(f, "{classes}") + // **< Background HELPERS >********************************************************************* + + /// Añade las clases de fondo a la cadena de clases. + /// + /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), + /// omitiendo los fragmentos vacíos. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + self.color.push_class(classes); + self.opacity.push_class(classes, "bg"); + } + + /// Devuelve las clases de fondo como cadena (`"bg-primary"`, `"bg-body-secondary bg-opacity-50"`, etc.). + /// + /// Si no se define ni color ni opacidad, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let mut classes = String::new(); + self.push_class(&mut classes); + classes } } @@ -83,7 +93,7 @@ impl From<(ColorBg, Opacity)> for Background { /// ``` /// # use pagetop_bootsier::prelude::*; /// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); - /// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); + /// assert_eq!(s.to_class(), "bg-white bg-opacity-25"); /// ``` fn from((color, opacity): (ColorBg, Opacity)) -> Self { Background::with(color).with_opacity(opacity) @@ -98,7 +108,7 @@ impl From<ColorBg> for Background { /// ``` /// # use pagetop_bootsier::prelude::*; /// let s: classes::Background = ColorBg::Black.into(); - /// assert_eq!(s.to_string(), "bg-black"); + /// assert_eq!(s.to_class(), "bg-black"); /// ``` fn from(color: ColorBg) -> Self { Background::with(color) @@ -115,28 +125,27 @@ impl From<ColorBg> for Background { /// # use pagetop_bootsier::prelude::*; /// // Sin clases. /// let s = classes::Text::new(); -/// assert_eq!(s.to_string(), ""); +/// assert_eq!(s.to_class(), ""); /// /// // Sólo color del texto. /// let s = classes::Text::with(ColorText::Theme(Color::Primary)); -/// assert_eq!(s.to_string(), "text-primary"); +/// assert_eq!(s.to_class(), "text-primary"); /// /// // Color del texto y opacidad. /// let s = classes::Text::new().with_color(ColorText::White).with_opacity(Opacity::SemiTransparent); -/// assert_eq!(s.to_string(), "text-white text-opacity-25"); +/// assert_eq!(s.to_class(), "text-white text-opacity-25"); /// /// // Usando `From<ColorText>`. /// let s: classes::Text = ColorText::Black.into(); -/// assert_eq!(s.to_string(), "text-black"); +/// assert_eq!(s.to_class(), "text-black"); /// /// // Usando `From<(ColorText, Opacity)>`. /// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); -/// assert_eq!(s.to_string(), "text-danger text-opacity-100"); +/// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Text { - color : ColorText, + color: ColorText, opacity: Opacity, } @@ -164,13 +173,27 @@ impl Text { self.opacity = opacity; self } -} -impl fmt::Display for Text { - /// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo las definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let classes = [self.color.to_string(), self.opacity.to_class("text")].join_classes(); - write!(f, "{classes}") + // **< Text HELPERS >*************************************************************************** + + /// Añade las clases de texto a la cadena de clases. + /// + /// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo los fragmentos vacíos. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + self.color.push_class(classes); + self.opacity.push_class(classes, "text"); + } + + /// Devuelve las clases de texto como cadena (`"text-primary"`, `"text-white text-opacity-25"`, + /// etc.). + /// + /// Si no se define ni color ni opacidad, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let mut classes = String::new(); + self.push_class(&mut classes); + classes } } @@ -183,7 +206,7 @@ impl From<(ColorText, Opacity)> for Text { /// ``` /// # use pagetop_bootsier::prelude::*; /// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); - /// assert_eq!(s.to_string(), "text-danger text-opacity-100"); + /// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// ``` fn from((color, opacity): (ColorText, Opacity)) -> Self { Text::with(color).with_opacity(opacity) @@ -199,7 +222,7 @@ impl From<ColorText> for Text { /// ``` /// # use pagetop_bootsier::prelude::*; /// let s: classes::Text = ColorText::Black.into(); - /// assert_eq!(s.to_string(), "text-black"); + /// assert_eq!(s.to_class(), "text-black"); /// ``` fn from(color: ColorText) -> Self { Text::with(color) diff --git a/extensions/pagetop-bootsier/src/theme/classes/layout.rs b/extensions/pagetop-bootsier/src/theme/classes/layout.rs new file mode 100644 index 00000000..e9d7e248 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/layout.rs @@ -0,0 +1,205 @@ +use pagetop::prelude::*; + +use crate::theme::aux::{ScaleSize, Side}; +use crate::theme::BreakPoint; + +// **< Margin >************************************************************************************* + +/// Clases para establecer **margin** por lado, tamaño y punto de ruptura. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let m = classes::Margin::with(Side::Top, ScaleSize::Three); +/// assert_eq!(m.to_class(), "mt-3"); +/// +/// let m = classes::Margin::with(Side::Start, ScaleSize::Auto).with_breakpoint(BreakPoint::LG); +/// assert_eq!(m.to_class(), "ms-lg-auto"); +/// +/// let m = classes::Margin::with(Side::All, ScaleSize::None); +/// assert_eq!(m.to_class(), ""); +/// ``` +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub struct Margin { + side: Side, + size: ScaleSize, + breakpoint: BreakPoint, +} + +impl Margin { + /// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de + /// ruptura. + pub fn with(side: Side, size: ScaleSize) -> Self { + Margin { + side, + size, + breakpoint: BreakPoint::None, + } + } + + // **< Margin BUILDER >************************************************************************* + + /// Establece el punto de ruptura a partir del cual se empieza a aplicar el **margin**. + pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self { + self.breakpoint = breakpoint; + self + } + + // **< Margin HELPERS >************************************************************************* + + // Devuelve el prefijo `m*` según el lado. + #[rustfmt::skip] + #[inline] + const fn side_prefix(&self) -> &'static str { + match self.side { + Side::All => "m", + Side::Top => "mt", + Side::Bottom => "mb", + Side::Start => "ms", + Side::End => "me", + Side::LeftAndRight => "mx", + Side::TopAndBottom => "my", + } + } + + // Devuelve el sufijo del tamaño (`auto`, `0`..`5`), o `None` si no define clase. + #[rustfmt::skip] + #[inline] + const fn size_suffix(&self) -> Option<&'static str> { + match self.size { + ScaleSize::None => None, + ScaleSize::Auto => Some("auto"), + ScaleSize::Zero => Some("0"), + ScaleSize::One => Some("1"), + ScaleSize::Two => Some("2"), + ScaleSize::Three => Some("3"), + ScaleSize::Four => Some("4"), + ScaleSize::Five => Some("5"), + } + } + + /* Añade la clase de **margin** a la cadena de clases (reservado). + // + // No añade nada si `size` es `ScaleSize::None`. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let Some(size) = self.size_suffix() else { + return; + }; + self.breakpoint + .push_class(classes, self.side_prefix(), size); + } */ + + /// Devuelve la clase de **margin** como cadena (`"mt-3"`, `"ms-lg-auto"`, etc.). + /// + /// Si `size` es `ScaleSize::None`, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let Some(size) = self.size_suffix() else { + return String::new(); + }; + self.breakpoint.class_with(self.side_prefix(), size) + } +} + +// **< Padding >************************************************************************************ + +/// Clases para establecer **padding** por lado, tamaño y punto de ruptura. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two); +/// assert_eq!(p.to_class(), "px-2"); +/// +/// let p = classes::Padding::with(Side::End, ScaleSize::Four).with_breakpoint(BreakPoint::SM); +/// assert_eq!(p.to_class(), "pe-sm-4"); +/// +/// let p = classes::Padding::with(Side::All, ScaleSize::Auto); +/// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding. +/// ``` +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub struct Padding { + side: Side, + size: ScaleSize, + breakpoint: BreakPoint, +} + +impl Padding { + /// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de + /// ruptura. + pub fn with(side: Side, size: ScaleSize) -> Self { + Padding { + side, + size, + breakpoint: BreakPoint::None, + } + } + + // **< Padding BUILDER >************************************************************************ + + /// Establece el punto de ruptura a partir del cual se empieza a aplicar el **padding**. + pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self { + self.breakpoint = breakpoint; + self + } + + // **< Padding HELPERS >************************************************************************ + + // Devuelve el prefijo `p*` según el lado. + #[rustfmt::skip] + #[inline] + const fn prefix(&self) -> &'static str { + match self.side { + Side::All => "p", + Side::Top => "pt", + Side::Bottom => "pb", + Side::Start => "ps", + Side::End => "pe", + Side::LeftAndRight => "px", + Side::TopAndBottom => "py", + } + } + + // Devuelve el sufijo del tamaño (`0`..`5`), o `None` si no define clase. + // + // Nota: `ScaleSize::Auto` **no aplica** a padding ⇒ devuelve `None`. + #[rustfmt::skip] + #[inline] + const fn suffix(&self) -> Option<&'static str> { + match self.size { + ScaleSize::None => None, + ScaleSize::Auto => None, + ScaleSize::Zero => Some("0"), + ScaleSize::One => Some("1"), + ScaleSize::Two => Some("2"), + ScaleSize::Three => Some("3"), + ScaleSize::Four => Some("4"), + ScaleSize::Five => Some("5"), + } + } + + /* Añade la clase de **padding** a la cadena de clases (reservado). + // + // No añade nada si `size` es `ScaleSize::None` o `ScaleSize::Auto`. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let Some(size) = self.suffix() else { + return; + }; + self.breakpoint.push_class(classes, self.prefix(), size); + } */ + + // Devuelve la clase de **padding** como cadena (`"px-2"`, `"pe-sm-4"`, etc.). + // + // Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let Some(size) = self.suffix() else { + return String::new(); + }; + self.breakpoint.class_with(self.prefix(), size) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs index b7510c14..58d50b86 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -2,8 +2,6 @@ use pagetop::prelude::*; use crate::theme::aux::RoundedRadius; -use std::fmt; - /// Clases para definir **esquinas redondeadas**. /// /// Permite: @@ -20,28 +18,28 @@ use std::fmt; /// ```rust /// # use pagetop_bootsier::prelude::*; /// let r = classes::Rounded::with(RoundedRadius::Default); -/// assert_eq!(r.to_string(), "rounded"); +/// assert_eq!(r.to_class(), "rounded"); /// ``` /// /// **Sin redondeo:** /// ```rust /// # use pagetop_bootsier::prelude::*; /// let r = classes::Rounded::new(); -/// assert_eq!(r.to_string(), ""); +/// assert_eq!(r.to_class(), ""); /// ``` /// /// **Radio en las esquinas de un lado lógico:** /// ```rust /// # use pagetop_bootsier::prelude::*; /// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2); -/// assert_eq!(r.to_string(), "rounded-end-2"); +/// assert_eq!(r.to_class(), "rounded-end-2"); /// ``` /// /// **Radio en una esquina concreta:** /// ```rust /// # use pagetop_bootsier::prelude::*; /// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3); -/// assert_eq!(r.to_string(), "rounded-top-start-3"); +/// assert_eq!(r.to_class(), "rounded-top-start-3"); /// ``` /// /// **Combinado (ejemplo completo):** @@ -52,10 +50,10 @@ use std::fmt; /// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. /// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. /// -/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); +/// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); /// ``` #[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Rounded { radius : RoundedRadius, top : RoundedRadius, @@ -136,28 +134,36 @@ impl Rounded { self.bottom_end = radius; self } -} -impl fmt::Display for Rounded { + // **< Rounded HELPERS >************************************************************************ + + /// Añade las clases de redondeo a la cadena de clases. + /// /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las /// definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - [ - self.radius.to_string(), - self.top.to_class("rounded-top"), - self.end.to_class("rounded-end"), - self.bottom.to_class("rounded-bottom"), - self.start.to_class("rounded-start"), - self.top_start.to_class("rounded-top-start"), - self.top_end.to_class("rounded-top-end"), - self.bottom_start.to_class("rounded-bottom-start"), - self.bottom_end.to_class("rounded-bottom-end"), - ] - .join_classes() - ) + #[rustfmt::skip] + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + self.radius .push_class(classes, ""); + self.top .push_class(classes, "rounded-top"); + self.end .push_class(classes, "rounded-end"); + self.bottom .push_class(classes, "rounded-bottom"); + self.start .push_class(classes, "rounded-start"); + self.top_start .push_class(classes, "rounded-top-start"); + self.top_end .push_class(classes, "rounded-top-end"); + self.bottom_start.push_class(classes, "rounded-bottom-start"); + self.bottom_end .push_class(classes, "rounded-bottom-end"); + } + + /// Devuelve las clases de redondeo como cadena (`"rounded"`, + /// `"rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"`, etc.). + /// + /// Si no se define ningún radio, devuelve `""`. + #[inline] + pub fn to_class(self) -> String { + let mut classes = String::new(); + self.push_class(&mut classes); + classes } } diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 1bcf1006..068d24a3 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -26,20 +26,7 @@ impl Component for Container { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [join_pair!( - "container", - "-", - match self.width() { - container::Width::Default => String::new(), - container::Width::From(bp) => bp.to_string(), - container::Width::Fluid => "fluid".to_string(), - container::Width::FluidMax(_) => "fluid".to_string(), - } - )] - .join_classes(), - ); + self.alter_classes(ClassesOp::Prepend, self.width().to_class()); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { diff --git a/extensions/pagetop-bootsier/src/theme/container/props.rs b/extensions/pagetop-bootsier/src/theme/container/props.rs index 796e32f4..2010ba8e 100644 --- a/extensions/pagetop-bootsier/src/theme/container/props.rs +++ b/extensions/pagetop-bootsier/src/theme/container/props.rs @@ -1,14 +1,14 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::aux::BreakPoint; // **< Kind >*************************************************************************************** -/// Tipo de contenedor ([`Container`]). +/// Tipo de contenedor ([`Container`](crate::theme::Container)). /// /// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API /// común a todos los contenedores. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Kind { /// Contenedor genérico (`<div>`). #[default] @@ -27,8 +27,8 @@ pub enum Kind { // **< Width >************************************************************************************** -/// Define el comportamiento para ajustar el ancho de un contenedor ([`Container`]). -#[derive(AutoDefault)] +/// Define cómo se comporta el ancho de un contenedor ([`Container`](crate::theme::Container)). +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Width { /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de /// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible. @@ -42,3 +42,31 @@ pub enum Width { /// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito. FluidMax(UnitValue), } + +impl Width { + const CONTAINER: &str = "container"; + + /* Añade el comportamiento del contenedor a la cadena de clases según ancho (reservado). + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + match self { + Self::Default => BreakPoint::None.push_class(classes, Self::CONTAINER, ""), + Self::From(bp) => bp.push_class(classes, Self::CONTAINER, ""), + Self::Fluid | Self::FluidMax(_) => { + BreakPoint::None.push_class(classes, Self::CONTAINER, "fluid") + } + } + } */ + + /// Devuelve la clase asociada al comportamiento del contenedor según el ajuste de su ancho. + #[inline] + pub fn to_class(self) -> String { + match self { + Self::Default => BreakPoint::None.class_with(Self::CONTAINER, ""), + Self::From(bp) => bp.class_with(Self::CONTAINER, ""), + Self::Fluid | Self::FluidMax(_) => { + BreakPoint::None.class_with(Self::CONTAINER, "fluid") + } + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 450edff2..5d27daf5 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -45,21 +45,11 @@ impl Component for Dropdown { self.id.get() } - #[rustfmt::skip] fn setup_before_prepare(&mut self, _cx: &mut Context) { - let g = self.button_grouped(); - self.alter_classes(ClassesOp::Prepend, [ - if g { "btn-group" } else { "" }, - match self.direction() { - dropdown::Direction::Default if g => "", - dropdown::Direction::Default => "dropdown", - dropdown::Direction::Centered => "dropdown-center", - dropdown::Direction::Dropup => "dropup", - dropdown::Direction::DropupCentered => "dropup-center", - dropdown::Direction::Dropend => "dropend", - dropdown::Direction::Dropstart => "dropstart", - } - ].join_classes()); + self.alter_classes( + ClassesOp::Prepend, + self.direction().class_with(self.button_grouped()), + ); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { @@ -75,41 +65,21 @@ impl Component for Dropdown { PrepareMarkup::With(html! { div id=[self.id()] class=[self.classes().get()] { @if !title.is_empty() { - @let mut btn_classes = AttrClasses::new([ - "btn", - &self.button_size().to_string(), - &self.button_color().to_string(), - ].join_classes()); - @let (offset, reference) = match self.menu_position() { - dropdown::MenuPosition::Default => (None, None), - dropdown::MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None), - dropdown::MenuPosition::Parent => (None, Some("parent")), - }; - @let auto_close = match self.auto_close { - dropdown::AutoClose::Default => None, - dropdown::AutoClose::ClickableInside => Some("inside"), - dropdown::AutoClose::ClickableOutside => Some("outside"), - dropdown::AutoClose::ManualClose => Some("false"), - }; - @let menu_classes = AttrClasses::new("dropdown-menu") - .with_value(ClassesOp::Add, match self.menu_align() { - dropdown::MenuAlign::Start => String::new(), - dropdown::MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu") - .map_or(String::new(), |class| join!(class, "-start")), - dropdown::MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu") - .map_or( - "dropdown-menu-start".into(), - |class| join!("dropdown-menu-start ", class, "-end") - ), - dropdown::MenuAlign::End => "dropdown-menu-end".into(), - dropdown::MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu") - .map_or(String::new(), |class| join!(class, "-end")), - dropdown::MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu") - .map_or( - "dropdown-menu-end".into(), - |class| join!("dropdown-menu-end ", class, "-start") - ), - }); + @let mut btn_classes = AttrClasses::new({ + let mut classes = "btn".to_string(); + self.button_size().push_class(&mut classes); + self.button_color().push_class(&mut classes); + classes + }); + @let pos = self.menu_position(); + @let offset = pos.data_offset(); + @let reference = pos.data_reference(); + @let auto_close = self.auto_close.as_str(); + @let menu_classes = AttrClasses::new({ + let mut classes = "dropdown-menu".to_string(); + self.menu_align().push_class(&mut classes); + classes + }); // Renderizado en modo split (dos botones) o simple (un botón). @if self.button_split() { diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index a13058dd..2f62f286 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -115,7 +115,7 @@ impl Component for Item { } ItemKind::Button { label, disabled } => { - let mut classes = "dropdown-item".to_owned(); + let mut classes = "dropdown-item".to_string(); if *disabled { classes.push_str(" disabled"); } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs index da305ea7..7571332b 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs @@ -7,7 +7,7 @@ use crate::prelude::*; /// Estrategia para el cierre automático de un menú [`Dropdown`]. /// /// Define cuándo se cierra el menú desplegado según la interacción del usuario. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum AutoClose { /// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`. #[default] @@ -21,12 +21,26 @@ pub enum AutoClose { ManualClose, } +impl AutoClose { + // Devuelve el valor para `data-bs-auto-close`, o `None` si es el comportamiento por defecto. + #[rustfmt::skip] + #[inline] + pub(crate) const fn as_str(self) -> Option<&'static str> { + match self { + Self::Default => None, + Self::ClickableInside => Some("inside"), + Self::ClickableOutside => Some("outside"), + Self::ManualClose => Some("false"), + } + } +} + // **< Direction >********************************************************************************** /// Dirección de despliegue de un menú [`Dropdown`]. /// /// Controla desde qué posición se muestra el menú respecto al botón. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Direction { /// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial, /// respetando LTR/RTL). @@ -44,13 +58,58 @@ pub enum Direction { Dropstart, } +impl Direction { + // Mapea la dirección teniendo en cuenta si se agrupa con otros menús [`Dropdown`]. + #[rustfmt::skip ] + #[inline] + const fn as_str(self, grouped: bool) -> &'static str { + match self { + Self::Default if grouped => "", + Self::Default => "dropdown", + Self::Centered => "dropdown-center", + Self::Dropup => "dropup", + Self::DropupCentered => "dropup-center", + Self::Dropend => "dropend", + Self::Dropstart => "dropstart", + } + } + + // Añade la dirección de despliegue a la cadena de clases teniendo en cuenta si se agrupa con + // otros menús [`Dropdown`]. + #[inline] + pub(crate) fn push_class(self, classes: &mut String, grouped: bool) { + if grouped { + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str("btn-group"); + } + let class = self.as_str(grouped); + if !class.is_empty() { + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + } + + // Devuelve la clase asociada a la dirección teniendo en cuenta si se agrupa con otros menús + // [`Dropdown`], o `""` si no corresponde ninguna. + #[inline] + pub(crate) fn class_with(self, grouped: bool) -> String { + let mut classes = String::new(); + self.push_class(&mut classes, grouped); + classes + } +} + // **< MenuAlign >********************************************************************************** /// Alineación horizontal del menú desplegable [`Dropdown`]. /// /// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una /// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum MenuAlign { /// Alineación al inicio (comportamiento por defecto). #[default] @@ -67,13 +126,74 @@ pub enum MenuAlign { EndAndStart(BreakPoint), } +impl MenuAlign { + #[inline] + fn push_one(classes: &mut String, class: &str) { + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + // Añade las clases de alineación a `classes` (sin incluir la base `dropdown-menu`). + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + match self { + // Alineación por defecto (start), no añade clases extra. + Self::Start => {} + + // `dropdown-menu-{bp}-start` + Self::StartAt(bp) => { + let class = bp.class_with("dropdown-menu", "start"); + Self::push_one(classes, &class); + } + + // `dropdown-menu-start` + `dropdown-menu-{bp}-end` + Self::StartAndEnd(bp) => { + Self::push_one(classes, "dropdown-menu-start"); + let bp_class = bp.class_with("dropdown-menu", "end"); + Self::push_one(classes, &bp_class); + } + + // `dropdown-menu-end` + Self::End => { + Self::push_one(classes, "dropdown-menu-end"); + } + + // `dropdown-menu-{bp}-end` + Self::EndAt(bp) => { + let class = bp.class_with("dropdown-menu", "end"); + Self::push_one(classes, &class); + } + + // `dropdown-menu-end` + `dropdown-menu-{bp}-start` + Self::EndAndStart(bp) => { + Self::push_one(classes, "dropdown-menu-end"); + let bp_class = bp.class_with("dropdown-menu", "start"); + Self::push_one(classes, &bp_class); + } + } + } + + /* Devuelve las clases de alineación sin incluir `dropdown-menu` (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + let mut classes = String::new(); + self.push_class(&mut classes); + classes + } */ +} + // **< MenuPosition >******************************************************************************* /// Posición relativa del menú desplegable [`Dropdown`]. /// /// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el /// cálculo de la posición. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum MenuPosition { /// Posicionamiento automático por defecto. #[default] @@ -84,3 +204,23 @@ pub enum MenuPosition { /// [`button_split()`](crate::theme::Dropdown::button_split) es `true`. Parent, } + +impl MenuPosition { + // Devuelve el valor para `data-bs-offset` o `None` si no aplica. + #[inline] + pub(crate) fn data_offset(self) -> Option<String> { + match self { + Self::Offset(x, y) => Some(format!("{x},{y}")), + _ => None, + } + } + + // Devuelve el valor para `data-bs-reference` o `None` si no aplica. + #[inline] + pub(crate) fn data_reference(self) -> Option<&'static str> { + match self { + Self::Parent => Some("parent"), + _ => None, + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index 2eda4302..bc3f2c9d 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -29,38 +29,11 @@ impl Component for Image { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - match self.source() { - image::Source::Logo(_) => "img-fluid", - image::Source::Responsive(_) => "img-fluid", - image::Source::Thumbnail(_) => "img-thumbnail", - image::Source::Plain(_) => "", - }, - ); + self.alter_classes(ClassesOp::Prepend, self.source().to_class()); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let dimensions = match self.size() { - image::Size::Auto => None, - image::Size::Dimensions(w, h) => { - let w = w.to_string(); - let h = h.to_string(); - Some(join!("width: ", w, "; height: ", h, ";")) - } - image::Size::Width(w) => { - let w = w.to_string(); - Some(join!("width: ", w, ";")) - } - image::Size::Height(h) => { - let h = h.to_string(); - Some(join!("height: ", h, ";")) - } - image::Size::Both(v) => { - let v = v.to_string(); - Some(join!("width: ", v, "; height: ", v, ";")) - } - }; + let dimensions = self.size().to_style(); let alt_text = self.alternative().lookup(cx).unwrap_or_default(); let is_decorative = alt_text.is_empty(); let source = match self.source() { diff --git a/extensions/pagetop-bootsier/src/theme/image/props.rs b/extensions/pagetop-bootsier/src/theme/image/props.rs index f871de70..e9b1286a 100644 --- a/extensions/pagetop-bootsier/src/theme/image/props.rs +++ b/extensions/pagetop-bootsier/src/theme/image/props.rs @@ -3,7 +3,7 @@ use pagetop::prelude::*; // **< Size >*************************************************************************************** /// Define las **dimensiones** de una imagen ([`Image`](crate::theme::Image)). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Size { /// Ajuste automático por defecto. /// @@ -30,10 +30,24 @@ pub enum Size { Both(UnitValue), } +impl Size { + // Devuelve el valor del atributo `style` en función del tamaño, o `None` si no aplica. + #[inline] + pub(crate) fn to_style(self) -> Option<String> { + match self { + Self::Auto => None, + Self::Dimensions(w, h) => Some(format!("width: {w}; height: {h};")), + Self::Width(w) => Some(format!("width: {w};")), + Self::Height(h) => Some(format!("height: {h};")), + Self::Both(v) => Some(format!("width: {v}; height: {v};")), + } + } +} + // **< Source >************************************************************************************* /// Especifica la **fuente** para publicar una imagen ([`Image`](crate::theme::Image)). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Debug, PartialEq)] pub enum Source { /// Imagen con el logotipo de PageTop. #[default] @@ -51,3 +65,44 @@ pub enum Source { /// El `String` asociado es la URL (o ruta) de la imagen. Plain(String), } + +impl Source { + const IMG_FLUID: &str = "img-fluid"; + const IMG_THUMBNAIL: &str = "img-thumbnail"; + + // Devuelve la clase base asociada a la imagen según la fuente. + #[inline] + fn as_str(&self) -> &'static str { + match self { + Source::Logo(_) | Source::Responsive(_) => Self::IMG_FLUID, + Source::Thumbnail(_) => Self::IMG_THUMBNAIL, + Source::Plain(_) => "", + } + } + + /* Añade la clase base asociada a la imagen según la fuente a la cadena de clases (reservado). + #[inline] + pub(crate) fn push_class(&self, classes: &mut String) { + let s = self.as_str(); + if s.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(s); + } */ + + // Devuelve la clase asociada a la imagen según la fuente. + #[inline] + pub(crate) fn to_class(&self) -> String { + let s = self.as_str(); + if s.is_empty() { + String::new() + } else { + let mut class = String::with_capacity(s.len()); + class.push_str(s); + class + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index fe886bdd..2bd43774 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -30,28 +30,12 @@ impl Component for Nav { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - "nav", - match self.nav_kind() { - nav::Kind::Default => "", - nav::Kind::Tabs => "nav-tabs", - nav::Kind::Pills => "nav-pills", - nav::Kind::Underline => "nav-underline", - }, - match self.nav_layout() { - nav::Layout::Default => "", - nav::Layout::Start => "justify-content-start", - nav::Layout::Center => "justify-content-center", - nav::Layout::End => "justify-content-end", - nav::Layout::Vertical => "flex-column", - nav::Layout::Fill => "nav-fill", - nav::Layout::Justified => "nav-justified", - }, - ] - .join_classes(), - ); + self.alter_classes(ClassesOp::Prepend, { + let mut classes = "nav".to_string(); + self.nav_kind().push_class(&mut classes); + self.nav_layout().push_class(&mut classes); + classes + }); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index bc097e0c..a79947cd 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -29,6 +29,40 @@ pub enum ItemKind { Dropdown(Typed<Dropdown>), } +impl ItemKind { + const ITEM: &str = "nav-item"; + const DROPDOWN: &str = "nav-item dropdown"; + + // Devuelve las clases base asociadas al tipo de elemento. + #[inline] + const fn as_str(&self) -> &'static str { + match self { + Self::Void => "", + Self::Dropdown(_) => Self::DROPDOWN, + _ => Self::ITEM, + } + } + + /* Añade las clases asociadas al tipo de elemento a la cadena de clases (reservado). + #[inline] + pub(crate) fn push_class(&self, classes: &mut String) { + let class = self.as_str(); + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } */ + + // Devuelve las clases asociadas al tipo de elemento. + #[inline] + pub(crate) fn to_class(&self) -> String { + self.as_str().to_owned() + } +} + // **< Item >*************************************************************************************** /// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav). @@ -56,14 +90,7 @@ impl Component for Item { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - if matches!(self.item_kind(), ItemKind::Dropdown(_)) { - "nav-item dropdown" - } else { - "nav-item" - }, - ); + self.alter_classes(ClassesOp::Prepend, self.item_kind().to_class()); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { @@ -96,12 +123,12 @@ impl Component for Item { classes.push_str(" disabled"); } - let href = (!disabled).then_some(path); - let target = (!disabled && *blank).then_some("_blank"); - let rel = (!disabled && *blank).then_some("noopener noreferrer"); + let href = (!*disabled).then_some(path); + let target = (!*disabled && *blank).then_some("_blank"); + let rel = (!*disabled && *blank).then_some("noopener noreferrer"); let aria_current = (href.is_some() && is_current).then_some("page"); - let aria_disabled = disabled.then_some("true"); + let aria_disabled = (*disabled).then_some("true"); PrepareMarkup::With(html! { li id=[self.id()] class=[self.classes().get()] { diff --git a/extensions/pagetop-bootsier/src/theme/nav/props.rs b/extensions/pagetop-bootsier/src/theme/nav/props.rs index bd8ac1e1..46a4e2bc 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/props.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/props.rs @@ -3,7 +3,7 @@ use pagetop::prelude::*; // **< Kind >*************************************************************************************** /// Define la variante de presentación de un menú [`Nav`](crate::theme::Nav). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Kind { /// Estilo por defecto, lista de enlaces flexible y minimalista. #[default] @@ -16,10 +16,47 @@ pub enum Kind { Underline, } +impl Kind { + const TABS: &str = "nav-tabs"; + const PILLS: &str = "nav-pills"; + const UNDERLINE: &str = "nav-underline"; + + // Devuelve la clase base asociada al tipo de menú, o una cadena vacía si no aplica. + #[rustfmt::skip] + #[inline] + const fn as_str(self) -> &'static str { + match self { + Self::Default => "", + Self::Tabs => Self::TABS, + Self::Pills => Self::PILLS, + Self::Underline => Self::UNDERLINE, + } + } + + // Añade la clase asociada al tipo de menú a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let class = self.as_str(); + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + /* Devuelve la clase asociada al tipo de menú, o una cadena vacía si no aplica (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + self.as_str().to_owned() + } */ +} + // **< Layout >************************************************************************************* /// Distribución y orientación de un menú [`Nav`](crate::theme::Nav). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Layout { /// Comportamiento por defecto, ancho definido por el contenido y sin alineación forzada. #[default] @@ -37,3 +74,47 @@ pub enum Layout { /// Todos los elementos ocupan el mismo ancho rellenando la fila. Justified, } + +impl Layout { + const START: &str = "justify-content-start"; + const CENTER: &str = "justify-content-center"; + const END: &str = "justify-content-end"; + const VERTICAL: &str = "flex-column"; + const FILL: &str = "nav-fill"; + const JUSTIFIED: &str = "nav-justified"; + + // Devuelve la clase base asociada a la distribución y orientación del menú. + #[rustfmt::skip] + #[inline] + const fn as_str(self) -> &'static str { + match self { + Self::Default => "", + Self::Start => Self::START, + Self::Center => Self::CENTER, + Self::End => Self::END, + Self::Vertical => Self::VERTICAL, + Self::Fill => Self::FILL, + Self::Justified => Self::JUSTIFIED, + } + } + + // Añade la clase asociada a la distribución y orientación del menú a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let class = self.as_str(); + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + /* Devuelve la clase asociada a la distribución y orientación del menú, o una cadena vacía si no + // aplica (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + self.as_str().to_owned() + } */ +} diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index ac592439..b40d06c2 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -35,22 +35,12 @@ impl Component for Navbar { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - "navbar".to_string(), - self.expand().try_class("navbar-expand").unwrap_or_default(), - match self.position() { - navbar::Position::Static => "", - navbar::Position::FixedTop => "fixed-top", - navbar::Position::FixedBottom => "fixed-bottom", - navbar::Position::StickyTop => "sticky-top", - navbar::Position::StickyBottom => "sticky-bottom", - } - .to_string(), - ] - .join_classes(), - ); + self.alter_classes(ClassesOp::Prepend, { + let mut classes = "navbar".to_string(); + self.expand().push_class(&mut classes, "navbar-expand", ""); + self.position().push_class(&mut classes); + classes + }); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index 07f52be6..7e912a49 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -38,6 +38,14 @@ impl Component for Item { } } + fn setup_before_prepare(&mut self, _cx: &mut Context) { + if let Self::Nav(nav) = self { + if let Some(mut nav) = nav.borrow_mut() { + nav.alter_classes(ClassesOp::Prepend, "navbar-nav"); + } + } + } + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { match self { Self::Void => PrepareMarkup::None, @@ -48,29 +56,8 @@ impl Component for Item { if items.is_empty() { return PrepareMarkup::None; } - let classes = AttrClasses::new( - [ - "navbar-nav", - match nav.nav_kind() { - nav::Kind::Default => "", - nav::Kind::Tabs => "nav-tabs", - nav::Kind::Pills => "nav-pills", - nav::Kind::Underline => "nav-underline", - }, - match nav.nav_layout() { - nav::Layout::Default => "", - nav::Layout::Start => "justify-content-start", - nav::Layout::Center => "justify-content-center", - nav::Layout::End => "justify-content-end", - nav::Layout::Vertical => "flex-column", - nav::Layout::Fill => "nav-fill", - nav::Layout::Justified => "nav-justified", - }, - ] - .join_classes(), - ); PrepareMarkup::With(html! { - ul id=[nav.id()] class=[classes.get()] { + ul id=[nav.id()] class=[nav.classes().get()] { (items) } }) diff --git a/extensions/pagetop-bootsier/src/theme/navbar/props.rs b/extensions/pagetop-bootsier/src/theme/navbar/props.rs index 86326574..1aeb6170 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/props.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/props.rs @@ -42,7 +42,7 @@ pub enum Layout { // **< Position >*********************************************************************************** /// Posición global de una barra de navegación [`Navbar`] en el documento. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Position { /// Barra normal, fluye con el documento. #[default] @@ -62,3 +62,37 @@ pub enum Position { /// La barra de navegación se fija en la parte inferior al hacer *scroll*. StickyBottom, } + +impl Position { + // Devuelve la clase base asociada a la posición de la barra de navegación. + #[inline] + const fn as_str(self) -> &'static str { + match self { + Self::Static => "", + Self::FixedTop => "fixed-top", + Self::FixedBottom => "fixed-bottom", + Self::StickyTop => "sticky-top", + Self::StickyBottom => "sticky-bottom", + } + } + + // Añade la clase asociada a la posición de la barra de navegación a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let class = self.as_str(); + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + /* Devuelve la clase asociada a la posición de la barra de navegación, o cadena vacía si no + // aplica (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + self.as_str().to_string() + } */ +} diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 7516238b..f17fe97a 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -44,25 +44,14 @@ impl Component for Offcanvas { self.id.get() } - #[rustfmt::skip] fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes( - ClassesOp::Prepend, - [ - self.breakpoint().to_class("offcanvas"), - match self.placement() { - offcanvas::Placement::Start => "offcanvas-start", - offcanvas::Placement::End => "offcanvas-end", - offcanvas::Placement::Top => "offcanvas-top", - offcanvas::Placement::Bottom => "offcanvas-bottom", - }.to_string(), - match self.visibility() { - offcanvas::Visibility::Default => "", - offcanvas::Visibility::Show => "show", - }.to_string(), - ] - .join_classes(), - ); + self.alter_classes(ClassesOp::Prepend, { + let mut classes = "offcanvas".to_string(); + self.breakpoint().push_class(&mut classes, "offcanvas", ""); + self.placement().push_class(&mut classes); + self.visibility().push_class(&mut classes); + classes + }); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs index cbacbd74..cdfe8623 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs @@ -4,7 +4,7 @@ use pagetop::prelude::*; /// Comportamiento de la capa de fondo (*backdrop*) de un panel /// [`Offcanvas`](crate::theme::Offcanvas) al deslizarse. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Backdrop { /// Sin capa de fondo, la página principal permanece visible e interactiva. Disabled, @@ -20,7 +20,7 @@ pub enum Backdrop { /// Controla si la página principal puede desplazarse al abrir un panel /// [`Offcanvas`](crate::theme::Offcanvas). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum BodyScroll { /// Opción por defecto, la página principal se bloquea centrando la interacción en el panel. #[default] @@ -34,7 +34,7 @@ pub enum BodyScroll { /// Posición de aparición de un panel [`Offcanvas`](crate::theme::Offcanvas) al deslizarse. /// /// Define desde qué borde de la ventana entra y se ancla el panel. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Placement { /// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL). #[default] @@ -47,10 +47,39 @@ pub enum Placement { Bottom, } +impl Placement { + // Devuelve la clase base asociada a la posición de aparición del panel. + #[rustfmt::skip] + #[inline] + const fn as_str(self) -> &'static str { + match self { + Placement::Start => "offcanvas-start", + Placement::End => "offcanvas-end", + Placement::Top => "offcanvas-top", + Placement::Bottom => "offcanvas-bottom", + } + } + + // Añade la clase asociada a la posición de aparición del panel a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(self.as_str()); + } + + /* Devuelve la clase asociada a la posición de aparición del panel (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + self.as_str().to_owned() + } */ +} + // **< Visibility >********************************************************************************* /// Estado inicial de un panel [`Offcanvas`](crate::theme::Offcanvas). -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Visibility { /// El panel permanece oculto desde el principio. #[default] @@ -58,3 +87,33 @@ pub enum Visibility { /// El panel se muestra abierto al cargar. Show, } + +impl Visibility { + // Devuelve la clase base asociada al estado inicial del panel. + #[inline] + const fn as_str(self) -> &'static str { + match self { + Visibility::Default => "", + Visibility::Show => "show", + } + } + + // Añade la clase asociada al estado inicial del panel a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let class = self.as_str(); + if class.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + /* Devuelve la clase asociada al estado inicial, o una cadena vacía si no aplica (reservado). + #[inline] + pub(crate) fn to_class(self) -> String { + self.as_str().to_owned() + } */ +} diff --git a/src/base/component.rs b/src/base/component.rs index 508a28e1..bdab35c6 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -2,11 +2,9 @@ use crate::prelude::*; -use std::fmt; - // **< FontSize >*********************************************************************************** -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum FontSize { ExtraLarge, XxLarge, @@ -24,7 +22,7 @@ pub enum FontSize { #[rustfmt::skip] impl FontSize { #[inline] - pub const fn as_str(&self) -> &'static str { + pub const fn as_str(self) -> &'static str { match self { FontSize::ExtraLarge => "fs__x3l", FontSize::XxLarge => "fs__x2l", @@ -40,12 +38,6 @@ impl FontSize { } } -impl fmt::Display for FontSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - // ************************************************************************************************* mod html; diff --git a/src/html.rs b/src/html.rs index f8709dc5..5f5b833a 100644 --- a/src/html.rs +++ b/src/html.rs @@ -87,9 +87,6 @@ use crate::{core, AutoDefault}; #[allow(type_alias_bounds)] pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>; -mod join_classes; -pub use join_classes::JoinClasses; - mod unit; pub use unit::UnitValue; diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 68a13da6..abadef8a 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -23,7 +23,7 @@ enum Source { /// /// Permite especificar en qué contexto se aplica el CSS, adaptándose a diferentes dispositivos o /// situaciones de impresión. -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum TargetMedia { /// Se aplica en todos los casos (el atributo `media` se omite). #[default] @@ -39,7 +39,7 @@ pub enum TargetMedia { /// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`. #[rustfmt::skip] impl TargetMedia { - fn as_str_opt(&self) -> Option<&str> { + const fn as_str(self) -> Option<&'static str> { match self { TargetMedia::Default => None, TargetMedia::Print => Some("print"), @@ -171,7 +171,7 @@ impl Asset for StyleSheet { link rel="stylesheet" href=(join_pair!(path, "?v=", &self.version)) - media=[self.media.as_str_opt()]; + media=[self.media.as_str()]; }, Source::Inline(_, f) => html! { style { (PreEscaped((f)(cx))) }; diff --git a/src/html/join_classes.rs b/src/html/join_classes.rs deleted file mode 100644 index 3f7d7e70..00000000 --- a/src/html/join_classes.rs +++ /dev/null @@ -1,67 +0,0 @@ -/// Añade a los *slices* de elementos [`AsRef<str>`] un método para unir clases CSS. -/// -/// El método es [`join_classes()`](JoinClasses::join_classes), que une las cadenas **no vacías** -/// del *slice* usando un espacio como separador. -pub trait JoinClasses { - /// Une las cadenas **no vacías** de un *slice* usando un espacio como separador. - /// - /// Son cadenas vacías únicamente los elementos del *slice* cuya longitud es `0` (p. ej., `""`); - /// no se realiza recorte ni normalización, por lo que elementos como `" "` no se consideran - /// vacíos. - /// - /// Si todas las cadenas están vacías, devuelve una cadena vacía. Acepta elementos que - /// implementen [`AsRef<str>`] como `&str`, [`String`] o `Cow<'_, str>`. - /// - /// # Ejemplos - /// - /// ```rust - /// # use pagetop::prelude::*; - /// let classes = ["btn", "", "btn-primary"]; - /// assert_eq!(classes.join_classes(), "btn btn-primary"); - /// - /// let empty: [&str; 3] = ["", "", ""]; - /// assert_eq!(empty.join_classes(), ""); - /// - /// let border = String::from("border"); - /// let border_top = String::from("border-top-0"); - /// let v = vec![&border, "", "", "", &border_top]; - /// assert_eq!(v.as_slice().join_classes(), "border border-top-0"); - /// - /// // Elementos con espacios afectan al resultado. - /// let spaced = ["btn", " ", "primary "]; - /// assert_eq!(spaced.join_classes(), "btn primary "); - /// ``` - fn join_classes(&self) -> String; -} - -impl<T> JoinClasses for [T] -where - T: AsRef<str>, -{ - #[inline] - fn join_classes(&self) -> String { - let mut count = 0usize; - let mut total = 0usize; - for s in self.iter().map(T::as_ref).filter(|s| !s.is_empty()) { - count += 1; - total += s.len(); - } - if count == 0 { - return String::new(); - } - let separator = " "; - let mut result = String::with_capacity(total + separator.len() * count.saturating_sub(1)); - for (i, s) in self - .iter() - .map(T::as_ref) - .filter(|s| !s.is_empty()) - .enumerate() - { - if i > 0 { - result.push_str(separator); - } - result.push_str(s); - } - result - } -} diff --git a/src/html/logo.rs b/src/html/logo.rs index f5057d94..fd604414 100644 --- a/src/html/logo.rs +++ b/src/html/logo.rs @@ -27,7 +27,7 @@ use crate::AutoDefault; /// }; /// ``` -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum PageTopSvg { /// Versión por defecto con el logotipo a color. #[default] diff --git a/src/prelude.rs b/src/prelude.rs index 0919e991..47fbbf64 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -33,8 +33,8 @@ pub use crate::trace; // alias obsoletos se volverá a declarar como `pub use crate::html::*;`. pub use crate::html::{ display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, - ClassesOp, Escaper, Favicon, JavaScript, JoinClasses, Markup, PageTopSvg, PreEscaped, - PrepareMarkup, StyleSheet, TargetMedia, UnitValue, DOCTYPE, + ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup, + StyleSheet, TargetMedia, UnitValue, DOCTYPE, }; pub use crate::locale::*; From 9e960c75735841cbb1227bb64a22efe2a011c592 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 15 Nov 2025 18:37:30 +0100 Subject: [PATCH 185/224] =?UTF-8?q?=F0=9F=9A=A7=20Aplica=20nuevas=20utilid?= =?UTF-8?q?ades=20para=20componer=20el=20men=C3=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/navbar-menus.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 22ef336c..071d24b1 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -82,6 +82,10 @@ impl Extension for SuperMenu { )) .add_item(navbar::Item::nav( Nav::new() + .with_classes( + ClassesOp::Add, + classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(), + ) .add_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), |_| "/auth/sign-up", @@ -91,7 +95,11 @@ impl Extension for SuperMenu { })), )); - InRegion::Key("header").add(Child::with(navbar_menu)); + InRegion::Key("header").add(Child::with( + Container::new() + .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) + .add_child(navbar_menu), + )); } } From ceaee54484928a47a543f46579fc1379a00d4957 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 17 Nov 2025 22:47:47 +0100 Subject: [PATCH 186/224] =?UTF-8?q?=F0=9F=8E=A8=20Protege=20el=20uso=20de?= =?UTF-8?q?=20`render`=20en=20PrepareMarkup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html.rs | 17 +++-- tests/component_html.rs | 31 +++------ tests/component_poweredby.rs | 28 +++----- tests/html_pm.rs | 126 ++++++++++++++++++++++------------- 4 files changed, 112 insertions(+), 90 deletions(-) diff --git a/src/html.rs b/src/html.rs index 5f5b833a..82fdcd73 100644 --- a/src/html.rs +++ b/src/html.rs @@ -104,11 +104,11 @@ pub use unit::UnitValue; /// # use pagetop::prelude::*; /// // Texto normal, se escapa automáticamente para evitar inyección de HTML. /// let fragment = PrepareMarkup::Escaped("Hola <b>mundo</b>".to_string()); -/// assert_eq!(fragment.render().into_string(), "Hola &lt;b&gt;mundo&lt;/b&gt;"); +/// assert_eq!(fragment.into_string(), "Hola &lt;b&gt;mundo&lt;/b&gt;"); /// /// // HTML literal, se inserta directamente, sin escapado adicional. /// let raw_html = PrepareMarkup::Raw("<b>negrita</b>".to_string()); -/// assert_eq!(raw_html.render().into_string(), "<b>negrita</b>"); +/// assert_eq!(raw_html.into_string(), "<b>negrita</b>"); /// /// // Fragmento ya preparado con la macro `html!`. /// let prepared = PrepareMarkup::With(html! { @@ -116,11 +116,11 @@ pub use unit::UnitValue; /// p { "Este es un párrafo con contenido dinámico." } /// }); /// assert_eq!( -/// prepared.render().into_string(), +/// prepared.into_string(), /// "<h2>Título de ejemplo</h2><p>Este es un párrafo con contenido dinámico.</p>" /// ); /// ``` -#[derive(AutoDefault)] +#[derive(AutoDefault, Clone)] pub enum PrepareMarkup { /// No se genera contenido HTML (equivale a `html! {}`). #[default] @@ -152,8 +152,13 @@ impl PrepareMarkup { } } - /// Integra el renderizado fácilmente en la macro [`html!`]. - pub fn render(&self) -> Markup { + /// Convierte el contenido en una cadena HTML renderizada. Usar sólo para pruebas o depuración. + pub fn into_string(&self) -> String { + self.render().into_string() + } + + // Integra el renderizado fácilmente en la macro [`html!`]. + pub(crate) fn render(&self) -> Markup { match self { PrepareMarkup::None => html! {}, PrepareMarkup::Escaped(text) => html! { (text) }, diff --git a/tests/component_html.rs b/tests/component_html.rs index 851315a9..06d77ec9 100644 --- a/tests/component_html.rs +++ b/tests/component_html.rs @@ -2,32 +2,28 @@ use pagetop::prelude::*; #[pagetop::test] async fn component_html_renders_static_markup() { - let component = Html::with(|_| { + let mut component = Html::with(|_| { html! { p { "Test" } } }); - let markup = component - .prepare_component(&mut Context::new(None)) - .render(); - + let markup = component.render(&mut Context::default()); assert_eq!(markup.0, "<p>Test</p>"); } #[pagetop::test] async fn component_html_renders_using_context_param() { - let mut cx = Context::new(None).with_param("username", "Alice".to_string()); + let mut cx = Context::default().with_param("username", "Alice".to_string()); - let component = Html::with(|cx| { + let mut component = Html::with(|cx| { let name = cx.param::<String>("username").cloned().unwrap_or_default(); html! { span { (name) } } }); - let markup = component.prepare_component(&mut cx).render(); - + let markup = component.render(&mut cx); assert_eq!(markup.0, "<span>Alice</span>"); } @@ -37,21 +33,15 @@ async fn component_html_allows_replacing_render_function() { component.alter_fn(|_| html! { div { "Modified" } }); - let markup = component - .prepare_component(&mut Context::new(None)) - .render(); - + let markup = component.render(&mut Context::default()); assert_eq!(markup.0, "<div>Modified</div>"); } #[pagetop::test] async fn component_html_default_renders_empty_markup() { - let component = Html::default(); - - let markup = component - .prepare_component(&mut Context::new(None)) - .render(); + let mut component = Html::default(); + let markup = component.render(&mut Context::default()); assert_eq!(markup.0, ""); } @@ -60,7 +50,7 @@ async fn component_html_can_access_http_method() { let req = service::test::TestRequest::with_uri("/").to_http_request(); let mut cx = Context::new(Some(req)); - let component = Html::with(|cx| { + let mut component = Html::with(|cx| { let method = cx .request() .map(|r| r.method().to_string()) @@ -68,7 +58,6 @@ async fn component_html_can_access_http_method() { html! { span { (method) } } }); - let markup = component.prepare_component(&mut cx).render(); - + let markup = component.render(&mut cx); assert_eq!(markup.0, "<span>GET</span>"); } diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs index 27683d95..7e5a062c 100644 --- a/tests/component_poweredby.rs +++ b/tests/component_poweredby.rs @@ -4,8 +4,8 @@ use pagetop::prelude::*; async fn poweredby_default_shows_only_pagetop_recognition() { let _app = service::test::init_service(Application::new().test()).await; - let p = PoweredBy::default(); - let html = render_component(&p); + let mut p = PoweredBy::default(); + let html = p.render(&mut Context::default()); // Debe mostrar el bloque de reconocimiento a PageTop. assert!(html.as_str().contains("poweredby__pagetop")); @@ -18,8 +18,8 @@ async fn poweredby_default_shows_only_pagetop_recognition() { async fn poweredby_new_includes_current_year_and_app_name() { let _app = service::test::init_service(Application::new().test()).await; - let p = PoweredBy::new(); - let html = render_component(&p); + let mut p = PoweredBy::new(); + let html = p.render(&mut Context::default()); let year = Utc::now().format("%Y").to_string(); assert!( @@ -43,8 +43,8 @@ async fn poweredby_with_copyright_overrides_text() { let _app = service::test::init_service(Application::new().test()).await; let custom = "2001 © FooBar Inc."; - let p = PoweredBy::default().with_copyright(Some(custom)); - let html = render_component(&p); + let mut p = PoweredBy::default().with_copyright(Some(custom)); + let html = p.render(&mut Context::default()); assert!(html.as_str().contains(custom)); assert!(html.as_str().contains("poweredby__copyright")); @@ -54,8 +54,8 @@ async fn poweredby_with_copyright_overrides_text() { async fn poweredby_with_copyright_none_hides_text() { let _app = service::test::init_service(Application::new().test()).await; - let p = PoweredBy::new().with_copyright(None::<String>); - let html = render_component(&p); + let mut p = PoweredBy::new().with_copyright(None::<String>); + let html = p.render(&mut Context::default()); assert!(!html.as_str().contains("poweredby__copyright")); // El reconocimiento a PageTop siempre debe aparecer. @@ -66,8 +66,8 @@ async fn poweredby_with_copyright_none_hides_text() { async fn poweredby_link_points_to_crates_io() { let _app = service::test::init_service(Application::new().test()).await; - let p = PoweredBy::default(); - let html = render_component(&p); + let mut p = PoweredBy::default(); + let html = p.render(&mut Context::default()); assert!( html.as_str().contains("https://pagetop.cillero.es"), @@ -89,11 +89,3 @@ async fn poweredby_getter_reflects_internal_state() { assert!(c1.contains(&Utc::now().format("%Y").to_string())); assert!(c1.contains(&global::SETTINGS.app.name)); } - -// **< HELPERS >************************************************************************************ - -fn render_component<C: Component>(c: &C) -> Markup { - let mut cx = Context::default(); - let pm = c.prepare_component(&mut cx); - pm.render() -} diff --git a/tests/html_pm.rs b/tests/html_pm.rs index ae4517bc..615ea470 100644 --- a/tests/html_pm.rs +++ b/tests/html_pm.rs @@ -1,70 +1,69 @@ use pagetop::prelude::*; -#[pagetop::test] -async fn prepare_markup_render_none_is_empty_string() { - assert_eq!(PrepareMarkup::None.render().as_str(), ""); +/// Componente mínimo para probar `PrepareMarkup` pasando por el ciclo real +/// de renderizado de componentes (`ComponentRender`). +#[derive(AutoDefault)] +struct TestPrepareComponent { + pm: PrepareMarkup, +} + +impl Component for TestPrepareComponent { + fn new() -> Self { + Self { + pm: PrepareMarkup::None, + } + } + + fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { + self.pm.clone() + } +} + +impl TestPrepareComponent { + fn render_pm(pm: PrepareMarkup) -> String { + let mut c = TestPrepareComponent { pm }; + c.render(&mut Context::default()).into_string() + } } #[pagetop::test] -async fn prepare_markup_render_escaped_escapes_html_and_ampersands() { +async fn prepare_markup_none_is_empty_string() { + assert_eq!(PrepareMarkup::None.into_string(), ""); +} + +#[pagetop::test] +async fn prepare_markup_escaped_escapes_html_and_ampersands() { let pm = PrepareMarkup::Escaped("<b>& \" ' </b>".to_string()); - assert_eq!(pm.render().as_str(), "&lt;b&gt;&amp; &quot; ' &lt;/b&gt;"); + assert_eq!(pm.into_string(), "&lt;b&gt;&amp; &quot; ' &lt;/b&gt;"); } #[pagetop::test] -async fn prepare_markup_render_raw_is_inserted_verbatim() { +async fn prepare_markup_raw_is_inserted_verbatim() { let pm = PrepareMarkup::Raw("<b>bold</b><script>1<2</script>".to_string()); - assert_eq!(pm.render().as_str(), "<b>bold</b><script>1<2</script>"); + assert_eq!(pm.into_string(), "<b>bold</b><script>1<2</script>"); } #[pagetop::test] -async fn prepare_markup_render_with_keeps_structure() { +async fn prepare_markup_with_keeps_structure() { let pm = PrepareMarkup::With(html! { h2 { "Sample title" } - p { "This is a paragraph." } + p { "This is a paragraph." } }); assert_eq!( - pm.render().as_str(), + pm.into_string(), "<h2>Sample title</h2><p>This is a paragraph.</p>" ); } -#[pagetop::test] -async fn prepare_markup_does_not_double_escape_when_wrapped_in_html_macro() { - // Escaped: dentro de `html!` no debe volver a escaparse. - let escaped = PrepareMarkup::Escaped("<i>x</i>".into()); - let wrapped_escaped = html! { div { (escaped.render()) } }; - assert_eq!( - wrapped_escaped.into_string(), - "<div>&lt;i&gt;x&lt;/i&gt;</div>" - ); - - // Raw: tampoco debe escaparse al integrarlo. - let raw = PrepareMarkup::Raw("<i>x</i>".into()); - let wrapped_raw = html! { div { (raw.render()) } }; - assert_eq!(wrapped_raw.into_string(), "<div><i>x</i></div>"); - - // With: debe incrustar el Markup tal cual. - let with = PrepareMarkup::With(html! { span.title { "ok" } }); - let wrapped_with = html! { div { (with.render()) } }; - assert_eq!( - wrapped_with.into_string(), - "<div><span class=\"title\">ok</span></div>" - ); -} - #[pagetop::test] async fn prepare_markup_unicode_is_preserved() { // Texto con acentos y emojis debe conservarse (salvo el escape HTML de signos). let esc = PrepareMarkup::Escaped("Hello, tomorrow coffee ☕ & donuts!".into()); - assert_eq!( - esc.render().as_str(), - "Hello, tomorrow coffee ☕ &amp; donuts!" - ); + assert_eq!(esc.into_string(), "Hello, tomorrow coffee ☕ &amp; donuts!"); // Raw debe pasar íntegro. let raw = PrepareMarkup::Raw("Title — section © 2025".into()); - assert_eq!(raw.render().as_str(), "Title — section © 2025"); + assert_eq!(raw.into_string(), "Title — section © 2025"); } #[pagetop::test] @@ -88,7 +87,36 @@ async fn prepare_markup_is_empty_semantics() { } #[pagetop::test] -async fn prepare_markup_equivalence_between_render_and_inline_in_html_macro() { +async fn prepare_markup_does_not_double_escape_when_markup_is_reinjected_in_html_macro() { + let mut cx = Context::default(); + + // Escaped: dentro de `html!` no debe volver a escaparse. + let mut comp = TestPrepareComponent { + pm: PrepareMarkup::Escaped("<i>x</i>".into()), + }; + let markup = comp.render(&mut cx); // Markup + let wrapped_escaped = html! { div { (markup) } }.into_string(); + assert_eq!(wrapped_escaped, "<div>&lt;i&gt;x&lt;/i&gt;</div>"); + + // Raw: tampoco debe escaparse al integrarlo. + let mut comp = TestPrepareComponent { + pm: PrepareMarkup::Raw("<i>x</i>".into()), + }; + let markup = comp.render(&mut cx); + let wrapped_raw = html! { div { (markup) } }.into_string(); + assert_eq!(wrapped_raw, "<div><i>x</i></div>"); + + // With: debe incrustar el Markup tal cual. + let mut comp = TestPrepareComponent { + pm: PrepareMarkup::With(html! { span.title { "ok" } }), + }; + let markup = comp.render(&mut cx); + let wrapped_with = html! { div { (markup) } }.into_string(); + assert_eq!(wrapped_with, "<div><span class=\"title\">ok</span></div>"); +} + +#[pagetop::test] +async fn prepare_markup_equivalence_between_component_render_and_markup_reinjected_in_html_macro() { let cases = [ PrepareMarkup::None, PrepareMarkup::Escaped("<b>x</b>".into()), @@ -97,12 +125,20 @@ async fn prepare_markup_equivalence_between_render_and_inline_in_html_macro() { ]; for pm in cases { - let rendered = pm.render(); - let in_macro = html! { (rendered) }.into_string(); + // Vía 1: renderizamos y obtenemos directamente el String. + let via_component = TestPrepareComponent::render_pm(pm.clone()); + + // Vía 2: renderizamos, reinyectamos el Markup en `html!` y volvemos a obtener String. + let via_macro = { + let mut cx = Context::default(); + let mut comp = TestPrepareComponent { pm }; + let markup = comp.render(&mut cx); + html! { (markup) }.into_string() + }; + assert_eq!( - rendered.as_str(), - in_macro, - "The output of Render and (pm) inside html! must match" + via_component, via_macro, + "The output of component render and (Markup) inside html! must match" ); } } From 77c4005b4e1dc10213dd8db5f8357dc610a10fdd Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 17 Nov 2025 22:50:56 +0100 Subject: [PATCH 187/224] =?UTF-8?q?=E2=9C=A8=20Incorpora=20`is=5Frenderabl?= =?UTF-8?q?e`=20en=20`Component`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/action/component.rs | 3 - src/base/action/component/is_renderable.rs | 96 ---------------------- src/core/component.rs | 53 ++++++++++++ src/core/component/definition.rs | 36 +++++--- 4 files changed, 78 insertions(+), 110 deletions(-) delete mode 100644 src/base/action/component/is_renderable.rs diff --git a/src/base/action/component.rs b/src/base/action/component.rs index aaef1ce9..30c7ba4a 100644 --- a/src/base/action/component.rs +++ b/src/base/action/component.rs @@ -1,8 +1,5 @@ //! Acciones que operan sobre componentes. -mod is_renderable; -pub use is_renderable::*; - mod before_render_component; pub use before_render_component::*; diff --git a/src/base/action/component/is_renderable.rs b/src/base/action/component/is_renderable.rs deleted file mode 100644 index 5a0e244e..00000000 --- a/src/base/action/component/is_renderable.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::prelude::*; - -/// Tipo de función para determinar si un componente se renderiza o no. -/// -/// Se usa en la acción [`IsRenderable`] para controlar dinámicamente la visibilidad del componente -/// `component` según el contexto `cx`. El componente **no se renderiza** en cuanto una de las -/// funciones devuelva `false`. -pub type FnIsRenderable<C> = fn(component: &C, cx: &Context) -> bool; - -/// Con la función [`FnIsRenderable`] se puede decidir si se renderiza o no un componente. -pub struct IsRenderable<C: Component> { - f: FnIsRenderable<C>, - referer_type_id: Option<UniqueId>, - referer_id: AttrId, - weight: Weight, -} - -/// Filtro para despachar [`FnIsRenderable`] para decidir si se renderiza o no un componente `C`. -impl<C: Component> ActionDispatcher for IsRenderable<C> { - /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. - fn referer_type_id(&self) -> Option<UniqueId> { - self.referer_type_id - } - - /// Devuelve el identificador del componente. - fn referer_id(&self) -> Option<String> { - self.referer_id.get() - } - - /// Devuelve el peso para definir el orden de ejecución. - fn weight(&self) -> Weight { - self.weight - } -} - -impl<C: Component> IsRenderable<C> { - /// Permite [registrar](Extension::actions) una nueva acción [`FnIsRenderable`]. - pub fn new(f: FnIsRenderable<C>) -> Self { - IsRenderable { - f, - referer_type_id: Some(UniqueId::of::<C>()), - referer_id: AttrId::default(), - weight: 0, - } - } - - /// Afina el registro para ejecutar la acción [`FnIsRenderable`] sólo para el componente `C` - /// con identificador `id`. - pub fn filter_by_referer_id(mut self, id: impl AsRef<str>) -> Self { - self.referer_id.alter_value(id); - self - } - - /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } - - // Despacha las acciones. Se detiene en cuanto una [`FnIsRenderable`] devuelve `false`. - #[inline] - pub(crate) fn dispatch(component: &C, cx: &mut Context) -> bool { - let mut renderable = true; - dispatch_actions( - &ActionKey::new( - UniqueId::of::<Self>(), - None, - Some(UniqueId::of::<C>()), - None, - ), - |action: &Self| { - if renderable && !(action.f)(component, cx) { - renderable = false; - } - }, - ); - if renderable { - if let Some(id) = component.id() { - dispatch_actions( - &ActionKey::new( - UniqueId::of::<Self>(), - None, - Some(UniqueId::of::<C>()), - Some(id), - ), - |action: &Self| { - if renderable && !(action.f)(component, cx) { - renderable = false; - } - }, - ); - } - } - renderable - } -} diff --git a/src/core/component.rs b/src/core/component.rs index a7faa2fb..9c9ade2e 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -11,6 +11,59 @@ pub use children::{Typed, TypedOp}; mod context; pub use context::{Context, ContextError, ContextOp, Contextual}; +/// Alias de función (*callback*) para **determinar si un componente se renderiza o no**. +/// +/// Puede usarse para permitir que una instancia concreta de un tipo de componente dado decida +/// dinámicamente durante el proceso de renderizado ([`Component::is_renderable()`]) si se renderiza +/// o no. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// #[derive(AutoDefault)] +/// struct SampleComponent { +/// renderable: Option<FnIsRenderable>, +/// } +/// +/// impl Component for SampleComponent { +/// fn new() -> Self { +/// Self::default() +/// } +/// +/// fn is_renderable(&self, cx: &mut Context) -> bool { +/// // Si hay callback, se usa; en caso contrario, se renderiza por defecto. +/// self.renderable.map_or(true, |f| f(cx)) +/// } +/// +/// fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { +/// PrepareMarkup::Escaped("Visible component".into()) +/// } +/// } +/// +/// impl SampleComponent { +/// /// Asigna una función que decidirá si el componente se renderiza o no. +/// #[builder_fn] +/// pub fn with_renderable(mut self, f: Option<FnIsRenderable>) -> Self { +/// self.renderable = f; +/// self +/// } +/// } +/// +/// fn sample() { +/// let mut cx = Context::default().with_param("user_logged_in", true); +/// +/// // Se instancia un componente que sólo se renderiza si `user_logged_in` es `true`. +/// let mut component = SampleComponent::new().with_renderable(Some(|cx: &Context| { +/// cx.param::<bool>("user_logged_in").copied().unwrap_or(false) +/// })); +/// +/// // Aquí simplemente se comprueba que compila y se puede invocar. +/// let _markup = component.render(&mut cx); +/// } +/// ``` +pub type FnIsRenderable = fn(cx: &Context) -> bool; + /// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. /// /// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). Debe diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index c0573b44..13b03851 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -45,6 +45,20 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { None } + /// Indica si el componente es renderizable. + /// + /// Por defecto, todos los componentes son renderizables (`true`). Sin embargo, este método + /// puede sobrescribirse para decidir dinámicamente si los componentes de este tipo se + /// renderizan o no en función del contexto de renderizado. + /// + /// También puede usarse junto con un alias de función como + /// ([`FnIsRenderable`](crate::core::component::FnIsRenderable)) para permitir que instancias + /// concretas del componente decidan si se renderizan o no. + #[allow(unused_variables)] + fn is_renderable(&self, cx: &mut Context) -> bool { + true + } + /// Configura el componente justo antes de preparar el renderizado. /// /// Este método puede sobrescribirse para modificar la estructura interna del componente o el @@ -72,30 +86,30 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { /// Implementa [`render()`](ComponentRender::render) para todos los componentes. /// -/// Y para cada componente ejecuta la siguiente secuencia: +/// El proceso de renderizado de cada componente sigue esta secuencia: /// -/// 1. Despacha [`action::component::IsRenderable`](crate::base::action::component::IsRenderable) -/// para ver si se puede renderizar. Si no es así, devuelve un [`Markup`] vacío. +/// 1. Ejecuta [`is_renderable()`](Component::is_renderable) para ver si puede renderizarse en el +/// contexto actual. Si no es así, devuelve un [`Markup`] vacío. /// 2. Ejecuta [`setup_before_prepare()`](Component::setup_before_prepare) para que el componente /// pueda ajustar su estructura interna o modificar el contexto. /// 3. Despacha [`action::theme::BeforeRender<C>`](crate::base::action::theme::BeforeRender) para -/// que el tema pueda hacer ajustes en el componente o el contexto. +/// permitir que el tema realice ajustes previos. /// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender) -/// para que otras extensiones puedan hacer ajustes. +/// para que otras extensiones puedan también hacer ajustes previos. /// 5. **Prepara el renderizado del componente**: /// - Despacha [`action::theme::PrepareRender<C>`](crate::base::action::theme::PrepareRender) -/// para permitir al tema preparar un renderizado diferente al predefinido. -/// - Si no es así, ejecuta [`prepare_component()`](Component::prepare_component) para preparar -/// el renderizado predefinido del componente. +/// para permitir al tema generar un renderizado alternativo. +/// - Si el tema no lo modifica, llama a [`prepare_component()`](Component::prepare_component) +/// para obtener el renderizado por defecto del componente. /// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para -/// que el tema pueda hacer sus últimos ajustes. +/// que el tema pueda aplicar ajustes finales. /// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender) /// para que otras extensiones puedan hacer sus últimos ajustes. -/// 8. Finalmente devuelve un [`Markup`] del renderizado preparado en el paso 5. +/// 8. Devuelve el [`Markup`] generado en el paso 5. impl<C: Component> ComponentRender for C { fn render(&mut self, cx: &mut Context) -> Markup { // Si no es renderizable, devuelve un bloque HTML vacío. - if !action::component::IsRenderable::dispatch(self, cx) { + if !self.is_renderable(cx) { return html! {}; } From 64264f234d1c2b1a5373eb716cb260a28baa98c5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 17 Nov 2025 22:51:34 +0100 Subject: [PATCH 188/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`ThemeRegion`?= =?UTF-8?q?=20por=20`DefaultRegions`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 6 ++-- extensions/pagetop-bootsier/src/lib.rs | 4 +-- src/base/theme.rs | 2 +- src/base/theme/basic.rs | 4 +-- src/core/theme.rs | 11 ++++---- src/core/theme/definition.rs | 39 ++++++++++++++------------ src/core/theme/regions.rs | 16 +++++------ 7 files changed, 43 insertions(+), 39 deletions(-) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index edbb5040..cbc0f526 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -82,13 +82,13 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { use pagetop::prelude::*; -/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. -pub type AlinerRegion = ThemeRegion; +/// El tema usa las mismas regiones predefinidas por [`DefaultRegions`]. +pub type AlinerRegions = DefaultRegions; /// Implementa el tema para usar en pruebas que muestran el esquema de páginas HTML. /// /// Tema mínimo ideal para **pruebas y demos** que renderiza el **esqueleto HTML** con las mismas -/// regiones básicas definidas por [`ThemeRegion`]. No pretende ser un tema para producción, está +/// regiones básicas definidas por [`DefaultRegions`]. No pretende ser un tema para producción, está /// pensado para: /// /// - Verificar integración de componentes y composiciones (*layouts*) sin estilos complejos. diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 4cb35a27..76f9d1e2 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -101,8 +101,8 @@ pub mod prelude { pub use crate::theme::*; } -/// El tema usa las mismas regiones predefinidas por [`ThemeRegion`]. -pub type BootsierRegion = ThemeRegion; +/// El tema usa las mismas regiones predefinidas por [`DefaultRegions`]. +pub type BootsierRegions = DefaultRegions; /// Implementa el tema. pub struct Bootsier; diff --git a/src/base/theme.rs b/src/base/theme.rs index a4b2df5b..1e5b1a85 100644 --- a/src/base/theme.rs +++ b/src/base/theme.rs @@ -1,4 +1,4 @@ //! Temas básicos soportados por PageTop. mod basic; -pub use basic::{Basic, BasicRegion}; +pub use basic::{Basic, BasicRegions}; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index a6711859..2d713e37 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -1,8 +1,8 @@ /// Es el tema básico que incluye PageTop por defecto. use crate::prelude::*; -/// El tema básico usa las mismas regiones predefinidas por [`ThemeRegion`]. -pub type BasicRegion = ThemeRegion; +/// El tema básico usa las mismas regiones predefinidas por [`DefaultRegions`]. +pub type BasicRegions = DefaultRegions; /// Tema básico por defecto que extiende el funcionamiento predeterminado de [`Theme`]. pub struct Basic; diff --git a/src/core/theme.rs b/src/core/theme.rs index 64f40f33..28638ba6 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -9,13 +9,14 @@ //! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (como animaciones, //! scripts de interfaz, etc.). //! -//! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension); por -//! lo que se instancian, declaran sus dependencias y se inician igual que el resto de extensiones; -//! pero serán temas si además implementan [`theme()`](crate::core::extension::Extension::theme) y -//! [`Theme`]. +//! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension), por +//! lo que se instancian, declaran dependencias y se inician igual que cualquier otra extensión. +//! También deben implementar [`Theme`] y sobrescribir el método +//! [`Extension::theme()`](crate::core::extension::Extension::theme) para que PageTop pueda +//! registrarlos como temas mod definition; -pub use definition::{Theme, ThemePage, ThemeRef, ThemeRegion}; +pub use definition::{Theme, ThemePage, ThemeRef, DefaultRegions}; mod regions; pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT}; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 2a20c078..7d21c146 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -4,7 +4,7 @@ use crate::core::theme::{Region, RegionRef, REGION_CONTENT}; use crate::html::{html, Markup, StyleSheet}; use crate::locale::L10n; use crate::response::page::Page; -use crate::{global, join}; +use crate::{global, join, AutoDefault}; use std::sync::LazyLock; @@ -14,16 +14,17 @@ use std::sync::LazyLock; /// implementen [`Theme`] y, a su vez, [`Extension`]. pub type ThemeRef = &'static dyn Theme; -/// Conjunto de regiones que los temas pueden exponer para el renderizado. +/// Conjunto de regiones predefinidas que los temas pueden exponer para el renderizado. /// -/// `ThemeRegion` define un conjunto de regiones predefinidas para estructurar un documento HTML. +/// `DefaultRegions` define un conjunto de regiones predefinidas para estructurar un documento HTML. /// Proporciona **identificadores estables** (vía [`Region::key()`]) y **etiquetas localizables** /// (vía [`Region::label()`]) a las regiones donde se añadirán los componentes. /// /// Se usa por defecto en [`Theme::page_regions()`](crate::core::theme::Theme::page_regions) y sus /// variantes representan el conjunto mínimo recomendado para cualquier tema. Sin embargo, cada tema /// podría exponer su propio conjunto de regiones. -pub enum ThemeRegion { +#[derive(AutoDefault)] +pub enum DefaultRegions { /// Cabecera de la página. /// /// Clave: `"header"`. Suele contener *branding*, navegación principal o avisos globales. @@ -32,6 +33,7 @@ pub enum ThemeRegion { /// Contenido principal de la página (**obligatoria**). /// /// Clave: `"content"`. Es el destino por defecto para insertar componentes a nivel de página. + #[default] Content, /// Pie de página. @@ -40,12 +42,12 @@ pub enum ThemeRegion { Footer, } -impl Region for ThemeRegion { +impl Region for DefaultRegions { fn key(&self) -> &str { match self { - ThemeRegion::Header => "header", - ThemeRegion::Content => REGION_CONTENT, - ThemeRegion::Footer => "footer", + Self::Header => "header", + Self::Content => REGION_CONTENT, + Self::Footer => "footer", } } @@ -60,16 +62,17 @@ impl Region for ThemeRegion { /// implementa automáticamente para cualquier tipo que implemente [`Theme`], por lo que normalmente /// no requiere implementación explícita. /// -/// Si un tema **sobrescribe** uno o más de estos métodos de [`Theme`]: +/// Si un tema **sobrescribe** uno o más de los siguientes métodos de [`Theme`]: /// /// - [`render_page_region()`](Theme::render_page_region), /// - [`render_page_head()`](Theme::render_page_head), o /// - [`render_page_body()`](Theme::render_page_body); /// -/// es posible volver al comportamiento por defecto usando FQS (*Fully Qualified Syntax*): +/// puede volver al comportamiento por defecto con una llamada FQS (*Fully Qualified Syntax*) a: /// -/// - `<Self as ThemePage>::render_body(self, page, self.page_regions())` -/// - `<Self as ThemePage>::render_head(self, page)` +/// - `<Self as ThemePage>::render_region(self, page, region)`, +/// - `<Self as ThemePage>::render_body(self, page, self.page_regions())`, o +/// - `<Self as ThemePage>::render_head(self, page)`. pub trait ThemePage { /// Renderiza el **contenedor** de una región concreta del `<body>` de la página. /// @@ -206,9 +209,9 @@ pub trait Theme: Extension + ThemePage + Send + Sync { /// fn page_regions(&self) -> &'static [RegionRef] { /// static REGIONS: LazyLock<[RegionRef; 4]> = LazyLock::new(|| { /// [ - /// &ThemeRegion::Header, - /// &ThemeRegion::Content, - /// &ThemeRegion::Footer, + /// &DefaultRegions::Header, + /// &DefaultRegions::Content, + /// &DefaultRegions::Footer, /// ] /// }); /// &*REGIONS @@ -217,9 +220,9 @@ pub trait Theme: Extension + ThemePage + Send + Sync { fn page_regions(&self) -> &'static [RegionRef] { static REGIONS: LazyLock<[RegionRef; 3]> = LazyLock::new(|| { [ - &ThemeRegion::Header, - &ThemeRegion::Content, - &ThemeRegion::Footer, + &DefaultRegions::Header, + &DefaultRegions::Content, + &DefaultRegions::Footer, ] }); &*REGIONS diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 8e386f55..17e15436 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -31,25 +31,25 @@ pub const REGION_CONTENT: &str = "content"; /// `aria-label` o en descripciones semánticas del contenedor). /// /// Las implementaciones típicas son *enumeraciones estáticas* declaradas por cada tema (ver como -/// ejemplo [`ThemeRegion`](crate::core::theme::ThemeRegion)), de modo que las claves y etiquetas -/// permanecen inmutables y fácilmente referenciables. +/// ejemplo [`DefaultRegions`](crate::core::theme::DefaultRegions)), de modo que las claves y +/// etiquetas permanecen inmutables y fácilmente referenciables. /// /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; -/// pub enum MyThemeRegion { +/// pub enum MyThemeRegions { /// Header, /// Content, /// Footer, /// } /// -/// impl Region for MyThemeRegion { +/// impl Region for MyThemeRegions { /// fn key(&self) -> &str { /// match self { -/// MyThemeRegion::Header => "header", -/// MyThemeRegion::Content => "content", -/// MyThemeRegion::Footer => "footer", +/// Self::Header => "header", +/// Self::Content => "content", +/// Self::Footer => "footer", /// } /// } /// @@ -111,7 +111,7 @@ impl ChildrenInRegions { } } -/// Punto de acceso para añadir componentes a regiones globales o específicas de un tema. +/// Permite añadir componentes a regiones globales o específicas de un tema. /// /// Según la variante, se pueden añadir componentes ([`add()`](Self::add)) que permanecerán /// disponibles durante toda la ejecución. From 4a3244d0e402d124047d92154e78bf8bfa86f80b Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Fri, 21 Nov 2025 05:57:10 +0100 Subject: [PATCH 189/224] =?UTF-8?q?=F0=9F=93=9D=20Mejora=20doc=20de=20`Aut?= =?UTF-8?q?oDefault`=20y=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 91 ++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 732bbad7..194cd378 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -54,6 +54,56 @@ pub fn html(input: TokenStream) -> TokenStream { /// [`Default`]. Aunque, a diferencia de un simple `#[derive(Default)]`, el atributo /// `#[derive(AutoDefault)]` permite usar anotaciones en los campos como `#[default = "..."]`, /// funcionando incluso en estructuras con campos que no implementan [`Default`] o en *enums*. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop_macros::AutoDefault; +/// # fn main() { +/// #[derive(AutoDefault)] +/// # #[derive(PartialEq)] +/// # #[allow(dead_code)] +/// enum Foo { +/// Bar, +/// #[default] +/// Baz { +/// #[default = 12] +/// a: i32, +/// b: i32, +/// #[default(Some(Default::default()))] +/// c: Option<i32>, +/// #[default(_code = "vec![1, 2, 3]")] +/// d: Vec<u32>, +/// #[default = "four"] +/// e: String, +/// }, +/// Qux(i32), +/// } +/// +/// assert!(Foo::default() == Foo::Baz { +/// a: 12, +/// b: 0, +/// c: Some(0), +/// d: vec![1, 2, 3], +/// e: "four".to_owned(), +/// }); +/// # } +/// ``` +/// +/// * `Baz` tiene el atributo `#[default]`. Esto significa que el valor por defecto de `Foo` es +/// `Foo::Baz`. Solo una variante puede tener el atributo `#[default]`, y dicho atributo no debe +/// tener ningún valor asociado. +/// * `a` tiene el atributo `#[default = 12]`. Esto significa que su valor por defecto es `12`. +/// * `b` no tiene ningún atributo `#[default = ...]`. Su valor por defecto será, por tanto, el +/// valor por defecto de `i32`, es decir, `0`. +/// * `c` es un `Option<i32>`, y su valor por defecto es `Some(Default::default())`. Rust no puede +/// (actualmente) analizar `#[default = Some(Default::default())]`, pero podemos escribir +/// `#[default(Some(Default::default))]`. +/// * `d` contiene el token `!`, que (actualmente) no puede ser analizado ni siquiera usando +/// `#[default(...)]`, así que debemos codificarlo como una cadena y marcarlo con `_code =`. +/// * `e` es un `String`, por lo que el literal de cadena `"four"` se convierte automáticamente en +/// él. Esta conversión automática **solo** ocurre con literales de cadena (o de bytes), y solo si +/// no se usa `_code`. #[proc_macro_derive(AutoDefault, attributes(default))] pub fn derive_auto_default(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -65,46 +115,49 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// Macro (*attribute*) que asocia un método *builder* `with_` con un método `alter_`. /// -/// La macro añade automáticamente un método `alter_` para modificar la instancia actual usando -/// `&mut self`, y redefine el método *builder* `with_`, que consume la instancia (`mut self`), para -/// delegar la lógica de la modificación al nuevo método `alter_`, reutilizando así la misma -/// implementación. +/// La macro añade automáticamente un método `alter_` que permite modificar la instancia actual +/// usando `&mut self`; y redefine el método *builder* `with_`, que consume `mut self`, para delegar +/// la lógica al nuevo método `alter_`, reutilizando así la misma implementación. /// /// Esta macro emitirá un error en tiempo de compilación si la función anotada no cumple con la /// firma esperada para el método *builder*: `pub fn with_...(mut self, ...) -> Self`. /// -/// # Ejemplos +/// # Ejemplo /// /// Si defines un método `with_` como este: /// -/// ```rust,ignore +/// ```rust +/// # use pagetop_macros::builder_fn; +/// # struct Example {value: Option<String>}; +/// # impl Example { /// #[builder_fn] /// pub fn with_example(mut self, value: impl Into<String>) -> Self { /// self.value = Some(value.into()); /// self /// } +/// # } /// ``` /// -/// la macro generará automáticamente el siguiente método `alter_`: +/// la macro rescribirá el método `with_` y generará un nuevo método `alter_`: /// -/// ```rust,ignore -/// pub fn alter_example(&mut self, value: impl Into<String>) -> &mut Self { -/// self.value = Some(value.into()); -/// self -/// } -/// ``` -/// -/// y reescribirá el método `with_` para delegar la modificación al método `alter_`: -/// -/// ```rust,ignore +/// ```rust +/// # struct Example {value: Option<String>}; +/// # impl Example { +/// #[inline] /// pub fn with_example(mut self, value: impl Into<String>) -> Self { /// self.alter_example(value); /// self /// } +/// +/// pub fn alter_example(&mut self, value: impl Into<String>) -> &mut Self { +/// self.value = Some(value.into()); +/// self +/// } +/// # } /// ``` /// -/// Así, cada método *builder* `with_...()` generará automáticamente su correspondiente método -/// `alter_...()`, que permitirá más adelante modificar instancias existentes. +/// De esta forma, cada método *builder* `with_...()` generará automáticamente su correspondiente +/// método `alter_...()` para dejar modificar instancias existentes. #[proc_macro_attribute] pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { use syn::{parse2, FnArg, Ident, ImplItemFn, Pat, ReturnType, TraitItemFn, Type}; From f0e5f50a7f4d622e5fd8548e094516e28d3f35e5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 22 Nov 2025 09:11:16 +0100 Subject: [PATCH 190/224] =?UTF-8?q?=E2=9C=A8=20(theme):=20A=C3=B1ade=20com?= =?UTF-8?q?ponentes=20`Region`=20y=20`Template`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incluye un componente base `Template` para gestionar la estructura del documento y sus regiones (`Region`). - Actualiza el *trait* `Contextual` para permitir la selección de la plantilla de renderizado. - Modifica `Page` y `Context`, y refactoriza el manejo de temas, para dar soporte al nuevo sistema de plantillas y eliminar la gestión obsoleta de regiones. --- examples/navbar-menus.rs | 2 +- extensions/pagetop-aliner/README.md | 3 +- extensions/pagetop-aliner/src/lib.rs | 11 +- extensions/pagetop-bootsier/README.md | 3 +- extensions/pagetop-bootsier/src/lib.rs | 6 +- .../pagetop-bootsier/src/theme}/icon.rs | 0 src/base/component.rs | 81 ++-- src/base/component/poweredby.rs | 2 +- src/base/component/region.rs | 150 ++++++++ src/base/component/template.rs | 84 +++++ src/base/theme.rs | 4 +- src/base/theme/basic.rs | 3 - src/core/action/all.rs | 2 +- src/core/component/children.rs | 2 +- src/core/component/context.rs | 77 ++-- src/core/theme.rs | 21 +- src/core/theme/definition.rs | 356 ++++++------------ src/core/theme/regions.rs | 130 ++----- src/response/page.rs | 38 +- src/response/page/error.rs | 6 +- 20 files changed, 506 insertions(+), 475 deletions(-) rename {src/base/component => extensions/pagetop-bootsier/src/theme}/icon.rs (100%) create mode 100644 src/base/component/region.rs create mode 100644 src/base/component/template.rs diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 071d24b1..341d394a 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -95,7 +95,7 @@ impl Extension for SuperMenu { })), )); - InRegion::Key("header").add(Child::with( + InRegion::Named("header").add(Child::with( Container::new() .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) .add_child(navbar_menu), diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 43fb65a5..f4670aae 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -63,10 +63,11 @@ theme = "Aliner" ```rust,no_run use pagetop::prelude::*; +use pagetop_aliner::Aliner; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .with_theme("Aliner") + .with_theme(&Aliner) .add_child( Block::new() .with_title(L10n::l("sample_title")) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index cbc0f526..4ae4121e 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -64,10 +64,11 @@ theme = "Aliner" ```rust,no_run use pagetop::prelude::*; +use pagetop_aliner::Aliner; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .with_theme("Aliner") + .with_theme(&Aliner) .add_child( Block::new() .with_title(L10n::l("sample_title")) @@ -82,15 +83,11 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { use pagetop::prelude::*; -/// El tema usa las mismas regiones predefinidas por [`DefaultRegions`]. -pub type AlinerRegions = DefaultRegions; - /// Implementa el tema para usar en pruebas que muestran el esquema de páginas HTML. /// -/// Tema mínimo ideal para **pruebas y demos** que renderiza el **esqueleto HTML** con las mismas -/// regiones básicas definidas por [`DefaultRegions`]. No pretende ser un tema para producción, está -/// pensado para: +/// Define un tema mínimo útil para: /// +/// - Comprobar el funcionamiento de temas, plantillas y regiones. /// - Verificar integración de componentes y composiciones (*layouts*) sin estilos complejos. /// - Realizar pruebas de renderizado rápido con salida estable y predecible. /// - Preparar ejemplos y documentación, sin dependencias visuales (CSS/JS) innecesarias. diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 84e11b57..d6e1666a 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -63,10 +63,11 @@ theme = "Bootsier" ```rust,no_run use pagetop::prelude::*; +use pagetop_bootsier::Bootsier; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .with_theme("Bootsier") + .with_theme(&Bootsier) .add_child( Block::new() .with_title(L10n::l("sample_title")) diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 76f9d1e2..0bf94f47 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -64,10 +64,11 @@ theme = "Bootsier" ```rust,no_run use pagetop::prelude::*; +use pagetop_bootsier::Bootsier; async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .with_theme("Bootsier") + .with_theme(&Bootsier) .add_child( Block::new() .with_title(L10n::l("sample_title")) @@ -101,9 +102,6 @@ pub mod prelude { pub use crate::theme::*; } -/// El tema usa las mismas regiones predefinidas por [`DefaultRegions`]. -pub type BootsierRegions = DefaultRegions; - /// Implementa el tema. pub struct Bootsier; diff --git a/src/base/component/icon.rs b/extensions/pagetop-bootsier/src/theme/icon.rs similarity index 100% rename from src/base/component/icon.rs rename to extensions/pagetop-bootsier/src/theme/icon.rs diff --git a/src/base/component.rs b/src/base/component.rs index bdab35c6..fa9ed2ad 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,48 +1,46 @@ //! Componentes nativos proporcionados por PageTop. - -use crate::prelude::*; - -// **< FontSize >*********************************************************************************** - -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum FontSize { - ExtraLarge, - XxLarge, - XLarge, - Large, - Medium, - #[default] - Normal, - Small, - XSmall, - XxSmall, - ExtraSmall, -} - -#[rustfmt::skip] -impl FontSize { - #[inline] - pub const fn as_str(self) -> &'static str { - match self { - FontSize::ExtraLarge => "fs__x3l", - FontSize::XxLarge => "fs__x2l", - FontSize::XLarge => "fs__xl", - FontSize::Large => "fs__l", - FontSize::Medium => "fs__m", - FontSize::Normal => "", - FontSize::Small => "fs__s", - FontSize::XSmall => "fs__xs", - FontSize::XxSmall => "fs__x2s", - FontSize::ExtraSmall => "fs__x3s", - } - } -} - -// ************************************************************************************************* +//! +//! Conviene destacar que PageTop distingue entre: +//! +//! - **Componentes estructurales** que definen el esqueleto de un documento HTML, como [`Template`] +//! y [`Region`], utilizados por [`Page`](crate::response::page::Page) para generar la estructura +//! final. +//! - **Componentes de contenido** (menús, barras, tarjetas, etc.), que se incluyen en las regiones +//! gestionadas por los componentes estructurales. +//! +//! El componente [`Template`] describe cómo maquetar el cuerpo del documento a partir de varias +//! regiones lógicas ([`Region`]). En función de la plantilla seleccionada, determina qué regiones +//! se renderizan y en qué orden. Por ejemplo, la plantilla predeterminada [`Template::DEFAULT`] +//! utiliza las regiones [`Region::HEADER`], [`Region::CONTENT`] y [`Region::FOOTER`]. +//! +//! Un componente [`Region`] es un contenedor lógico asociado a un nombre de región. Su contenido se +//! obtiene del [`Context`](crate::core::component::Context), donde los componentes se registran +//! mediante [`Contextual::with_child_in()`](crate::core::component::Contextual::with_child_in) y +//! otros mecanismos similares, y se integra en el documento a través de [`Template`]. +//! +//! Por su parte, una página ([`Page`](crate::response::page::Page)) representa un documento HTML +//! completo. Implementa [`Contextual`](crate::core::component::Contextual) para mantener su propio +//! [`Context`](crate::core::component::Context), donde gestiona el tema activo, la plantilla +//! seleccionada y los componentes asociados a cada región, y se encarga de generar la estructura +//! final de la página. +//! +//! De este modo, temas y extensiones colaboran sobre una estructura común: las aplicaciones +//! registran componentes en el [`Context`](crate::core::component::Context), las plantillas +//! organizan las regiones y las páginas generan el documento HTML resultante. +//! +//! Los temas pueden sobrescribir [`Template`] para exponer nuevas plantillas o adaptar las +//! predeterminadas, y lo mismo con [`Region`] para añadir regiones adicionales o personalizar su +//! representación. mod html; pub use html::Html; +mod region; +pub use region::Region; + +mod template; +pub use template::Template; + mod block; pub use block::Block; @@ -51,6 +49,3 @@ pub use intro::{Intro, IntroOpening}; mod poweredby; pub use poweredby::PoweredBy; - -mod icon; -pub use icon::{Icon, IconKind}; diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 51ab79d8..797253dc 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -3,7 +3,7 @@ use crate::prelude::*; // Enlace a la página oficial de PageTop. const LINK: &str = "<a href=\"https://pagetop.cillero.es\" rel=\"noopener noreferrer\">PageTop</a>"; -/// Componente que renderiza la sección 'Powered by' (*Funciona con*) típica del pie de página. +/// Componente que informa del 'Powered by' (*Funciona con*) típica del pie de página. /// /// Por defecto, usando [`default()`](Self::default) sólo se muestra un reconocimiento a PageTop. /// Sin embargo, se puede usar [`new()`](Self::new) para crear una instancia con un texto de diff --git a/src/base/component/region.rs b/src/base/component/region.rs new file mode 100644 index 00000000..5dfa25ce --- /dev/null +++ b/src/base/component/region.rs @@ -0,0 +1,150 @@ +use crate::prelude::*; + +/// Componente estructural que renderiza el contenido de una región del documento. +/// +/// `Region` actúa como un contenedor lógico asociado a un nombre de región. Su contenido se obtiene +/// del contexto de renderizado ([`Context`]), donde los componentes suelen registrarse con métodos +/// como [`Contextual::with_child_in()`]. Cada región puede integrarse posteriormente en el cuerpo +/// del documento mediante [`Template`], normalmente desde una página ([`Page`]). +#[derive(AutoDefault)] +pub struct Region { + #[default(AttrName::new(Self::DEFAULT))] + name: AttrName, + #[default(L10n::l("region-content"))] + label: L10n, +} + +impl Component for Region { + fn new() -> Self { + Region::default() + } + + fn id(&self) -> Option<String> { + self.name.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let Some(name) = self.name().get() else { + return PrepareMarkup::None; + }; + let output = cx.render_region(&name); + if output.is_empty() { + return PrepareMarkup::None; + } + PrepareMarkup::With(html! { + div + id=[self.id()] + class=(join!("region region-", &name)) + role="region" + aria-label=[self.label().lookup(cx)] + { + (output) + } + }) + } +} + +impl Region { + /// Región especial situada al **inicio del documento**. + /// + /// Su función es proporcionar un punto estable donde las extensiones puedan inyectar contenido + /// global antes de renderizar el resto de regiones principales (cabecera, contenido, etc.). + /// + /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, + /// sino como punto de anclaje para elementos auxiliares, marcadores técnicos, inicializadores o + /// contenido de depuración que deban situarse en la parte superior del documento. + /// + /// Se considera una región **reservada** para este tipo de usos globales. + pub const PAGETOP: &str = "page-top"; + + /// Región estándar para la **cabecera** del documento. + /// + /// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc. + pub const HEADER: &str = "header"; + + /// Región principal de **contenido**. + /// + /// Es la región donde se espera que se renderice el contenido principal de la página (p. ej. + /// cuerpo de la ruta actual, bloques centrales, vistas principales, etc.). En muchos temas será + /// la región mínima imprescindible para que la página tenga sentido. + pub const CONTENT: &str = "content"; + + /// Región estándar para el **pie de página**. + /// + /// Suele contener información legal, enlaces secundarios, créditos, etc. + pub const FOOTER: &str = "footer"; + + /// Región especial situada al **final del documento**. + /// + /// Pensada para proporcionar un punto estable donde las extensiones puedan inyectar contenido + /// global después de renderizar el resto de regiones principales (cabecera, contenido, etc.). + /// + /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, + /// sino como punto de anclaje para elementos auxiliares asociados a comportamientos dinámicos + /// que deban situarse en la parte inferior del documento. + /// + /// Igual que [`Self::PAGETOP`], se considera una región **reservada** para este tipo de usos + /// globales. + pub const PAGEBOTTOM: &str = "page-bottom"; + + /// Región por defecto que se asigna cuando no se especifica ningún nombre. + /// + /// Por diseño, la región por defecto es la de contenido principal ([`Self::CONTENT`]), de + /// manera que un tema sencillo pueda limitarse a definir una sola región funcional. + pub const DEFAULT: &str = Self::CONTENT; + + /// Prepara una región para el nombre indicado. + /// + /// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del + /// contenedor. Al renderizarse, este componente mostrará el contenido registrado en el contexto + /// bajo ese nombre. + pub fn named(name: impl AsRef<str>) -> Self { + Region { + name: AttrName::new(name), + label: L10n::default(), + } + } + + /// Prepara una región para el nombre indicado con una etiqueta de accesibilidad. + /// + /// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del + /// contenedor, mientras que `label` será el texto localizado que se usará como `aria-label` del + /// contenedor. + pub fn labeled(name: impl AsRef<str>, label: L10n) -> Self { + Region { + name: AttrName::new(name), + label, + } + } + + // **< Region BUILDER >************************************************************************* + + /// Establece o modifica el nombre de la región. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef<str>) -> Self { + self.name.alter_value(name); + self + } + + /// Establece la etiqueta localizada de la región. + /// + /// Esta etiqueta se utiliza como `aria-label` del contenedor predefinido `<div role="region">`, + /// lo que mejora la accesibilidad para lectores de pantalla y otras tecnologías de apoyo. + #[builder_fn] + pub fn with_label(mut self, label: L10n) -> Self { + self.label = label; + self + } + + // **< Region GETTERS >************************************************************************* + + /// Devuelve el nombre de la región. + pub fn name(&self) -> &AttrName { + &self.name + } + + /// Devuelve la etiqueta localizada asociada a la región. + pub fn label(&self) -> &L10n { + &self.label + } +} diff --git a/src/base/component/template.rs b/src/base/component/template.rs new file mode 100644 index 00000000..6c70d00e --- /dev/null +++ b/src/base/component/template.rs @@ -0,0 +1,84 @@ +use crate::prelude::*; + +/// Componente estructural para renderizar plantillas de contenido. +/// +/// `Template` describe cómo se compone el cuerpo del documento a partir de varias regiones lógicas +/// ([`Region`]). En función de su nombre, decide qué regiones se renderizan y en qué orden. +/// +/// Normalmente se invoca desde una página ([`Page`]), que consulta el nombre de plantilla guardado +/// en el [`Context`] y delega en `Template` la composición de las regiones que forman el cuerpo del +/// documento. +/// +/// Los temas pueden sobrescribir este componente para exponer sus propias plantillas o adaptar las +/// plantillas predeterminadas. +#[derive(AutoDefault)] +pub struct Template { + #[default(AttrName::new(Self::DEFAULT))] + name: AttrName, +} + +impl Component for Template { + fn new() -> Self { + Template::default() + } + + fn id(&self) -> Option<String> { + self.name.get() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let Some(name) = self.name().get() else { + return PrepareMarkup::None; + }; + match name.as_str() { + Self::DEFAULT | Self::ERROR => PrepareMarkup::With(html! { + (Region::labeled(Region::HEADER, L10n::l("region-header")).render(cx)) + (Region::default().render(cx)) + (Region::labeled(Region::FOOTER, L10n::l("region-footer")).render(cx)) + }), + _ => PrepareMarkup::None, + } + } +} + +impl Template { + /// Nombre de la plantilla predeterminada. + /// + /// Por defecto define una estructura básica con las regiones [`Region::HEADER`], + /// [`Region::CONTENT`] y [`Region::FOOTER`], en ese orden. Esta plantilla se usa cuando no se + /// selecciona ninguna otra de forma explícita (ver [`Contextual::with_template()`]). + pub const DEFAULT: &str = "default"; + + /// Nombre de la plantilla de error. + /// + /// Se utiliza para páginas de error u otros estados excepcionales. Por defecto reutiliza + /// la misma estructura que [`Self::DEFAULT`], pero permite a temas y extensiones distinguir + /// el contexto de error para aplicar estilos o contenidos específicos. + pub const ERROR: &str = "error"; + + /// Selecciona la plantilla asociada al nombre indicado. + /// + /// El valor de `name` se utiliza como nombre de la plantilla y como identificador (`id`) del + /// componente. + pub fn named(name: impl AsRef<str>) -> Self { + Template { + name: AttrName::new(name), + } + } + + // **< Template BUILDER >*********************************************************************** + + /// Establece o modifica el nombre de la plantilla seleccionada. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef<str>) -> Self { + self.name.alter_value(name); + self + } + + // **< Template GETTERS >*********************************************************************** + + /// Devuelve el nombre de la plantilla seleccionada. + pub fn name(&self) -> &AttrName { + &self.name + } +} diff --git a/src/base/theme.rs b/src/base/theme.rs index 1e5b1a85..4a13a4e4 100644 --- a/src/base/theme.rs +++ b/src/base/theme.rs @@ -1,4 +1,4 @@ -//! Temas básicos soportados por PageTop. +//! Tema básico soportados por PageTop. mod basic; -pub use basic::{Basic, BasicRegions}; +pub use basic::Basic; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 2d713e37..eb2274f6 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -1,9 +1,6 @@ /// Es el tema básico que incluye PageTop por defecto. use crate::prelude::*; -/// El tema básico usa las mismas regiones predefinidas por [`DefaultRegions`]. -pub type BasicRegions = DefaultRegions; - /// Tema básico por defecto que extiende el funcionamiento predeterminado de [`Theme`]. pub struct Basic; diff --git a/src/core/action/all.rs b/src/core/action/all.rs index fbbf8427..2a3dfd2d 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -19,7 +19,7 @@ static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> = // // Las extensiones llamarán a esta función durante su inicialización para instalar acciones // personalizadas que modifiquen el comportamiento del *core* o de otros componentes. -pub fn add_action(action: ActionBox) { +pub(crate) fn add_action(action: ActionBox) { let key = ActionKey::new( action.type_id(), action.theme_type_id(), diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 3b8f2abf..b3670433 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -200,7 +200,7 @@ pub enum TypedOp<C: Component> { /// Esta lista permite añadir, modificar, renderizar y consultar componentes hijo en orden de /// inserción, soportando operaciones avanzadas como inserción relativa o reemplazo por /// identificador. -#[derive(Clone, Default)] +#[derive(AutoDefault, Clone)] pub struct Children(Vec<Child>); impl Children { diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 8c4e47e1..5cc5d2e8 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -1,5 +1,6 @@ +use crate::base::component::Template; use crate::core::component::ChildOp; -use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME}; +use crate::core::theme::all::DEFAULT_THEME; use crate::core::theme::{ChildrenInRegions, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup}; @@ -13,19 +14,16 @@ use std::collections::HashMap; /// Operaciones para modificar recursos asociados al contexto ([`Context`]) de un documento. pub enum ContextOp { - // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. SetFavicon(Option<Favicon>), /// Define el *favicon* solo si no se ha establecido previamente. SetFaviconIfNone(Favicon), - // Stylesheets. /// Añade una hoja de estilos CSS al documento. AddStyleSheet(StyleSheet), /// Elimina una hoja de estilos por su ruta o identificador. RemoveStyleSheet(&'static str), - // JavaScripts. /// Añade un script JavaScript al documento. AddJavaScript(JavaScript), /// Elimina un script por su ruta o identificador. @@ -50,27 +48,27 @@ pub enum ContextError { /// Interfaz para gestionar el **contexto de renderizado** de un documento HTML. /// -/// `Contextual` extiende [`LangId`] y define los métodos para: +/// `Contextual` extiende [`LangId`] para establecer el idioma del documento y añade métodos para: /// -/// - Establecer el **idioma** del documento. /// - Almacenar la **solicitud HTTP** de origen. -/// - Seleccionar **tema** y **composición** (*layout*) de renderizado. +/// - Seleccionar el **tema** y la **plantilla** de renderizado. /// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo /// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`ContextOp`]. /// - Leer y mantener **parámetros dinámicos tipados** de contexto. /// - Generar **identificadores únicos** por tipo de componente. /// -/// Lo implementan, típicamente, estructuras que representan el contexto de renderizado, como +/// Lo implementan, típicamente, estructuras que manejan el contexto de renderizado, como /// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page). /// /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; +/// # use pagetop_aliner::Aliner; /// fn prepare_context<C: Contextual>(cx: C) -> C { /// cx.with_langid(&LangMatch::resolve("es-ES")) -/// .with_theme("aliner") -/// .with_layout("default") +/// .with_theme(&Aliner) +/// .with_template(Template::DEFAULT) /// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) /// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/app.js"))) @@ -90,11 +88,11 @@ pub trait Contextual: LangId { /// Especifica el tema para renderizar el documento. #[builder_fn] - fn with_theme(self, theme_name: &'static str) -> Self; + fn with_theme(self, theme: ThemeRef) -> Self; - /// Especifica la composición para renderizar el documento. + /// Especifica la plantilla para renderizar el documento. #[builder_fn] - fn with_layout(self, layout_name: &'static str) -> Self; + fn with_template(self, template_name: &'static str) -> Self; /// Añade o modifica un parámetro dinámico del contexto. #[builder_fn] @@ -104,9 +102,9 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: ContextOp) -> Self; - /// Opera con [`ChildOp`] en una región (`region_key`) de la página. + /// Opera con [`ChildOp`] en una región (`region_name`) del documento. #[builder_fn] - fn with_child_in(self, region_key: &'static str, op: ChildOp) -> Self; + fn with_child_in(self, region_name: impl AsRef<str>, op: ChildOp) -> Self; // **< Contextual GETTERS >********************************************************************* @@ -116,8 +114,8 @@ pub trait Contextual: LangId { /// Devuelve el tema que se usará para renderizar el documento. fn theme(&self) -> ThemeRef; - /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. - fn layout(&self) -> &str; + /// Devuelve el nombre de la plantilla usada para renderizar el documento. + fn template(&self) -> &str; /// Recupera un parámetro como [`Option`]. fn param<T: 'static>(&self, key: &'static str) -> Option<&T>; @@ -168,12 +166,13 @@ pub trait Contextual: LangId { /// /// ```rust /// # use pagetop::prelude::*; +/// # use pagetop_aliner::Aliner; /// fn new_context(request: HttpRequest) -> Context { /// Context::new(Some(request)) /// // Establece el idioma del documento a español. /// .with_langid(&LangMatch::resolve("es-ES")) -/// // Selecciona un tema (por su nombre corto). -/// .with_theme("aliner") +/// // Establece el tema para renderizar. +/// .with_theme(&Aliner) /// // Asigna un favicon. /// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// // Añade una hoja de estilo externa. @@ -208,8 +207,8 @@ pub trait Contextual: LangId { pub struct Context { request : Option<HttpRequest>, // Solicitud HTTP de origen. langid : &'static LanguageIdentifier, // Identificador de idioma. - theme : ThemeRef, // Referencia al tema para renderizar. - layout : &'static str, // Composición del documento para renderizar. + theme : ThemeRef, // Referencia al tema usado para renderizar. + template : &'static str, // Nombre de la plantilla usada para renderizar. favicon : Option<Favicon>, // Favicon, si se ha definido. stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS. javascripts: Assets<JavaScript>, // Scripts JavaScript. @@ -227,8 +226,8 @@ impl Default for Context { impl Context { /// Crea un nuevo contexto asociado a una solicitud HTTP. /// - /// El contexto inicializa el idioma, tema y composición por defecto, sin favicon ni recursos - /// cargados. + /// El contexto inicializa el idioma, el tema y la plantilla por defecto, sin favicon ni otros + /// recursos cargados. #[rustfmt::skip] pub fn new(request: Option<HttpRequest>) -> Self { // Se intenta DEFAULT_LANGID. @@ -249,7 +248,7 @@ impl Context { request, langid, theme : *DEFAULT_THEME, - layout : "default", + template : Template::DEFAULT, favicon : None, stylesheets: Assets::<StyleSheet>::new(), javascripts: Assets::<JavaScript>::new(), @@ -287,10 +286,10 @@ impl Context { markup } - /// Renderiza los componentes de una región (`region_key`). - pub fn render_components_of(&mut self, region_key: &'static str) -> Markup { + /// Renderiza los componentes de la región `region_name`. + pub fn render_region(&mut self, region_name: impl AsRef<str>) -> Markup { self.regions - .merge_all_components(self.theme, region_key) + .children_for(self.theme, region_name) .render(self) } @@ -364,7 +363,7 @@ impl Context { /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. /// - /// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada. + /// Devuelve `false` en caso contrario. Usar cuando sólo interesa borrar la entrada. /// /// # Ejemplos /// @@ -411,19 +410,15 @@ impl Contextual for Context { self } - /// Asigna el tema para renderizar el documento. - /// - /// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica - /// ninguno entonces usará el tema por defecto. #[builder_fn] - fn with_theme(mut self, theme_name: &'static str) -> Self { - self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); + fn with_theme(mut self, theme: ThemeRef) -> Self { + self.theme = theme; self } #[builder_fn] - fn with_layout(mut self, layout_name: &'static str) -> Self { - self.layout = layout_name; + fn with_template(mut self, template_name: &'static str) -> Self { + self.template = template_name; self } @@ -467,7 +462,7 @@ impl Contextual for Context { ContextOp::RemoveStyleSheet(path) => { self.stylesheets.remove(path); } - // JavaScripts. + // Scripts JavaScript. ContextOp::AddJavaScript(js) => { self.javascripts.add(js); } @@ -479,8 +474,8 @@ impl Contextual for Context { } #[builder_fn] - fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { - self.regions.alter_child_in(region_key, op); + fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { + self.regions.alter_child_in(region_name, op); self } @@ -494,8 +489,8 @@ impl Contextual for Context { self.theme } - fn layout(&self) -> &str { - self.layout + fn template(&self) -> &str { + self.template } /// Recupera un parámetro como [`Option`], simplificando el acceso. diff --git a/src/core/theme.rs b/src/core/theme.rs index 28638ba6..8774276e 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -1,25 +1,24 @@ //! API para añadir y gestionar nuevos temas. //! -//! En PageTop un tema es la *piel* de la aplicación, decide cómo se muestra cada documento HTML, -//! especialmente las páginas de contenido ([`Page`](crate::response::page::Page)), sin alterar la -//! lógica interna de sus componentes. +//! En PageTop un tema es la *piel* de la aplicación. Es responsable último de los estilos, +//! tipografías, espaciados y cualquier otro detalle visual o interactivo (animaciones, scripts de +//! interfaz, etc.). //! -//! Un tema **declara las regiones** (*cabecera*, *barra lateral*, *pie*, etc.) que estarán -//! disponibles para colocar contenido. Los temas son responsables últimos de los estilos, -//! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (como animaciones, -//! scripts de interfaz, etc.). +//! Un tema determina el aspecto final de un documento HTML sin alterar la lógica interna de los +//! componentes ni la estructura del documento, que queda definida por la plantilla +//! ([`Template`](crate::base::component::Template)) utilizada por cada página. //! //! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension), por //! lo que se instancian, declaran dependencias y se inician igual que cualquier otra extensión. //! También deben implementar [`Theme`] y sobrescribir el método //! [`Extension::theme()`](crate::core::extension::Extension::theme) para que PageTop pueda -//! registrarlos como temas +//! registrarlos como temas. mod definition; -pub use definition::{Theme, ThemePage, ThemeRef, DefaultRegions}; +pub use definition::{Theme, ThemeRef}; mod regions; -pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT}; -pub use regions::{InRegion, Region, RegionRef}; +pub(crate) use regions::ChildrenInRegions; +pub use regions::InRegion; pub(crate) mod all; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 7d21c146..de11d1ba 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,129 +1,136 @@ -use crate::core::component::{ContextOp, Contextual}; +use crate::base::component::Template; +use crate::core::component::{ComponentRender, ContextOp, Contextual}; use crate::core::extension::Extension; -use crate::core::theme::{Region, RegionRef, REGION_CONTENT}; +use crate::global; use crate::html::{html, Markup, StyleSheet}; use crate::locale::L10n; use crate::response::page::Page; -use crate::{global, join, AutoDefault}; - -use std::sync::LazyLock; /// Referencia estática a un tema. /// /// Los temas son también extensiones. Por tanto, deben declararse como **instancias estáticas** que -/// implementen [`Theme`] y, a su vez, [`Extension`]. +/// implementen [`Theme`] y, a su vez, [`Extension`]. Estas instancias se exponen usando +/// [`Extension::theme()`](crate::core::extension::Extension::theme). pub type ThemeRef = &'static dyn Theme; -/// Conjunto de regiones predefinidas que los temas pueden exponer para el renderizado. +/// Interfaz común que debe implementar cualquier tema de PageTop. /// -/// `DefaultRegions` define un conjunto de regiones predefinidas para estructurar un documento HTML. -/// Proporciona **identificadores estables** (vía [`Region::key()`]) y **etiquetas localizables** -/// (vía [`Region::label()`]) a las regiones donde se añadirán los componentes. +/// Un tema es una [`Extension`](crate::core::extension::Extension) que define el aspecto general de +/// las páginas: cómo se renderiza el `<head>`, cómo se presenta el `<body>` mediante plantillas +/// ([`Template`]) y qué contenido mostrar en las páginas de error. /// -/// Se usa por defecto en [`Theme::page_regions()`](crate::core::theme::Theme::page_regions) y sus -/// variantes representan el conjunto mínimo recomendado para cualquier tema. Sin embargo, cada tema -/// podría exponer su propio conjunto de regiones. -#[derive(AutoDefault)] -pub enum DefaultRegions { - /// Cabecera de la página. +/// Todos los métodos de este *trait* tienen una implementación por defecto, por lo que pueden +/// sobrescribirse selectivamente para crear nuevos temas con comportamientos distintos a los +/// predeterminados. +/// +/// El único método **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme), +/// que debe devolver una referencia estática al propio tema: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// pub struct MyTheme; +/// +/// impl Extension for MyTheme { +/// fn name(&self) -> L10n { +/// L10n::n("My theme") +/// } +/// +/// fn description(&self) -> L10n { +/// L10n::n("A personal theme") +/// } +/// +/// fn theme(&self) -> Option<ThemeRef> { +/// Some(&Self) +/// } +/// } +/// +/// impl Theme for MyTheme {} +/// ``` +pub trait Theme: Extension + Send + Sync { + /// Acciones específicas del tema antes de renderizar el `<body>` de la página. /// - /// Clave: `"header"`. Suele contener *branding*, navegación principal o avisos globales. - Header, - - /// Contenido principal de la página (**obligatoria**). + /// Se invoca antes de que se procese la plantilla ([`Template`]) asociada a la página + /// ([`Page::template()`](crate::response::page::Page::template)). Es un buen lugar para + /// inicializar o ajustar recursos en función del contexto de la página, por ejemplo: /// - /// Clave: `"content"`. Es el destino por defecto para insertar componentes a nivel de página. - #[default] - Content, + /// - Añadir metadatos o propiedades a la página. + /// - Preparar atributos compartidos. + /// - Registrar *assets* condicionales en el contexto. + #[allow(unused_variables)] + fn before_render_page_body(&self, page: &mut Page) {} - /// Pie de página. + /// Renderiza el contenido del `<body>` de la página. /// - /// Clave: `"footer"`. Suele contener enlaces legales, créditos o navegación secundaria. - Footer, -} + /// Por defecto, delega en la plantilla ([`Template`]) asociada a la página + /// ([`Page::template()`](crate::response::page::Page::template)). La plantilla se encarga de + /// procesar las regiones y renderizar los componentes registrados en el contexto. + /// + /// Los temas pueden sobrescribir este método para: + /// + /// - Forzar una plantilla concreta en determinadas páginas. + /// - Envolver el contenido en marcadores adicionales. + /// - Implementar lógicas de composición alternativas. + #[inline] + fn render_page_body(&self, page: &mut Page) -> Markup { + Template::named(page.template()).render(page.context()) + } -impl Region for DefaultRegions { - fn key(&self) -> &str { - match self { - Self::Header => "header", - Self::Content => REGION_CONTENT, - Self::Footer => "footer", + /// Acciones específicas del tema después de renderizar el `<body>` de la página. + /// + /// Se invoca tras la generación del contenido del `<body>`. Es útil para: + /// + /// - Ajustar o registrar recursos en función de lo que se haya renderizado. + /// - Realizar *tracing* o recopilar métricas. + /// - Aplicar ajustes finales al estado de la página antes de producir el `<head>` o la + /// respuesta final. + /// + /// La implementación por defecto añade una serie de hojas de estilo básicas (`normalize.css`, + /// `root.css`, `basic.css`) cuando el parámetro `include_basic_assets` de la página está + /// activado. + #[allow(unused_variables)] + fn after_render_page_body(&self, page: &mut Page) { + if page.param_or("include_basic_assets", false) { + let pkg_version = env!("CARGO_PKG_VERSION"); + + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/normalize.css") + .with_version("8.0.1") + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/root.css") + .with_version(pkg_version) + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(pkg_version) + .with_weight(-99), + )); } } - fn label(&self) -> L10n { - L10n::l(join!("region_", self.key())) - } -} - -/// Métodos predefinidos de renderizado para las páginas de un tema. -/// -/// Contiene las implementaciones base para renderizar las **secciones** `<head>` y `<body>`. Se -/// implementa automáticamente para cualquier tipo que implemente [`Theme`], por lo que normalmente -/// no requiere implementación explícita. -/// -/// Si un tema **sobrescribe** uno o más de los siguientes métodos de [`Theme`]: -/// -/// - [`render_page_region()`](Theme::render_page_region), -/// - [`render_page_head()`](Theme::render_page_head), o -/// - [`render_page_body()`](Theme::render_page_body); -/// -/// puede volver al comportamiento por defecto con una llamada FQS (*Fully Qualified Syntax*) a: -/// -/// - `<Self as ThemePage>::render_region(self, page, region)`, -/// - `<Self as ThemePage>::render_body(self, page, self.page_regions())`, o -/// - `<Self as ThemePage>::render_head(self, page)`. -pub trait ThemePage { - /// Renderiza el **contenedor** de una región concreta del `<body>` de la página. + /// Renderiza el contenido del `<head>` de la página. /// - /// Obtiene los componentes asociados a `region.key()` desde el contexto de la página y, si hay - /// salida, envuelve el contenido en un contenedor `<div>` predefinido. + /// Aunque en una página el `<head>` se encuentra antes del `<body>`, internamente se renderiza + /// después para contar con los ajustes que hayan ido acumulando los componentes. Por ejemplo, + /// permitiría añadir un archivo de iconos sólo si se ha incluido un icono en la página. /// - /// Si la región **no produce contenido**, devuelve un `Markup` vacío. + /// Por defecto incluye: + /// + /// - La codificación (`charset="utf-8"`). + /// - El título, usando el título de la página si existe y, en caso contrario, sólo el nombre de + /// la aplicación. + /// - La descripción (`<meta name="description">`), si está definida. + /// - La etiqueta `viewport` básica para diseño adaptable. + /// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la + /// página. + /// - Todos los *assets* registrados en el contexto de la página. + /// + /// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo, + /// *favicons* personalizados, manifest, etiquetas de analítica, etc.). #[inline] - fn render_region(&self, page: &mut Page, region: RegionRef) -> Markup { - html! { - @let key = region.key(); - @let output = page.context().render_components_of(key); - @if !output.is_empty() { - div - id=(key) - class={ "region region--" (key) } - role="region" - aria-label=[region.label().lookup(page)] - { - (output) - } - } - } - } - - /// Renderiza el **contenido interior** del `<body>` de la página. - /// - /// Recorre `regions` en el **orden declarado** y, para cada región con contenido, delega en - /// [`render_region()`](Self::render_region) la generación del contenedor. Las regiones sin - /// contenido **no** producen salida. Se asume que cada identificador de región es **único** - /// dentro de la página. - /// - /// La etiqueta `<body>` no se incluye aquí; únicamente renderiza su contenido. - #[inline] - fn render_body(&self, page: &mut Page, regions: &[RegionRef]) -> Markup { - html! { - @for region in regions { - (self.render_region(page, *region)) - } - } - } - - /// Renderiza el **contenido interior** del `<head>` de la página. - /// - /// Incluye por defecto las etiquetas básicas (`charset`, `title`, `description`, `viewport`, - /// `X-UA-Compatible`), los metadatos (`name/content`) y propiedades (`property/content`), - /// además de los recursos CSS/JS de la página. - /// - /// La etiqueta `<head>` no se incluye aquí; únicamente se renderiza su contenido. - #[inline] - fn render_head(&self, page: &mut Page) -> Markup { + fn render_page_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { meta charset="utf-8"; @@ -151,155 +158,20 @@ pub trait ThemePage { (page.context().render_assets()) } } -} - -/// Interfaz común que debe implementar cualquier tema de PageTop. -/// -/// Un tema implementa [`Theme`] y los métodos necesarios de [`Extension`]. El único método -/// **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme). -/// -/// ```rust -/// # use pagetop::prelude::*; -/// pub struct MyTheme; -/// -/// impl Extension for MyTheme { -/// fn name(&self) -> L10n { -/// L10n::n("My theme") -/// } -/// -/// fn description(&self) -> L10n { -/// L10n::n("A personal theme") -/// } -/// -/// fn theme(&self) -> Option<ThemeRef> { -/// Some(&Self) -/// } -/// } -/// -/// impl Theme for MyTheme {} -/// ``` -pub trait Theme: Extension + ThemePage + Send + Sync { - /// **Obsoleto desde la versión 0.4.0**: usar [`page_regions()`](Self::page_regions) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `page_regions()` instead")] - fn regions(&self) -> Vec<(&'static str, L10n)> { - vec![("content", L10n::l("content"))] - } - - /// Declaración ordenada de las regiones disponibles en la página. - /// - /// Retorna una **lista estática** de referencias ([`RegionRef`](crate::core::theme::RegionRef)) - /// que representan las regiones que el tema admite dentro del `<body>`. - /// - /// Cada referencia apunta a una instancia que implementa [`Region`](crate::core::theme::Region) - /// para definir cada región de forma segura y estable. Y si un tema necesita un conjunto - /// distinto de regiones, puede **sobrescribir** este método siguiendo estas recomendaciones: - /// - /// - Los identificadores devueltos por [`Region::key()`](crate::core::theme::Region::key) - /// deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`). - /// - La región `"content"` es **obligatoria**, ya que se usa como destino por defecto para - /// insertar componentes y renderizarlos. - /// - El orden de la lista podría tener relevancia como **orden de renderizado** dentro del - /// `<body>` segun la implementación de [`render_page_body()`](Self::render_page_body). - /// - Las etiquetas (`L10n`) de cada región se evaluarán con el idioma activo de la página. - /// - /// # Ejemplo - /// - /// ```rust,ignore - /// fn page_regions(&self) -> &'static [RegionRef] { - /// static REGIONS: LazyLock<[RegionRef; 4]> = LazyLock::new(|| { - /// [ - /// &DefaultRegions::Header, - /// &DefaultRegions::Content, - /// &DefaultRegions::Footer, - /// ] - /// }); - /// &*REGIONS - /// } - /// ``` - fn page_regions(&self) -> &'static [RegionRef] { - static REGIONS: LazyLock<[RegionRef; 3]> = LazyLock::new(|| { - [ - &DefaultRegions::Header, - &DefaultRegions::Content, - &DefaultRegions::Footer, - ] - }); - &*REGIONS - } - - /// Renderiza una región de la página. - /// - /// Si se sobrescribe este método, se puede volver al comportamiento base con: - /// `<Self as ThemePage>::render_region(self, page, region)`. - #[inline] - fn render_page_region(&self, page: &mut Page, region: RegionRef) -> Markup { - <Self as ThemePage>::render_region(self, page, region) - } - - /// Acciones específicas del tema antes de renderizar el `<body>` de la página. - /// - /// Útil para preparar clases, inyectar recursos o ajustar metadatos. - #[allow(unused_variables)] - fn before_render_page_body(&self, page: &mut Page) {} - - /// Renderiza el contenido del `<body>` de la página. - /// - /// Si se sobrescribe este método, se puede volver al renderizado base con: - /// `<Self as ThemePage>::render_body(self, page, self.page_regions())`. - #[inline] - fn render_page_body(&self, page: &mut Page) -> Markup { - <Self as ThemePage>::render_body(self, page, self.page_regions()) - } - - /// Acciones específicas del tema después de renderizar el `<body>` de la página. - /// - /// Útil para *tracing*, métricas o ajustes finales del estado de la página. - #[allow(unused_variables)] - fn after_render_page_body(&self, page: &mut Page) {} - - /// Renderiza el contenido del `<head>` de la página. - /// - /// Si se sobrescribe este método, se puede volver al renderizado base con: - /// `<Self as ThemePage>::render_head(self, page)`. - #[inline] - fn render_page_head(&self, page: &mut Page) -> Markup { - if page.param_or("include_basic_assets", false) { - let pkg_version = env!("CARGO_PKG_VERSION"); - - page.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/normalize.css") - .with_version("8.0.1") - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/root.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/basic.css") - .with_version(pkg_version) - .with_weight(-99), - )); - } - <Self as ThemePage>::render_head(self, page) - } /// Contenido predeterminado para la página de error "*403 - Forbidden*". /// - /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. + /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la + /// página de error, manteniendo o no el mensaje de los textos localizados. fn error403(&self, page: &mut Page) -> Markup { html! { div { h1 { (L10n::l("error403_notice").using(page)) } } } } /// Contenido predeterminado para la página de error "*404 - Not Found*". /// - /// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema. + /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la + /// página de error, manteniendo o no el mensaje de los textos localizados. fn error404(&self, page: &mut Page) -> Markup { html! { div { h1 { (L10n::l("error404_notice").using(page)) } } } } } - -/// Se implementa automáticamente `ThemePage` para cualquier tema. -impl<T: Theme> ThemePage for T {} diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 17e15436..259417eb 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -1,6 +1,6 @@ +use crate::base::component::Region; use crate::core::component::{Child, ChildOp, Children}; use crate::core::theme::ThemeRef; -use crate::locale::L10n; use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; @@ -16,97 +16,36 @@ static THEME_REGIONS: LazyLock<RwLock<HashMap<UniqueId, ChildrenInRegions>>> = static COMMON_REGIONS: LazyLock<RwLock<ChildrenInRegions>> = LazyLock::new(|| RwLock::new(ChildrenInRegions::default())); -/// Nombre de la región de contenido por defecto (`"content"`). -pub const REGION_CONTENT: &str = "content"; - -/// Define la interfaz mínima que describe una **región de renderizado** dentro de una página. -/// -/// Una *región* representa una zona del documento HTML (por ejemplo: `"header"`, `"content"` o -/// `"sidebar-left"`), en la que se pueden incluir y renderizar componentes dinámicamente. -/// -/// Este `trait` abstrae los metadatos básicos de cada región, esencialmente: -/// -/// - su **clave interna** (`key()`), que la identifica de forma única dentro de la página, y -/// - su **etiqueta localizada** (`label()`), que se usa como texto accesible (por ejemplo en -/// `aria-label` o en descripciones semánticas del contenedor). -/// -/// Las implementaciones típicas son *enumeraciones estáticas* declaradas por cada tema (ver como -/// ejemplo [`DefaultRegions`](crate::core::theme::DefaultRegions)), de modo que las claves y -/// etiquetas permanecen inmutables y fácilmente referenciables. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// pub enum MyThemeRegions { -/// Header, -/// Content, -/// Footer, -/// } -/// -/// impl Region for MyThemeRegions { -/// fn key(&self) -> &str { -/// match self { -/// Self::Header => "header", -/// Self::Content => "content", -/// Self::Footer => "footer", -/// } -/// } -/// -/// fn label(&self) -> L10n { -/// L10n::l(join!("region__", self.key())) -/// } -/// } -/// ``` -pub trait Region: Send + Sync { - /// Devuelve la **clave interna** que identifica de forma única una región. - /// - /// La clave se utiliza para asociar los componentes de la región con su contenedor HTML - /// correspondiente. Por convención, se emplean nombres en minúsculas y con guiones (`"header"`, - /// `"main"`, `"sidebar-right"`, etc.), y la región `"content"` es **obligatoria** en todos los - /// temas. - fn key(&self) -> &str; - - /// Devuelve la **etiqueta localizada** (`L10n`) asociada a la región. - /// - /// Esta etiqueta se evalúa en el idioma activo de la página y se utiliza principalmente para - /// accesibilidad, como el valor de `aria-label` en el contenedor generado por - /// [`ThemePage::render_region()`](crate::core::theme::ThemePage::render_region). - fn label(&self) -> L10n; -} - -/// Referencia estática a una región. -pub type RegionRef = &'static dyn Region; - // Contenedor interno de componentes agrupados por región. #[derive(AutoDefault)] -pub struct ChildrenInRegions(HashMap<&'static str, Children>); +pub(crate) struct ChildrenInRegions(HashMap<String, Children>); impl ChildrenInRegions { - pub fn with(region_key: &'static str, child: Child) -> Self { - ChildrenInRegions::default().with_child_in(region_key, ChildOp::Add(child)) + pub fn with(region_name: impl AsRef<str>, child: Child) -> Self { + Self::default().with_child_in(region_name, ChildOp::Add(child)) } #[builder_fn] - pub fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { - if let Some(region) = self.0.get_mut(region_key) { + pub fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { + let name = region_name.as_ref(); + if let Some(region) = self.0.get_mut(name) { region.alter_child(op); } else { - self.0.insert(region_key, Children::new().with_child(op)); + self.0 + .insert(name.to_owned(), Children::new().with_child(op)); } self } - pub fn merge_all_components(&self, theme_ref: ThemeRef, region_key: &'static str) -> Children { + pub fn children_for(&self, theme_ref: ThemeRef, region_name: impl AsRef<str>) -> Children { + let name = region_name.as_ref(); let common = COMMON_REGIONS.read(); - if let Some(r) = THEME_REGIONS.read().get(&theme_ref.type_id()) { - Children::merge(&[ - common.0.get(region_key), - self.0.get(region_key), - r.0.get(region_key), - ]) + let themed = THEME_REGIONS.read(); + + if let Some(r) = themed.get(&theme_ref.type_id()) { + Children::merge(&[common.0.get(name), self.0.get(name), r.0.get(name)]) } else { - Children::merge(&[common.0.get(region_key), self.0.get(region_key)]) + Children::merge(&[common.0.get(name), self.0.get(name)]) } } } @@ -120,10 +59,10 @@ impl ChildrenInRegions { /// estas regiones, como las páginas de contenido ([`Page`](crate::response::page::Page)). pub enum InRegion { /// Región de contenido por defecto. - Content, - /// Región identificada por la clave proporcionado. - Key(&'static str), - /// Región identificada por una clave para un tema concreto. + Default, + /// Región identificada por el nombre proporcionado. + Named(&'static str), + /// Región identificada por su nombre para un tema concreto. OfTheme(&'static str, ThemeRef), } @@ -135,39 +74,38 @@ impl InRegion { /// ```rust /// # use pagetop::prelude::*; /// // Banner global, en la región por defecto de cualquier página. - /// InRegion::Content.add(Child::with(Html::with(|_| + /// InRegion::Default.add(Child::with(Html::with(|_| /// html! { ("🎉 ¡Bienvenido!") } /// ))); /// /// // Texto en la región "sidebar". - /// InRegion::Key("sidebar").add(Child::with(Html::with(|_| + /// InRegion::Named("sidebar").add(Child::with(Html::with(|_| /// html! { ("Publicidad") } /// ))); /// ``` pub fn add(&self, child: Child) -> &Self { match self { - InRegion::Content => { - COMMON_REGIONS - .write() - .alter_child_in(REGION_CONTENT, ChildOp::Add(child)); - } - InRegion::Key(region_key) => { - COMMON_REGIONS - .write() - .alter_child_in(region_key, ChildOp::Add(child)); - } - InRegion::OfTheme(region_key, theme_ref) => { + InRegion::Default => Self::add_to_common(Region::DEFAULT, child), + InRegion::Named(region_name) => Self::add_to_common(region_name, child), + InRegion::OfTheme(region_name, theme_ref) => { let mut regions = THEME_REGIONS.write(); if let Some(r) = regions.get_mut(&theme_ref.type_id()) { - r.alter_child_in(region_key, ChildOp::Add(child)); + r.alter_child_in(region_name, ChildOp::Add(child)); } else { regions.insert( theme_ref.type_id(), - ChildrenInRegions::with(region_key, child), + ChildrenInRegions::with(region_name, child), ); } } } self } + + #[inline] + fn add_to_common(region_name: &str, child: Child) { + COMMON_REGIONS + .write() + .alter_child_in(region_name, ChildOp::Add(child)); + } } diff --git a/src/response/page.rs b/src/response/page.rs index 036c999c..7d7789d4 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -4,8 +4,10 @@ pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::core::component::{Child, ChildOp, Component, Context, ContextOp, Contextual}; -use crate::core::theme::{ThemeRef, REGION_CONTENT}; +use crate::base::component::Region; +use crate::core::component::{Child, ChildOp, Component, ComponentRender}; +use crate::core::component::{Context, ContextOp, Contextual}; +use crate::core::theme::ThemeRef; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{AttrClasses, ClassesOp}; @@ -109,14 +111,14 @@ impl Page { /// Añade un componente hijo a la región de contenido por defecto. pub fn add_child(mut self, component: impl Component) -> Self { self.context - .alter_child_in(REGION_CONTENT, ChildOp::Add(Child::with(component))); + .alter_child_in(Region::DEFAULT, ChildOp::Add(Child::with(component))); self } - /// Añade un componente hijo en una región (`region_key`) de la página. - pub fn add_child_in(mut self, region_key: &'static str, component: impl Component) -> Self { + /// Añade un componente hijo en la región `region_name` de la página. + pub fn add_child_in(mut self, region_name: &'static str, component: impl Component) -> Self { self.context - .alter_child_in(region_key, ChildOp::Add(Child::with(component))); + .alter_child_in(region_name, ChildOp::Add(Child::with(component))); self } @@ -191,7 +193,11 @@ impl Page { action::page::BeforeRenderBody::dispatch(self); // Renderiza el <body>. - let body = self.context.theme().render_page_body(self); + let body = html! { + (Region::named(Region::PAGETOP).render(&mut self.context)) + (self.context.theme().render_page_body(self)) + (Region::named(Region::PAGEBOTTOM).render(&mut self.context)) + }; // Acciones específicas del tema después de renderizar el <body>. self.context.theme().after_render_page_body(self); @@ -216,9 +222,7 @@ impl Page { (head) } body id=[self.body_id().get()] class=[self.body_classes().get()] { - (self.context.render_components_of("page-top")) (body) - (self.context.render_components_of("page-bottom")) } } }) @@ -247,14 +251,14 @@ impl Contextual for Page { } #[builder_fn] - fn with_theme(mut self, theme_name: &'static str) -> Self { - self.context.alter_theme(theme_name); + fn with_theme(mut self, theme: ThemeRef) -> Self { + self.context.alter_theme(theme); self } #[builder_fn] - fn with_layout(mut self, layout_name: &'static str) -> Self { - self.context.alter_layout(layout_name); + fn with_template(mut self, template_name: &'static str) -> Self { + self.context.alter_template(template_name); self } @@ -271,8 +275,8 @@ impl Contextual for Page { } #[builder_fn] - fn with_child_in(mut self, region_key: &'static str, op: ChildOp) -> Self { - self.context.alter_child_in(region_key, op); + fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { + self.context.alter_child_in(region_name, op); self } @@ -286,8 +290,8 @@ impl Contextual for Page { self.context.theme() } - fn layout(&self) -> &str { - self.context.layout() + fn template(&self) -> &str { + self.context.template() } fn param<T: 'static>(&self, key: &'static str) -> Option<&T> { diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 9945a948..7a590e6e 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,4 +1,4 @@ -use crate::base::component::Html; +use crate::base::component::{Html, Template}; use crate::core::component::Contextual; use crate::locale::L10n; use crate::response::ResponseError; @@ -33,7 +33,7 @@ impl Display for ErrorPage { let error403 = error_page.theme().error403(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) - .with_layout("error") + .with_template(Template::ERROR) .add_child(Html::with(move |_| error403.clone())) .render() { @@ -48,7 +48,7 @@ impl Display for ErrorPage { let error404 = error_page.theme().error404(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) - .with_layout("error") + .with_template(Template::ERROR) .add_child(Html::with(move |_| error404.clone())) .render() { From 9657672ffd5eda3ac7cfa5a423ad6c6970392728 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 23 Nov 2025 14:11:13 +0100 Subject: [PATCH 191/224] =?UTF-8?q?=F0=9F=93=9D=20Mejora=20documentaci?= =?UTF-8?q?=C3=B3n=20generada=20por=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 87 ++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 194cd378..e1ea55cb 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -138,7 +138,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// # } /// ``` /// -/// la macro rescribirá el método `with_` y generará un nuevo método `alter_`: +/// la macro reescribirá el método `with_` y generará un nuevo método `alter_`: /// /// ```rust /// # struct Example {value: Option<String>}; @@ -157,7 +157,11 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// ``` /// /// De esta forma, cada método *builder* `with_...()` generará automáticamente su correspondiente -/// método `alter_...()` para dejar modificar instancias existentes. +/// método `alter_...()` para modificar instancias existentes. +/// +/// La documentación del método `with_...()` incluirá también la firma resumida del método +/// `alter_...()` y un alias de búsqueda con su nombre, de tal manera que buscando `alter_...` en la +/// documentación se mostrará la entrada del método `with_...()`. #[proc_macro_attribute] pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { use syn::{parse2, FnArg, Ident, ImplItemFn, Pat, ReturnType, TraitItemFn, Type}; @@ -282,11 +286,11 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { } } - // Genera el nombre del método alter_...(). + // Genera el nombre del método `alter_...()`. let stem = with_name_str.strip_prefix("with_").expect("validated"); let alter_ident = Ident::new(&format!("alter_{stem}"), with_name.span()); - // Extrae genéricos y cláusulas where. + // Extrae genéricos y cláusulas `where`. let generics = &sig.generics; let where_clause = &sig.generics.where_clause; @@ -319,28 +323,70 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { v }; - // Filtra los atributos descartando `#[doc]` y `#[inline]` para el método `alter_...()`. - let non_doc_or_inline_attrs: Vec<_> = attrs - .iter() - .filter(|a| { - let p = a.path(); - !p.is_ident("doc") && !p.is_ident("inline") - }) - .cloned() - .collect(); + // Separa atributos de documentación y resto. + let mut doc_attrs = Vec::new(); + let mut other_attrs = Vec::new(); + let mut non_doc_or_inline_attrs = Vec::new(); - // Documentación del método alter_...(). - let doc = format!("Equivale a [`Self::{with_name_str}()`], pero fuera del patrón *builder*."); + for a in attrs.iter() { + let p = a.path(); + if p.is_ident("doc") { + doc_attrs.push(a.clone()); + } else { + other_attrs.push(a.clone()); + if !p.is_ident("inline") { + non_doc_or_inline_attrs.push(a.clone()); + } + } + } + + // Firma resumida de la función `alter_...()` para mostrarla en la doc de `with_...()`. + let alter_sig_tokens = if args.is_empty() { + // Sin argumentos sólo se muestra `&mut self` (puede que no tenga mucho sentido). + quote! { #vis_pub fn #alter_ident #generics (&mut self) -> &mut Self #where_clause } + } else { + // Con argumentos se muestra `&mut self, ...`. + quote! { #vis_pub fn #alter_ident #generics (&mut self, ...) -> &mut Self #where_clause } + }; + + // Normaliza espacios raros tipo `& mut`. + let alter_sig_str = alter_sig_tokens.to_string().replace("& mut", "&mut"); + + // Nombre de la función `alter_...()` como alias de búsqueda. + let alter_name_str = alter_ident.to_string(); + + // Texto introductorio para la documentación adicional de `with_...()`. + let with_alter_title = format!( + "# Añade método `{}()` generado por [`#[builder_fn]`](pagetop_macros::builder_fn)", + alter_name_str + ); + let with_alter_doc = concat!( + "Modifica la instancia actual (`&mut self`) con los mismos argumentos ", + "en lugar de consumirla." + ); + + // Atributos completos que se aplican siempre a `with_...()`. + let with_prefix = quote! { + #(#other_attrs)* + #(#doc_attrs)* + #[doc(alias = #alter_name_str)] + #[doc = ""] + #[doc = #with_alter_title] + #[doc = #with_alter_doc] + #[doc = "```text"] + #[doc = #alter_sig_str] + #[doc = "```"] + }; // Genera el código final. let expanded = match body_opt { None => { quote! { - #(#attrs)* + #with_prefix fn #with_name #generics (self, #(#args),*) -> Self #where_clause; #(#non_doc_or_inline_attrs)* - #[doc = #doc] + #[doc(hidden)] fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause; } } @@ -351,8 +397,10 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { } else { quote! { #[inline] } }; + let with_fn = if is_trait { quote! { + #with_prefix #force_inline #vis_pub fn #with_name #generics (self, #(#args),*) -> Self #where_clause { let mut s = self; @@ -362,6 +410,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { } } else { quote! { + #with_prefix #force_inline #vis_pub fn #with_name #generics (mut self, #(#args),*) -> Self #where_clause { self.#alter_ident(#(#call_idents),*); @@ -369,12 +418,12 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { } } }; + quote! { - #(#attrs)* #with_fn #(#non_doc_or_inline_attrs)* - #[doc = #doc] + #[doc(hidden)] #vis_pub fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause { #body } From a2fe2114cdf04e5c5f95bb148528429515fcb7d9 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 23 Nov 2025 14:35:38 +0100 Subject: [PATCH 192/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(theme):=20Refacto?= =?UTF-8?q?riza=20renderizado=20de=20temas=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 8 +++- extensions/pagetop-bootsier/src/lib.rs | 12 +++++- src/base/component/intro.rs | 18 ++++----- src/base/theme/basic.rs | 6 ++- src/core/component/children.rs | 13 +++++++ src/core/theme/definition.rs | 54 +++++++++++++------------- static/css/basic.css | 9 +++-- static/css/intro.css | 6 +-- 8 files changed, 79 insertions(+), 47 deletions(-) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 4ae4121e..5e915578 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -104,12 +104,16 @@ impl Extension for Aliner { } impl Theme for Aliner { - fn after_render_page_body(&self, page: &mut Page) { + fn before_render_page_body(&self, page: &mut Page) { page.alter_param("include_basic_assets", true) .alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/aliner/css/styles.css") .with_version(env!("CARGO_PKG_VERSION")) .with_weight(-90), - )); + )) + .alter_child_in( + Region::FOOTER, + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ); } } diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 0bf94f47..fb9b7206 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -117,7 +117,7 @@ impl Extension for Bootsier { } impl Theme for Bootsier { - fn after_render_page_body(&self, page: &mut Page) { + fn before_render_page_body(&self, page: &mut Page) { page.alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/bootsier/bs/bootstrap.min.css") .with_version(BOOTSTRAP_VERSION) @@ -129,4 +129,14 @@ impl Theme for Bootsier { .with_weight(-90), )); } + + fn render_page_body(&self, page: &mut Page) -> Markup { + theme::Container::new() + .with_id("container-wrapper") + .with_width(theme::container::Width::FluidMax( + config::SETTINGS.bootsier.max_width, + )) + .add_child(Template::named(page.template())) + .render(page.context()) + } } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 052f9c60..5de7349a 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -17,17 +17,17 @@ pub enum IntroOpening { Custom, } -/// Componente para presentar PageTop (como [`Welcome`](crate::base::extension::Welcome)), o mostrar -/// introducciones. +/// Componente para divulgar PageTop (como hace [`Welcome`](crate::base::extension::Welcome)), o +/// mostrar presentaciones. /// -/// Usa la imagen de PageTop para presentar contenidos con: +/// Usa la imagen de PageTop para mostrar: /// -/// - Una **imagen decorativa** (el *monster* de PageTop) antecediendo al contenido. -/// - Una vista destacada con **título + eslogan**. +/// - Una **figura decorativa** (que incluye el *monster* de PageTop) antecediendo al contenido. +/// - Una vista destacada del **título** de la página con un **eslogan** de presentación. /// - Un **botón opcional** de llamada a la acción con texto y enlace configurables. -/// - El **área de textos** con *badges* predefinidos (en modo [`IntroOpening::PageTop`]) y bloques -/// ([`Block`](crate::base::component::Block)) para crear párrafos vistosos de texto. Aunque -/// admite todo tipo de componentes. +/// - Un **área para la presentación de contenidos**, con *badges* informativos de PageTop (si se +/// opta por [`IntroOpening::PageTop`]) y bloques ([`Block`](crate::base::component::Block)) de +/// contenido libre para crear párrafos vistosos de texto. Aunque admite todo tipo de componentes. /// /// ### Ejemplos /// @@ -51,7 +51,7 @@ pub enum IntroOpening { /// ))); /// ``` /// -/// **Sin botón + modo *Custom* (sin *badges* predefinidos)** +/// **Sin botón y en modo *Custom* (sin *badges* predefinidos)** /// /// ```rust /// # use pagetop::prelude::*; diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index eb2274f6..63ebe7a3 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -12,6 +12,10 @@ impl Extension for Basic { impl Theme for Basic { fn before_render_page_body(&self, page: &mut Page) { - page.alter_param("include_basic_assets", true); + page.alter_param("include_basic_assets", true) + .alter_child_in( + Region::FOOTER, + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ); } } diff --git a/src/core/component/children.rs b/src/core/component/children.rs index b3670433..15a6de22 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -172,6 +172,7 @@ impl<C: Component> Typed<C> { /// Operaciones para componentes hijo [`Child`] en una lista [`Children`]. pub enum ChildOp { Add(Child), + AddIfEmpty(Child), AddMany(Vec<Child>), InsertAfterId(&'static str, Child), InsertBeforeId(&'static str, Child), @@ -185,6 +186,7 @@ pub enum ChildOp { /// Operaciones con un componente hijo tipado [`Typed<C>`] en una lista [`Children`]. pub enum TypedOp<C: Component> { Add(Typed<C>), + AddIfEmpty(Typed<C>), AddMany(Vec<Typed<C>>), InsertAfterId(&'static str, Typed<C>), InsertBeforeId(&'static str, Typed<C>), @@ -230,6 +232,7 @@ impl Children { pub fn with_child(mut self, op: ChildOp) -> Self { match op { ChildOp::Add(any) => self.add(any), + ChildOp::AddIfEmpty(any) => self.add_if_empty(any), ChildOp::AddMany(many) => self.add_many(many), ChildOp::InsertAfterId(id, any) => self.insert_after_id(id, any), ChildOp::InsertBeforeId(id, any) => self.insert_before_id(id, any), @@ -246,6 +249,7 @@ impl Children { pub fn with_typed<C: Component>(mut self, op: TypedOp<C>) -> Self { match op { TypedOp::Add(typed) => self.add(typed.into()), + TypedOp::AddIfEmpty(typed) => self.add_if_empty(typed.into()), TypedOp::AddMany(many) => self.add_many(many.into_iter().map(Typed::<C>::into)), TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.into()), TypedOp::InsertBeforeId(id, typed) => self.insert_before_id(id, typed.into()), @@ -266,6 +270,15 @@ impl Children { self } + /// Añade un componente hijo en la lista sólo si está vacía. + #[inline] + pub fn add_if_empty(&mut self, child: Child) -> &mut Self { + if self.0.is_empty() { + self.0.push(child); + } + self + } + // **< Children GETTERS >*********************************************************************** /// Devuelve el número de componentes hijo de la lista. diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index de11d1ba..dda58b18 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -83,12 +83,33 @@ pub trait Theme: Extension + Send + Sync { /// - Realizar *tracing* o recopilar métricas. /// - Aplicar ajustes finales al estado de la página antes de producir el `<head>` o la /// respuesta final. - /// - /// La implementación por defecto añade una serie de hojas de estilo básicas (`normalize.css`, - /// `root.css`, `basic.css`) cuando el parámetro `include_basic_assets` de la página está - /// activado. #[allow(unused_variables)] - fn after_render_page_body(&self, page: &mut Page) { + fn after_render_page_body(&self, page: &mut Page) {} + + /// Renderiza el contenido del `<head>` de la página. + /// + /// Aunque en una página el `<head>` se encuentra antes del `<body>`, internamente se renderiza + /// después para contar con los ajustes que hayan ido acumulando los componentes. Por ejemplo, + /// permitiría añadir un archivo de iconos sólo si se ha incluido un icono en la página. + /// + /// Por defecto incluye: + /// + /// - La codificación (`charset="utf-8"`). + /// - El título, usando el título de la página si existe y, en caso contrario, sólo el nombre de + /// la aplicación. + /// - La descripción (`<meta name="description">`), si está definida. + /// - La etiqueta `viewport` básica para diseño adaptable. + /// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la + /// página. + /// - Los *assets* registrados en el contexto de la página. Si el parámetro + /// `include_basic_assets` está activado, añade de serie las siguientes hojas de estilo + /// básicas: `normalize.css`, `root.css`, `basic.css`, útiles para temas sencillos o de uso + /// general. + /// + /// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo, + /// *favicons* personalizados, manifest, etiquetas de analítica, etc.). + #[inline] + fn render_page_head(&self, page: &mut Page) -> Markup { if page.param_or("include_basic_assets", false) { let pkg_version = env!("CARGO_PKG_VERSION"); @@ -108,29 +129,6 @@ pub trait Theme: Extension + Send + Sync { .with_weight(-99), )); } - } - - /// Renderiza el contenido del `<head>` de la página. - /// - /// Aunque en una página el `<head>` se encuentra antes del `<body>`, internamente se renderiza - /// después para contar con los ajustes que hayan ido acumulando los componentes. Por ejemplo, - /// permitiría añadir un archivo de iconos sólo si se ha incluido un icono en la página. - /// - /// Por defecto incluye: - /// - /// - La codificación (`charset="utf-8"`). - /// - El título, usando el título de la página si existe y, en caso contrario, sólo el nombre de - /// la aplicación. - /// - La descripción (`<meta name="description">`), si está definida. - /// - La etiqueta `viewport` básica para diseño adaptable. - /// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la - /// página. - /// - Todos los *assets* registrados en el contexto de la página. - /// - /// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo, - /// *favicons* personalizados, manifest, etiquetas de analítica, etc.). - #[inline] - fn render_page_head(&self, page: &mut Page) -> Markup { let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { meta charset="utf-8"; diff --git a/static/css/basic.css b/static/css/basic.css index 058e1736..f87e6fdc 100644 --- a/static/css/basic.css +++ b/static/css/basic.css @@ -14,8 +14,11 @@ body { -webkit-tap-highlight-color: transparent; } -/* Page layout */ +/* + * Region Footer + */ -.region--footer { - padding-bottom: 2rem; +.region-footer { + padding: .75rem 0 3rem; + text-align: center; } diff --git a/static/css/intro.css b/static/css/intro.css index dbc72252..9c47c5c4 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -50,7 +50,7 @@ } /* - * Header + * Intro Header */ .intro-header { @@ -125,7 +125,7 @@ } /* - * Content + * Intro Content */ .intro-content { @@ -445,7 +445,7 @@ } /* - * Footer + * Intro Footer */ .intro-footer { From 4ac7caddd4cfd6dfee6c6cf22bfaa5951e3d8532 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 23 Nov 2025 14:37:00 +0100 Subject: [PATCH 193/224] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Elimina=20m?= =?UTF-8?q?=C3=A9todos=20y=20definiciones=20obsoletas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/html.rs | 58 ++------------------------------------- src/html/attr_l10n.rs | 7 ----- src/locale.rs | 6 ---- src/prelude.rs | 12 ++------ src/response/page.rs | 29 -------------------- src/service.rs | 64 ------------------------------------------- src/util.rs | 10 ------- 7 files changed, 4 insertions(+), 182 deletions(-) diff --git a/src/html.rs b/src/html.rs index 82fdcd73..d94aeea8 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,5 +1,7 @@ //! HTML en código. +use crate::AutoDefault; + mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; @@ -14,78 +16,22 @@ pub use assets::{Asset, Assets}; mod logo; pub use logo::PageTopSvg; -// **< HTML DOCUMENT CONTEXT >********************************************************************** - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Context`] en su lugar. -#[deprecated(since = "0.5.0", note = "Moved to `pagetop::core::component::Context`")] -pub type Context = crate::core::component::Context; - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextOp`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::ContextOp`" -)] -pub type ContextOp = crate::core::component::ContextOp; - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Contextual`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::Contextual`" -)] -pub trait Contextual: crate::core::component::Contextual {} - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextError`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::ContextError`" -)] -pub type ContextError = crate::core::component::ContextError; - -/// **Obsoleto desde la versión 0.5.0**: usar [`ContextOp`] en su lugar. -#[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] -pub type AssetsOp = crate::core::component::ContextOp; - // **< HTML ATTRIBUTES >**************************************************************************** mod attr_id; pub use attr_id::AttrId; -/// **Obsoleto desde la versión 0.4.0**: usar [`AttrId`] en su lugar. -#[deprecated(since = "0.4.0", note = "Use `AttrId` instead")] -pub type OptionId = AttrId; mod attr_name; pub use attr_name::AttrName; -/// **Obsoleto desde la versión 0.4.0**: usar [`AttrName`] en su lugar. -#[deprecated(since = "0.4.0", note = "Use `AttrName` instead")] -pub type OptionName = AttrName; mod attr_value; pub use attr_value::AttrValue; -/// **Obsoleto desde la versión 0.4.0**: usar [`AttrValue`] en su lugar. -#[deprecated(since = "0.4.0", note = "Use `AttrValue` instead")] -pub type OptionString = AttrValue; mod attr_l10n; pub use attr_l10n::AttrL10n; -/// **Obsoleto desde la versión 0.4.0**: usar [`AttrL10n`] en su lugar. -#[deprecated(since = "0.4.0", note = "Use `AttrL10n` instead")] -pub type OptionTranslated = AttrL10n; mod attr_classes; pub use attr_classes::{AttrClasses, ClassesOp}; -/// **Obsoleto desde la versión 0.4.0**: usar [`AttrClasses`] en su lugar. -#[deprecated(since = "0.4.0", note = "Use `AttrClasses` instead")] -pub type OptionClasses = AttrClasses; - -use crate::{core, AutoDefault}; - -/// **Obsoleto desde la versión 0.4.0**: usar [`Typed`](crate::core::component::Typed) en su lugar. -#[deprecated( - since = "0.4.0", - note = "Use `pagetop::core::component::Typed` instead" -)] -#[allow(type_alias_bounds)] -pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>; mod unit; pub use unit::UnitValue; diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 86d1c4a3..d25869c5 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -1,4 +1,3 @@ -use crate::html::Markup; use crate::locale::{L10n, LangId}; use crate::{builder_fn, AutoDefault}; @@ -58,10 +57,4 @@ impl AttrL10n { pub fn value(&self, language: &impl LangId) -> String { self.0.lookup(language).unwrap_or_default() } - - /// **Obsoleto desde la versión 0.4.0**: no recomendado para atributos HTML. - #[deprecated(since = "0.4.0", note = "For attributes use `lookup()` or `value()`")] - pub fn to_markup(&self, language: &impl LangId) -> Markup { - self.0.using(language) - } } diff --git a/src/locale.rs b/src/locale.rs index 5c000f7c..9ee87d7f 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -463,12 +463,6 @@ impl L10n { pub fn using(&self, language: &impl LangId) -> Markup { PreEscaped(self.lookup(language).unwrap_or_default()) } - - /// **Obsoleto desde la versión 0.4.0**: usar [`using()`](Self::using) en su lugar. - #[deprecated(since = "0.4.0", note = "Use `using()` instead")] - pub fn to_markup(&self, language: &impl LangId) -> Markup { - self.using(language) - } } impl fmt::Debug for L10n { diff --git a/src/prelude.rs b/src/prelude.rs index 47fbbf64..15121036 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,8 +15,7 @@ pub use crate::include_config; // crate::locale pub use crate::include_locales; // crate::service -#[allow(deprecated)] -pub use crate::{include_files, include_files_service, static_files_service}; +pub use crate::static_files_service; // crate::core::action pub use crate::actions_boxed; @@ -28,14 +27,7 @@ pub use crate::global; pub use crate::trace; -// No se usa `pub use crate::html::*;` para evitar duplicar alias marcados como obsoletos -// (*deprecated*) porque han sido trasladados a `crate::core::component`. Cuando se retiren estos -// alias obsoletos se volverá a declarar como `pub use crate::html::*;`. -pub use crate::html::{ - display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, - ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup, - StyleSheet, TargetMedia, UnitValue, DOCTYPE, -}; +pub use crate::html::*; pub use crate::locale::*; diff --git a/src/response/page.rs b/src/response/page.rs index 7d7789d4..b5516b8a 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -95,19 +95,6 @@ impl Page { self } - /// **Obsoleto desde la versión 0.4.0**: usar [`add_child()`](Self::add_child) en su lugar. - #[deprecated(since = "0.4.0", note = "Use `add_child()` instead")] - pub fn with_component(self, component: impl Component) -> Self { - self.add_child(component) - } - - /// **Obsoleto desde la versión 0.4.0**: usar [`add_child_in()`](Self::add_child_in) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `add_child_in()` instead")] - pub fn with_component_in(self, region_key: &'static str, component: impl Component) -> Self { - self.add_child_in(region_key, component) - } - /// Añade un componente hijo a la región de contenido por defecto. pub fn add_child(mut self, component: impl Component) -> Self { self.context @@ -122,22 +109,6 @@ impl Page { self } - /// **Obsoleto desde la versión 0.4.0**: usar [`with_child_in()`](Self::with_child_in) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `with_child_in()` instead")] - pub fn with_child_in_region(mut self, region_key: &'static str, op: ChildOp) -> Self { - self.alter_child_in(region_key, op); - self - } - - /// **Obsoleto desde la versión 0.4.0**: usar [`alter_child_in()`](Self::alter_child_in) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `alter_child_in()` instead")] - pub fn alter_child_in_region(&mut self, region_key: &'static str, op: ChildOp) -> &mut Self { - self.alter_child_in(region_key, op); - self - } - // **< Page GETTERS >*************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. diff --git a/src/service.rs b/src/service.rs index 1b2f766c..cb69d76a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,70 +15,6 @@ pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; -/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) -/// en su lugar. -#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] -#[macro_export] -macro_rules! include_files { - // Forma 1: incluye un conjunto de recursos por nombre. - ( $bundle:ident ) => { - $crate::util::paste! { - mod [<static_files_ $bundle>] { - include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); - } - } - }; - // Forma 2: asigna a una variable estática $STATIC un conjunto de recursos. - ( $STATIC:ident => $bundle:ident ) => { - $crate::util::paste! { - mod [<static_files_ $bundle>] { - include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); - } - pub static $STATIC: std::sync::LazyLock<$crate::StaticResources> = - std::sync::LazyLock::new( - $crate::StaticResources::new([<static_files_ $bundle>]::$bundle) - ); - } - }; -} - -/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) -/// en su lugar. -#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] -#[macro_export] -macro_rules! include_files_service { - ( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{ - $crate::util::paste! { - let span = $crate::trace::debug_span!("Configuring static files ", path = $route); - let _ = span.in_scope(|| { - // Determina si se sirven recursos embebidos (`true`) o desde disco (`false`). - #[allow(unused_mut)] - let mut serve_embedded:bool = true; - $( - // Si `$root` y `$relative` no están vacíos, se comprueba la ruta absoluta. - if !$root.is_empty() && !$relative.is_empty() { - if let Ok(absolute) = $crate::util::absolute_dir($root, $relative) { - // Servimos directamente desde el sistema de ficheros. - $scfg.service($crate::service::ActixFiles::new( - $route, - absolute, - ).show_files_listing()); - serve_embedded = false - } - } - )? - // Si no se localiza el directorio, se exponen entonces los recursos embebidos. - if serve_embedded { - $scfg.service($crate::service::ResourceFiles::new( - $route, - [<static_files_ $bundle>]::$bundle(), - )); - } - }); - } - }}; -} - /// Configura un servicio web para publicar archivos estáticos. /// /// La macro ofrece tres modos para configurar el servicio: diff --git a/src/util.rs b/src/util.rs index 30b994f2..bfb50ec0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -167,13 +167,3 @@ pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { }) } } - -/// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar. -#[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")] -pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf> -where - P: AsRef<Path>, - Q: AsRef<Path>, -{ - resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref())) -} From 0849d23e3fc4784e126f325b87e4b2c56ce369ea Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 29 Nov 2025 13:55:35 +0100 Subject: [PATCH 194/224] =?UTF-8?q?=F0=9F=9A=A7=20A=C3=B1ade=20constante?= =?UTF-8?q?=20`PAGETOP=5FVERSION`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 29 ++++++++++++++++-------- src/app.rs | 4 ++-- src/base/component/intro.rs | 2 +- src/base/theme/basic.rs | 19 ++++++++++++---- src/lib.rs | 34 ++++++++++++++++++++++++++++ src/prelude.rs | 2 ++ 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 5e915578..80e6ca15 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -105,15 +105,24 @@ impl Extension for Aliner { impl Theme for Aliner { fn before_render_page_body(&self, page: &mut Page) { - page.alter_param("include_basic_assets", true) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/aliner/css/styles.css") - .with_version(env!("CARGO_PKG_VERSION")) - .with_weight(-90), - )) - .alter_child_in( - Region::FOOTER, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), - ); + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/normalize.css") + .with_version("8.0.1") + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(PAGETOP_VERSION) + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/aliner/css/styles.css") + .with_version(env!("CARGO_PKG_VERSION")) + .with_weight(-99), + )) + .alter_child_in( + Region::FOOTER, + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ); } } diff --git a/src/app.rs b/src/app.rs index c8ffba11..6ecff369 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use crate::core::{extension, extension::ExtensionRef}; use crate::html::Markup; use crate::response::page::{ErrorPage, ResultPage}; use crate::service::HttpRequest; -use crate::{global, locale, service, trace}; +use crate::{global, locale, service, trace, PAGETOP_VERSION}; use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; use actix_session::storage::CookieSessionStore; @@ -108,7 +108,7 @@ impl Application { println!( "{} {}\n", "Powered by PageTop".yellow(), - env!("CARGO_PKG_VERSION").yellow() + PAGETOP_VERSION.yellow() ); } } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 5de7349a..ea01ccc8 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -105,7 +105,7 @@ impl Component for Intro { fn setup_before_prepare(&mut self, cx: &mut Context) { cx.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/intro.css").with_version(env!("CARGO_PKG_VERSION")), + StyleSheet::from("/css/intro.css").with_version(PAGETOP_VERSION), )); } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 63ebe7a3..83dc4a8d 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -12,10 +12,19 @@ impl Extension for Basic { impl Theme for Basic { fn before_render_page_body(&self, page: &mut Page) { - page.alter_param("include_basic_assets", true) - .alter_child_in( - Region::FOOTER, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), - ); + page.alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/normalize.css") + .with_version("8.0.1") + .with_weight(-99), + )) + .alter_assets(ContextOp::AddStyleSheet( + StyleSheet::from("/css/basic.css") + .with_version(PAGETOP_VERSION) + .with_weight(-99), + )) + .alter_child_in( + Region::FOOTER, + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ); } } diff --git a/src/lib.rs b/src/lib.rs index a2683495..caa74536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,40 @@ use std::ops::Deref; // **< RE-EXPORTED >******************************************************************************** +/// Versión del *crate* `pagetop`, obtenida en tiempo de compilación (`CARGO_PKG_VERSION`). +/// +/// Útil para versionar recursos estáticos de PageTop desde otros *crates*. Por ejemplo: +/// +/// ```rust +/// use pagetop::prelude::*; +/// +/// pub struct MyTheme; +/// +/// impl Extension for MyTheme { +/// fn theme(&self) -> Option<ThemeRef> { +/// Some(&Self) +/// } +/// } +/// +/// impl Theme for MyTheme { +/// fn before_render_page_body(&self, page: &mut Page) { +/// page +/// .alter_assets(ContextOp::AddStyleSheet( +/// StyleSheet::from("/css/normalize.css").with_version("8.0.1"), +/// )) +/// .alter_assets(ContextOp::AddStyleSheet( +/// StyleSheet::from("/css/basic.css").with_version(PAGETOP_VERSION), +/// )) +/// .alter_assets(ContextOp::AddStyleSheet( +/// StyleSheet::from("/mytheme/styles.css").with_version(env!("CARGO_PKG_VERSION")), +/// )); +/// } +/// } +/// ``` +/// Donde `PAGETOP_VERSION` identifica la versión de PageTop y `env!("CARGO_PKG_VERSION")` hace +/// referencia a la versión del *crate* que lo usa. +pub const PAGETOP_VERSION: &str = env!("CARGO_PKG_VERSION"); + pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; pub use pagetop_statics::{resource, StaticResource}; diff --git a/src/prelude.rs b/src/prelude.rs index 15121036..1d649fec 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,6 +2,8 @@ // RE-EXPORTED. +pub use crate::PAGETOP_VERSION; + pub use crate::{builder_fn, html, main, test}; pub use crate::{AutoDefault, StaticResources, UniqueId, Weight}; From 2ce74fec8e8544a84ddd9949ba045a2a8166abe8 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 29 Nov 2025 14:42:05 +0100 Subject: [PATCH 195/224] =?UTF-8?q?=F0=9F=9A=A7=20Retoques=20menores=20en?= =?UTF-8?q?=20los=20comentarios=20del=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme/navbar/component.rs | 2 +- src/base/component/html.rs | 2 +- src/base/component/intro.rs | 2 +- src/base/component/poweredby.rs | 4 ++-- src/html/unit.rs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index b40d06c2..225c2af5 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -73,7 +73,7 @@ impl Component for Navbar { return PrepareMarkup::None; } - // Asegura que la barra tiene un id estable para poder asociarlo al colapso/offcanvas. + // Asegura que la barra tiene un `id` para poder asociarlo al colapso/offcanvas. let id = cx.required_id::<Self>(self.id()); PrepareMarkup::With(html! { diff --git a/src/base/component/html.rs b/src/base/component/html.rs index a60d30f9..1cca9899 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -/// Componente básico para renderizar dinámicamente código HTML recibiendo el contexto. +/// Componente básico que renderiza dinámicamente código HTML según el contexto. /// /// Este componente permite generar contenido HTML arbitrario, usando la macro `html!` y accediendo /// opcionalmente al contexto de renderizado. diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index ea01ccc8..7a3b2812 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -29,7 +29,7 @@ pub enum IntroOpening { /// opta por [`IntroOpening::PageTop`]) y bloques ([`Block`](crate::base::component::Block)) de /// contenido libre para crear párrafos vistosos de texto. Aunque admite todo tipo de componentes. /// -/// ### Ejemplos +/// # Ejemplos /// /// **Intro mínima por defecto** /// diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 797253dc..5a2dc5b2 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -3,7 +3,7 @@ use crate::prelude::*; // Enlace a la página oficial de PageTop. const LINK: &str = "<a href=\"https://pagetop.cillero.es\" rel=\"noopener noreferrer\">PageTop</a>"; -/// Componente que informa del 'Powered by' (*Funciona con*) típica del pie de página. +/// Componente que muestra el típico mensaje *Powered by* (*Funciona con*) en el pie de página. /// /// Por defecto, usando [`default()`](Self::default) sólo se muestra un reconocimiento a PageTop. /// Sin embargo, se puede usar [`new()`](Self::new) para crear una instancia con un texto de @@ -17,7 +17,7 @@ impl Component for PoweredBy { /// Crea una nueva instancia de `PoweredBy`. /// /// El copyright se genera automáticamente con el año actual y el nombre de la aplicación - /// configurada en [`global::SETTINGS`]. + /// configurada en [`global::SETTINGS`], en el formato `YYYY © Nombre de la aplicación`. fn new() -> Self { let year = Utc::now().format("%Y").to_string(); let c = join!(year, " © ", global::SETTINGS.app.name); diff --git a/src/html/unit.rs b/src/html/unit.rs index 4a99de16..aec40372 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -167,7 +167,7 @@ impl fmt::Display for UnitValue { /// Convierte una cadena a [`UnitValue`] siguiendo una gramática CSS acotada. /// -/// ## Acepta +/// # Acepta /// /// - `""` para `UnitValue::None` /// - `"auto"` @@ -178,7 +178,7 @@ impl fmt::Display for UnitValue { /// /// (Se toleran espacios entre número y unidad: `"12 px"`, `"1.5 rem"`). /// -/// ## Ejemplo +/// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; @@ -188,7 +188,7 @@ impl fmt::Display for UnitValue { /// assert!(UnitValue::from_str("12").is_err()); /// ``` /// -/// ## Errores de interpretación +/// # Errores de interpretación /// /// - Falta la unidad cuando es necesaria (p. ej., `"12"`, excepto para el valor cero). /// - Decimales en valores que deben ser absolutos (p. ej. `"1.5px"`). @@ -262,7 +262,7 @@ impl FromStr for UnitValue { /// Deserializa desde una cadena usando la misma gramática que [`FromStr`]. /// -/// ### Ejemplo con `serde_json` +/// # Ejemplo con `serde_json` /// ```rust /// # use pagetop::prelude::*; /// use serde::Deserialize; From bfdc0da407aacf38ae3590605192a411f692f016 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sat, 29 Nov 2025 14:43:37 +0100 Subject: [PATCH 196/224] =?UTF-8?q?=F0=9F=9A=A7=20Mejora=20documentaci?= =?UTF-8?q?=C3=B3n=20generada=20por=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index e1ea55cb..5772a6ce 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -357,12 +357,17 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { // Texto introductorio para la documentación adicional de `with_...()`. let with_alter_title = format!( - "# Añade método `{}()` generado por [`#[builder_fn]`](pagetop_macros::builder_fn)", + "# {} el método `{}()` generado por [`#[builder_fn]`](pagetop_macros::builder_fn)", + if doc_attrs.is_empty() { + "Añade" + } else { + "También añade" + }, alter_name_str ); let with_alter_doc = concat!( - "Modifica la instancia actual (`&mut self`) con los mismos argumentos ", - "en lugar de consumirla." + "Modifica la instancia actual (`&mut self`) con los mismos argumentos, ", + "sin consumirla." ); // Atributos completos que se aplican siempre a `with_...()`. From f2733bb25001e6a4df53cc588602fdc519ca2761 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 00:16:54 +0100 Subject: [PATCH 197/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactoriza=20la?= =?UTF-8?q?=20gesti=C3=B3n=20de=20regiones=20y=20plantillas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 2 +- src/base/component.rs | 38 ----- src/base/component/region.rs | 150 ------------------- src/base/component/template.rs | 84 ----------- src/base/theme/basic.rs | 2 +- src/core/component/context.rs | 35 +++-- src/core/extension/definition.rs | 52 ++++--- src/core/theme.rs | 210 ++++++++++++++++++++++++-- src/core/theme/definition.rs | 93 ++++++------ src/core/theme/regions.rs | 93 +++++++----- src/response/page.rs | 123 +++++++++++++--- src/response/page/error.rs | 24 ++- static/css/basic.css | 19 ++- static/css/components.css | 12 -- static/css/root.css | 212 --------------------------- 15 files changed, 494 insertions(+), 655 deletions(-) delete mode 100644 src/base/component/region.rs delete mode 100644 src/base/component/template.rs delete mode 100644 static/css/components.css delete mode 100644 static/css/root.css diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 80e6ca15..04b5ad1a 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -121,7 +121,7 @@ impl Theme for Aliner { .with_weight(-99), )) .alter_child_in( - Region::FOOTER, + &DefaultRegion::Footer, ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), ); } diff --git a/src/base/component.rs b/src/base/component.rs index fa9ed2ad..7ea596d3 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,46 +1,8 @@ //! Componentes nativos proporcionados por PageTop. -//! -//! Conviene destacar que PageTop distingue entre: -//! -//! - **Componentes estructurales** que definen el esqueleto de un documento HTML, como [`Template`] -//! y [`Region`], utilizados por [`Page`](crate::response::page::Page) para generar la estructura -//! final. -//! - **Componentes de contenido** (menús, barras, tarjetas, etc.), que se incluyen en las regiones -//! gestionadas por los componentes estructurales. -//! -//! El componente [`Template`] describe cómo maquetar el cuerpo del documento a partir de varias -//! regiones lógicas ([`Region`]). En función de la plantilla seleccionada, determina qué regiones -//! se renderizan y en qué orden. Por ejemplo, la plantilla predeterminada [`Template::DEFAULT`] -//! utiliza las regiones [`Region::HEADER`], [`Region::CONTENT`] y [`Region::FOOTER`]. -//! -//! Un componente [`Region`] es un contenedor lógico asociado a un nombre de región. Su contenido se -//! obtiene del [`Context`](crate::core::component::Context), donde los componentes se registran -//! mediante [`Contextual::with_child_in()`](crate::core::component::Contextual::with_child_in) y -//! otros mecanismos similares, y se integra en el documento a través de [`Template`]. -//! -//! Por su parte, una página ([`Page`](crate::response::page::Page)) representa un documento HTML -//! completo. Implementa [`Contextual`](crate::core::component::Contextual) para mantener su propio -//! [`Context`](crate::core::component::Context), donde gestiona el tema activo, la plantilla -//! seleccionada y los componentes asociados a cada región, y se encarga de generar la estructura -//! final de la página. -//! -//! De este modo, temas y extensiones colaboran sobre una estructura común: las aplicaciones -//! registran componentes en el [`Context`](crate::core::component::Context), las plantillas -//! organizan las regiones y las páginas generan el documento HTML resultante. -//! -//! Los temas pueden sobrescribir [`Template`] para exponer nuevas plantillas o adaptar las -//! predeterminadas, y lo mismo con [`Region`] para añadir regiones adicionales o personalizar su -//! representación. mod html; pub use html::Html; -mod region; -pub use region::Region; - -mod template; -pub use template::Template; - mod block; pub use block::Block; diff --git a/src/base/component/region.rs b/src/base/component/region.rs deleted file mode 100644 index 5dfa25ce..00000000 --- a/src/base/component/region.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::prelude::*; - -/// Componente estructural que renderiza el contenido de una región del documento. -/// -/// `Region` actúa como un contenedor lógico asociado a un nombre de región. Su contenido se obtiene -/// del contexto de renderizado ([`Context`]), donde los componentes suelen registrarse con métodos -/// como [`Contextual::with_child_in()`]. Cada región puede integrarse posteriormente en el cuerpo -/// del documento mediante [`Template`], normalmente desde una página ([`Page`]). -#[derive(AutoDefault)] -pub struct Region { - #[default(AttrName::new(Self::DEFAULT))] - name: AttrName, - #[default(L10n::l("region-content"))] - label: L10n, -} - -impl Component for Region { - fn new() -> Self { - Region::default() - } - - fn id(&self) -> Option<String> { - self.name.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let Some(name) = self.name().get() else { - return PrepareMarkup::None; - }; - let output = cx.render_region(&name); - if output.is_empty() { - return PrepareMarkup::None; - } - PrepareMarkup::With(html! { - div - id=[self.id()] - class=(join!("region region-", &name)) - role="region" - aria-label=[self.label().lookup(cx)] - { - (output) - } - }) - } -} - -impl Region { - /// Región especial situada al **inicio del documento**. - /// - /// Su función es proporcionar un punto estable donde las extensiones puedan inyectar contenido - /// global antes de renderizar el resto de regiones principales (cabecera, contenido, etc.). - /// - /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, - /// sino como punto de anclaje para elementos auxiliares, marcadores técnicos, inicializadores o - /// contenido de depuración que deban situarse en la parte superior del documento. - /// - /// Se considera una región **reservada** para este tipo de usos globales. - pub const PAGETOP: &str = "page-top"; - - /// Región estándar para la **cabecera** del documento. - /// - /// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc. - pub const HEADER: &str = "header"; - - /// Región principal de **contenido**. - /// - /// Es la región donde se espera que se renderice el contenido principal de la página (p. ej. - /// cuerpo de la ruta actual, bloques centrales, vistas principales, etc.). En muchos temas será - /// la región mínima imprescindible para que la página tenga sentido. - pub const CONTENT: &str = "content"; - - /// Región estándar para el **pie de página**. - /// - /// Suele contener información legal, enlaces secundarios, créditos, etc. - pub const FOOTER: &str = "footer"; - - /// Región especial situada al **final del documento**. - /// - /// Pensada para proporcionar un punto estable donde las extensiones puedan inyectar contenido - /// global después de renderizar el resto de regiones principales (cabecera, contenido, etc.). - /// - /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, - /// sino como punto de anclaje para elementos auxiliares asociados a comportamientos dinámicos - /// que deban situarse en la parte inferior del documento. - /// - /// Igual que [`Self::PAGETOP`], se considera una región **reservada** para este tipo de usos - /// globales. - pub const PAGEBOTTOM: &str = "page-bottom"; - - /// Región por defecto que se asigna cuando no se especifica ningún nombre. - /// - /// Por diseño, la región por defecto es la de contenido principal ([`Self::CONTENT`]), de - /// manera que un tema sencillo pueda limitarse a definir una sola región funcional. - pub const DEFAULT: &str = Self::CONTENT; - - /// Prepara una región para el nombre indicado. - /// - /// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del - /// contenedor. Al renderizarse, este componente mostrará el contenido registrado en el contexto - /// bajo ese nombre. - pub fn named(name: impl AsRef<str>) -> Self { - Region { - name: AttrName::new(name), - label: L10n::default(), - } - } - - /// Prepara una región para el nombre indicado con una etiqueta de accesibilidad. - /// - /// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del - /// contenedor, mientras que `label` será el texto localizado que se usará como `aria-label` del - /// contenedor. - pub fn labeled(name: impl AsRef<str>, label: L10n) -> Self { - Region { - name: AttrName::new(name), - label, - } - } - - // **< Region BUILDER >************************************************************************* - - /// Establece o modifica el nombre de la región. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef<str>) -> Self { - self.name.alter_value(name); - self - } - - /// Establece la etiqueta localizada de la región. - /// - /// Esta etiqueta se utiliza como `aria-label` del contenedor predefinido `<div role="region">`, - /// lo que mejora la accesibilidad para lectores de pantalla y otras tecnologías de apoyo. - #[builder_fn] - pub fn with_label(mut self, label: L10n) -> Self { - self.label = label; - self - } - - // **< Region GETTERS >************************************************************************* - - /// Devuelve el nombre de la región. - pub fn name(&self) -> &AttrName { - &self.name - } - - /// Devuelve la etiqueta localizada asociada a la región. - pub fn label(&self) -> &L10n { - &self.label - } -} diff --git a/src/base/component/template.rs b/src/base/component/template.rs deleted file mode 100644 index 6c70d00e..00000000 --- a/src/base/component/template.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::prelude::*; - -/// Componente estructural para renderizar plantillas de contenido. -/// -/// `Template` describe cómo se compone el cuerpo del documento a partir de varias regiones lógicas -/// ([`Region`]). En función de su nombre, decide qué regiones se renderizan y en qué orden. -/// -/// Normalmente se invoca desde una página ([`Page`]), que consulta el nombre de plantilla guardado -/// en el [`Context`] y delega en `Template` la composición de las regiones que forman el cuerpo del -/// documento. -/// -/// Los temas pueden sobrescribir este componente para exponer sus propias plantillas o adaptar las -/// plantillas predeterminadas. -#[derive(AutoDefault)] -pub struct Template { - #[default(AttrName::new(Self::DEFAULT))] - name: AttrName, -} - -impl Component for Template { - fn new() -> Self { - Template::default() - } - - fn id(&self) -> Option<String> { - self.name.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let Some(name) = self.name().get() else { - return PrepareMarkup::None; - }; - match name.as_str() { - Self::DEFAULT | Self::ERROR => PrepareMarkup::With(html! { - (Region::labeled(Region::HEADER, L10n::l("region-header")).render(cx)) - (Region::default().render(cx)) - (Region::labeled(Region::FOOTER, L10n::l("region-footer")).render(cx)) - }), - _ => PrepareMarkup::None, - } - } -} - -impl Template { - /// Nombre de la plantilla predeterminada. - /// - /// Por defecto define una estructura básica con las regiones [`Region::HEADER`], - /// [`Region::CONTENT`] y [`Region::FOOTER`], en ese orden. Esta plantilla se usa cuando no se - /// selecciona ninguna otra de forma explícita (ver [`Contextual::with_template()`]). - pub const DEFAULT: &str = "default"; - - /// Nombre de la plantilla de error. - /// - /// Se utiliza para páginas de error u otros estados excepcionales. Por defecto reutiliza - /// la misma estructura que [`Self::DEFAULT`], pero permite a temas y extensiones distinguir - /// el contexto de error para aplicar estilos o contenidos específicos. - pub const ERROR: &str = "error"; - - /// Selecciona la plantilla asociada al nombre indicado. - /// - /// El valor de `name` se utiliza como nombre de la plantilla y como identificador (`id`) del - /// componente. - pub fn named(name: impl AsRef<str>) -> Self { - Template { - name: AttrName::new(name), - } - } - - // **< Template BUILDER >*********************************************************************** - - /// Establece o modifica el nombre de la plantilla seleccionada. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef<str>) -> Self { - self.name.alter_value(name); - self - } - - // **< Template GETTERS >*********************************************************************** - - /// Devuelve el nombre de la plantilla seleccionada. - pub fn name(&self) -> &AttrName { - &self.name - } -} diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 83dc4a8d..ca3c4a82 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -23,7 +23,7 @@ impl Theme for Basic { .with_weight(-99), )) .alter_child_in( - Region::FOOTER, + &DefaultRegion::Footer, ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), ); } diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 5cc5d2e8..922067f4 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -1,7 +1,6 @@ -use crate::base::component::Template; use crate::core::component::ChildOp; use crate::core::theme::all::DEFAULT_THEME; -use crate::core::theme::{ChildrenInRegions, ThemeRef}; +use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; @@ -68,7 +67,7 @@ pub enum ContextError { /// fn prepare_context<C: Contextual>(cx: C) -> C { /// cx.with_langid(&LangMatch::resolve("es-ES")) /// .with_theme(&Aliner) -/// .with_template(Template::DEFAULT) +/// .with_template(&DefaultTemplate::Standard) /// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) /// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/app.js"))) @@ -92,7 +91,7 @@ pub trait Contextual: LangId { /// Especifica la plantilla para renderizar el documento. #[builder_fn] - fn with_template(self, template_name: &'static str) -> Self; + fn with_template(self, template: TemplateRef) -> Self; /// Añade o modifica un parámetro dinámico del contexto. #[builder_fn] @@ -102,9 +101,9 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: ContextOp) -> Self; - /// Opera con [`ChildOp`] en una región (`region_name`) del documento. + /// Opera con [`ChildOp`] en una región del documento. #[builder_fn] - fn with_child_in(self, region_name: impl AsRef<str>, op: ChildOp) -> Self; + fn with_child_in(self, region_ref: RegionRef, op: ChildOp) -> Self; // **< Contextual GETTERS >********************************************************************* @@ -114,8 +113,8 @@ pub trait Contextual: LangId { /// Devuelve el tema que se usará para renderizar el documento. fn theme(&self) -> ThemeRef; - /// Devuelve el nombre de la plantilla usada para renderizar el documento. - fn template(&self) -> &str; + /// Devuelve la plantilla configurada para renderizar el documento. + fn template(&self) -> TemplateRef; /// Recupera un parámetro como [`Option`]. fn param<T: 'static>(&self, key: &'static str) -> Option<&T>; @@ -208,7 +207,7 @@ pub struct Context { request : Option<HttpRequest>, // Solicitud HTTP de origen. langid : &'static LanguageIdentifier, // Identificador de idioma. theme : ThemeRef, // Referencia al tema usado para renderizar. - template : &'static str, // Nombre de la plantilla usada para renderizar. + template : TemplateRef, // Plantilla usada para renderizar. favicon : Option<Favicon>, // Favicon, si se ha definido. stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS. javascripts: Assets<JavaScript>, // Scripts JavaScript. @@ -248,7 +247,7 @@ impl Context { request, langid, theme : *DEFAULT_THEME, - template : Template::DEFAULT, + template : DEFAULT_THEME.default_template(), favicon : None, stylesheets: Assets::<StyleSheet>::new(), javascripts: Assets::<JavaScript>::new(), @@ -286,10 +285,10 @@ impl Context { markup } - /// Renderiza los componentes de la región `region_name`. - pub fn render_region(&mut self, region_name: impl AsRef<str>) -> Markup { + /// Renderiza los componentes de una región. + pub fn render_region(&mut self, region_ref: RegionRef) -> Markup { self.regions - .children_for(self.theme, region_name) + .children_for(self.theme, region_ref) .render(self) } @@ -417,8 +416,8 @@ impl Contextual for Context { } #[builder_fn] - fn with_template(mut self, template_name: &'static str) -> Self { - self.template = template_name; + fn with_template(mut self, template: TemplateRef) -> Self { + self.template = template; self } @@ -474,8 +473,8 @@ impl Contextual for Context { } #[builder_fn] - fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { - self.regions.alter_child_in(region_name, op); + fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { + self.regions.alter_child_in(region_ref, op); self } @@ -489,7 +488,7 @@ impl Contextual for Context { self.theme } - fn template(&self) -> &str { + fn template(&self) -> TemplateRef { self.template } diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 520153d0..304eae87 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -4,24 +4,23 @@ use crate::core::AnyInfo; use crate::locale::L10n; use crate::{actions_boxed, service}; -/// Representa una referencia a una extensión. -/// -/// Las extensiones se definen como instancias estáticas globales para poder acceder a ellas desde -/// cualquier hilo de la ejecución sin necesidad de sincronización adicional. -pub type ExtensionRef = &'static dyn Extension; - /// Interfaz común que debe implementar cualquier extensión de PageTop. /// -/// Este *trait* es fácil de implementar, basta con declarar una estructura de tamaño cero para la -/// extensión y sobreescribir los métodos que sea necesario. +/// Este *trait* es fácil de implementar, basta con declarar una estructura sin campos para la +/// extensión y sobrescribir los métodos que sean necesarios. Por ejemplo: /// /// ```rust /// # use pagetop::prelude::*; /// pub struct Blog; /// /// impl Extension for Blog { -/// fn name(&self) -> L10n { L10n::n("Blog") } -/// fn description(&self) -> L10n { L10n::n("Blog system") } +/// fn name(&self) -> L10n { +/// L10n::n("Blog") +/// } +/// +/// fn description(&self) -> L10n { +/// L10n::n("Blog system") +/// } /// } /// ``` pub trait Extension: AnyInfo + Send + Sync { @@ -34,14 +33,19 @@ pub trait Extension: AnyInfo + Send + Sync { } /// Descripción corta localizada de la extensión para paneles, listados, etc. + /// + /// Por defecto devuelve un valor vacío (`L10n::default()`). fn description(&self) -> L10n { L10n::default() } - /// Devuelve una referencia a esta misma extensión cuando se trata de un tema. + /// Devuelve una referencia a esta misma extensión cuando actúa como un tema. /// - /// Para ello, debe implementar [`Extension`] y también [`Theme`](crate::core::theme::Theme). Si - /// la extensión no es un tema, este método devuelve `None` por defecto. + /// Para ello, la implementación concreta debe ser una extensión que también implemente + /// [`Theme`](crate::core::theme::Theme). Por defecto, asume que la extensión no es un tema y + /// devuelve `None`. + /// + /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; @@ -61,17 +65,17 @@ pub trait Extension: AnyInfo + Send + Sync { /// Otras extensiones que deben habilitarse **antes** de esta. /// - /// PageTop las resolverá automáticamente respetando el orden durante el arranque de la - /// aplicación. + /// PageTop resolverá automáticamente estas dependencias respetando el orden durante el arranque + /// de la aplicación. fn dependencies(&self) -> Vec<ExtensionRef> { vec![] } - /// Devuelve la lista de acciones que la extensión va a registrar. + /// Devuelve la lista de acciones que la extensión registra. /// /// Estas [acciones](crate::core::action) se despachan por orden de registro o por - /// [peso](crate::Weight), permitiendo personalizar el comportamiento de la aplicación en puntos - /// específicos. + /// [peso](crate::Weight) (ver [`actions_boxed!`](crate::actions_boxed)), permitiendo + /// personalizar el comportamiento de la aplicación en puntos específicos. fn actions(&self) -> Vec<ActionBox> { actions_boxed![] } @@ -85,6 +89,8 @@ pub trait Extension: AnyInfo + Send + Sync { /// Configura los servicios web de la extensión, como rutas, *middleware*, acceso a ficheros /// estáticos, etc., usando [`ServiceConfig`](crate::service::web::ServiceConfig). /// + /// # Ejemplo + /// /// ```rust,ignore /// # use pagetop::prelude::*; /// pub struct ExtensionSample; @@ -98,11 +104,15 @@ pub trait Extension: AnyInfo + Send + Sync { #[allow(unused_variables)] fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {} - /// Permite crear extensiones para deshabilitar y desinstalar recursos de otras de versiones - /// anteriores de la aplicación. + /// Permite declarar extensiones destinadas a deshabilitar o desinstalar recursos de otras + /// extensiones asociadas a versiones anteriores de la aplicación. /// - /// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones. + /// Actualmente PageTop no utiliza este método, pero se reserva como *placeholder* para futuras + /// implementaciones. fn drop_extensions(&self) -> Vec<ExtensionRef> { vec![] } } + +/// Representa una referencia a una extensión. +pub type ExtensionRef = &'static dyn Extension; diff --git a/src/core/theme.rs b/src/core/theme.rs index 8774276e..e238df79 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -1,18 +1,206 @@ //! API para añadir y gestionar nuevos temas. //! -//! En PageTop un tema es la *piel* de la aplicación. Es responsable último de los estilos, -//! tipografías, espaciados y cualquier otro detalle visual o interactivo (animaciones, scripts de -//! interfaz, etc.). +//! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension) y +//! también [`Theme`], de modo que [`Extension::theme()`](crate::core::extension::Extension::theme) +//! permita identificar y registrar los temas disponibles. //! -//! Un tema determina el aspecto final de un documento HTML sin alterar la lógica interna de los -//! componentes ni la estructura del documento, que queda definida por la plantilla -//! ([`Template`](crate::base::component::Template)) utilizada por cada página. +//! Un tema es la *piel* de la aplicación: define estilos, tipografías, espaciados o comportamientos +//! interactivos. Para ello utiliza plantillas ([`Template`]) que describen cómo maquetar el cuerpo +//! del documento a partir de varias regiones ([`Region`]). Cada región es un contenedor lógico +//! identificado por un nombre, cuyo contenido se obtiene del [`Context`] de la página. //! -//! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension), por -//! lo que se instancian, declaran dependencias y se inician igual que cualquier otra extensión. -//! También deben implementar [`Theme`] y sobrescribir el método -//! [`Extension::theme()`](crate::core::extension::Extension::theme) para que PageTop pueda -//! registrarlos como temas. +//! Una página ([`Page`](crate::response::page::Page)) representa un documento HTML completo. +//! Implementa [`Contextual`](crate::core::component::Contextual) para gestionar su propio +//! [`Context`], donde mantiene el tema activo, la plantilla seleccionada y los componentes +//! asociados a cada región. +//! +//! De este modo, temas y extensiones colaboran sobre una estructura común: las aplicaciones +//! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas +//! generan el documento HTML resultante. +//! +//! Los temas pueden definir sus propias implementaciones de [`Template`] y [`Region`] (por ejemplo, +//! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas. + +use crate::core::component::Context; +use crate::html::{html, Markup}; +use crate::locale::L10n; +use crate::{join, AutoDefault}; + +// **< Region >************************************************************************************* + +/// Interfaz común para las regiones lógicas de un documento. +/// +/// Una `Region` representa un contenedor lógico identificado por un nombre de región. Su contenido +/// se obtiene del [`Context`], donde los componentes suelen registrarse usando implementaciones de +/// métodos como [`Contextual::with_child_in()`](crate::core::component::Contextual::with_child_in). +/// +/// El contenido de una región viene determinado únicamente por su nombre, no por su tipo. Distintas +/// implementaciones de [`Region`] que devuelvan el mismo nombre compartirán el mismo conjunto de +/// componentes registrados en el [`Context`], aunque cada región puede renderizar ese contenido de +/// forma diferente. Por ejemplo, [`DefaultRegion::Header`] y `BootsierRegion::Header` mostrarían +/// los mismos componentes si ambas devuelven el nombre `"header"`, pero podrían maquetarse de +/// manera distinta. +/// +/// El tema decide qué regiones mostrar en el cuerpo del documento, normalmente usando una plantilla +/// ([`Template`]) al renderizar la página ([`Page`](crate::response::page::Page)). +pub trait Region { + /// Devuelve el nombre de la región. + /// + /// Este nombre es el identificador lógico de la región y se usa como clave en el [`Context`] + /// para recuperar y renderizar el contenido registrado bajo ese nombre. Cualquier + /// implementación de [`Region`] que devuelva el mismo nombre compartirá el mismo conjunto de + /// componentes. + /// + /// En la implementación predeterminada de [`Self::render()`] también se utiliza para construir + /// las clases del contenedor de la región (`"region region-<name>"`). + fn name(&self) -> &'static str; + + /// Devuelve la etiqueta de accesibilidad localizada asociada a la región. + /// + /// En la implementación predeterminada de [`Self::render()`], este valor se usa como + /// `aria-label` del contenedor de la región. + fn label(&self) -> L10n; + + /// Renderiza el contenedor de la región. + /// + /// Por defecto, recupera del [`Context`] el contenido de la región y, si no está vacío, lo + /// envuelve en un `<div>` con clases `"region region-<name>"` y un `aria-label` basado en la + /// etiqueta localizada de la región: + /// + /// ```html + /// <div class="region region-<name>" role="region" aria-label="<label>"> + /// <!-- Componentes de la región "name" --> + /// </div> + /// ``` + /// + /// Se puede sobrescribir este método para modificar la estructura del contenedor, las clases + /// utilizadas o la semántica del marcado generado para cada región. + fn render(&'static self, cx: &mut Context) -> Markup + where + Self: Sized, + { + html! { + @let region = cx.render_region(self); + @if !region.is_empty() { + div + class=(join!("region region-", self.name())) + role="region" + aria-label=[self.label().lookup(cx)] + { + (region) + } + } + } + } +} + +/// Referencia estática a una región. +pub type RegionRef = &'static dyn Region; + +// **< DefaultRegion >****************************************************************************** + +/// Regiones básicas que PageTop proporciona por defecto. +/// +/// Estas regiones comparten sus nombres (`"header"`, `"content"`, `"footer"`) con cualquier región +/// equivalente definida por otros temas, por lo que comparten también el contenido registrado bajo +/// esos nombres. +#[derive(AutoDefault)] +pub enum DefaultRegion { + /// Región estándar para la **cabecera** del documento, de nombre `"header"`. + /// + /// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc. + Header, + + /// Región principal de **contenido**, de nombre `"content"`. + /// + /// Es la región donde se renderiza el contenido principal del documento. En general será la + /// región mínima imprescindible para que una página tenga sentido. + #[default] + Content, + + /// Región estándar para el **pie de página**, de nombre `"footer"`. + /// + /// Suele contener información legal, enlaces secundarios, créditos, etc. + Footer, +} + +impl Region for DefaultRegion { + #[inline] + fn name(&self) -> &'static str { + match self { + Self::Header => "header", + Self::Content => "content", + Self::Footer => "footer", + } + } + + #[inline] + fn label(&self) -> L10n { + match self { + Self::Header => L10n::l("region-header"), + Self::Content => L10n::l("region-content"), + Self::Footer => L10n::l("region-footer"), + } + } +} + +// **< Template >*********************************************************************************** + +/// Interfaz común para definir plantillas de contenido. +/// +/// Una `Template` puede proporcionar una o más variantes para decidir la composición del `<body>` +/// de una página ([`Page`](crate::response::page::Page)). El tema utiliza esta información para +/// determinar qué regiones ([`Region`]) deben renderizarse y en qué orden. +pub trait Template { + /// Renderiza el contenido de la plantilla. + /// + /// Por defecto, renderiza las regiones básicas de [`DefaultRegion`] en este orden: + /// [`DefaultRegion::Header`], [`DefaultRegion::Content`] y [`DefaultRegion::Footer`]. + /// + /// Se puede sobrescribir este método para: + /// + /// - Cambiar el conjunto de regiones que se renderizan según variantes de la plantilla. + /// - Alterar el orden de dichas regiones. + /// - Envolver las regiones en contenedores adicionales. + /// - Implementar distribuciones específicas (por ejemplo, con barras laterales). + /// + /// Este método se invoca normalmente desde [`Theme::render_page_body()`] para generar el + /// contenido del `<body>` de una página según la plantilla devuelta por el contexto de la + /// propia página ([`Contextual::template()`](crate::core::component::Contextual::template())). + fn render(&'static self, cx: &mut Context) -> Markup { + html! { + (DefaultRegion::Header.render(cx)) + (DefaultRegion::Content.render(cx)) + (DefaultRegion::Footer.render(cx)) + } + } +} + +/// Referencia estática a una plantilla. +pub type TemplateRef = &'static dyn Template; + +// **< DefaultTemplate >**************************************************************************** + +/// Plantillas que PageTop proporciona por defecto. +#[derive(AutoDefault)] +pub enum DefaultTemplate { + /// Plantilla predeterminada. + /// + /// Utiliza la implementación por defecto de [`Template::render()`] y se emplea cuando no se + /// selecciona ninguna otra plantilla explícitamente. + #[default] + Standard, + + /// Plantilla de error. + /// + /// Se utiliza para páginas de error u otros estados excepcionales. Por defecto utiliza la misma + /// implementación de [`Template::render()`] que [`Self::Standard`]. + Error, +} + +impl Template for DefaultTemplate {} + +// **< Definitions >******************************************************************************** mod definition; pub use definition::{Theme, ThemeRef}; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index dda58b18..4ff38fc1 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,30 +1,26 @@ -use crate::base::component::Template; -use crate::core::component::{ComponentRender, ContextOp, Contextual}; +use crate::core::component::Contextual; use crate::core::extension::Extension; use crate::global; -use crate::html::{html, Markup, StyleSheet}; +use crate::html::{html, Markup}; use crate::locale::L10n; +use crate::prelude::{DefaultTemplate, TemplateRef}; use crate::response::page::Page; -/// Referencia estática a un tema. -/// -/// Los temas son también extensiones. Por tanto, deben declararse como **instancias estáticas** que -/// implementen [`Theme`] y, a su vez, [`Extension`]. Estas instancias se exponen usando -/// [`Extension::theme()`](crate::core::extension::Extension::theme). -pub type ThemeRef = &'static dyn Theme; - /// Interfaz común que debe implementar cualquier tema de PageTop. /// /// Un tema es una [`Extension`](crate::core::extension::Extension) que define el aspecto general de -/// las páginas: cómo se renderiza el `<head>`, cómo se presenta el `<body>` mediante plantillas -/// ([`Template`]) y qué contenido mostrar en las páginas de error. +/// las páginas: cómo se renderiza el `<head>`, cómo se presenta el `<body>` usando plantillas +/// ([`Template`](crate::core::theme::Template)) que maquetan regiones +/// ([`Region`](crate::core::theme::Region)) y qué contenido mostrar en las páginas de error. El +/// contenido de cada región depende del [`Context`](crate::core::component::Context) y de su nombre +/// lógico. /// /// Todos los métodos de este *trait* tienen una implementación por defecto, por lo que pueden /// sobrescribirse selectivamente para crear nuevos temas con comportamientos distintos a los /// predeterminados. /// /// El único método **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme), -/// que debe devolver una referencia estática al propio tema: +/// que debe devolver una referencia al propio tema: /// /// ```rust /// # use pagetop::prelude::*; @@ -47,32 +43,55 @@ pub type ThemeRef = &'static dyn Theme; /// impl Theme for MyTheme {} /// ``` pub trait Theme: Extension + Send + Sync { + /// Devuelve la plantilla ([`Template`](crate::core::theme::Template)) que el propio tema + /// propone como predeterminada. + /// + /// Se utiliza al inicializar un [`Context`](crate::core::component::Context) o una página + /// ([`Page`](crate::response::page::Page)) por si no se elige ninguna otra plantilla con + /// [`Contextual::with_template()`](crate::core::component::Contextual::with_template). + /// + /// La implementación por defecto devuelve la plantilla estándar ([`DefaultTemplate::Standard`]) + /// con una estructura básica para la página. Los temas pueden sobrescribir este método para + /// seleccionar otra plantilla predeterminada o una plantilla propia. + #[inline] + fn default_template(&self) -> TemplateRef { + &DefaultTemplate::Standard + } + /// Acciones específicas del tema antes de renderizar el `<body>` de la página. /// - /// Se invoca antes de que se procese la plantilla ([`Template`]) asociada a la página - /// ([`Page::template()`](crate::response::page::Page::template)). Es un buen lugar para - /// inicializar o ajustar recursos en función del contexto de la página, por ejemplo: + /// Es un buen lugar para inicializar o ajustar recursos en función del contexto de la página, + /// por ejemplo: /// - /// - Añadir metadatos o propiedades a la página. + /// - Añadir metadatos o propiedades a la cabecera de la página. /// - Preparar atributos compartidos. /// - Registrar *assets* condicionales en el contexto. + /// + /// La implementación por defecto no realiza ninguna acción. #[allow(unused_variables)] fn before_render_page_body(&self, page: &mut Page) {} /// Renderiza el contenido del `<body>` de la página. /// - /// Por defecto, delega en la plantilla ([`Template`]) asociada a la página - /// ([`Page::template()`](crate::response::page::Page::template)). La plantilla se encarga de - /// procesar las regiones y renderizar los componentes registrados en el contexto. + /// La implementación predeterminada delega en la plantilla asociada a la página, obtenida desde + /// su [`Context`](crate::core::component::Context), y llama a + /// [`Template::render()`](crate::core::theme::Template::render) para componer el `<body>` a + /// partir de las regiones. + /// + /// Con la configuración por defecto, la plantilla estándar utiliza las regiones + /// [`DefaultRegion::Header`](crate::core::theme::DefaultRegion::Header), + /// [`DefaultRegion::Content`](crate::core::theme::DefaultRegion::Content) y + /// [`DefaultRegion::Footer`](crate::core::theme::DefaultRegion::Footer) en ese orden. /// /// Los temas pueden sobrescribir este método para: /// /// - Forzar una plantilla concreta en determinadas páginas. - /// - Envolver el contenido en marcadores adicionales. + /// - Consultar la plantilla de la página y variar la composición según su nombre. + /// - Envolver el contenido en contenedores adicionales. /// - Implementar lógicas de composición alternativas. #[inline] fn render_page_body(&self, page: &mut Page) -> Markup { - Template::named(page.template()).render(page.context()) + page.template().render(page.context()) } /// Acciones específicas del tema después de renderizar el `<body>` de la página. @@ -83,6 +102,8 @@ pub trait Theme: Extension + Send + Sync { /// - Realizar *tracing* o recopilar métricas. /// - Aplicar ajustes finales al estado de la página antes de producir el `<head>` o la /// respuesta final. + /// + /// La implementación por defecto no realiza ninguna acción. #[allow(unused_variables)] fn after_render_page_body(&self, page: &mut Page) {} @@ -101,34 +122,11 @@ pub trait Theme: Extension + Send + Sync { /// - La etiqueta `viewport` básica para diseño adaptable. /// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la /// página. - /// - Los *assets* registrados en el contexto de la página. Si el parámetro - /// `include_basic_assets` está activado, añade de serie las siguientes hojas de estilo - /// básicas: `normalize.css`, `root.css`, `basic.css`, útiles para temas sencillos o de uso - /// general. + /// - Los *assets* registrados en el contexto de la página. /// /// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo, /// *favicons* personalizados, manifest, etiquetas de analítica, etc.). - #[inline] fn render_page_head(&self, page: &mut Page) -> Markup { - if page.param_or("include_basic_assets", false) { - let pkg_version = env!("CARGO_PKG_VERSION"); - - page.alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/normalize.css") - .with_version("8.0.1") - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/root.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/basic.css") - .with_version(pkg_version) - .with_weight(-99), - )); - } let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; html! { meta charset="utf-8"; @@ -173,3 +171,6 @@ pub trait Theme: Extension + Send + Sync { html! { div { h1 { (L10n::l("error404_notice").using(page)) } } } } } + +/// Referencia estática a un tema. +pub type ThemeRef = &'static dyn Theme; diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 259417eb..52307287 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -1,6 +1,5 @@ -use crate::base::component::Region; use crate::core::component::{Child, ChildOp, Children}; -use crate::core::theme::ThemeRef; +use crate::core::theme::{DefaultRegion, RegionRef, ThemeRef}; use crate::{builder_fn, AutoDefault, UniqueId}; use parking_lot::RwLock; @@ -21,24 +20,23 @@ static COMMON_REGIONS: LazyLock<RwLock<ChildrenInRegions>> = pub(crate) struct ChildrenInRegions(HashMap<String, Children>); impl ChildrenInRegions { - pub fn with(region_name: impl AsRef<str>, child: Child) -> Self { - Self::default().with_child_in(region_name, ChildOp::Add(child)) + pub fn with(region_ref: RegionRef, child: Child) -> Self { + Self::default().with_child_in(region_ref, ChildOp::Add(child)) } #[builder_fn] - pub fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { - let name = region_name.as_ref(); - if let Some(region) = self.0.get_mut(name) { + pub fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { + if let Some(region) = self.0.get_mut(region_ref.name()) { region.alter_child(op); } else { self.0 - .insert(name.to_owned(), Children::new().with_child(op)); + .insert(region_ref.name().to_owned(), Children::new().with_child(op)); } self } - pub fn children_for(&self, theme_ref: ThemeRef, region_name: impl AsRef<str>) -> Children { - let name = region_name.as_ref(); + pub fn children_for(&self, theme_ref: ThemeRef, region_ref: RegionRef) -> Children { + let name = region_ref.name(); let common = COMMON_REGIONS.read(); let themed = THEME_REGIONS.read(); @@ -50,20 +48,36 @@ impl ChildrenInRegions { } } -/// Permite añadir componentes a regiones globales o específicas de un tema. +/// Añade componentes a regiones globales o específicas de un tema. /// -/// Según la variante, se pueden añadir componentes ([`add()`](Self::add)) que permanecerán -/// disponibles durante toda la ejecución. -/// -/// Estos componentes se renderizarán automáticamente al procesar los documentos HTML que incluyen -/// estas regiones, como las páginas de contenido ([`Page`](crate::response::page::Page)). +/// Cada variante indica la región en la que se añade el componente usando [`Self::add()`]. Los +/// componentes añadidos se mantienen durante toda la ejecución y se inyectan automáticamente al +/// renderizar los documentos HTML que utilizan esas regiones, como las páginas de contenido +/// ([`Page`](crate::response::page::Page)). pub enum InRegion { - /// Región de contenido por defecto. - Default, - /// Región identificada por el nombre proporcionado. - Named(&'static str), - /// Región identificada por su nombre para un tema concreto. - OfTheme(&'static str, ThemeRef), + /// Región principal de **contenido** por defecto. + /// + /// Añade el componente a la región lógica de contenido principal de la aplicación. Por + /// convención, esta región corresponde a [`DefaultRegion::Content`], cuyo nombre es + /// `"content"`. Cualquier tema que renderice esa misma región de contenido, ya sea usando + /// directamente [`DefaultRegion::Content`] o cualquier otra implementación de + /// [`Region`](crate::core::theme::Region) que devuelva ese mismo nombre, mostrará los + /// componentes registrados aquí, aunque lo harán según su propio método de renderizado + /// ([`Region::render()`](crate::core::theme::Region::render)). + Content, + /// Región global compartida por todos los temas. + /// + /// Los componentes añadidos aquí se asocian al nombre de la región indicado por [`RegionRef`], + /// es decir, al valor devuelto por [`Region::name()`](crate::core::theme::Region::name) para + /// esa región. Se mostrarán en cualquier tema cuya plantilla renderice una región que devuelva + /// ese mismo nombre. + Global(RegionRef), + /// Región asociada a un tema concreto. + /// + /// Los componentes sólo se renderizarán cuando el documento se procese con el tema indicado y + /// se utilice la región referenciada. Resulta útil para añadir contenido específico en un tema + /// sin afectar a otros. + ForTheme(ThemeRef, RegionRef), } impl InRegion { @@ -73,28 +87,33 @@ impl InRegion { /// /// ```rust /// # use pagetop::prelude::*; - /// // Banner global, en la región por defecto de cualquier página. - /// InRegion::Default.add(Child::with(Html::with(|_| - /// html! { ("🎉 ¡Bienvenido!") } - /// ))); + /// // Banner global en la región por defecto. + /// InRegion::Content.add(Child::with(Html::with(|_| { + /// html! { "🎉 ¡Bienvenido!" } + /// }))); /// - /// // Texto en la región "sidebar". - /// InRegion::Named("sidebar").add(Child::with(Html::with(|_| - /// html! { ("Publicidad") } - /// ))); + /// // Texto en la cabecera. + /// InRegion::Global(&DefaultRegion::Header).add(Child::with(Html::with(|_| { + /// html! { "Publicidad" } + /// }))); + /// + /// // Contenido sólo para la región del pie de página en un tema concreto. + /// InRegion::ForTheme(&theme::Basic, &DefaultRegion::Footer).add(Child::with(Html::with(|_| { + /// html! { "Aviso legal" } + /// }))); /// ``` pub fn add(&self, child: Child) -> &Self { match self { - InRegion::Default => Self::add_to_common(Region::DEFAULT, child), - InRegion::Named(region_name) => Self::add_to_common(region_name, child), - InRegion::OfTheme(region_name, theme_ref) => { + InRegion::Content => Self::add_to_common(&DefaultRegion::Content, child), + InRegion::Global(region_ref) => Self::add_to_common(*region_ref, child), + InRegion::ForTheme(theme_ref, region_ref) => { let mut regions = THEME_REGIONS.write(); if let Some(r) = regions.get_mut(&theme_ref.type_id()) { - r.alter_child_in(region_name, ChildOp::Add(child)); + r.alter_child_in(*region_ref, ChildOp::Add(child)); } else { regions.insert( theme_ref.type_id(), - ChildrenInRegions::with(region_name, child), + ChildrenInRegions::with(*region_ref, child), ); } } @@ -103,9 +122,9 @@ impl InRegion { } #[inline] - fn add_to_common(region_name: &str, child: Child) { + fn add_to_common(region_ref: RegionRef, child: Child) { COMMON_REGIONS .write() - .alter_child_in(region_name, ChildOp::Add(child)); + .alter_child_in(region_ref, ChildOp::Add(child)); } } diff --git a/src/response/page.rs b/src/response/page.rs index b5516b8a..c4534f4c 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -1,13 +1,27 @@ +//! Responde a una petición web generando una página HTML completa. +//! +//! Este módulo define [`Page`], que representa una página HTML lista para renderizar. Cada página +//! se construye a partir de un [`Context`] propio, donde se registran el tema activo, la plantilla +//! ([`Template`](crate::core::theme::Template)) que define la disposición de las regiones +//! ([`Region`]), los componentes asociados y los recursos adicionales (hojas de estilo, scripts, +//! *favicon*, etc.). +//! +//! El renderizado ([`Page::render()`]) delega en el tema ([`Theme`](crate::core::theme::Theme)) la +//! composición del `<head>` y del `<body>`, y se ejecutan las acciones registradas por las +//! extensiones antes y después de generar los contenidos. +//! +//! También introduce regiones internas reservadas ([`ReservedRegion`]) que actúan como puntos de +//! anclaje globales al inicio y al final del documento. + mod error; pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::base::component::Region; -use crate::core::component::{Child, ChildOp, Component, ComponentRender}; +use crate::core::component::{Child, ChildOp, Component}; use crate::core::component::{Context, ContextOp, Contextual}; -use crate::core::theme::ThemeRef; +use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{AttrClasses, ClassesOp}; @@ -16,6 +30,57 @@ use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; use crate::{builder_fn, AutoDefault}; +// **< ReservedRegion >***************************************************************************** + +/// Regiones internas reservadas como puntos de anclaje globales. +/// +/// Representan contenedores especiales situados al inicio y al final de un documento. Están +/// pensadas para proporcionar regiones donde inyectar contenido global o técnico. No suelen usarse +/// como regiones visibles en los temas. +pub enum ReservedRegion { + /// Región interna situada al **inicio del documento**. + /// + /// Su función es proporcionar un contenedor donde las extensiones puedan inyectar contenido + /// global antes del resto de regiones principales (cabecera, contenido, etc.). + /// + /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, + /// sino como punto de anclaje para elementos auxiliares, marcadores técnicos, inicializadores o + /// contenido de depuración que deban situarse en la parte superior del documento. + /// + /// Se considera una región **reservada** para este tipo de usos globales. + PageTop, + + /// Región interna situada al **final del documento**. + /// + /// Pensada para proporcionar un contenedor donde las extensiones puedan inyectar contenido + /// global después del resto de regiones principales (cabecera, contenido, etc.). + /// + /// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual, + /// sino como punto de anclaje para elementos auxiliares asociados a comportamientos dinámicos + /// que deban situarse en la parte inferior del documento. + /// + /// Igual que [`Self::PageTop`], se considera una región **reservada** para este tipo de usos + /// globales. + PageBottom, +} + +impl Region for ReservedRegion { + #[inline] + fn name(&self) -> &'static str { + match self { + Self::PageTop => "page-top", + Self::PageBottom => "page-bottom", + } + } + + #[inline] + fn label(&self) -> L10n { + L10n::default() + } +} + +// **< Page >*************************************************************************************** + /// Representa una página HTML completa lista para renderizar. /// /// Una instancia de `Page` se compone dinámicamente permitiendo establecer título, descripción, @@ -77,7 +142,7 @@ impl Page { /// Añade una entrada `<meta property="..." content="...">` al `<head>`. #[builder_fn] pub fn with_property(mut self, property: &'static str, content: &'static str) -> Self { - self.metadata.push((property, content)); + self.properties.push((property, content)); self } @@ -97,15 +162,17 @@ impl Page { /// Añade un componente hijo a la región de contenido por defecto. pub fn add_child(mut self, component: impl Component) -> Self { - self.context - .alter_child_in(Region::DEFAULT, ChildOp::Add(Child::with(component))); + self.context.alter_child_in( + &DefaultRegion::Content, + ChildOp::Add(Child::with(component)), + ); self } /// Añade un componente hijo en la región `region_name` de la página. - pub fn add_child_in(mut self, region_name: &'static str, component: impl Component) -> Self { + pub fn add_child_in(mut self, region_ref: RegionRef, component: impl Component) -> Self { self.context - .alter_child_in(region_name, ChildOp::Add(Child::with(component))); + .alter_child_in(region_ref, ChildOp::Add(Child::with(component))); self } @@ -154,8 +221,30 @@ impl Page { /// Renderiza la página completa en formato HTML. /// - /// Ejecuta las acciones correspondientes antes y después de renderizar el `<body>`, - /// así como del `<head>`, e inserta los atributos `lang` y `dir` en la etiqueta `<html>`. + /// El proceso de renderizado de la página sigue esta secuencia: + /// + /// 1. Ejecuta + /// [`Theme::before_render_page_body()`](crate::core::theme::Theme::before_render_page_body) + /// para que el tema pueda ejecutar acciones específicas antes de renderizar el `<body>`. + /// 2. Despacha [`action::page::BeforeRenderBody`] para que otras extensiones puedan realizar + /// ajustes previos sobre la página. + /// 3. **Construye el contenido del `<body>`**: + /// - Renderiza la región reservada superior ([`ReservedRegion::PageTop`]). + /// - Llama a [`Theme::render_page_body()`](crate::core::theme::Theme::render_page_body) para + /// renderizar las regiones del cuerpo principal de la página. + /// - Renderiza la región reservada inferior ([`ReservedRegion::PageBottom`]). + /// 4. Ejecuta + /// [`Theme::after_render_page_body()`](crate::core::theme::Theme::after_render_page_body) + /// para que el tema pueda aplicar ajustes finales. + /// 5. Despacha [`action::page::AfterRenderBody`] para permitir que otras extensiones realicen + /// sus últimos ajustes tras generar el `<body>`. + /// 6. Renderiza el `<head>` llamando a + /// [`Theme::render_page_head()`](crate::core::theme::Theme::render_page_head). + /// 7. Obtiene el idioma y la dirección del texto a partir de + /// [`Context::langid()`](crate::core::component::Context::langid) e inserta los atributos + /// `lang` y `dir` en la etiqueta `<html>`. + /// 8. Compone el documento HTML completo (`<!DOCTYPE html>`, `<html>`, `<head>`, `<body>`) y + /// devuelve un [`ResultPage`] con el [`Markup`] final. pub fn render(&mut self) -> ResultPage<Markup, ErrorPage> { // Acciones específicas del tema antes de renderizar el <body>. self.context.theme().before_render_page_body(self); @@ -165,9 +254,9 @@ impl Page { // Renderiza el <body>. let body = html! { - (Region::named(Region::PAGETOP).render(&mut self.context)) + (ReservedRegion::PageTop.render(&mut self.context)) (self.context.theme().render_page_body(self)) - (Region::named(Region::PAGEBOTTOM).render(&mut self.context)) + (ReservedRegion::PageBottom.render(&mut self.context)) }; // Acciones específicas del tema después de renderizar el <body>. @@ -228,8 +317,8 @@ impl Contextual for Page { } #[builder_fn] - fn with_template(mut self, template_name: &'static str) -> Self { - self.context.alter_template(template_name); + fn with_template(mut self, template: TemplateRef) -> Self { + self.context.alter_template(template); self } @@ -246,8 +335,8 @@ impl Contextual for Page { } #[builder_fn] - fn with_child_in(mut self, region_name: impl AsRef<str>, op: ChildOp) -> Self { - self.context.alter_child_in(region_name, op); + fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { + self.context.alter_child_in(region_ref, op); self } @@ -261,7 +350,7 @@ impl Contextual for Page { self.context.theme() } - fn template(&self) -> &str { + fn template(&self) -> TemplateRef { self.context.template() } diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 7a590e6e..7d6cf33b 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,5 +1,6 @@ -use crate::base::component::{Html, Template}; +use crate::base::component::Html; use crate::core::component::Contextual; +use crate::core::theme::DefaultTemplate; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode}; @@ -7,8 +8,21 @@ use crate::service::{HttpRequest, HttpResponse}; use super::Page; -use std::fmt::{self, Display}; +use std::fmt; +/// Página de error asociada a un código de estado HTTP. +/// +/// Este enumerado agrupa los distintos tipos de error que pueden devolverse como página HTML +/// completa. Cada variante encapsula la solicitud original ([`HttpRequest`]) y se corresponde con +/// un código de estado concreto. +/// +/// Para algunos errores (como [`ErrorPage::AccessDenied`] y [`ErrorPage::NotFound`]) se construye +/// una [`Page`] usando la plantilla de error del tema activo ([`DefaultTemplate::Error`]), lo que +/// permite personalizar el contenido del mensaje. En el resto de casos se devuelve un cuerpo HTML +/// mínimo basado en una descripción genérica del error. +/// +/// `ErrorPage` implementa [`ResponseError`], por lo que puede utilizarse directamente como tipo de +/// error en los controladores HTTP. #[derive(Debug)] pub enum ErrorPage { NotModified(HttpRequest), @@ -20,7 +34,7 @@ pub enum ErrorPage { Timeout(HttpRequest), } -impl Display for ErrorPage { +impl fmt::Display for ErrorPage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // Error 304. @@ -33,7 +47,7 @@ impl Display for ErrorPage { let error403 = error_page.theme().error403(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) - .with_template(Template::ERROR) + .with_template(&DefaultTemplate::Error) .add_child(Html::with(move |_| error403.clone())) .render() { @@ -48,7 +62,7 @@ impl Display for ErrorPage { let error404 = error_page.theme().error404(&mut error_page); if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) - .with_template(Template::ERROR) + .with_template(&DefaultTemplate::Error) .add_child(Html::with(move |_| error404.clone())) .render() { diff --git a/static/css/basic.css b/static/css/basic.css index f87e6fdc..6ffe4c6e 100644 --- a/static/css/basic.css +++ b/static/css/basic.css @@ -1,16 +1,31 @@ +:root { + /* Font families */ + --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif; + --val-font-serif: Georgia,"Times New Roman",serif; + --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; + --val-font-family: var(--val-font-sans); + /* Font size */ + --val-fs--base: 1rem; + /* Font weight */ + --val-fw--base: 400; + /* Line height */ + --val-lh--base: 1.5; + /* Colors */ + --val-color--bg: #fafafa; + --val-color--text: #212529; +} + html { scroll-behavior: smooth; } body { - margin: 0; font-family: var(--val-font-family); font-size: var(--val-fs--base); font-weight: var(--val-fw--base); line-height: var(--val-lh--base); color: var(--val-color--text); background-color: var(--val-color--bg); - -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; } diff --git a/static/css/components.css b/static/css/components.css deleted file mode 100644 index ec5d3f00..00000000 --- a/static/css/components.css +++ /dev/null @@ -1,12 +0,0 @@ -/* Icon component */ - -.icon { - width: 1rem; - height: 1rem; -} - -/* PoweredBy component */ - -.poweredby { - text-align: center; -} diff --git a/static/css/root.css b/static/css/root.css deleted file mode 100644 index aeab1c67..00000000 --- a/static/css/root.css +++ /dev/null @@ -1,212 +0,0 @@ -:root { - --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - --val-font-serif: "Lora","georgia",serif; - --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - --val-font-family: var(--val-font-sans); - - /* Font size */ - --val-fs--x3l: 2.5rem; - --val-fs--x2l: 2rem; - --val-fs--xl: 1.75rem; - --val-fs--l: 1.5rem; - --val-fs--m: 1.25rem; - --val-fs--base: 1rem; - --val-fs--s: 0.875rem; - --val-fs--xs: 0.75rem; - --val-fs--x2s: 0.5625rem; - --val-fs--x3s: 0.375rem; - - /* Font weight */ - --val-fw--light: 300; - --val-fw--base: 400; - --val-fw--bold: 500; - - /* Line height */ - --val-lh--base: 1.5; - --val-lh--header: 1.2; - - --val-max-width: 90rem; -/* - --val-color-rgb: 33,37,41; - --val-main--bg-rgb: 255,255,255; - --val-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - - --line-height-base: 1.6875rem; - --line-height-s: 1.125rem; - --max-bg-color: 98.125rem; -*/ - --val-gap: 1.125rem; -/* - --content-left: 5.625rem; - --site-header-height-wide: var(--val-gap10); - --container-padding: var(--val-gap); -*/ -} -/* -@media (min-width: 75rem) { - :root { - --container-padding:var(--val-gap2); - } -} - -:root { - --scrollbar-width: 0px; - --grid-col-count: 6; - --grid-gap: var(--val-gap); - --grid-gap-count: calc(var(--grid-col-count) - 1); - --grid-full-width: calc(100vw - var(--val-gap2) - var(--scrollbar-width)); - --grid-col-width: calc((var(--grid-full-width) - (var(--grid-gap-count) * var(--grid-gap))) / var(--grid-col-count)); -} - -@media (min-width: 43.75rem) { - :root { - --grid-col-count:14; - --grid-gap: var(--val-gap2); - } -} - -@media (min-width: 62.5rem) { - :root { - --scrollbar-width:0.9375rem; - } -} - -@media (min-width: 75rem) { - :root { - --grid-full-width:calc(100vw - var(--scrollbar-width) - var(--content-left) - var(--val-gap4)); - } -} - -@media (min-width: 90rem) { - :root { - --grid-full-width:calc(var(--max-width) - var(--val-gap4)); - } -} -*/ -:root { - --val-gap-0-15: calc(0.15 * var(--val-gap)); - --val-gap-0-25: calc(0.25 * var(--val-gap)); - --val-gap-0-35: calc(0.35 * var(--val-gap)); - --val-gap-0-5: calc(0.5 * var(--val-gap)); - --val-gap-0-75: calc(0.75 * var(--val-gap)); - --val-gap-1-5: calc(1.5 * var(--val-gap)); - --val-gap-2: calc(2 * var(--val-gap)); - - --primary-hue: 216; - --primary-sat: 60%; - --val-color--primary: hsl(var(--primary-hue), var(--primary-sat), 50%); - --val-color--primary-light: hsl(var(--primary-hue), var(--primary-sat), 60%); - --val-color--primary-dark: hsl(var(--primary-hue), var(--primary-sat), 40%); - --val-color--primary-link: hsl(var(--primary-hue), var(--primary-sat), 55%); - --val-color--primary-link-hover: hsl(var(--primary-hue), var(--primary-sat), 30%); - --val-color--primary-link-active: hsl(var(--primary-hue), var(--primary-sat), 70%); - - --info-hue: 190; - --info-sat: 90%; - --val-color--info: hsl(var(--info-hue), var(--info-sat), 54%); - --val-color--info-light: hsl(var(--info-hue), var(--info-sat), 70%); - --val-color--info-dark: hsl(var(--info-hue), var(--info-sat), 45%); - --val-color--info-link: hsl(var(--info-hue), var(--info-sat), 30%); - --val-color--info-link-hover: hsl(var(--info-hue), var(--info-sat), 20%); - --val-color--info-link-active: hsl(var(--info-hue), var(--info-sat), 40%); - - --success-hue: 150; - --success-sat: 50%; - --val-color--success: hsl(var(--success-hue), var(--success-sat), 50%); - --val-color--success-light: hsl(var(--success-hue), var(--success-sat), 68%); - --val-color--success-dark: hsl(var(--success-hue), var(--success-sat), 38%); - --val-color--success-link: hsl(var(--success-hue), var(--success-sat), 26%); - --val-color--success-link-hover: hsl(var(--success-hue), var(--success-sat), 18%); - --val-color--success-link-active: hsl(var(--success-hue), var(--success-sat), 36%); - - --warning-hue: 44; - --warning-sat: 100%; - --val-color--warning: hsl(var(--warning-hue), var(--warning-sat), 50%); - --val-color--warning-light: hsl(var(--warning-hue), var(--warning-sat), 60%); - --val-color--warning-dark: hsl(var(--warning-hue), var(--warning-sat), 40%); - --val-color--warning-link: hsl(var(--warning-hue), var(--warning-sat), 30%); - --val-color--warning-link-hover: hsl(var(--warning-hue), var(--warning-sat), 20%); - --val-color--warning-link-active: hsl(var(--warning-hue), var(--warning-sat), 38%); - - --danger-hue: 348; - --danger-sat: 86%; - --val-color--danger: hsl(var(--danger-hue), var(--danger-sat), 50%); - --val-color--danger-light: hsl(var(--danger-hue), var(--danger-sat), 60%); - --val-color--danger-dark: hsl(var(--danger-hue), var(--danger-sat), 35%); - --val-color--danger-link: hsl(var(--danger-hue), var(--danger-sat), 25%); - --val-color--danger-link-hover: hsl(var(--danger-hue), var(--danger-sat), 10%); - --val-color--danger-link-active: hsl(var(--danger-hue), var(--danger-sat), 30%); - - --light-hue: 0; - --light-sat: 0%; - --val-color--light: hsl(var(--light-hue), var(--light-sat), 96%); - --val-color--light-light: hsl(var(--light-hue), var(--light-sat), 98%); - --val-color--light-dark: hsl(var(--light-hue), var(--light-sat), 92%); - - --dark-hue: 0; - --dark-sat: 0%; - --val-color--dark: hsl(var(--dark-hue), var(--dark-sat), 25%); - --val-color--dark-light: hsl(var(--dark-hue), var(--dark-sat), 40%); - --val-color--dark-dark: hsl(var(--dark-hue), var(--dark-sat), 8%); - --val-color--dark-link: hsl(var(--dark-hue), var(--dark-sat), 90%); - --val-color--dark-link-hover: hsl(var(--dark-hue), var(--dark-sat), 100%); - --val-color--dark-link-active: hsl(var(--dark-hue), var(--dark-sat), 70%); - - - - - --gray-hue: 201; - --gray-sat: 15%; - --val-color--gray-5: hsl(var(--gray-hue), var(--gray-sat), 5%); - --val-color--gray-10: hsl(var(--gray-hue), var(--gray-sat) ,11%); - --val-color--gray-20: hsl(var(--gray-hue), var(--gray-sat),20%); - --val-color--gray-45: hsl(var(--gray-hue), var(--gray-sat), 44%); - --val-color--gray-60: hsl(var(--gray-hue), var(--gray-sat), 57%); - --val-color--gray-65: hsl(var(--gray-hue), var(--gray-sat), 63%); - --val-color--gray-70: hsl(var(--gray-hue), var(--gray-sat), 72%); - --val-color--gray-90: hsl(var(--gray-hue), var(--gray-sat), 88%); - --val-color--gray-95: hsl(var(--gray-hue), var(--gray-sat), 93%); - --val-color--gray-100: hsl(var(--gray-hue), var(--gray-sat), 97%); - - - - - --val-color--bg: #fafafa; - --val-color--text: #212529; - --val-color--white: #fff; - -/* - - - --color-text-neutral-soft: var(--color--gray-45); - --color-text-neutral-medium: var(--color--gray-20); - --color-text-neutral-loud: var(--color--gray-5); - --color-text-primary-medium: var(--val-color--primary-40); - --color-text-primary-loud: var(--val-color--primary-30); - --color--black: #000; -*/ -/* - --color--red: #e33f1e; - --color--gold: #fdca40; - --color--green: #3fa21c; - --header-height-wide-when-fixed: calc(6 * var(--val-gap)); - --mobile-nav-width: 31.25rem; - - --val-menu--border-radius: 0.625rem; -*/ - --val-border-radius: 0.375rem; - - /* Menu component */ - --val-menu--color-bg: var(--val-color--bg); - --val-menu--color-highlight: #e91e63; - --val-menu--color-border: rgba(0, 0, 0, 0.1); - --val-menu--color-shadow: rgba(0, 0, 0, 0.06); - --val-menu--line-padding: 0.625rem; - --val-menu--line-height: calc(1.875rem + 1px); - --val-menu--item-height: calc(var(--val-menu--line-padding) + var(--val-menu--line-height)); - --val-menu--item-width-min: 14rem; - --val-menu--item-width-max: 20rem; - --val-menu--item-gap: 1rem; - --val-menu--trigger-width: 2.675rem; - --val-menu--side-width: 20rem; -} From 9c6888e378f598ca2c126beff855c07eecb3faa4 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 10:53:49 +0100 Subject: [PATCH 198/224] =?UTF-8?q?=E2=9C=A8=20(bootsier):=20A=C3=B1ade=20?= =?UTF-8?q?plantilla=20est=C3=A1ndar=20propia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/navbar-menus.rs | 2 +- extensions/pagetop-bootsier/src/lib.rs | 49 ++++++++++++++----- .../static/scss/_customs.scss | 6 +++ static/css/intro.css | 11 ++++- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 341d394a..079508e9 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -95,7 +95,7 @@ impl Extension for SuperMenu { })), )); - InRegion::Named("header").add(Child::with( + InRegion::Global(&DefaultRegion::Header).add(Child::with( Container::new() .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) .add_child(navbar_menu), diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index fb9b7206..5c88959a 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -102,6 +102,34 @@ pub mod prelude { pub use crate::theme::*; } +/// Plantillas que Bootsier añade. +#[derive(AutoDefault)] +pub enum BootsierTemplate { + /// Plantilla predeterminada de Bootsier. + #[default] + Standard, +} + +impl Template for BootsierTemplate { + fn render(&'static self, cx: &mut Context) -> Markup { + match self { + Self::Standard => theme::Container::new() + .with_classes(ClassesOp::Add, "container-wrapper") + .with_width(theme::container::Width::FluidMax( + config::SETTINGS.bootsier.max_width, + )) + .add_child(Html::with(|cx| { + html! { + (DefaultRegion::Header.render(cx)) + (DefaultRegion::Content.render(cx)) + (DefaultRegion::Footer.render(cx)) + } + })), + } + .render(cx) + } +} + /// Implementa el tema. pub struct Bootsier; @@ -117,6 +145,11 @@ impl Extension for Bootsier { } impl Theme for Bootsier { + #[inline] + fn default_template(&self) -> TemplateRef { + &BootsierTemplate::Standard + } + fn before_render_page_body(&self, page: &mut Page) { page.alter_assets(ContextOp::AddStyleSheet( StyleSheet::from("/bootsier/bs/bootstrap.min.css") @@ -127,16 +160,10 @@ impl Theme for Bootsier { JavaScript::defer("/bootsier/js/bootstrap.bundle.min.js") .with_version(BOOTSTRAP_VERSION) .with_weight(-90), - )); - } - - fn render_page_body(&self, page: &mut Page) -> Markup { - theme::Container::new() - .with_id("container-wrapper") - .with_width(theme::container::Width::FluidMax( - config::SETTINGS.bootsier.max_width, - )) - .add_child(Template::named(page.template())) - .render(page.context()) + )) + .alter_child_in( + &DefaultRegion::Footer, + ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ); } } diff --git a/extensions/pagetop-bootsier/static/scss/_customs.scss b/extensions/pagetop-bootsier/static/scss/_customs.scss index 988d7055..45e41001 100644 --- a/extensions/pagetop-bootsier/static/scss/_customs.scss +++ b/extensions/pagetop-bootsier/static/scss/_customs.scss @@ -106,3 +106,9 @@ $utilities: map-merge( ), ) ); + +// Region Footer +.region-footer { + padding: .75rem 0 3rem; + text-align: center; +} diff --git a/static/css/intro.css b/static/css/intro.css index 9c47c5c4..e3de4153 100644 --- a/static/css/intro.css +++ b/static/css/intro.css @@ -17,13 +17,22 @@ --intro-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } +body { + overflow-x: clip; +} + .intro { position: relative; min-width: 350px; color: var(--intro-color); background-color: var(--intro-bg-color); - width: 100%; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; + width: 100vw; + display: flex; flex-direction: column; justify-content: center; From 12e617f35b5a593d5ec939385b551b7f87061f56 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 11:11:39 +0100 Subject: [PATCH 199/224] =?UTF-8?q?=F0=9F=9A=A7=20Afina=20el=20mensaje=20g?= =?UTF-8?q?enerado=20por=20`builder=5Ffn`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- helpers/pagetop-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 5772a6ce..6fa12357 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -366,7 +366,7 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream { alter_name_str ); let with_alter_doc = concat!( - "Modifica la instancia actual (`&mut self`) con los mismos argumentos, ", + "Permite modificar la instancia actual (`&mut self`) con los mismos argumentos, ", "sin consumirla." ); From 76b980017d3d88e5513e7ce340cc813cd98f755f Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 11:14:08 +0100 Subject: [PATCH 200/224] =?UTF-8?q?=F0=9F=92=84=20Mejora=20alineaci=C3=B3n?= =?UTF-8?q?=20del=20texto=20en=20ejemplos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/hello-name.rs | 6 +++++- examples/hello-world.rs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/hello-name.rs b/examples/hello-name.rs index c6a82aaf..b6f9c113 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,11 @@ async fn hello_name( ) -> ResultPage<Markup, ErrorPage> { let name = path.into_inner(); Page::new(request) - .add_child(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) + .add_child(Html::with(move |_| { + html! { + h1 style="text-align: center;" { "Hello " (name) "!" } + } + })) .render() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 64817466..74727ac2 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,11 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { Page::new(request) - .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| { + html! { + h1 style="text-align: center;" { "Hello World!" } + } + })) .render() } From 1fa1ddf5285be06b25083ff2fe0d03ce1f2231ae Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 11:14:34 +0100 Subject: [PATCH 201/224] =?UTF-8?q?=F0=9F=92=A1Retoques=20menores=20en=20c?= =?UTF-8?q?omentarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/html.rs | 9 ++++----- src/core/component/context.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/base/component/html.rs b/src/base/component/html.rs index 1cca9899..48e47bbb 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -51,9 +51,8 @@ impl Html { /// Crea una instancia que generará el `Markup`, con acceso opcional al contexto. /// - /// El método [`prepare_component()`](crate::core::component::Component::prepare_component) - /// delega el renderizado en la función proporcionada, que recibe una referencia mutable al - /// contexto de renderizado ([`Context`]). + /// El método [`Self::prepare_component()`] delega el renderizado a la función que aquí se + /// proporciona, que recibe una referencia mutable al [`Context`]. pub fn with<F>(f: F) -> Self where F: Fn(&mut Context) -> Markup + Send + Sync + 'static, @@ -64,8 +63,8 @@ impl Html { /// Sustituye la función que genera el `Markup`. /// /// Permite a otras extensiones modificar la función de renderizado que se ejecutará cuando - /// [`prepare_component()`](crate::core::component::Component::prepare_component) invoque esta - /// instancia. La nueva función también recibe una referencia al contexto ([`Context`]). + /// [`Self::prepare_component()`] invoque esta instancia. La nueva función también recibe una + /// referencia al [`Context`]. #[builder_fn] pub fn with_fn<F>(mut self, f: F) -> Self where diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 922067f4..4f2cdf18 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -11,7 +11,7 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// Operaciones para modificar recursos asociados al contexto ([`Context`]) de un documento. +/// Operaciones para modificar recursos asociados al [`Context`] de un documento. pub enum ContextOp { /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. SetFavicon(Option<Favicon>), @@ -386,7 +386,7 @@ impl Context { /// 3. Un idioma válido extraído de la cabecera `Accept-Language` del navegador. /// 4. Y si ninguna de las opciones anteriores aplica, se usa el idioma de respaldo (`"en-US"`). /// -/// Resulta útil para usar un contexto ([`Context`]) como fuente de traducción en +/// Resulta útil para usar el [`Context`] como fuente de traducción en /// [`L10n::lookup()`](crate::locale::L10n::lookup) o [`L10n::using()`](crate::locale::L10n::using). impl LangId for Context { fn langid(&self) -> &'static LanguageIdentifier { From af26e6aef9ef1c7a65ac6910dfeed71bd297ca61 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 11:42:03 +0100 Subject: [PATCH 202/224] =?UTF-8?q?=F0=9F=8C=90=20Normaliza=20textos=20y?= =?UTF-8?q?=20localizaci=C3=B3n=20a=20*snake=5Fcase*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/locale/en-US/bootsier.ftl | 10 +++++----- .../src/locale/en-US/regions.ftl | 18 +++++++++--------- .../src/locale/es-ES/bootsier.ftl | 10 +++++----- .../src/locale/es-ES/regions.ftl | 18 +++++++++--------- .../src/theme/image/component.rs | 2 +- .../pagetop-bootsier/src/theme/navbar.rs | 2 +- .../pagetop-bootsier/src/theme/navbar/item.rs | 4 ++-- src/core/extension/definition.rs | 4 ++-- src/core/theme.rs | 6 +++--- src/core/theme/definition.rs | 4 ++-- src/html/attr_l10n.rs | 2 +- src/locale.rs | 2 +- src/locale/en-US/test.ftl | 6 +++--- src/locale/es-ES/test.ftl | 6 +++--- tests/locale.rs | 8 ++++---- 15 files changed, 51 insertions(+), 51 deletions(-) diff --git a/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl b/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl index 0e8969cd..2454c84e 100644 --- a/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl +++ b/extensions/pagetop-bootsier/src/locale/en-US/bootsier.ftl @@ -1,5 +1,5 @@ -e404-description = Oops! Page Not Found -e404-message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable. -e500-description = Oops! Unexpected Error -e500-message = We're having an issue. Please report this error to an administrator. -back-homepage = Back to homepage +e404_description = Oops! Page Not Found +e404_message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable. +e500_description = Oops! Unexpected Error +e500_message = We're having an issue. Please report this error to an administrator. +back_homepage = Back to homepage diff --git a/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl b/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl index f3b76e22..af830a4e 100644 --- a/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl +++ b/extensions/pagetop-bootsier/src/locale/en-US/regions.ftl @@ -1,9 +1,9 @@ -header = Header -nav_branding = Navigation branding region -nav_main = Main navigation region -nav_additional = Additional navigation region (eg search form, social icons, etc) -breadcrumb = Breadcrumb -content = Main content -sidebar_first = Sidebar first -sidebar_second = Sidebar second -footer = Footer +region_header = Header +region_nav_branding = Navigation branding region +region_nav_main = Main navigation region +region_nav_additional = Additional navigation region (eg search form, social icons, etc) +region_breadcrumb = Breadcrumb +region_content = Main content +region_sidebar_first = Sidebar first +region_sidebar_second = Sidebar second +region_footer = Footer diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl index 998b54f2..bd97c2ed 100644 --- a/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl +++ b/extensions/pagetop-bootsier/src/locale/es-ES/bootsier.ftl @@ -1,5 +1,5 @@ -e404-description = ¡Vaya! Página No Encontrada -e404-message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente. -e500-description = ¡Vaya! Error Inesperado -e500-message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador. -back-homepage = Volver al inicio +e404_description = ¡Vaya! Página No Encontrada +e404_message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente. +e500_description = ¡Vaya! Error Inesperado +e500_message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador. +back_homepage = Volver al inicio diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl index 674fc4b1..e4665add 100644 --- a/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl +++ b/extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl @@ -1,9 +1,9 @@ -header = Cabecera -nav_branding = Navegación y marca -nav_main = Navegación principal -nav_additional = Navegación adicional (p.e. formulario de búsqueda, iconos sociales, etc.) -breadcrumb = Ruta de posicionamiento -content = Contenido principal -sidebar_first = Barra lateral primera -sidebar_second = Barra lateral segunda -footer = Pie +region_header = Cabecera +region_nav_branding = Navegación y marca +region_nav_main = Navegación principal +region_nav_additional = Navegación adicional (p.e. formulario de búsqueda, iconos sociales, etc.) +region_breadcrumb = Ruta de posicionamiento +region_content = Contenido principal +region_sidebar_first = Barra lateral primera +region_sidebar_second = Barra lateral segunda +region_footer = Pie diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index bc3f2c9d..31fa7083 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -107,7 +107,7 @@ impl Image { self } - /// Define el texto alternativo localizado ([`L10n`]) para la imagen. + /// Define un *texto localizado* ([`L10n`]) alternativo para la imagen. /// /// Se recomienda siempre aportar un texto alternativo salvo que la imagen sea puramente /// decorativa. diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 7b958d7f..bd605508 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -2,7 +2,7 @@ //! //! Cada [`navbar::Item`](crate::theme::navbar::Item) representa un elemento individual de la barra //! de navegación [`Navbar`], con distintos comportamientos según su finalidad, como menús -//! [`Nav`](crate::theme::Nav) o textos localizados usando [`L10n`](pagetop::locale::L10n). +//! [`Nav`](crate::theme::Nav) o *textos localizados* usando [`L10n`](pagetop::locale::L10n). //! //! También puede mostrar una marca de identidad ([`navbar::Brand`](crate::theme::navbar::Brand)) //! que identifique la compañía, producto o nombre del proyecto asociado a la solución web. diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index 7e912a49..1b1c5204 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -20,7 +20,7 @@ pub enum Item { Brand(Typed<navbar::Brand>), /// Representa un menú de navegación [`Nav`](crate::theme::Nav). Nav(Typed<Nav>), - /// Representa un texto libre localizado. + /// Representa un *texto localizado* libre. Text(L10n), } @@ -88,7 +88,7 @@ impl Item { Self::Nav(Typed::with(item)) } - /// Crea un elemento de texto localizado, mostrado sin interacción. + /// Crea un elemento con un *texto localizado*, mostrado sin interacción. pub fn text(item: L10n) -> Self { Self::Text(item) } diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 304eae87..331b1d8a 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -24,7 +24,7 @@ use crate::{actions_boxed, service}; /// } /// ``` pub trait Extension: AnyInfo + Send + Sync { - /// Nombre localizado de la extensión legible para el usuario. + /// Nombre de la extensión como *texto localizado* legible para el usuario. /// /// Predeterminado por el [`short_name()`](AnyInfo::short_name) del tipo asociado a la /// extensión. @@ -32,7 +32,7 @@ pub trait Extension: AnyInfo + Send + Sync { L10n::n(self.short_name()) } - /// Descripción corta localizada de la extensión para paneles, listados, etc. + /// Descripción corta de la extensión como *texto localizado* para paneles, listados, etc. /// /// Por defecto devuelve un valor vacío (`L10n::default()`). fn description(&self) -> L10n { diff --git a/src/core/theme.rs b/src/core/theme.rs index e238df79..8485bf70 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -55,7 +55,7 @@ pub trait Region { /// las clases del contenedor de la región (`"region region-<name>"`). fn name(&self) -> &'static str; - /// Devuelve la etiqueta de accesibilidad localizada asociada a la región. + /// Devuelve un *texto localizado* como etiqueta de accesibilidad asociada a la región. /// /// En la implementación predeterminada de [`Self::render()`], este valor se usa como /// `aria-label` del contenedor de la región. @@ -64,8 +64,8 @@ pub trait Region { /// Renderiza el contenedor de la región. /// /// Por defecto, recupera del [`Context`] el contenido de la región y, si no está vacío, lo - /// envuelve en un `<div>` con clases `"region region-<name>"` y un `aria-label` basado en la - /// etiqueta localizada de la región: + /// envuelve en un `<div>` con clases `"region region-<name>"` y un `aria-label` basado en el + /// *texto localizado* de la etiqueta asociada a la región: /// /// ```html /// <div class="region region-<name>" role="region" aria-label="<label>"> diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 4ff38fc1..a0edbd9a 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -158,7 +158,7 @@ pub trait Theme: Extension + Send + Sync { /// Contenido predeterminado para la página de error "*403 - Forbidden*". /// /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la - /// página de error, manteniendo o no el mensaje de los textos localizados. + /// página de error, manteniendo o no el mensaje de los *textos localizados*. fn error403(&self, page: &mut Page) -> Markup { html! { div { h1 { (L10n::l("error403_notice").using(page)) } } } } @@ -166,7 +166,7 @@ pub trait Theme: Extension + Send + Sync { /// Contenido predeterminado para la página de error "*404 - Not Found*". /// /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la - /// página de error, manteniendo o no el mensaje de los textos localizados. + /// página de error, manteniendo o no el mensaje de los *textos localizados*. fn error404(&self, page: &mut Page) -> Markup { html! { div { h1 { (L10n::l("error404_notice").using(page)) } } } } diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index d25869c5..a92e8344 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -10,7 +10,7 @@ use crate::{builder_fn, AutoDefault}; /// ```rust /// # use pagetop::prelude::*; /// // Traducción por clave en las locales por defecto de PageTop. -/// let hello = AttrL10n::new(L10n::l("test-hello-world")); +/// let hello = AttrL10n::new(L10n::l("test_hello_world")); /// /// // Español disponible. /// assert_eq!( diff --git a/src/locale.rs b/src/locale.rs index 9ee87d7f..0c1ad345 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -310,7 +310,7 @@ enum L10nOp { Translate(Cow<'static, str>), } -/// Crea instancias para traducir textos localizados. +/// Crea instancias para traducir *textos localizados*. /// /// Cada instancia puede representar: /// diff --git a/src/locale/en-US/test.ftl b/src/locale/en-US/test.ftl index 3c317fcb..9f343491 100644 --- a/src/locale/en-US/test.ftl +++ b/src/locale/en-US/test.ftl @@ -1,6 +1,6 @@ -test-hello-world = Hello world! -test-hello-user = Hello, { $userName }! -test-shared-photos = +test_hello_world = Hello world! +test_hello_user = Hello, { $userName }! +test_shared_photos = { $userName } { $photoCount -> [one] added a new photo *[other] added { $photoCount } new photos diff --git a/src/locale/es-ES/test.ftl b/src/locale/es-ES/test.ftl index 02cd22e3..ee91497e 100644 --- a/src/locale/es-ES/test.ftl +++ b/src/locale/es-ES/test.ftl @@ -1,6 +1,6 @@ -test-hello-world = ¡Hola mundo! -test-hello-user = ¡Hola, { $userName }! -test-shared-photos = +test_hello_world = ¡Hola mundo! +test_hello_user = ¡Hola, { $userName }! +test_shared_photos = { $userName } { $photoCount -> [one] ha añadido una nueva foto *[other] ha añadido { $photoCount } nuevas fotos diff --git a/tests/locale.rs b/tests/locale.rs index ee7ac27c..91829e62 100644 --- a/tests/locale.rs +++ b/tests/locale.rs @@ -12,7 +12,7 @@ async fn literal_text() { async fn translation_without_args() { let _app = service::test::init_service(Application::new().test()).await; - let l10n = L10n::l("test-hello-world"); + let l10n = L10n::l("test_hello_world"); let translation = l10n.lookup(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola mundo!".to_string())); } @@ -21,7 +21,7 @@ async fn translation_without_args() { async fn translation_with_args() { let _app = service::test::init_service(Application::new().test()).await; - let l10n = L10n::l("test-hello-user").with_arg("userName", "Manuel"); + let l10n = L10n::l("test_hello_user").with_arg("userName", "Manuel"); let translation = l10n.lookup(&LangMatch::resolve("es-ES")); assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); } @@ -30,7 +30,7 @@ async fn translation_with_args() { async fn translation_with_plural_and_select() { let _app = service::test::init_service(Application::new().test()).await; - let l10n = L10n::l("test-shared-photos").with_args(vec![ + let l10n = L10n::l("test_shared_photos").with_args(vec![ ("userName", "Roberto"), ("photoCount", "3"), ("userGender", "male"), @@ -43,7 +43,7 @@ async fn translation_with_plural_and_select() { async fn check_fallback_language() { let _app = service::test::init_service(Application::new().test()).await; - let l10n = L10n::l("test-hello-world"); + let l10n = L10n::l("test_hello_world"); let translation = l10n.lookup(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". assert_eq!(translation, Some("Hello world!".to_string())); } From 33669d90f60a80dfc4284f4c511f0a6228cc743e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 30 Nov 2025 12:51:45 +0100 Subject: [PATCH 203/224] =?UTF-8?q?=E2=9C=A8=20(pagetop):=20A=C3=B1ade=20m?= =?UTF-8?q?acro=20`Getters`=20para=20estructuras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 12 +++ Cargo.toml | 1 + .../src/theme/container/component.rs | 42 +++----- .../src/theme/dropdown/component.rs | 98 +++++-------------- .../src/theme/dropdown/item.rs | 22 ++--- extensions/pagetop-bootsier/src/theme/icon.rs | 23 +---- .../src/theme/image/component.rs | 40 +++----- .../src/theme/nav/component.rs | 38 ++----- .../pagetop-bootsier/src/theme/nav/item.rs | 22 ++--- .../src/theme/navbar/brand.rs | 38 ++----- .../src/theme/navbar/component.rs | 48 +++------ .../src/theme/offcanvas/component.rs | 70 ++++--------- src/base/component/block.rs | 30 ++---- src/base/component/intro.rs | 46 +++------ src/base/component/poweredby.rs | 10 +- src/lib.rs | 2 + src/prelude.rs | 2 +- 17 files changed, 154 insertions(+), 390 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f9fa42e..4912a6df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -982,6 +982,17 @@ dependencies = [ "wasi 0.14.7+wasi-0.2.4", ] +[[package]] +name = "getter-methods" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43d815f896a3c730f0d76b8348a1700dc8d8fd6c377e4590d531bdd646574d8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1558,6 +1569,7 @@ dependencies = [ "config", "figlet-rs", "fluent-templates", + "getter-methods", "indoc", "itoa", "pagetop-aliner", diff --git a/Cargo.toml b/Cargo.toml index b1bae0a6..d8c42c6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ colored = "3.0" concat-string = "1.0" config = { version = "0.15", default-features = false, features = ["toml"] } figlet-rs = "0.1" +getter-methods = "2.0" indoc = "2.0" itoa = "1.0" parking_lot = "0.12" diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 068d24a3..d20ca154 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -6,14 +6,18 @@ use crate::prelude::*; /// /// Envuelve un contenido con la etiqueta HTML indicada por [`container::Kind`]. Sólo se renderiza /// si existen componentes hijos (*children*). -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Container { - id : AttrId, - classes : AttrClasses, - container_kind : container::Kind, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al contenedor. + classes: AttrClasses, + /// Devuelve el tipo semántico del contenedor. + container_kind: container::Kind, + /// Devuelve el comportamiento para el ancho del contenedor. container_width: container::Width, - children : Children, + /// Devuelve la lista de componentes (`children`) del contenedor. + children: Children, } impl Component for Container { @@ -26,7 +30,7 @@ impl Component for Container { } fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes(ClassesOp::Prepend, self.width().to_class()); + self.alter_classes(ClassesOp::Prepend, self.container_width().to_class()); } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { @@ -34,7 +38,7 @@ impl Component for Container { if output.is_empty() { return PrepareMarkup::None; } - let style = match self.width() { + let style = match self.container_width() { container::Width::FluidMax(w) if w.is_measurable() => { Some(join!("max-width: ", w.to_string(), ";")) } @@ -159,26 +163,4 @@ impl Container { self.children.alter_child(op); self } - - // **< Container GETTERS >********************************************************************** - - /// Devuelve las clases CSS asociadas al contenedor. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el tipo semántico del contenedor. - pub fn container_kind(&self) -> &container::Kind { - &self.container_kind - } - - /// Devuelve el comportamiento para el ancho del contenedor. - pub fn width(&self) -> &container::Width { - &self.container_width - } - - /// Devuelve la lista de componentes (`children`) del contenedor. - pub fn children(&self) -> &Children { - &self.children - } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 5d27daf5..dc02b281 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -19,21 +19,32 @@ use crate::LOCALES_BOOTSIER; /// /// Ver ejemplo en el módulo [`dropdown`]. /// Si no contiene elementos, el componente **no se renderiza**. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Dropdown { - id : AttrId, - classes : AttrClasses, - title : L10n, - button_size : ButtonSize, - button_color : ButtonColor, - button_split : bool, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al menú desplegable. + classes: AttrClasses, + /// Devuelve el título del menú desplegable. + title: L10n, + /// Devuelve el tamaño configurado del botón. + button_size: ButtonSize, + /// Devuelve el color/estilo configurado del botón. + button_color: ButtonColor, + /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*). + button_split: bool, + /// Devuelve si el botón del menú está integrado en un grupo de botones. button_grouped: bool, - auto_close : dropdown::AutoClose, - direction : dropdown::Direction, - menu_align : dropdown::MenuAlign, - menu_position : dropdown::MenuPosition, - items : Children, + /// Devuelve la política de cierre automático del menú desplegado. + auto_close: dropdown::AutoClose, + /// Devuelve la dirección de despliegue configurada. + direction: dropdown::Direction, + /// Devuelve la configuración de alineación horizontal del menú desplegable. + menu_align: dropdown::MenuAlign, + /// Devuelve la posición configurada para el menú desplegable. + menu_position: dropdown::MenuPosition, + /// Devuelve la lista de elementos del menú. + items: Children, } impl Component for Dropdown { @@ -48,7 +59,7 @@ impl Component for Dropdown { fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes( ClassesOp::Prepend, - self.direction().class_with(self.button_grouped()), + self.direction().class_with(*self.button_grouped()), ); } @@ -82,7 +93,7 @@ impl Component for Dropdown { }); // Renderizado en modo split (dos botones) o simple (un botón). - @if self.button_split() { + @if *self.button_split() { // Botón principal (acción/etiqueta). @let btn = html! { button @@ -242,61 +253,4 @@ impl Dropdown { self.items.alter_typed(op); self } - - // **< Dropdown GETTERS >*********************************************************************** - - /// Devuelve las clases CSS asociadas al menú desplegable. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el título del menú desplegable. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve el tamaño configurado del botón. - pub fn button_size(&self) -> &ButtonSize { - &self.button_size - } - - /// Devuelve el color/estilo configurado del botón. - pub fn button_color(&self) -> &ButtonColor { - &self.button_color - } - - /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*). - pub fn button_split(&self) -> bool { - self.button_split - } - - /// Devuelve si el botón del menú está integrado en un grupo de botones. - pub fn button_grouped(&self) -> bool { - self.button_grouped - } - - /// Devuelve la política de cierre automático del menú desplegado. - pub fn auto_close(&self) -> &dropdown::AutoClose { - &self.auto_close - } - - /// Devuelve la dirección de despliegue configurada. - pub fn direction(&self) -> &dropdown::Direction { - &self.direction - } - - /// Devuelve la configuración de alineación horizontal del menú desplegable. - pub fn menu_align(&self) -> &dropdown::MenuAlign { - &self.menu_align - } - - /// Devuelve la posición configurada para el menú desplegable. - pub fn menu_position(&self) -> &dropdown::MenuPosition { - &self.menu_position - } - - /// Devuelve la lista de elementos (`children`) del menú. - pub fn items(&self) -> &Children { - &self.items - } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 2f62f286..1ff894e8 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -42,11 +42,13 @@ pub enum ItemKind { /// /// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, /// manteniendo una interfaz común para renderizar todos los elementos del menú. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Item { - id : AttrId, - classes : AttrClasses, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al elemento. + classes: AttrClasses, + /// Devuelve el tipo de elemento representado. item_kind: ItemKind, } @@ -266,16 +268,4 @@ impl Item { self.classes.alter_value(op, classes); self } - - // **< Item GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al elemento. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el tipo de elemento representado. - pub fn item_kind(&self) -> &ItemKind { - &self.item_kind - } } diff --git a/extensions/pagetop-bootsier/src/theme/icon.rs b/extensions/pagetop-bootsier/src/theme/icon.rs index 73e5ac4e..d0639e52 100644 --- a/extensions/pagetop-bootsier/src/theme/icon.rs +++ b/extensions/pagetop-bootsier/src/theme/icon.rs @@ -13,11 +13,11 @@ pub enum IconKind { }, } -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Icon { - classes : AttrClasses, - icon_kind : IconKind, + /// Devuelve las clases CSS asociadas al icono. + classes: AttrClasses, + icon_kind: IconKind, aria_label: AttrL10n, } @@ -116,19 +116,4 @@ impl Icon { self.aria_label.alter_value(label); self } - - // **< Icon GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al icono. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - pub fn icon_kind(&self) -> &IconKind { - &self.icon_kind - } - - pub fn aria_label(&self) -> &AttrL10n { - &self.aria_label - } } diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index 31fa7083..4e30ff06 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -9,14 +9,18 @@ use crate::prelude::*; /// ([`classes::Border`](crate::theme::classes::Border)) y **redondeo de esquinas** /// ([`classes::Rounded`](crate::theme::classes::Rounded)). /// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`]. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Image { - id : AttrId, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas a la imagen. classes: AttrClasses, - size : image::Size, - source : image::Source, - alt : AttrL10n, + /// Devuelve las dimensiones de la imagen. + size: image::Size, + /// Devuelve el origen de la imagen. + source: image::Source, + /// Devuelve el texto alternativo localizado. + alternative: AttrL10n, } impl Component for Image { @@ -113,29 +117,7 @@ impl Image { /// decorativa. #[builder_fn] pub fn with_alternative(mut self, alt: L10n) -> Self { - self.alt.alter_value(alt); + self.alternative.alter_value(alt); self } - - // **< Image GETTERS >************************************************************************** - - /// Devuelve las clases CSS asociadas a la imagen. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve las dimensiones de la imagen. - pub fn size(&self) -> &image::Size { - &self.size - } - - /// Devuelve el origen de la imagen. - pub fn source(&self) -> &image::Source { - &self.source - } - - /// Devuelve el texto alternativo localizado. - pub fn alternative(&self) -> &AttrL10n { - &self.alt - } } diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index 2bd43774..d6aaa3c8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -10,14 +10,18 @@ use crate::prelude::*; /// /// Ver ejemplo en el módulo [`nav`]. /// Si no contiene elementos, el componente **no se renderiza**. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Nav { - id : AttrId, - classes : AttrClasses, - items : Children, - nav_kind : nav::Kind, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al menú. + classes: AttrClasses, + /// Devuelve el estilo visual seleccionado. + nav_kind: nav::Kind, + /// Devuelve la distribución y orientación seleccionada. nav_layout: nav::Layout, + /// Devuelve la lista de elementos del menú. + items: Children, } impl Component for Nav { @@ -110,26 +114,4 @@ impl Nav { self.items.alter_typed(op); self } - - // **< Nav GETTERS >**************************************************************************** - - /// Devuelve las clases CSS asociadas al menú. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el estilo visual seleccionado. - pub fn nav_kind(&self) -> &nav::Kind { - &self.nav_kind - } - - /// Devuelve la distribución y orientación seleccionada. - pub fn nav_layout(&self) -> &nav::Layout { - &self.nav_layout - } - - /// Devuelve la lista de elementos (`children`) del menú. - pub fn items(&self) -> &Children { - &self.items - } } diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index a79947cd..0aa30037 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -72,11 +72,13 @@ impl ItemKind { /// /// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, /// manteniendo una interfaz común para renderizar todos los elementos del menú. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Item { - id : AttrId, - classes : AttrClasses, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al elemento. + classes: AttrClasses, + /// Devuelve el tipo de elemento representado. item_kind: ItemKind, } @@ -269,16 +271,4 @@ impl Item { self.classes.alter_value(op, classes); self } - - // **< Item GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al elemento. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el tipo de elemento representado. - pub fn item_kind(&self) -> &ItemKind { - &self.item_kind - } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index e969b0e4..eb2d17a8 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -11,16 +11,20 @@ use crate::prelude::*; /// - Si no hay imagen ([`with_image()`](Self::with_image)) ni título /// ([`with_title()`](Self::with_title)), la marca de identidad no se renderiza. /// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Brand { - id : AttrId, - image : Typed<Image>, + #[getters(skip)] + id: AttrId, + /// Devuelve la imagen de marca (si la hay). + image: Typed<Image>, + /// Devuelve el título de la identidad de marca. #[default(_code = "L10n::n(&global::SETTINGS.app.name)")] - title : L10n, + title: L10n, + /// Devuelve el eslogan de la marca. slogan: L10n, + /// Devuelve la función que resuelve la URL asociada a la marca (si existe). #[default(_code = "Some(|_| \"/\")")] - path : Option<FnPathByContext>, + path: Option<FnPathByContext>, } impl Component for Brand { @@ -86,26 +90,4 @@ impl Brand { self.path = path; self } - - // **< Brand GETTERS >************************************************************************** - - /// Devuelve la imagen de marca (si la hay). - pub fn image(&self) -> &Typed<Image> { - &self.image - } - - /// Devuelve el título de la identidad de marca. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve el eslogan de la marca. - pub fn slogan(&self) -> &L10n { - &self.slogan - } - - /// Devuelve la función que resuelve la URL asociada a la marca (si existe). - pub fn path(&self) -> &Option<FnPathByContext> { - &self.path - } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 225c2af5..21fc87f6 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -14,15 +14,20 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Ver ejemplos en el módulo [`navbar`]. /// Si no contiene elementos, el componente **no se renderiza**. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Navbar { - id : AttrId, - classes : AttrClasses, - expand : BreakPoint, - layout : navbar::Layout, - position : navbar::Position, - items : Children, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas a la barra de navegación. + classes: AttrClasses, + /// Devuelve el punto de ruptura configurado. + expand: BreakPoint, + /// Devuelve la disposición configurada para la barra de navegación. + layout: navbar::Layout, + /// Devuelve la posición configurada para la barra de navegación. + position: navbar::Position, + /// Devuelve la lista de contenidos. + items: Children, } impl Component for Navbar { @@ -263,31 +268,4 @@ impl Navbar { self.items.alter_typed(op); self } - - // **< Navbar GETTERS >************************************************************************* - - /// Devuelve las clases CSS asociadas a la barra de navegación. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el punto de ruptura configurado. - pub fn expand(&self) -> &BreakPoint { - &self.expand - } - - /// Devuelve la disposición configurada para la barra de navegación. - pub fn layout(&self) -> &navbar::Layout { - &self.layout - } - - /// Devuelve la posición configurada para la barra de navegación. - pub fn position(&self) -> &navbar::Position { - &self.position - } - - /// Devuelve la lista de contenidos (`children`). - pub fn items(&self) -> &Children { - &self.items - } } diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index f17fe97a..8b543cfc 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -21,18 +21,26 @@ use crate::LOCALES_BOOTSIER; /// /// Ver ejemplo en el módulo [`offcanvas`]. /// Si no contiene elementos, el componente **no se renderiza**. -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Offcanvas { - id : AttrId, - classes : AttrClasses, - title : L10n, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al panel. + classes: AttrClasses, + /// Devuelve el título del panel. + title: L10n, + /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. breakpoint: BreakPoint, - backdrop : offcanvas::Backdrop, - scrolling : offcanvas::BodyScroll, - placement : offcanvas::Placement, + /// Devuelve el comportamiento configurado para la capa de fondo. + backdrop: offcanvas::Backdrop, + /// Indica si la página principal puede desplazarse mientras el panel está abierto. + body_scroll: offcanvas::BodyScroll, + /// Devuelve la posición de inicio del panel. + placement: offcanvas::Placement, + /// Devuelve el estado inicial del panel. visibility: offcanvas::Visibility, - children : Children, + /// Devuelve la lista de componentes (`children`) del panel. + children: Children, } impl Component for Offcanvas { @@ -109,7 +117,7 @@ impl Offcanvas { /// Permite o bloquea el desplazamiento de la página principal mientras el panel está abierto. #[builder_fn] pub fn with_body_scroll(mut self, scrolling: offcanvas::BodyScroll) -> Self { - self.scrolling = scrolling; + self.body_scroll = scrolling; self } @@ -141,48 +149,6 @@ impl Offcanvas { self } - // **< Offcanvas GETTERS >********************************************************************** - - /// Devuelve las clases CSS asociadas al panel. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el título del panel. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. - pub fn breakpoint(&self) -> &BreakPoint { - &self.breakpoint - } - - /// Devuelve el comportamiento configurado para la capa de fondo. - pub fn backdrop(&self) -> &offcanvas::Backdrop { - &self.backdrop - } - - /// Indica si la página principal puede desplazarse mientras el panel está abierto. - pub fn body_scroll(&self) -> &offcanvas::BodyScroll { - &self.scrolling - } - - /// Devuelve la posición de inicio del panel. - pub fn placement(&self) -> &offcanvas::Placement { - &self.placement - } - - /// Devuelve el estado inicial del panel. - pub fn visibility(&self) -> &offcanvas::Visibility { - &self.visibility - } - - /// Devuelve la lista de componentes (`children`) del panel. - pub fn children(&self) -> &Children { - &self.children - } - // **< Offcanvas HELPERS >********************************************************************** pub(crate) fn render_offcanvas(&self, cx: &mut Context, extra: Option<&Children>) -> Markup { diff --git a/src/base/component/block.rs b/src/base/component/block.rs index b2a754e5..3be418cf 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -4,12 +4,15 @@ use crate::prelude::*; /// /// Los bloques se utilizan como contenedores de otros componentes o contenidos, con un título /// opcional y un cuerpo que sólo se renderiza si existen componentes hijos (*children*). -#[rustfmt::skip] -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct Block { - id : AttrId, - classes : AttrClasses, - title : L10n, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al bloque. + classes: AttrClasses, + /// Devuelve el título del bloque. + title: L10n, + /// Devuelve la lista de componentes hijo del bloque. children: Children, } @@ -83,21 +86,4 @@ impl Block { self.children.alter_child(op); self } - - // **< Block GETTERS >************************************************************************** - - /// Devuelve las clases CSS asociadas al bloque. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve el título del bloque. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve la lista de componentes (`children`) del bloque. - pub fn children(&self) -> &Children { - &self.children - } } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 7a3b2812..64d2dddd 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -76,12 +76,17 @@ pub enum IntroOpening { /// })), /// ); /// ``` -#[rustfmt::skip] +#[derive(Getters)] pub struct Intro { - title : L10n, - slogan : L10n, - button : Option<(L10n, FnPathByContext)>, - opening : IntroOpening, + /// Devuelve el título de entrada. + title: L10n, + /// Devuelve el eslogan de la entrada. + slogan: L10n, + /// Devuelve el botón de llamada a la acción, si existe. + button: Option<(L10n, FnPathByContext)>, + /// Devuelve el modo de apertura configurado. + opening: IntroOpening, + /// Devuelve la lista de componentes hijo de la intro. children: Children, } @@ -110,7 +115,7 @@ impl Component for Intro { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - if self.opening() == IntroOpening::PageTop { + if *self.opening() == IntroOpening::PageTop { cx.alter_assets(ContextOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| util::indoc!(r#" try { @@ -163,7 +168,7 @@ impl Component for Intro { } } div class="intro-text__children" { - @if self.opening() == IntroOpening::PageTop { + @if *self.opening() == IntroOpening::PageTop { p { (L10n::l("intro_text1").using(cx)) } div id="intro-badges" { img @@ -289,31 +294,4 @@ impl Intro { self.children.alter_child(op); self } - - // **< Intro GETTERS >************************************************************************** - - /// Devuelve el título de entrada. - pub fn title(&self) -> &L10n { - &self.title - } - - /// Devuelve el eslogan de la entrada. - pub fn slogan(&self) -> &L10n { - &self.slogan - } - - /// Devuelve el botón de llamada a la acción, si existe. - pub fn button(&self) -> Option<(&L10n, &FnPathByContext)> { - self.button.as_ref().map(|(txt, lnk)| (txt, lnk)) - } - - /// Devuelve el modo de apertura configurado. - pub fn opening(&self) -> IntroOpening { - self.opening - } - - /// Devuelve la lista de componentes (`children`) de la intro. - pub fn children(&self) -> &Children { - &self.children - } } diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 5a2dc5b2..245f16b4 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -8,8 +8,9 @@ const LINK: &str = "<a href=\"https://pagetop.cillero.es\" rel=\"noopener norefe /// Por defecto, usando [`default()`](Self::default) sólo se muestra un reconocimiento a PageTop. /// Sin embargo, se puede usar [`new()`](Self::new) para crear una instancia con un texto de /// copyright predeterminado. -#[derive(AutoDefault)] +#[derive(AutoDefault, Getters)] pub struct PoweredBy { + /// Devuelve el texto de copyright actual, si existe. copyright: Option<String>, } @@ -56,11 +57,4 @@ impl PoweredBy { self.copyright = copyright.map(Into::into); self } - - // **< PoweredBy GETTERS >********************************************************************** - - /// Devuelve el texto de copyright actual, si existe. - pub fn copyright(&self) -> Option<&str> { - self.copyright.as_deref() - } } diff --git a/src/lib.rs b/src/lib.rs index caa74536..8013de6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,6 +137,8 @@ pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; pub use pagetop_statics::{resource, StaticResource}; +pub use getter_methods::Getters; + /// Contenedor para un conjunto de recursos embebidos. #[derive(AutoDefault)] pub struct StaticResources { diff --git a/src/prelude.rs b/src/prelude.rs index 1d649fec..be36a1d3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,7 +6,7 @@ pub use crate::PAGETOP_VERSION; pub use crate::{builder_fn, html, main, test}; -pub use crate::{AutoDefault, StaticResources, UniqueId, Weight}; +pub use crate::{AutoDefault, Getters, StaticResources, UniqueId, Weight}; // MACROS. From 498df42b5bcefe3d0376c9b0c55a409138095b35 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 06:41:52 +0100 Subject: [PATCH 204/224] =?UTF-8?q?=F0=9F=8E=A8=20(pagetop):=20Mejora=20ge?= =?UTF-8?q?sti=C3=B3n=20de=20URLs=20seg=C3=BAn=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/navbar-menus.rs | 31 +++++++++---------- .../src/theme/dropdown/item.rs | 2 +- .../pagetop-bootsier/src/theme/nav/item.rs | 2 +- .../src/theme/navbar/brand.rs | 2 +- src/base/component/intro.rs | 11 ++++--- src/core/component.rs | 12 +++++-- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 079508e9..8d330542 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -10,10 +10,7 @@ impl Extension for SuperMenu { } fn initialize(&self) { - let home_path = |cx: &Context| match cx.langid().language.as_str() { - "en" => "/en", - _ => "/", - }; + let home_path = |cx: &Context| join!("/lang/", cx.langid().language.as_str()).into(); let navbar_menu = Navbar::brand_left(navbar::Brand::new().with_path(Some(home_path))) .with_expand(BreakPoint::LG) @@ -25,7 +22,7 @@ impl Extension for SuperMenu { )) .add_item(nav::Item::link_blank( L10n::l("sample_menus_item_blank"), - |_| "https://docs.rs/pagetop", + |_| "https://docs.rs/pagetop".into(), )) .add_item(nav::Item::dropdown( Dropdown::new() @@ -33,28 +30,28 @@ impl Extension for SuperMenu { .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_getting_started"), - |_| "/dev/getting-started", + |_| "/dev/getting-started".into(), )) .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_guides"), - |_| "/dev/guides", + |_| "/dev/guides".into(), )) .add_item(dropdown::Item::link_blank( L10n::l("sample_menus_dev_forum"), - |_| "https://forum.example.dev", + |_| "https://forum.example.dev".into(), )) .add_item(dropdown::Item::divider()) .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_rust"), - |_| "/dev/sdks/rust", + |_| "/dev/sdks/rust".into(), )) .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |_| { - "/dev/sdks/js" + "/dev/sdks/js".into() })) .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_python"), - |_| "/dev/sdks/python", + |_| "/dev/sdks/python".into(), )) .add_item(dropdown::Item::divider()) .add_item(dropdown::Item::header(L10n::l( @@ -62,22 +59,22 @@ impl Extension for SuperMenu { ))) .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_auth"), - |_| "/dev/sdks/rust/plugins/auth", + |_| "/dev/sdks/rust/plugins/auth".into(), )) .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_cache"), - |_| "/dev/sdks/rust/plugins/cache", + |_| "/dev/sdks/rust/plugins/cache".into(), )) .add_item(dropdown::Item::divider()) .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) .add_item(dropdown::Item::link_disabled( L10n::l("sample_menus_item_disabled"), - |_| "#", + |_| "#".into(), )), )) .add_item(nav::Item::link_disabled( L10n::l("sample_menus_item_disabled"), - |_| "#", + |_| "#".into(), )), )) .add_item(navbar::Item::nav( @@ -88,10 +85,10 @@ impl Extension for SuperMenu { ) .add_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), - |_| "/auth/sign-up", + |_| "/auth/sign-up".into(), )) .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |_| { - "/auth/login" + "/auth/login".into() })), )); diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 1ff894e8..81f0ab08 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -81,7 +81,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(path)); + let is_current = !*disabled && (current_path == Some(&path)); let mut classes = "dropdown-item".to_string(); if is_current { diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 0aa30037..faf68032 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -115,7 +115,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(path)); + let is_current = !*disabled && (current_path == Some(&path)); let mut classes = "nav-link".to_string(); if is_current { diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index eb2d17a8..2fc31ef7 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -23,7 +23,7 @@ pub struct Brand { /// Devuelve el eslogan de la marca. slogan: L10n, /// Devuelve la función que resuelve la URL asociada a la marca (si existe). - #[default(_code = "Some(|_| \"/\")")] + #[default(_code = "Some(|_| \"/\".into())")] path: Option<FnPathByContext>, } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 64d2dddd..1a7c440b 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -91,13 +91,14 @@ pub struct Intro { } impl Default for Intro { - #[rustfmt::skip] fn default() -> Self { + const BUTTON_LINK: &str = "https://pagetop.cillero.es"; + Intro { - title : L10n::l("intro_default_title"), - slogan : L10n::l("intro_default_slogan").with_arg("app", &global::SETTINGS.app.name), - button : Some((L10n::l("intro_default_button"), |_| "https://pagetop.cillero.es")), - opening : IntroOpening::default(), + title: L10n::l("intro_default_title"), + slogan: L10n::l("intro_default_slogan").with_arg("app", &global::SETTINGS.app.name), + button: Some((L10n::l("intro_default_button"), |_| BUTTON_LINK.into())), + opening: IntroOpening::default(), children: Children::default(), } } diff --git a/src/core/component.rs b/src/core/component.rs index 9c9ade2e..b905a495 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -1,5 +1,7 @@ //! API para construir nuevos componentes. +use std::borrow::Cow; + mod definition; pub use definition::{Component, ComponentRender}; @@ -66,6 +68,10 @@ pub type FnIsRenderable = fn(cx: &Context) -> bool; /// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. /// -/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). Debe -/// devolver una referencia válida durante el renderizado. -pub type FnPathByContext = fn(cx: &Context) -> &str; +/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). El +/// resultado se devuelve como [`Cow<'static, str>`](std::borrow::Cow), lo que permite: +/// +/// - Usar rutas estáticas sin asignaciones adicionales (`"/path".into()`). +/// - Construir rutas dinámicas en tiempo de ejecución (`format!(...).into()`), por ejemplo, en +/// función de parámetros almacenados en [`Context`]. +pub type FnPathByContext = fn(cx: &Context) -> Cow<'static, str>; From da229e494d3ef76fd328a4ff1c242062278fb008 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 06:47:33 +0100 Subject: [PATCH 205/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20soporte=20a=20?= =?UTF-8?q?contenido=20HTML=20en=20=C3=ADtems=20de=20men=C3=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/nav/item.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index faf68032..192f8df8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -25,6 +25,9 @@ pub enum ItemKind { blank: bool, disabled: bool, }, + /// Contenido HTML arbitrario. El componente [`Html`] se renderiza tal cual como elemento del + /// menú, sin añadir ningún comportamiento de navegación adicional. + Html(Typed<Html>), /// Elemento que despliega un menú [`Dropdown`]. Dropdown(Typed<Dropdown>), } @@ -148,6 +151,12 @@ impl Component for Item { }) } + ItemKind::Html(html) => PrepareMarkup::With(html! { + li id=[self.id()] class=[self.classes().get()] { + (html.render(cx)) + } + }), + ItemKind::Dropdown(menu) => { if let Some(dd) = menu.borrow() { let items = dd.items().render(cx); @@ -244,6 +253,17 @@ impl Item { } } + /// Crea un elemento con contenido HTML arbitrario. + /// + /// El contenido se renderiza tal cual lo devuelve el componente [`Html`], dentro de un `<li>` + /// con las clases de navegación asociadas a [`Item`]. + pub fn html(html: Html) -> Self { + Item { + item_kind: ItemKind::Html(Typed::with(html)), + ..Default::default() + } + } + /// Crea un elemento de navegación que contiene un menú desplegable [`Dropdown`]. /// /// Sólo se tienen en cuenta **el título** (si no existe le asigna uno por defecto) y **la lista From c4d0a2f6131ec5ab0972d2108989fee1fe30e56c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 22:48:05 +0100 Subject: [PATCH 206/224] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20config.=20para?= =?UTF-8?q?=20activar=20p=C3=A1gina=20de=20bienvenida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/extension/welcome.rs | 35 +++++++++++++++++++++++++++++------ src/config.rs | 9 +++++---- src/core/extension/all.rs | 6 ++++-- src/global.rs | 11 +++++++++-- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index b875163b..d4a78547 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -1,10 +1,15 @@ use crate::prelude::*; -/// Página de bienvenida predeterminada de PageTop. +/// Página de bienvenida de PageTop. /// -/// Esta extensión se instala por defecto y muestra una página en la ruta raíz (`/`) cuando no se ha -/// configurado ninguna página de inicio personalizada. Permite confirmar que el servidor está -/// funcionando correctamente. +/// Esta extensión se instala por defecto si el ajuste de configuración [`global::App::welcome`] es +/// `true`. Muestra una página de bienvenida de PageTop en la ruta raíz (`/`) o en `/lang/{lang}`, +/// siempre que `{lang}` sea un idioma soportado (si no, devuelve una página de error 404). +/// +/// No obstante, cualquier extensión puede sobrescribir este comportamiento si utiliza estas mismas +/// rutas. +/// +/// Resulta útil en demos o para comprobar rápidamente que el servidor ha arrancado correctamente. pub struct Welcome; impl Extension for Welcome { @@ -17,15 +22,33 @@ impl Extension for Welcome { } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - scfg.route("/", service::web::get().to(homepage)); + scfg.route("/", service::web::get().to(home_page)) + .route("/lang/{lang}", service::web::get().to(home_lang)); } } -async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { +async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { + let language = LangMatch::from_request(Some(&request)); + home(request, &language) +} + +async fn home_lang( + request: HttpRequest, + path: service::web::Path<String>, +) -> ResultPage<Markup, ErrorPage> { + let language = LangMatch::resolve(path.into_inner()); + match language { + LangMatch::Found(_) => home(request, &language), + _ => Err(ErrorPage::NotFound(request)), + } +} + +fn home(request: HttpRequest, language: &impl LangId) -> ResultPage<Markup, ErrorPage> { let app = &global::SETTINGS.app.name; Page::new(request) .with_title(L10n::l("welcome_title")) + .with_langid(language) .add_child( Intro::new() .add_child( diff --git a/src/config.rs b/src/config.rs index 1607a874..32a73083 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -//! Carga las opciones de configuración. +//! Carga las opciones de configuración de la aplicación. //! //! Estos ajustes se obtienen de archivos [TOML](https://toml.io) como pares `clave = valor` que se //! mapean a estructuras **fuertemente tipadas** y valores predefinidos. @@ -125,7 +125,7 @@ const DEFAULT_CONFIG_DIR: &str = "config"; // Modo de ejecución por defecto. const DEFAULT_RUN_MODE: &str = "default"; -/// Valores originales cargados desde los archivos de configuración como pares `clave = valor`. +/// Valores originales de los archivos de configuración como pares `clave = valor`. pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(|| { // CONFIG_DIR (si existe) o DEFAULT_CONFIG_DIR. Si no se puede resolver, se usa tal cual. let dir = env::var_os("CONFIG_DIR").unwrap_or_else(|| DEFAULT_CONFIG_DIR.into()); @@ -229,10 +229,11 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new( macro_rules! include_config { ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { #[doc = concat!( - "Instancia los ajustes de configuración para [`", stringify!($Settings_Type), "`]." + "Ajustes de configuración y **valores por defecto** para ", + "[`", stringify!($Settings_Type), "`]." )] #[doc = ""] - #[doc = "Valores por defecto:"] + #[doc = "Valores predeterminados que se aplican en ausencia de configuración:"] #[doc = "```text"] $( #[doc = concat!($k, " = ", stringify!($v))] diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index fa67671d..b787c9a8 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -29,8 +29,10 @@ pub fn register_extensions(root_extension: Option<ExtensionRef>) { add_to_enabled(&mut enabled_list, extension); } - // Añade la página de bienvenida por defecto a la lista de extensiones habilitadas. - add_to_enabled(&mut enabled_list, &crate::base::extension::Welcome); + // Añade la página de bienvenida predefinida si se habilita en la configuración. + if global::SETTINGS.app.welcome { + add_to_enabled(&mut enabled_list, &crate::base::extension::Welcome); + } // Guarda la lista final de extensiones habilitadas. ENABLED_EXTENSIONS.write().append(&mut enabled_list); diff --git a/src/global.rs b/src/global.rs index ee07d818..6726a3ef 100644 --- a/src/global.rs +++ b/src/global.rs @@ -11,6 +11,7 @@ include_config!(SETTINGS: Settings => [ "app.theme" => "Basic", "app.language" => "", "app.startup_banner" => "Slant", + "app.welcome" => true, // [dev] "dev.pagetop_static_dir" => "", @@ -59,13 +60,19 @@ pub struct App { /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. pub startup_banner: String, + /// Activa la página de bienvenida de PageTop. + /// + /// Si está activada, se instala la extensión [`Welcome`](crate::base::extension::Welcome), que + /// ofrece una página de bienvenida predefinida en `"/"` y también en `"/lang/{lang}"`, para + /// mostrar el contenido en el idioma `{lang}`, siempre que esté soportado. + pub welcome: bool, /// Modo de ejecución, dado por la variable de entorno `PAGETOP_RUN_MODE`, o *"default"* si no /// está definido. pub run_mode: String, } #[derive(Debug, Deserialize)] -/// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. +/// Sección `[dev]` de la configuración. Forma parte de [`Settings`]. pub struct Dev { /// Directorio desde el que servir los archivos estáticos de PageTop. /// @@ -87,7 +94,7 @@ pub struct Log { /// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*. /// Ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug". pub tracing: String, - /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o las registra en archivos con + /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o los vuelca en archivos con /// rotación: *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. pub rolling: String, /// Directorio para los archivos de traza (si `rolling` ≠ *"Stdout"*). From 10a8a1136c5c9eb5f515d7af946fe131c37f3184 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 22:55:24 +0100 Subject: [PATCH 207/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactoriza=20gest?= =?UTF-8?q?i=C3=B3n=20de=20idiomas=20en=20el=20contexto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component/context.rs | 19 +++----------- src/global.rs | 15 +++++------ src/locale.rs | 48 ++++++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 4f2cdf18..3a531312 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -4,7 +4,7 @@ use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::locale::{LangId, LangMatch, LanguageIdentifier, DEFAULT_LANGID, FALLBACK_LANGID}; +use crate::locale::{LangId, LangMatch, LanguageIdentifier}; use crate::service::HttpRequest; use crate::{builder_fn, join}; @@ -223,26 +223,13 @@ impl Default for Context { } impl Context { - /// Crea un nuevo contexto asociado a una solicitud HTTP. + /// Crea un nuevo contexto asociado a una petición HTTP. /// /// El contexto inicializa el idioma, el tema y la plantilla por defecto, sin favicon ni otros /// recursos cargados. #[rustfmt::skip] pub fn new(request: Option<HttpRequest>) -> Self { - // Se intenta DEFAULT_LANGID. - let langid = DEFAULT_LANGID - // Si es None evalúa la cadena de extracción desde la cabecera HTTP. - .or_else(|| { - request - // Se usa `as_ref()` sobre `Option<HttpRequest>` para no mover el valor. - .as_ref() - .and_then(|req| req.headers().get("Accept-Language")) - .and_then(|value| value.to_str().ok()) - .and_then(|language| LangMatch::resolve(language).as_option()) - }) - // Si todo falla, se recurre a &FALLBACK_LANGID. - .unwrap_or(&FALLBACK_LANGID); - + let langid = LangMatch::from_request(request.as_ref()).langid(); Context { request, langid, diff --git a/src/global.rs b/src/global.rs index 6726a3ef..a40669df 100644 --- a/src/global.rs +++ b/src/global.rs @@ -9,7 +9,6 @@ include_config!(SETTINGS: Settings => [ "app.name" => "PageTop App", "app.description" => "Developed with the amazing PageTop framework.", "app.theme" => "Basic", - "app.language" => "", "app.startup_banner" => "Slant", "app.welcome" => true, @@ -49,14 +48,14 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma por defecto para la aplicación. + /// Idioma predeterminado de la aplicación. /// - /// Si no está definido o no es válido, [`LangId`](crate::locale::LangId) determinará el idioma - /// efectivo para el renderizado en este orden: primero intentará usar el establecido mediante - /// [`Contextual::with_langid()`](crate::core::component::Contextual::with_langid); si no se ha - /// definido explícitamente, probará el indicado en la cabecera `Accept-Language` del navegador; - /// y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). - pub language: String, + /// Si queda en `None`, el idioma de renderizado se decide intentando usar el asignado con + /// [`Contextual::with_langid()`](crate::core::component::Contextual::with_langid) en el + /// contexto del documento. Si no se ha establecido, prueba el recibido en la cabecera + /// `Accept-Language` enviada por el navegador. Y si ninguno aplica, emplea el idioma de + /// respaldo (`"en-US"`). + pub language: Option<String>, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. pub startup_banner: String, diff --git a/src/locale.rs b/src/locale.rs index 0c1ad345..11db7252 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -90,6 +90,7 @@ //! traducir textos con [`L10n`]. use crate::html::{Markup, PreEscaped}; +use crate::service::HttpRequest; use crate::{global, hm, AutoDefault}; pub use fluent_templates; @@ -122,15 +123,16 @@ static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = LazyLock // // Se usa cuando el valor del identificador de idioma en las traducciones no corresponde con ningún // idioma soportado por la aplicación. -pub(crate) static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = - LazyLock::new(|| langid!("en-US")); +static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US")); // Identificador de idioma **por defecto** para la aplicación. // // Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador -// de idioma no es válido o no está disponible, se usa [`FALLBACK_LANGID`]. -pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = - LazyLock::new(|| LangMatch::resolve(&global::SETTINGS.app.language).as_option()); +// de idioma no es válido o no está disponible, se deja sin definir (`None`) y se delega en +// [`LangMatch::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo. +pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = LazyLock::new(|| { + LangMatch::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() +}); /// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso. /// @@ -168,7 +170,7 @@ pub trait LangId { /// /// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque /// resuelve un idioma soportado o porque se aplica el idioma por defecto o, en último caso, el de -/// respaldo ("en-US"): +/// respaldo (`"en-US"`): /// /// ```rust /// # use pagetop::prelude::*; @@ -181,15 +183,15 @@ pub enum LangMatch { /// Cuando el identificador de idioma es una cadena vacía. Unspecified, /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que - /// coincide exactamente con el identificador de idioma (p. ej. "es-ES"), o con el identificador - /// del idioma base (p. ej. "es"). + /// coincide exactamente con el identificador de idioma (p. ej. `"es-ES"`), o con el + /// identificador del idioma base (p. ej. `"es"`). Found(&'static LanguageIdentifier), /// Si el identificador de idioma no está entre los soportados por PageTop. Unsupported(String), } impl Default for LangMatch { - /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo ("en-US"). + /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`). fn default() -> Self { LangMatch::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) } @@ -222,6 +224,34 @@ impl LangMatch { Self::Unsupported(language.to_string()) } + /// Crea un [`LangMatch`] a partir de una petición HTTP. + /// + /// El orden de resolución del idioma es el siguiente: + /// + /// 1. Idioma por defecto de la aplicación, si se ha definido en la configuración global + /// ([`global::SETTINGS.app.language`]). + /// 2. Si no hay idioma por defecto válido, se intenta extraer el idioma de la cabecera HTTP + /// `Accept-Language` usando [`LangMatch::resolve`]. + /// 3. Si no hay cabecera o el valor no es legible, se devuelve [`LangMatch::Unspecified`]. + /// + /// Este método **no aplica** idioma de respaldo. Para obtener siempre un [`LanguageIdentifier`] + /// válido (aplicando idioma por defecto y, en último término, el de respaldo), utiliza + /// [`LangId::langid()`] sobre el valor devuelto. + pub fn from_request(request: Option<&HttpRequest>) -> Self { + // 1) Se usa `DEFAULT_LANGID` si la aplicación tiene un idioma por defecto válido. + if let Some(default) = *DEFAULT_LANGID { + return LangMatch::Found(default); + } + // 2) Sin idioma por defecto, se evalúa la cabecera `Accept-Language` de la petición HTTP. + request + .and_then(|req| req.headers().get("Accept-Language")) + .and_then(|value| value.to_str().ok()) + // Aplica `resolve()` para devolver `Found`, `Unspecified` o `Unsupported`. + .map(LangMatch::resolve) + // 3) Si no hay cabecera o no puede leerse, se considera no especificado. + .unwrap_or(LangMatch::Unspecified) + } + /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. /// /// Solo retorna `Some` si la variante es [`LangMatch::Found`]. En cualquier otro caso (por From efd4975a505317e1162014e4800c3ae404dead75 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 22:56:04 +0100 Subject: [PATCH 208/224] =?UTF-8?q?=F0=9F=9A=A7=20Retoques=20menores=20en?= =?UTF-8?q?=20la=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/component/context.rs | 8 ++++---- src/html/attr_l10n.rs | 2 +- src/response/page.rs | 8 ++++++-- src/util.rs | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 3a531312..59dd1f1a 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -49,7 +49,7 @@ pub enum ContextError { /// /// `Contextual` extiende [`LangId`] para establecer el idioma del documento y añade métodos para: /// -/// - Almacenar la **solicitud HTTP** de origen. +/// - Almacenar la **petición HTTP** de origen. /// - Seleccionar el **tema** y la **plantilla** de renderizado. /// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo /// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`ContextOp`]. @@ -81,7 +81,7 @@ pub trait Contextual: LangId { #[builder_fn] fn with_langid(self, language: &impl LangId) -> Self; - /// Almacena la solicitud HTTP de origen en el contexto. + /// Almacena la petición HTTP de origen en el contexto. #[builder_fn] fn with_request(self, request: Option<HttpRequest>) -> Self; @@ -107,7 +107,7 @@ pub trait Contextual: LangId { // **< Contextual GETTERS >********************************************************************* - /// Devuelve una referencia a la solicitud HTTP asociada, si existe. + /// Devuelve una referencia a la petición HTTP asociada, si existe. fn request(&self) -> Option<&HttpRequest>; /// Devuelve el tema que se usará para renderizar el documento. @@ -161,7 +161,7 @@ pub trait Contextual: LangId { /// /// # Ejemplos /// -/// Crea un nuevo contexto asociado a una solicitud HTTP: +/// Crea un nuevo contexto asociado a una petición HTTP: /// /// ```rust /// # use pagetop::prelude::*; diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index a92e8344..6323701c 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -18,7 +18,7 @@ use crate::{builder_fn, AutoDefault}; /// Some("¡Hola mundo!".to_string()) /// ); /// -/// // Japonés no disponible, traduce al idioma de respaldo ("en-US"). +/// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`). /// assert_eq!( /// hello.lookup(&LangMatch::resolve("ja-JP")), /// Some("Hello world!".to_string()) diff --git a/src/response/page.rs b/src/response/page.rs index c4534f4c..90ce84e1 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -101,7 +101,7 @@ pub struct Page { impl Page { /// Crea una nueva instancia de página. /// - /// La solicitud HTTP se guardará en el contexto de renderizado de la página para poder ser + /// La petición HTTP se guardará en el contexto de renderizado de la página para poder ser /// recuperada por los componentes si es necesario. #[rustfmt::skip] pub fn new(request: HttpRequest) -> Self { @@ -211,7 +211,7 @@ impl Page { /// Devuelve una referencia mutable al [`Context`] de la página. /// /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, - /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente + /// *layout*, recursos, petición HTTP, etc.). Resulta especialmente útil cuando un componente /// o un tema necesita recibir el contexto como parámetro. pub fn context(&mut self) -> &mut Context { &mut self.context @@ -289,6 +289,10 @@ impl Page { } } +/// Permite a [`Page`] actuar como proveedor de idioma usando el [`Context`] de la página. +/// +/// Resulta útil para usar [`Page`] directamente como fuente de traducción en [`L10n::lookup()`] o +/// [`L10n::using()`]. impl LangId for Page { fn langid(&self) -> &'static LanguageIdentifier { self.context.langid() diff --git a/src/util.rs b/src/util.rs index bfb50ec0..1cb21ae7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -18,7 +18,6 @@ pub use indoc::{concatdoc, formatdoc, indoc}; // **< MACROS ÚTILES >****************************************************************************** -#[macro_export] /// Macro para construir una colección de pares clave-valor. /// /// ```rust @@ -31,6 +30,7 @@ pub use indoc::{concatdoc, formatdoc, indoc}; /// "userGender" => "male", /// ]; /// ``` +#[macro_export] macro_rules! hm { ( $($key:expr => $value:expr),* $(,)? ) => {{ let mut a = std::collections::HashMap::new(); From e271437da8fec5aef06389dfe436a0accf99612c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 3 Dec 2025 23:06:35 +0100 Subject: [PATCH 209/224] =?UTF-8?q?=F0=9F=A9=B9=20Corrige=20ejemplos=20con?= =?UTF-8?q?=20enlaces=20din=C3=A1micos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/component/intro.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 1a7c440b..0c49a9fc 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -47,7 +47,7 @@ pub enum IntroOpening { /// .with_slogan(L10n::l("intro_custom_slogan")) /// .with_button(Some(( /// L10n::l("intro_learn_more"), -/// |_| "/learn-more" +/// |_| "/learn-more".into() /// ))); /// ``` /// @@ -252,7 +252,7 @@ impl Intro { /// ```rust /// # use pagetop::prelude::*; /// // Define un botón con texto y una URL fija. - /// let intro = Intro::default().with_button(Some((L10n::n("Learn more"), |_| "/start"))); + /// let intro = Intro::default().with_button(Some((L10n::n("Start"), |_| "/start".into()))); /// // Descarta el botón de la intro. /// let intro_no_button = Intro::default().with_button(None); /// ``` From 03510004873898262f4758e527c1c102cfc9325c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 7 Dec 2025 11:37:23 +0100 Subject: [PATCH 210/224] =?UTF-8?q?=F0=9F=A9=B9=20(bootsier):=20Corrige=20?= =?UTF-8?q?m=C3=A1s=20enlaces=20din=C3=A1micos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/dropdown.rs | 4 +- extensions/pagetop-bootsier/src/theme/nav.rs | 10 ++-- .../pagetop-bootsier/src/theme/navbar.rs | 48 ++++++++++--------- .../pagetop-bootsier/src/theme/offcanvas.rs | 4 +- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index ed4cbec0..ec62c531 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -17,8 +17,8 @@ //! .with_button_color(ButtonColor::Background(Color::Secondary)) //! .with_auto_close(dropdown::AutoClose::ClickableInside) //! .with_direction(dropdown::Direction::Dropend) -//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/")) -//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) //! .add_item(dropdown::Item::divider()) //! .add_item(dropdown::Item::header(L10n::n("User session"))) //! .add_item(dropdown::Item::button(L10n::n("Sign out"))); diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index c74ab3b5..b5ae84a5 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -14,17 +14,17 @@ //! # use pagetop_bootsier::prelude::*; //! let nav = Nav::tabs() //! .with_layout(nav::Layout::End) -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) -//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es")) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) //! .add_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Options")) //! .with_items(TypedOp::AddMany(vec![ -//! Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")), -//! Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")), +//! Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action".into())), +//! Typed::with(dropdown::Item::link(L10n::n("Another"), |_| "/another".into())), //! ])), //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")); +//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index bd605508..b293b614 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -17,9 +17,9 @@ //! let navbar = Navbar::simple() //! .add_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) -//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about")) -//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact")) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) +//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) //! )); //! ``` //! @@ -32,9 +32,9 @@ //! .with_expand(BreakPoint::MD) //! .add_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) -//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.example.com")) -//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support")) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://sample.com".into())) +//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) //! )); //! ``` //! @@ -45,19 +45,23 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("PageTop")) -//! .with_path(Some(|_| "/")); +//! .with_path(Some(|_| "/".into())); //! //! let navbar = Navbar::brand_left(brand) //! .add_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) //! .add_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Tools")) -//! .add_item(dropdown::Item::link(L10n::n("Generator"), |_| "/tools/gen")) -//! .add_item(dropdown::Item::link(L10n::n("Reports"), |_| "/tools/reports")) +//! .add_item(dropdown::Item::link( +//! L10n::n("Generator"), |_| "/tools/gen".into()) +//! ) +//! .add_item(dropdown::Item::link( +//! L10n::n("Reports"), |_| "/tools/reports".into()) +//! ) //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#")) +//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) //! )); //! ``` //! @@ -68,14 +72,14 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("Intranet")) -//! .with_path(Some(|_| "/")); +//! .with_path(Some(|_| "/".into())); //! //! let navbar = Navbar::brand_right(brand) //! .with_expand(BreakPoint::LG) //! .add_item(navbar::Item::nav( //! Nav::pills() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard")) -//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users")) +//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) +//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) //! )); //! ``` //! @@ -93,13 +97,13 @@ //! let navbar = Navbar::offcanvas(oc) //! .add_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/")) -//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile")) +//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) //! .add_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("More")) -//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings")) -//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help")) +//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) +//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) //! )) //! )); //! ``` @@ -111,15 +115,15 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("Main App")) -//! .with_path(Some(|_| "/")); +//! .with_path(Some(|_| "/".into())); //! //! let navbar = Navbar::brand_left(brand) //! .with_position(navbar::Position::FixedTop) //! .add_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/")) -//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors")) -//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock")) +//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) +//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) +//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) //! )); //! ``` diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index 18cc253a..c8b2677e 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -15,8 +15,8 @@ //! .add_child(Dropdown::new() //! .with_title(L10n::n("Menu")) //! .add_item(dropdown::Item::label(L10n::n("Label"))) -//! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es")) -//! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout")) +//! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://google.es".into())) +//! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into())) //! ); //! ``` From 6c024da51e33a6abf9546060651e8d6aa16f7870 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 7 Dec 2025 11:55:26 +0100 Subject: [PATCH 211/224] =?UTF-8?q?=E2=9C=A8=20(minimal):=20A=C3=B1ade=20m?= =?UTF-8?q?acros=20declarativas=20a=20utilidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incorpora nuevo *crate* `pagetop-minimal` con macros básicas para operaciones con cadenas, bloques de texto o colecciones clave-valor. - Refactoriza código para usar `util::join!` y `util::join_pair!` en la concatenación de cadenas. - Normaliza la gestión de localización usando `util::kv!` para los argumentos con pares clave-valor. - Actualizada documentación y archivos README para reflejar la nueva estructura y funcionalidades. --- Cargo.lock | 18 +- Cargo.toml | 5 +- README.md | 16 +- examples/navbar-menus.rs | 2 +- .../src/theme/container/component.rs | 2 +- .../src/theme/navbar/component.rs | 8 +- .../src/theme/offcanvas/component.rs | 4 +- helpers/pagetop-macros/README.md | 2 +- helpers/pagetop-macros/src/lib.rs | 2 +- helpers/pagetop-minimal/Cargo.toml | 20 ++ helpers/pagetop-minimal/LICENSE-APACHE | 201 ++++++++++++++++++ helpers/pagetop-minimal/LICENSE-MIT | 21 ++ helpers/pagetop-minimal/README.md | 56 +++++ helpers/pagetop-minimal/src/lib.rs | 145 +++++++++++++ helpers/pagetop-statics/README.md | 2 +- helpers/pagetop-statics/src/lib.rs | 2 +- src/base/component/poweredby.rs | 2 +- src/core/component/context.rs | 4 +- src/core/theme.rs | 4 +- src/html/assets/javascript.rs | 12 +- src/html/assets/stylesheet.rs | 4 +- src/locale.rs | 6 +- src/prelude.rs | 2 - src/service.rs | 7 +- src/util.rs | 111 +--------- 25 files changed, 504 insertions(+), 154 deletions(-) create mode 100644 helpers/pagetop-minimal/Cargo.toml create mode 100644 helpers/pagetop-minimal/LICENSE-APACHE create mode 100644 helpers/pagetop-minimal/LICENSE-MIT create mode 100644 helpers/pagetop-minimal/README.md create mode 100644 helpers/pagetop-minimal/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4912a6df..7dd0c50b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1300,9 +1300,12 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inout" @@ -1565,17 +1568,16 @@ dependencies = [ "actix-web", "chrono", "colored", - "concat-string", "config", "figlet-rs", "fluent-templates", "getter-methods", - "indoc", "itoa", "pagetop-aliner", "pagetop-bootsier", "pagetop-build", "pagetop-macros", + "pagetop-minimal", "pagetop-statics", "parking_lot", "pastey", @@ -1626,6 +1628,14 @@ dependencies = [ "syn", ] +[[package]] +name = "pagetop-minimal" +version = "0.0.10" +dependencies = [ + "concat-string", + "indoc", +] + [[package]] name = "pagetop-statics" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index d8c42c6e..db70e370 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,9 @@ authors.workspace = true [dependencies] chrono = "0.4" colored = "3.0" -concat-string = "1.0" config = { version = "0.15", default-features = false, features = ["toml"] } figlet-rs = "0.1" getter-methods = "2.0" -indoc = "2.0" itoa = "1.0" parking_lot = "0.12" paste = { package = "pastey", version = "0.1" } @@ -43,6 +41,7 @@ actix-web-files = { package = "actix-files", version = "0.6" } serde.workspace = true pagetop-macros.workspace = true +pagetop-minimal.workspace = true pagetop-statics.workspace = true [features] @@ -65,6 +64,7 @@ members = [ # Helpers "helpers/pagetop-build", "helpers/pagetop-macros", + "helpers/pagetop-minimal", "helpers/pagetop-statics", # Extensions "extensions/pagetop-aliner", @@ -83,6 +83,7 @@ serde = { version = "1.0", features = ["derive"] } # Helpers pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } +pagetop-minimal = { version = "0.0", path = "helpers/pagetop-minimal" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } # Extensions pagetop-aliner = { version = "0.0", path = "extensions/pagetop-aliner" } diff --git a/README.md b/README.md index 9a12c845..eceb5058 100644 --- a/README.md +++ b/README.md @@ -84,17 +84,21 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui ## Auxiliares - * **[pagetop-statics](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics)**, - es la librería que permite incluir archivos estáticos en el ejecutable de las aplicaciones - PageTop para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo de - compilación. - * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las aplicaciones PageTop durante la compilación de los ejecutables. * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, - proporciona una colección de macros que mejoran la experiencia de desarrollo con PageTop. + proporciona una colección de macros procedurales que mejoran la experiencia de desarrollo con + PageTop. + + * **[pagetop-minimal](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-minimal)**, + ofrece macros declarativas esenciales para optimizar tareas comunes como la composición de + texto, la concatenación de cadenas y el manejo de colecciones clave-valor. + + * **[pagetop-statics](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics)**, + permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos + de forma eficiente, con detección de cambios que optimizan el tiempo de compilación. ## Extensiones diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 8d330542..d534afb8 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -10,7 +10,7 @@ impl Extension for SuperMenu { } fn initialize(&self) { - let home_path = |cx: &Context| join!("/lang/", cx.langid().language.as_str()).into(); + let home_path = |cx: &Context| util::join!("/lang/", cx.langid().language.as_str()).into(); let navbar_menu = Navbar::brand_left(navbar::Brand::new().with_path(Some(home_path))) .with_expand(BreakPoint::LG) diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index d20ca154..101d847c 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -40,7 +40,7 @@ impl Component for Container { } let style = match self.container_width() { container::Width::FluidMax(w) if w.is_measurable() => { - Some(join!("max-width: ", w.to_string(), ";")) + Some(util::join!("max-width: ", w.to_string(), ";")) } _ => None, }; diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 21fc87f6..297827e5 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -51,7 +51,7 @@ impl Component for Navbar { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { // Botón de despliegue (colapso u offcanvas) para la barra. fn button(cx: &mut Context, data_bs_toggle: &str, id_content: &str) -> Markup { - let id_content_target = join!("#", id_content); + let id_content_target = util::join!("#", id_content); let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE { Some("false") } else { @@ -92,7 +92,7 @@ impl Component for Navbar { // Barra sencilla que se puede contraer/expandir. navbar::Layout::SimpleToggle => { - @let id_content = join!(id, "-content"); + @let id_content = util::join!(id, "-content"); (button(cx, TOGGLE_COLLAPSE, &id_content)) div id=(id_content) class="collapse navbar-collapse" { @@ -108,7 +108,7 @@ impl Component for Navbar { // Barra con marca a la izquierda y botón a la derecha. navbar::Layout::BrandLeft(brand) => { - @let id_content = join!(id, "-content"); + @let id_content = util::join!(id, "-content"); (brand.render(cx)) (button(cx, TOGGLE_COLLAPSE, &id_content)) @@ -119,7 +119,7 @@ impl Component for Navbar { // Barra con botón a la izquierda y marca a la derecha. navbar::Layout::BrandRight(brand) => { - @let id_content = join!(id, "-content"); + @let id_content = util::join!(id, "-content"); (button(cx, TOGGLE_COLLAPSE, &id_content)) (brand.render(cx)) diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 8b543cfc..bee55a24 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -159,8 +159,8 @@ impl Offcanvas { } let id = cx.required_id::<Self>(self.id()); - let id_label = join!(id, "-label"); - let id_target = join!("#", id); + let id_label = util::join!(id, "-label"); + let id_target = util::join!("#", id); let body_scroll = match self.body_scroll() { offcanvas::BodyScroll::Disabled => None, diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 66fdc1fa..b7f1ad5e 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -19,7 +19,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Créditos -Esta librería incluye entre sus macros una adaptación de +Este *crate* incluye entre sus macros una adaptación de [maud-macros](https://crates.io/crates/maud_macros) ([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de [Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 6fa12357..6916d3fe 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -20,7 +20,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Créditos -Esta librería incluye entre sus macros una adaptación de +Este *crate* incluye entre sus macros una adaptación de [maud-macros](https://crates.io/crates/maud_macros) ([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de [Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de diff --git a/helpers/pagetop-minimal/Cargo.toml b/helpers/pagetop-minimal/Cargo.toml new file mode 100644 index 00000000..a91f340b --- /dev/null +++ b/helpers/pagetop-minimal/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pagetop-minimal" +version = "0.0.10" +edition = "2021" + +description = """ + Reúne un conjunto mínimo de macros para mejorar el formato y la eficiencia de operaciones + básicas en PageTop. +""" +categories = ["development-tools::build-utils"] +keywords = ["pagetop", "build", "assets", "resources", "static"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +concat-string = "1.0" +indoc = "2.0" diff --git a/helpers/pagetop-minimal/LICENSE-APACHE b/helpers/pagetop-minimal/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/helpers/pagetop-minimal/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/helpers/pagetop-minimal/LICENSE-MIT b/helpers/pagetop-minimal/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/helpers/pagetop-minimal/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/helpers/pagetop-minimal/README.md b/helpers/pagetop-minimal/README.md new file mode 100644 index 00000000..4ffbf60f --- /dev/null +++ b/helpers/pagetop-minimal/README.md @@ -0,0 +1,56 @@ +/*! +<div align="center"> + +<h1>PageTop Minimal</h1> + +<p>Reúne un conjunto mínimo de macros para mejorar el formato y la eficiencia de operaciones básicas en <strong>PageTop</strong>.</p> + +[![Doc API](https://img.shields.io/docsrs/pagetop-minimal?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-minimal) +[![Crates.io](https://img.shields.io/crates/v/pagetop-minimal.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-minimal) +[![Descargas](https://img.shields.io/crates/d/pagetop-minimal.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-minimal) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-minimal#licencia) + +</div> + +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + +## Descripción general + +Este *crate* proporciona un conjunto básico de macros que se integran en las utilidades de PageTop +para optimizar operaciones habituales relacionadas con la composición estructurada de texto, la +concatenación de cadenas y el uso rápido de colecciones clave-valor. + +## Créditos + +Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc!`** se reexportan del +*crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). + +Las macros para la concatenación de cadenas **`join!`** y **`join_pair!`** se apoyan internamente en +el **crate** [concat-string](https://crates.io/crates/concat_string), desarrollado por +[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el uso del formato de cadenas cuando la +eficiencia pueda ser relevante. + + +# 🚧 Advertencia + +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +# 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/helpers/pagetop-minimal/src/lib.rs b/helpers/pagetop-minimal/src/lib.rs new file mode 100644 index 00000000..dd55cfdc --- /dev/null +++ b/helpers/pagetop-minimal/src/lib.rs @@ -0,0 +1,145 @@ +/*! +<div align="center"> + +<h1>PageTop Minimal</h1> + +<p>Reúne un conjunto mínimo de macros para mejorar el formato y la eficiencia de operaciones básicas en <strong>PageTop</strong>.</p> + +[![Doc API](https://img.shields.io/docsrs/pagetop-minimal?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-minimal) +[![Crates.io](https://img.shields.io/crates/v/pagetop-minimal.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-minimal) +[![Descargas](https://img.shields.io/crates/d/pagetop-minimal.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-minimal) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-minimal#licencia) + +</div> + +## Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + +## Descripción general + +Este *crate* proporciona un conjunto básico de macros que se integran en las utilidades de PageTop +para optimizar operaciones habituales relacionadas con la composición estructurada de texto, la +concatenación de cadenas y el uso rápido de colecciones clave-valor. + +## Créditos + +Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc!`** se reexportan del +*crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). + +Las macros para la concatenación de cadenas **`join!`** y **`join_pair!`** se apoyan internamente en +el **crate** [concat-string](https://crates.io/crates/concat_string), desarrollado por +[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el uso del formato de cadenas cuando la +eficiencia pueda ser relevante. +*/ + +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + +#[doc(hidden)] +pub use concat_string::concat_string; + +pub use indoc::{concatdoc, formatdoc, indoc}; + +/// Concatena eficientemente varios fragmentos en un [`String`]. +/// +/// Esta macro exporta [`concat_string!`](https://docs.rs/concat-string). Acepta cualquier número de +/// fragmentos que implementen [`AsRef<str>`] y construye un [`String`] con el tamaño óptimo, de +/// forma eficiente y evitando el uso de cadenas de formato que penalicen el rendimiento. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop_minimal::join; +/// // Concatena todos los fragmentos directamente. +/// let result = join!("Hello", " ", "World"); +/// assert_eq!(result, "Hello World".to_string()); +/// +/// // También funciona con valores vacíos. +/// let result_with_empty = join!("Hello", "", "World"); +/// assert_eq!(result_with_empty, "HelloWorld".to_string()); +/// +/// // Un único fragmento devuelve el mismo valor. +/// let single_result = join!("Hello"); +/// assert_eq!(single_result, "Hello".to_string()); +/// ``` +#[macro_export] +macro_rules! join { + ($($arg:expr),+) => { + $crate::concat_string!($($arg),+) + }; +} + +/// Concatena dos fragmentos en un [`String`] usando un separador inteligente. +/// +/// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado. +/// Si uno de ellos está vacío, devuelve directamente el otro; y si ambos están vacíos devuelve un +/// [`String`] vacío. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop_minimal::join_pair; +/// let first = "Hello"; +/// let separator = "-"; +/// let second = "World"; +/// +/// // Concatena los dos fragmentos cuando ambos no están vacíos. +/// let result = join_pair!(first, separator, second); +/// assert_eq!(result, "Hello-World".to_string()); +/// +/// // Si el primer fragmento está vacío, devuelve el segundo. +/// let result_empty_first = join_pair!("", separator, second); +/// assert_eq!(result_empty_first, "World".to_string()); +/// +/// // Si el segundo fragmento está vacío, devuelve el primero. +/// let result_empty_second = join_pair!(first, separator, ""); +/// assert_eq!(result_empty_second, "Hello".to_string()); +/// +/// // Si ambos fragmentos están vacíos, devuelve una cadena vacía. +/// let result_both_empty = join_pair!("", separator, ""); +/// assert_eq!(result_both_empty, "".to_string()); +/// ``` +#[macro_export] +macro_rules! join_pair { + ($first:expr, $separator:expr, $second:expr) => {{ + let first_val = $first; + let second_val = $second; + let separator_val = $separator; + + let first = AsRef::<str>::as_ref(&first_val); + let second = AsRef::<str>::as_ref(&second_val); + let separator = if first.is_empty() || second.is_empty() { + "" + } else { + AsRef::<str>::as_ref(&separator_val) + }; + + $crate::concat_string!(first, separator, second) + }}; +} + +/// Macro para construir una colección de pares clave-valor. +/// +/// ```rust +/// # use pagetop_minimal::kv; +/// # use std::collections::HashMap; +/// let args:HashMap<&str, String> = kv![ +/// "userName" => "Roberto", +/// "photoCount" => "3", +/// "userGender" => "male", +/// ]; +/// ``` +#[macro_export] +macro_rules! kv { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut a = std::collections::HashMap::new(); + $( + a.insert($key.into(), $value.into()); + )* + a + }}; +} diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index cd1da6ac..99e24b4d 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -19,7 +19,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Descripción general -Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para +Este *crate* permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de compilación. diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index 6f04bd2a..d2f147e3 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -20,7 +20,7 @@ configurables, basadas en HTML, CSS y JavaScript. ## Descripción general -Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para +Este *crate* permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de compilación. diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 245f16b4..e90263c9 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -21,7 +21,7 @@ impl Component for PoweredBy { /// configurada en [`global::SETTINGS`], en el formato `YYYY © Nombre de la aplicación`. fn new() -> Self { let year = Utc::now().format("%Y").to_string(); - let c = join!(year, " © ", global::SETTINGS.app.name); + let c = util::join!(year, " © ", global::SETTINGS.app.name); PoweredBy { copyright: Some(c) } } diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 59dd1f1a..74290569 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -6,7 +6,7 @@ use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::locale::{LangId, LangMatch, LanguageIdentifier}; use crate::service::HttpRequest; -use crate::{builder_fn, join}; +use crate::{builder_fn, util}; use std::any::Any; use std::collections::HashMap; @@ -546,7 +546,7 @@ impl Contextual for Context { prefix }; self.id_counter += 1; - join!(prefix, "-", self.id_counter.to_string()) + util::join!(prefix, "-", self.id_counter.to_string()) } } } diff --git a/src/core/theme.rs b/src/core/theme.rs index 8485bf70..91646ab0 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -24,7 +24,7 @@ use crate::core::component::Context; use crate::html::{html, Markup}; use crate::locale::L10n; -use crate::{join, AutoDefault}; +use crate::{util, AutoDefault}; // **< Region >************************************************************************************* @@ -83,7 +83,7 @@ pub trait Region { @let region = cx.render_region(self); @if !region.is_empty() { div - class=(join!("region region-", self.name())) + class=(util::join!("region region-", self.name())) role="region" aria-label=[self.label().lookup(cx)] { diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 6394842a..309c47f2 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,7 +1,7 @@ use crate::core::component::Context; use crate::html::assets::Asset; use crate::html::{html, Markup, PreEscaped}; -use crate::{join, join_pair, AutoDefault, Weight}; +use crate::{util, AutoDefault, Weight}; // Define el origen del recurso JavaScript y cómo debe cargarse en el navegador. // @@ -215,21 +215,21 @@ impl Asset for JavaScript { fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { - script src=(join_pair!(path, "?v=", &self.version)) {}; + script src=(util::join_pair!(path, "?v=", &self.version)) {}; }, Source::Defer(path) => html! { - script src=(join_pair!(path, "?v=", &self.version)) defer {}; + script src=(util::join_pair!(path, "?v=", &self.version)) defer {}; }, Source::Async(path) => html! { - script src=(join_pair!(path, "?v=", &self.version)) async {}; + script src=(util::join_pair!(path, "?v=", &self.version)) async {}; }, Source::Inline(_, f) => html! { script { (PreEscaped((f)(cx))) }; }, - Source::OnLoad(_, f) => html! { script { (PreEscaped(join!( + Source::OnLoad(_, f) => html! { script { (PreEscaped(util::join!( "document.addEventListener(\"DOMContentLoaded\",function(){", (f)(cx), "});" ))) } }, - Source::OnLoadAsync(_, f) => html! { script { (PreEscaped(join!( + Source::OnLoadAsync(_, f) => html! { script { (PreEscaped(util::join!( "document.addEventListener(\"DOMContentLoaded\",async()=>{", (f)(cx), "});" ))) } }, } diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index abadef8a..f86ba8fe 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,7 +1,7 @@ use crate::core::component::Context; use crate::html::assets::Asset; use crate::html::{html, Markup, PreEscaped}; -use crate::{join_pair, AutoDefault, Weight}; +use crate::{util, AutoDefault, Weight}; // Define el origen del recurso CSS y cómo se incluye en el documento. // @@ -170,7 +170,7 @@ impl Asset for StyleSheet { Source::From(path) => html! { link rel="stylesheet" - href=(join_pair!(path, "?v=", &self.version)) + href=(util::join_pair!(path, "?v=", &self.version)) media=[self.media.as_str()]; }, Source::Inline(_, f) => html! { diff --git a/src/locale.rs b/src/locale.rs index 11db7252..ba037491 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -91,7 +91,7 @@ use crate::html::{Markup, PreEscaped}; use crate::service::HttpRequest; -use crate::{global, hm, AutoDefault}; +use crate::{global, util, AutoDefault}; pub use fluent_templates; pub use unic_langid::{CharacterDirection, LanguageIdentifier}; @@ -110,7 +110,7 @@ use std::fmt; // Asocia cada identificador de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la // clave en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = LazyLock::new(|| { - hm![ + util::kv![ "en" => ( langid!("en-US"), "english" ), "en-gb" => ( langid!("en-GB"), "english_british" ), "en-us" => ( langid!("en-US"), "english_united_states" ), @@ -411,7 +411,7 @@ impl L10n { self } - /// Añade varios argumentos a la traducción de una sola vez (p. ej. usando la macro [`hm!`], + /// Añade varios argumentos a la traducción de una vez (p. ej. usando la macro [`util::kv!`], /// también vec![("k", "v")], incluso un array de duplas u otras colecciones). pub fn with_args<I, K, V>(mut self, args: I) -> Self where diff --git a/src/prelude.rs b/src/prelude.rs index be36a1d3..54e37694 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -10,8 +10,6 @@ pub use crate::{AutoDefault, Getters, StaticResources, UniqueId, Weight}; // MACROS. -// crate::util -pub use crate::{hm, join, join_pair}; // crate::config pub use crate::include_config; // crate::locale diff --git a/src/service.rs b/src/service.rs index cb69d76a..60131def 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,6 +15,9 @@ pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; +#[doc(hidden)] +pub use paste::paste; + /// Configura un servicio web para publicar archivos estáticos. /// /// La macro ofrece tres modos para configurar el servicio: @@ -72,7 +75,7 @@ macro_rules! static_files_service { } } if serve_embedded { - $crate::util::paste! { + $crate::service::paste! { mod [<static_files_ $bundle>] { include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); } @@ -92,7 +95,7 @@ macro_rules! static_files_service { route = $route, ); let _ = span.in_scope(|| { - $crate::util::paste! { + $crate::service::paste! { mod [<static_files_ $bundle>] { include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); } diff --git a/src/util.rs b/src/util.rs index 1cb21ae7..b3df8d43 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,116 +8,7 @@ use std::path::{Path, PathBuf}; // **< MACROS INTEGRADAS >************************************************************************** -#[doc(hidden)] -pub use paste::paste; - -#[doc(hidden)] -pub use concat_string::concat_string; - -pub use indoc::{concatdoc, formatdoc, indoc}; - -// **< MACROS ÚTILES >****************************************************************************** - -/// Macro para construir una colección de pares clave-valor. -/// -/// ```rust -/// use pagetop::hm; -/// use std::collections::HashMap; -/// -/// let args:HashMap<&str, String> = hm![ -/// "userName" => "Roberto", -/// "photoCount" => "3", -/// "userGender" => "male", -/// ]; -/// ``` -#[macro_export] -macro_rules! hm { - ( $($key:expr => $value:expr),* $(,)? ) => {{ - let mut a = std::collections::HashMap::new(); - $( - a.insert($key.into(), $value.into()); - )* - a - }}; -} - -/// Concatena eficientemente varios fragmentos en un [`String`]. -/// -/// Esta macro exporta [`concat_string!`](https://docs.rs/concat-string). Acepta cualquier número de -/// fragmentos que implementen [`AsRef<str>`] y construye un [`String`] con el tamaño óptimo, de -/// forma eficiente y evitando el uso de cadenas de formato que penalicen el rendimiento. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Concatena todos los fragmentos directamente. -/// let result = join!("Hello", " ", "World"); -/// assert_eq!(result, "Hello World".to_string()); -/// -/// // También funciona con valores vacíos. -/// let result_with_empty = join!("Hello", "", "World"); -/// assert_eq!(result_with_empty, "HelloWorld".to_string()); -/// -/// // Un único fragmento devuelve el mismo valor. -/// let single_result = join!("Hello"); -/// assert_eq!(single_result, "Hello".to_string()); -/// ``` -#[macro_export] -macro_rules! join { - ($($arg:expr),+) => { - $crate::util::concat_string!($($arg),+) - }; -} - -/// Concatena dos fragmentos en un [`String`] usando un separador. -/// -/// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado. -/// Si uno de ellos está vacío, devuelve directamente el otro; y si ambos están vacíos devuelve un -/// [`String`] vacío. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// let first = "Hello"; -/// let separator = "-"; -/// let second = "World"; -/// -/// // Concatena los dos fragmentos cuando ambos no están vacíos. -/// let result = join_pair!(first, separator, second); -/// assert_eq!(result, "Hello-World".to_string()); -/// -/// // Si el primer fragmento está vacío, devuelve el segundo. -/// let result_empty_first = join_pair!("", separator, second); -/// assert_eq!(result_empty_first, "World".to_string()); -/// -/// // Si el segundo fragmento está vacío, devuelve el primero. -/// let result_empty_second = join_pair!(first, separator, ""); -/// assert_eq!(result_empty_second, "Hello".to_string()); -/// -/// // Si ambos fragmentos están vacíos, devuelve una cadena vacía. -/// let result_both_empty = join_pair!("", separator, ""); -/// assert_eq!(result_both_empty, "".to_string()); -/// ``` -#[macro_export] -macro_rules! join_pair { - ($first:expr, $separator:expr, $second:expr) => {{ - let first_val = $first; - let second_val = $second; - let separator_val = $separator; - - let first = AsRef::<str>::as_ref(&first_val); - let second = AsRef::<str>::as_ref(&second_val); - let separator = if first.is_empty() || second.is_empty() { - "" - } else { - AsRef::<str>::as_ref(&separator_val) - }; - - $crate::util::concat_string!(first, separator, second) - }}; -} +pub use pagetop_minimal::{concatdoc, formatdoc, indoc, join, join_pair, kv}; // **< FUNCIONES ÚTILES >*************************************************************************** From a46cf35fee6c9ca0e1d27619c6a4ab28c268ea6d Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 7 Dec 2025 12:49:51 +0100 Subject: [PATCH 212/224] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(minimal):=20Incor?= =?UTF-8?q?pora=20`paste!`=20a=20las=20utilidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 6 +++--- Cargo.toml | 1 - helpers/pagetop-minimal/Cargo.toml | 1 + helpers/pagetop-minimal/README.md | 9 ++++++--- helpers/pagetop-minimal/src/lib.rs | 20 ++++++++++++++++++-- src/service.rs | 7 ++----- src/util.rs | 12 ++++++++++++ 7 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dd0c50b..eda0a31e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1580,7 +1580,6 @@ dependencies = [ "pagetop-minimal", "pagetop-statics", "parking_lot", - "pastey", "serde", "serde_json", "substring", @@ -1634,6 +1633,7 @@ version = "0.0.10" dependencies = [ "concat-string", "indoc", + "pastey", ] [[package]] @@ -1673,9 +1673,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +checksum = "57d6c094ee800037dff99e02cab0eaf3142826586742a270ab3d7a62656bd27a" [[package]] name = "path-matchers" diff --git a/Cargo.toml b/Cargo.toml index db70e370..a96620d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ figlet-rs = "0.1" getter-methods = "2.0" itoa = "1.0" parking_lot = "0.12" -paste = { package = "pastey", version = "0.1" } substring = "1.4" terminal_size = "0.4" diff --git a/helpers/pagetop-minimal/Cargo.toml b/helpers/pagetop-minimal/Cargo.toml index a91f340b..4f9b67a0 100644 --- a/helpers/pagetop-minimal/Cargo.toml +++ b/helpers/pagetop-minimal/Cargo.toml @@ -18,3 +18,4 @@ authors.workspace = true [dependencies] concat-string = "1.0" indoc = "2.0" +pastey = "0.2" diff --git a/helpers/pagetop-minimal/README.md b/helpers/pagetop-minimal/README.md index 4ffbf60f..16f73963 100644 --- a/helpers/pagetop-minimal/README.md +++ b/helpers/pagetop-minimal/README.md @@ -1,4 +1,3 @@ -/*! <div align="center"> <h1>PageTop Minimal</h1> @@ -30,10 +29,14 @@ Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc! *crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). Las macros para la concatenación de cadenas **`join!`** y **`join_pair!`** se apoyan internamente en -el **crate** [concat-string](https://crates.io/crates/concat_string), desarrollado por -[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el uso del formato de cadenas cuando la +el *crate* [concat-string](https://crates.io/crates/concat_string), desarrollado por +[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el formato de cadenas cuando la eficiencia pueda ser relevante. +La macro para generar identificadores dinámicos **`paste!`** se reexporta del *crate* +[pastey](https://crates.io/crates/pastey), una implementación avanzada y soportada del popular +`paste!` de [David Tolnay](https://crates.io/users/dtolnay). + # 🚧 Advertencia diff --git a/helpers/pagetop-minimal/src/lib.rs b/helpers/pagetop-minimal/src/lib.rs index dd55cfdc..3b8c9036 100644 --- a/helpers/pagetop-minimal/src/lib.rs +++ b/helpers/pagetop-minimal/src/lib.rs @@ -30,9 +30,13 @@ Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc! *crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). Las macros para la concatenación de cadenas **`join!`** y **`join_pair!`** se apoyan internamente en -el **crate** [concat-string](https://crates.io/crates/concat_string), desarrollado por -[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el uso del formato de cadenas cuando la +el *crate* [concat-string](https://crates.io/crates/concat_string), desarrollado por +[FaultyRAM](https://crates.io/users/FaultyRAM), para evitar el formato de cadenas cuando la eficiencia pueda ser relevante. + +La macro para generar identificadores dinámicos **`paste!`** se reexporta del *crate* +[pastey](https://crates.io/crates/pastey), una implementación avanzada y soportada del popular +`paste!` de [David Tolnay](https://crates.io/users/dtolnay). */ #![doc( @@ -44,6 +48,18 @@ pub use concat_string::concat_string; pub use indoc::{concatdoc, formatdoc, indoc}; +/// Permite *pegar* tokens y generar identificadores a partir de otros. +/// +/// Dentro de `paste!`, los identificadores escritos como `[< ... >]` se combinan en uno solo que +/// puede reutilizarse para referirse a items existentes o para definir nuevos (funciones, +/// estructuras, métodos, etc.). +/// +/// También admite modificadores de estilo (`lower`, `upper`, `snake`, `camel`, etc.) para +/// transformar fragmentos interpolados antes de construir el nuevo identificador. +pub use pastey::paste; +// La documentación anterior se copia en `pagetop::util::paste!` porque el *crate* original no la +// define y `pagetop` no la hereda automáticamente. + /// Concatena eficientemente varios fragmentos en un [`String`]. /// /// Esta macro exporta [`concat_string!`](https://docs.rs/concat-string). Acepta cualquier número de diff --git a/src/service.rs b/src/service.rs index 60131def..cb69d76a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,9 +15,6 @@ pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; -#[doc(hidden)] -pub use paste::paste; - /// Configura un servicio web para publicar archivos estáticos. /// /// La macro ofrece tres modos para configurar el servicio: @@ -75,7 +72,7 @@ macro_rules! static_files_service { } } if serve_embedded { - $crate::service::paste! { + $crate::util::paste! { mod [<static_files_ $bundle>] { include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); } @@ -95,7 +92,7 @@ macro_rules! static_files_service { route = $route, ); let _ = span.in_scope(|| { - $crate::service::paste! { + $crate::util::paste! { mod [<static_files_ $bundle>] { include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); } diff --git a/src/util.rs b/src/util.rs index b3df8d43..ee48e28f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -10,6 +10,18 @@ use std::path::{Path, PathBuf}; pub use pagetop_minimal::{concatdoc, formatdoc, indoc, join, join_pair, kv}; +/// Permite *pegar* tokens y generar identificadores a partir de otros. +/// +/// Dentro de `paste!`, los identificadores escritos como `[< ... >]` se combinan en uno solo que +/// puede reutilizarse para referirse a items existentes o para definir nuevos (funciones, +/// estructuras, métodos, etc.). +/// +/// También admite modificadores de estilo (`lower`, `upper`, `snake`, `camel`, etc.) para +/// transformar fragmentos interpolados antes de construir el nuevo identificador. +pub use pagetop_minimal::paste; +// La documentación anterior está copiada de `pagetop_minimal::paste!` porque el *crate* original +// no la define y la de `pagetop_minimal` no se hereda automáticamente. + // **< FUNCIONES ÚTILES >*************************************************************************** /// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. From caa4cf6096ceac5496d6c7729328f2438db375d6 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 10 Dec 2025 15:18:07 +0100 Subject: [PATCH 213/224] =?UTF-8?q?=F0=9F=9A=9A=20Renombra=20`LangMatch`?= =?UTF-8?q?=20por=20`Locale`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/extension/welcome.rs | 6 +-- src/core/component/context.rs | 8 ++-- src/html/attr_l10n.rs | 6 +-- src/locale.rs | 72 +++++++++++++++++------------------ tests/locale.rs | 10 ++--- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index d4a78547..c4f8b07f 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -28,7 +28,7 @@ impl Extension for Welcome { } async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { - let language = LangMatch::from_request(Some(&request)); + let language = Locale::from_request(Some(&request)); home(request, &language) } @@ -36,9 +36,9 @@ async fn home_lang( request: HttpRequest, path: service::web::Path<String>, ) -> ResultPage<Markup, ErrorPage> { - let language = LangMatch::resolve(path.into_inner()); + let language = Locale::resolve(path.into_inner()); match language { - LangMatch::Found(_) => home(request, &language), + Locale::Found(_) => home(request, &language), _ => Err(ErrorPage::NotFound(request)), } } diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 74290569..01b329c1 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -4,7 +4,7 @@ use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::locale::{LangId, LangMatch, LanguageIdentifier}; +use crate::locale::{LangId, LanguageIdentifier, Locale}; use crate::service::HttpRequest; use crate::{builder_fn, util}; @@ -65,7 +65,7 @@ pub enum ContextError { /// # use pagetop::prelude::*; /// # use pagetop_aliner::Aliner; /// fn prepare_context<C: Contextual>(cx: C) -> C { -/// cx.with_langid(&LangMatch::resolve("es-ES")) +/// cx.with_langid(&Locale::resolve("es-ES")) /// .with_theme(&Aliner) /// .with_template(&DefaultTemplate::Standard) /// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) @@ -169,7 +169,7 @@ pub trait Contextual: LangId { /// fn new_context(request: HttpRequest) -> Context { /// Context::new(Some(request)) /// // Establece el idioma del documento a español. -/// .with_langid(&LangMatch::resolve("es-ES")) +/// .with_langid(&Locale::resolve("es-ES")) /// // Establece el tema para renderizar. /// .with_theme(&Aliner) /// // Asigna un favicon. @@ -229,7 +229,7 @@ impl Context { /// recursos cargados. #[rustfmt::skip] pub fn new(request: Option<HttpRequest>) -> Self { - let langid = LangMatch::from_request(request.as_ref()).langid(); + let langid = Locale::from_request(request.as_ref()).langid(); Context { request, langid, diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 6323701c..aa18f6c2 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -14,18 +14,18 @@ use crate::{builder_fn, AutoDefault}; /// /// // Español disponible. /// assert_eq!( -/// hello.lookup(&LangMatch::resolve("es-ES")), +/// hello.lookup(&Locale::resolve("es-ES")), /// Some("¡Hola mundo!".to_string()) /// ); /// /// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`). /// assert_eq!( -/// hello.lookup(&LangMatch::resolve("ja-JP")), +/// hello.lookup(&Locale::resolve("ja-JP")), /// Some("Hello world!".to_string()) /// ); /// /// // Uso típico en un atributo: -/// let title = hello.value(&LangMatch::resolve("es-ES")); +/// let title = hello.value(&Locale::resolve("es-ES")); /// // Ejemplo: html! { a title=(title) { "Link" } } /// ``` #[derive(AutoDefault, Clone, Debug)] diff --git a/src/locale.rs b/src/locale.rs index ba037491..1a644e66 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -86,8 +86,8 @@ //! include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); //! ``` //! -//! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`LangMatch`] y -//! traducir textos con [`L10n`]. +//! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`Locale`] y traducir +//! textos con [`L10n`]. use crate::html::{Markup, PreEscaped}; use crate::service::HttpRequest; @@ -129,9 +129,9 @@ static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!( // // Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador // de idioma no es válido o no está disponible, se deja sin definir (`None`) y se delega en -// [`LangMatch::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo. +// [`Locale::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo. pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = LazyLock::new(|| { - LangMatch::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() + Locale::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() }); /// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso. @@ -144,7 +144,7 @@ pub trait LangId { /// Operaciones con los idiomas soportados por PageTop. /// -/// Utiliza [`LangMatch`] para transformar un identificador de idioma en un [`LanguageIdentifier`] +/// Utiliza [`Locale`] para transformar un identificador de idioma en un [`LanguageIdentifier`] /// soportado por PageTop. /// /// # Ejemplos @@ -152,20 +152,20 @@ pub trait LangId { /// ```rust /// # use pagetop::prelude::*; /// // Coincidencia exacta. -/// let lang = LangMatch::resolve("es-ES"); +/// let lang = Locale::resolve("es-ES"); /// assert_eq!(lang.langid().to_string(), "es-ES"); /// /// // Coincidencia parcial (retrocede al idioma base si no hay variante regional). -/// let lang = LangMatch::resolve("es-EC"); +/// let lang = Locale::resolve("es-EC"); /// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. /// /// // Idioma no especificado. -/// let lang = LangMatch::resolve(""); -/// assert_eq!(lang, LangMatch::Unspecified); +/// let lang = Locale::resolve(""); +/// assert_eq!(lang, Locale::Unspecified); /// /// // Idioma no soportado. -/// let lang = LangMatch::resolve("ja-JP"); -/// assert_eq!(lang, LangMatch::Unsupported("ja-JP".to_string())); +/// let lang = Locale::resolve("ja-JP"); +/// assert_eq!(lang, Locale::Unsupported("ja-JP".to_string())); /// ``` /// /// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque @@ -175,11 +175,11 @@ pub trait LangId { /// ```rust /// # use pagetop::prelude::*; /// // Idioma por defecto o de respaldo si no resuelve. -/// let lang = LangMatch::resolve("it-IT"); +/// let lang = Locale::resolve("it-IT"); /// let langid = lang.langid(); /// ``` #[derive(Clone, Debug, Eq, PartialEq)] -pub enum LangMatch { +pub enum Locale { /// Cuando el identificador de idioma es una cadena vacía. Unspecified, /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que @@ -190,15 +190,15 @@ pub enum LangMatch { Unsupported(String), } -impl Default for LangMatch { +impl Default for Locale { /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`). fn default() -> Self { - LangMatch::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) + Locale::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) } } -impl LangMatch { - /// Resuelve `language` y devuelve la variante [`LangMatch`] apropiada. +impl Locale { + /// Resuelve `language` y devuelve la variante [`Locale`] apropiada. pub fn resolve(language: impl AsRef<str>) -> Self { let language = language.as_ref().trim(); @@ -224,15 +224,15 @@ impl LangMatch { Self::Unsupported(language.to_string()) } - /// Crea un [`LangMatch`] a partir de una petición HTTP. + /// Crea un [`Locale`] a partir de una petición HTTP. /// /// El orden de resolución del idioma es el siguiente: /// /// 1. Idioma por defecto de la aplicación, si se ha definido en la configuración global /// ([`global::SETTINGS.app.language`]). /// 2. Si no hay idioma por defecto válido, se intenta extraer el idioma de la cabecera HTTP - /// `Accept-Language` usando [`LangMatch::resolve`]. - /// 3. Si no hay cabecera o el valor no es legible, se devuelve [`LangMatch::Unspecified`]. + /// `Accept-Language` usando [`Locale::resolve`]. + /// 3. Si no hay cabecera o el valor no es legible, se devuelve [`Locale::Unspecified`]. /// /// Este método **no aplica** idioma de respaldo. Para obtener siempre un [`LanguageIdentifier`] /// válido (aplicando idioma por defecto y, en último término, el de respaldo), utiliza @@ -240,21 +240,21 @@ impl LangMatch { pub fn from_request(request: Option<&HttpRequest>) -> Self { // 1) Se usa `DEFAULT_LANGID` si la aplicación tiene un idioma por defecto válido. if let Some(default) = *DEFAULT_LANGID { - return LangMatch::Found(default); + return Locale::Found(default); } // 2) Sin idioma por defecto, se evalúa la cabecera `Accept-Language` de la petición HTTP. request .and_then(|req| req.headers().get("Accept-Language")) .and_then(|value| value.to_str().ok()) // Aplica `resolve()` para devolver `Found`, `Unspecified` o `Unsupported`. - .map(LangMatch::resolve) + .map(Locale::resolve) // 3) Si no hay cabecera o no puede leerse, se considera no especificado. - .unwrap_or(LangMatch::Unspecified) + .unwrap_or(Locale::Unspecified) } /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. /// - /// Solo retorna `Some` si la variante es [`LangMatch::Found`]. En cualquier otro caso (por + /// Solo retorna `Some` si la variante es [`Locale::Found`]. En cualquier otro caso (por /// ejemplo, si el identificador es vacío o no está soportado), devuelve `None`. /// /// Este método es útil cuando se desea acceder directamente al idioma reconocido sin aplicar el @@ -264,33 +264,33 @@ impl LangMatch { /// /// ```rust /// # use pagetop::prelude::*; - /// let lang = LangMatch::resolve("es-ES").as_option(); + /// let lang = Locale::resolve("es-ES").as_option(); /// assert_eq!(lang.unwrap().to_string(), "es-ES"); /// - /// let lang = LangMatch::resolve("ja-JP").as_option(); + /// let lang = Locale::resolve("ja-JP").as_option(); /// assert!(lang.is_none()); /// ``` #[inline] pub fn as_option(&self) -> Option<&'static LanguageIdentifier> { match self { - LangMatch::Found(l) => Some(l), + Locale::Found(l) => Some(l), _ => None, } } } -/// Permite a [`LangMatch`] actuar como proveedor de idioma. +/// Permite a [`Locale`] actuar como proveedor de idioma. /// -/// Devuelve el [`LanguageIdentifier`] si la variante es [`LangMatch::Found`]; en caso contrario, +/// Devuelve el [`LanguageIdentifier`] si la variante es [`Locale::Found`]; en caso contrario, /// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de /// respaldo ("en-US"). /// -/// Resulta útil para usar un valor de [`LangMatch`] como fuente de traducción en [`L10n::lookup()`] +/// Resulta útil para usar un valor de [`Locale`] como fuente de traducción en [`L10n::lookup()`] /// o [`L10n::using()`]. -impl LangId for LangMatch { +impl LangId for Locale { fn langid(&self) -> &'static LanguageIdentifier { match self { - LangMatch::Found(l) => l, + Locale::Found(l) => l, _ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID), } } @@ -367,7 +367,7 @@ enum L10nOp { /// /// ```rust,ignore /// // Traducción con clave, conjunto de traducciones y fuente de idioma. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&LangMatch::resolve("it")); +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&Locale::resolve("it")); /// ``` #[derive(AutoDefault, Clone)] pub struct L10n { @@ -436,7 +436,7 @@ impl L10n { /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); /// ``` pub fn get(&self) -> Option<String> { - self.lookup(&LangMatch::default()) + self.lookup(&Locale::default()) } /// Resuelve la traducción usando la fuente de idioma proporcionada. @@ -451,7 +451,7 @@ impl L10n { /// /// impl LangId for ResourceLang { /// fn langid(&self) -> &'static LanguageIdentifier { - /// LangMatch::resolve("es-MX").langid() + /// Locale::resolve("es-MX").langid() /// } /// } /// @@ -488,7 +488,7 @@ impl L10n { /// /// ```rust /// # use pagetop::prelude::*; - /// let html = L10n::l("welcome.message").using(&LangMatch::resolve("es")); + /// let html = L10n::l("welcome.message").using(&Locale::resolve("es")); /// ``` pub fn using(&self, language: &impl LangId) -> Markup { PreEscaped(self.lookup(language).unwrap_or_default()) diff --git a/tests/locale.rs b/tests/locale.rs index 91829e62..e15d4f75 100644 --- a/tests/locale.rs +++ b/tests/locale.rs @@ -13,7 +13,7 @@ async fn translation_without_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test_hello_world"); - let translation = l10n.lookup(&LangMatch::resolve("es-ES")); + let translation = l10n.lookup(&Locale::resolve("es-ES")); assert_eq!(translation, Some("¡Hola mundo!".to_string())); } @@ -22,7 +22,7 @@ async fn translation_with_args() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test_hello_user").with_arg("userName", "Manuel"); - let translation = l10n.lookup(&LangMatch::resolve("es-ES")); + let translation = l10n.lookup(&Locale::resolve("es-ES")); assert_eq!(translation, Some("¡Hola, Manuel!".to_string())); } @@ -35,7 +35,7 @@ async fn translation_with_plural_and_select() { ("photoCount", "3"), ("userGender", "male"), ]); - let translation = l10n.lookup(&LangMatch::resolve("es-ES")).unwrap(); + let translation = l10n.lookup(&Locale::resolve("es-ES")).unwrap(); assert!(translation.contains("añadido 3 nuevas fotos de él")); } @@ -44,7 +44,7 @@ async fn check_fallback_language() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("test_hello_world"); - let translation = l10n.lookup(&LangMatch::resolve("xx-YY")); // Retrocede a "en-US". + let translation = l10n.lookup(&Locale::resolve("xx-YY")); // Retrocede a "en-US". assert_eq!(translation, Some("Hello world!".to_string())); } @@ -53,6 +53,6 @@ async fn check_unknown_key() { let _app = service::test::init_service(Application::new().test()).await; let l10n = L10n::l("non-existent-key"); - let translation = l10n.lookup(&LangMatch::resolve("en-US")); + let translation = l10n.lookup(&Locale::resolve("en-US")); assert_eq!(translation, None); } From 476aff1d8e94948b88cce67b0e849596166da42d Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Fri, 12 Dec 2025 00:14:55 +0100 Subject: [PATCH 214/224] =?UTF-8?q?=E2=9C=A8=20(pagetop):=20A=C3=B1ade=20g?= =?UTF-8?q?esti=C3=B3n=20de=20rutas=20con=20par=C3=A1metros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 11 +- Cargo.toml | 1 + .../src/theme/dropdown/item.rs | 43 +++---- .../pagetop-bootsier/src/theme/nav/item.rs | 47 ++++---- src/core/component.rs | 35 ++++-- src/html.rs | 3 + src/html/attr_classes.rs | 7 +- src/html/route.rs | 106 ++++++++++++++++++ 8 files changed, 197 insertions(+), 56 deletions(-) create mode 100644 src/html/route.rs diff --git a/Cargo.lock b/Cargo.lock index eda0a31e..16b0fd66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,9 +1084,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hkdf" @@ -1290,12 +1290,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -1572,6 +1572,7 @@ dependencies = [ "figlet-rs", "fluent-templates", "getter-methods", + "indexmap", "itoa", "pagetop-aliner", "pagetop-bootsier", diff --git a/Cargo.toml b/Cargo.toml index a96620d5..a801b786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ config = { version = "0.15", default-features = false, features = ["toml"] } figlet-rs = "0.1" getter-methods = "2.0" itoa = "1.0" +indexmap = "2.12" parking_lot = "0.12" substring = "1.4" terminal_size = "0.4" diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 81f0ab08..4031078b 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -14,11 +14,12 @@ pub enum ItemKind { Void, /// Etiqueta sin comportamiento interactivo. Label(L10n), - /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar - /// inicialmente deshabilitado. + /// Elemento de navegación basado en una [`RoutePath`] dinámica devuelta por + /// [`FnPathByContext`]. Opcionalmente, puede abrirse en una nueva ventana y estar inicialmente + /// deshabilitado. Link { label: L10n, - path: FnPathByContext, + route: FnPathByContext, blank: bool, disabled: bool, }, @@ -40,8 +41,8 @@ pub enum ItemKind { /// visible que puede comportarse como texto, enlace, botón, encabezado o separador, según su /// [`ItemKind`]. /// -/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, -/// manteniendo una interfaz común para renderizar todos los elementos del menú. +/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción +/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Getters)] pub struct Item { #[getters(skip)] @@ -75,13 +76,13 @@ impl Component for Item { ItemKind::Link { label, - path, + route, blank, disabled, } => { - let path = path(cx); + let route_link = route(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(&path)); + let is_current = !*disabled && (current_path == Some(route_link.path())); let mut classes = "dropdown-item".to_string(); if is_current { @@ -91,9 +92,9 @@ impl Component for Item { classes.push_str(" disabled"); } - let href = (!disabled).then_some(path); - let target = (!disabled && *blank).then_some("_blank"); - let rel = (!disabled && *blank).then_some("noopener noreferrer"); + let href = (!*disabled).then_some(route_link); + let target = (!*disabled && *blank).then_some("_blank"); + let rel = (!*disabled && *blank).then_some("noopener noreferrer"); let aria_current = (href.is_some() && is_current).then_some("page"); let aria_disabled = disabled.then_some("true"); @@ -164,11 +165,15 @@ impl Item { } /// Crea un enlace para la navegación. - pub fn link(label: L10n, path: FnPathByContext) -> Self { + /// + /// La ruta se obtiene invocando [`FnPathByContext`], que devuelve dinámicamente una + /// [`RoutePath`] en función del [`Context`]. El enlace se marca como `active` si la ruta actual + /// del *request* coincide con la ruta de destino (devuelta por `RoutePath::path`). + pub fn link(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: false, disabled: false, }, @@ -177,11 +182,11 @@ impl Item { } /// Crea un enlace deshabilitado que no permite la interacción. - pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { + pub fn link_disabled(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: false, disabled: true, }, @@ -190,11 +195,11 @@ impl Item { } /// Crea un enlace que se abre en una nueva ventana o pestaña. - pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + pub fn link_blank(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: true, disabled: false, }, @@ -203,11 +208,11 @@ impl Item { } /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. - pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { + pub fn link_blank_disabled(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: true, disabled: true, }, diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 192f8df8..6c42a76a 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -17,11 +17,12 @@ pub enum ItemKind { Void, /// Etiqueta sin comportamiento interactivo. Label(L10n), - /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar - /// inicialmente deshabilitado. + /// Elemento de navegación basado en una [`RoutePath`] dinámica devuelta por + /// [`FnPathByContext`]. Opcionalmente, puede abrirse en una nueva ventana y estar inicialmente + /// deshabilitado. Link { label: L10n, - path: FnPathByContext, + route: FnPathByContext, blank: bool, disabled: bool, }, @@ -71,10 +72,10 @@ impl ItemKind { /// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav). /// /// Cada instancia de [`nav::Item`](crate::theme::nav::Item) se traduce en un componente visible que -/// puede comportarse como texto, enlace, botón o menú desplegable según su [`ItemKind`]. +/// puede comportarse como texto, enlace, contenido HTML o menú desplegable, según su [`ItemKind`]. /// -/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, -/// manteniendo una interfaz común para renderizar todos los elementos del menú. +/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción +/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Getters)] pub struct Item { #[getters(skip)] @@ -112,13 +113,13 @@ impl Component for Item { ItemKind::Link { label, - path, + route, blank, disabled, } => { - let path = path(cx); + let route_link = route(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(&path)); + let is_current = !*disabled && (current_path == Some(route_link.path())); let mut classes = "nav-link".to_string(); if is_current { @@ -128,7 +129,7 @@ impl Component for Item { classes.push_str(" disabled"); } - let href = (!*disabled).then_some(path); + let href = (!*disabled).then_some(route_link); let target = (!*disabled && *blank).then_some("_blank"); let rel = (!*disabled && *blank).then_some("noopener noreferrer"); @@ -202,11 +203,15 @@ impl Item { } /// Crea un enlace para la navegación. - pub fn link(label: L10n, path: FnPathByContext) -> Self { + /// + /// La ruta se obtiene invocando [`FnPathByContext`], que devuelve dinámicamente una + /// [`RoutePath`] en función del [`Context`]. El enlace se marca como `active` si la ruta actual + /// del *request* coincide con la ruta de destino (devuelta por `RoutePath::path`). + pub fn link(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: false, disabled: false, }, @@ -215,11 +220,11 @@ impl Item { } /// Crea un enlace deshabilitado que no permite la interacción. - pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { + pub fn link_disabled(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: false, disabled: true, }, @@ -228,11 +233,11 @@ impl Item { } /// Crea un enlace que se abre en una nueva ventana o pestaña. - pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { + pub fn link_blank(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: true, disabled: false, }, @@ -241,11 +246,11 @@ impl Item { } /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. - pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { + pub fn link_blank_disabled(label: L10n, route: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - path, + route, blank: true, disabled: true, }, @@ -266,9 +271,9 @@ impl Item { /// Crea un elemento de navegación que contiene un menú desplegable [`Dropdown`]. /// - /// Sólo se tienen en cuenta **el título** (si no existe le asigna uno por defecto) y **la lista - /// de elementos** del [`Dropdown`]; el resto de propiedades del componente no afectarán a su - /// representación en [`Nav`]. + /// Sólo se tienen en cuenta **el título** (si no existe, se asigna uno por defecto) y **la + /// lista de elementos** del [`Dropdown`]; el resto de propiedades del componente no afectarán + /// a su representación en [`Nav`]. pub fn dropdown(menu: Dropdown) -> Self { Item { item_kind: ItemKind::Dropdown(Typed::with(menu)), diff --git a/src/core/component.rs b/src/core/component.rs index b905a495..db959cea 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -1,6 +1,6 @@ //! API para construir nuevos componentes. -use std::borrow::Cow; +use crate::html::RoutePath; mod definition; pub use definition::{Component, ComponentRender}; @@ -66,12 +66,31 @@ pub use context::{Context, ContextError, ContextOp, Contextual}; /// ``` pub type FnIsRenderable = fn(cx: &Context) -> bool; -/// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. +/// Alias de función (*callback*) para **resolver una ruta URL** según el contexto de renderizado. /// -/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). El -/// resultado se devuelve como [`Cow<'static, str>`](std::borrow::Cow), lo que permite: +/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, parámetros, +/// etc.). El resultado se devuelve como una [`RoutePath`], que representa un *path* base junto con +/// una lista opcional de parámetros de consulta. /// -/// - Usar rutas estáticas sin asignaciones adicionales (`"/path".into()`). -/// - Construir rutas dinámicas en tiempo de ejecución (`format!(...).into()`), por ejemplo, en -/// función de parámetros almacenados en [`Context`]. -pub type FnPathByContext = fn(cx: &Context) -> Cow<'static, str>; +/// Gracias a la implementación de [`RoutePath`] puedes usar rutas estáticas sin asignaciones +/// adicionales: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # let static_path: FnPathByContext = +/// |_| "/path/to/resource".into() +/// # ; +/// ``` +/// +/// O construir rutas dinámicas en tiempo de ejecución: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # let dynamic_path: FnPathByContext = +/// |cx| RoutePath::new("/user").with_param("id", cx.param::<u64>("user_id").unwrap().to_string()) +/// # ; +/// ``` +/// +/// El componente que reciba un [`FnPathByContext`] invocará esta función durante el renderizado +/// para obtener la URL final para asignarla al atributo HTML correspondiente. +pub type FnPathByContext = fn(cx: &Context) -> RoutePath; diff --git a/src/html.rs b/src/html.rs index d94aeea8..e0725dde 100644 --- a/src/html.rs +++ b/src/html.rs @@ -5,6 +5,9 @@ use crate::AutoDefault; mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; +mod route; +pub use route::RoutePath; + // **< HTML DOCUMENT ASSETS >*********************************************************************** mod assets; diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index bb88f587..57a679bb 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -67,14 +67,15 @@ impl AttrClasses { } ClassesOp::Remove => { for class in classes { - self.0.retain(|c| c.ne(&class.to_string())); + self.0.retain(|c| c != class); } } ClassesOp::Replace(classes_to_replace) => { let mut pos = self.0.len(); - let replace: Vec<&str> = classes_to_replace.split_ascii_whitespace().collect(); + let replace = classes_to_replace.to_ascii_lowercase(); + let replace: Vec<&str> = replace.split_ascii_whitespace().collect(); for class in replace { - if let Some(replace_pos) = self.0.iter().position(|c| c.eq(class)) { + if let Some(replace_pos) = self.0.iter().position(|c| c == class) { self.0.remove(replace_pos); if pos > replace_pos { pos = replace_pos; diff --git a/src/html/route.rs b/src/html/route.rs new file mode 100644 index 00000000..c7dac096 --- /dev/null +++ b/src/html/route.rs @@ -0,0 +1,106 @@ +use crate::AutoDefault; + +use std::borrow::Cow; +use std::fmt; + +/// Representa una ruta como un *path* inicial más una lista opcional de parámetros. +/// +/// Modela rutas del estilo `/path/to/resource?foo=bar&debug` o `https://example.com/path?foo=bar`, +/// pensadas para usarse en atributos HTML como `href`, `action` o `src`. +/// +/// `RoutePath` no valida ni interpreta la estructura del *path*; simplemente concatena los +/// parámetros de consulta sobre el valor proporcionado. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop::prelude::*; +/// // Ruta relativa con parámetros y una *flag* sin valor. +/// let route = RoutePath::new("/search") +/// .with_param("q", "rust") +/// .with_param("page", "2") +/// .with_flag("debug"); +/// assert_eq!(route.to_string(), "/search?q=rust&page=2&debug"); +/// +/// // Ruta absoluta a un recurso externo. +/// let external = RoutePath::new("https://example.com/export").with_param("format", "csv"); +/// assert_eq!(external.to_string(), "https://example.com/export?format=csv"); +/// ``` +#[derive(AutoDefault)] +pub struct RoutePath { + // *Path* inicial sobre el que se añadirán los parámetros. + // + // Puede ser relativo (p. ej. `/about`) o una ruta completa (`https://example.com/about`). + // `RoutePath` no realiza ninguna validación ni normalización. + // + // Se almacena como `Cow<'static, str>` para reutilizar literales estáticos sin asignación + // adicional y, al mismo tiempo, aceptar rutas dinámicas representadas como `String`. + path: Cow<'static, str>, + + // Conjunto de parámetros asociados a la ruta. + // + // Cada clave es única y se mantiene el orden de inserción. El valor vacío se utiliza para + // representar *flags* sin valor explícito (por ejemplo `?debug`). + query: indexmap::IndexMap<String, String>, +} + +impl RoutePath { + /// Crea un `RoutePath` a partir de un *path* inicial. + /// + /// Por ejemplo: `RoutePath::new("/about")`. + pub fn new(path: impl Into<Cow<'static, str>>) -> Self { + Self { + path: path.into(), + query: indexmap::IndexMap::new(), + } + } + + /// Añade o sustituye un parámetro `key=value`. Si la clave ya existe, el valor se sobrescribe. + pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { + self.query.insert(key.into(), value.into()); + self + } + + /// Añade o sustituye un *flag* sin valor, por ejemplo `?debug`. + pub fn with_flag(mut self, flag: impl Into<String>) -> Self { + self.query.insert(flag.into(), String::new()); + self + } + + /// Devuelve el *path* inicial tal y como se pasó a [`RoutePath::new`], sin parámetros. + pub fn path(&self) -> &str { + &self.path + } +} + +impl fmt::Display for RoutePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.path)?; + if !self.query.is_empty() { + f.write_str("?")?; + for (i, (key, value)) in self.query.iter().enumerate() { + if i > 0 { + f.write_str("&")?; + } + f.write_str(key)?; + if !value.is_empty() { + f.write_str("=")?; + f.write_str(value)?; + } + } + } + Ok(()) + } +} + +impl From<&'static str> for RoutePath { + fn from(path: &'static str) -> Self { + RoutePath::new(path) + } +} + +impl From<String> for RoutePath { + fn from(path: String) -> Self { + RoutePath::new(path) + } +} From 9297f51b426e273770e92994248aa7ccde5b7c00 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Fri, 12 Dec 2025 00:20:25 +0100 Subject: [PATCH 215/224] =?UTF-8?q?=F0=9F=94=A8=20Actualiza=20*script*=20d?= =?UTF-8?q?e=20generaci=C3=B3n=20de=20CHANGELOGs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/changelog.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tools/changelog.sh b/tools/changelog.sh index bd5f20bb..035fa729 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -35,10 +35,6 @@ cd "$(dirname "$0")/.." || exit 1 # Determina ruta del archivo y ámbito de los archivos afectados para el crate # ------------------------------------------------------------------------------ case "$CRATE" in - pagetop-statics) - CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") - ;; pagetop-build) CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") @@ -47,13 +43,22 @@ case "$CRATE" in CHANGELOG_FILE="helpers/pagetop-macros/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-macros/**/*") ;; + pagetop-minimal) + CHANGELOG_FILE="helpers/pagetop-minimal/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-minimal/**/*") + ;; + pagetop-statics) + CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") + ;; pagetop) CHANGELOG_FILE="CHANGELOG.md" PATH_FLAGS=( # Helpers - --exclude-path "helpers/pagetop-statics/**/*" --exclude-path "helpers/pagetop-build/**/*" --exclude-path "helpers/pagetop-macros/**/*" + --exclude-path "helpers/pagetop-minimal/**/*" + --exclude-path "helpers/pagetop-statics/**/*" # Extensions --exclude-path "extensions/pagetop-aliner/**/*" --exclude-path "extensions/pagetop-bootsier/**/*" From 7b340a19f3af5e450340030297b6940cdfe2084b Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 14 Dec 2025 14:33:35 +0100 Subject: [PATCH 216/224] =?UTF-8?q?=E2=9C=A8=20(locale):=20Refactoriza=20e?= =?UTF-8?q?l=20sistema=20de=20localizaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modulariza la lógica de localización. - Actualiza la estructura de `Locale` para mejorar la resolución y gestión de idiomas. - Introduce `RequestLocale` para manejar la negociación de idioma basada en las peticiones HTTP. - Mejora `L10n` para ofrecer una gestión más flexible de traducciones con argumentos dinámicos. - Actualiza la implementación de `LangId` en `Page` para garantizar una identificación de idioma coherente. - Elimina código obsoleto y simplifica la gestión de identificadores de idioma. --- examples/navbar-menus.rs | 37 +- .../pagetop-bootsier/src/theme/navbar.rs | 6 +- .../src/theme/navbar/brand.rs | 14 +- src/app.rs | 7 +- src/base/extension/welcome.rs | 25 +- src/core/component.rs | 33 +- src/core/component/context.rs | 51 ++- src/global.rs | 68 ++- src/html/route.rs | 4 +- src/locale.rs | 399 ++---------------- src/locale/definition.rs | 210 +++++++++ src/locale/l10n.rs | 194 +++++++++ src/locale/languages.rs | 27 ++ src/locale/request.rs | 178 ++++++++ src/response/page.rs | 1 + 15 files changed, 789 insertions(+), 465 deletions(-) create mode 100644 src/locale/definition.rs create mode 100644 src/locale/l10n.rs create mode 100644 src/locale/languages.rs create mode 100644 src/locale/request.rs diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index d534afb8..a2ae76d0 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -10,16 +10,13 @@ impl Extension for SuperMenu { } fn initialize(&self) { - let home_path = |cx: &Context| util::join!("/lang/", cx.langid().language.as_str()).into(); - - let navbar_menu = Navbar::brand_left(navbar::Brand::new().with_path(Some(home_path))) + let navbar_menu = Navbar::brand_left(navbar::Brand::new()) .with_expand(BreakPoint::LG) .add_item(navbar::Item::nav( Nav::new() - .add_item(nav::Item::link( - L10n::l("sample_menus_item_link"), - home_path, - )) + .add_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { + cx.route("/") + })) .add_item(nav::Item::link_blank( L10n::l("sample_menus_item_blank"), |_| "https://docs.rs/pagetop".into(), @@ -30,11 +27,11 @@ impl Extension for SuperMenu { .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_getting_started"), - |_| "/dev/getting-started".into(), + |cx| cx.route("/dev/getting-started"), )) .add_item(dropdown::Item::link( L10n::l("sample_menus_dev_guides"), - |_| "/dev/guides".into(), + |cx| cx.route("/dev/guides"), )) .add_item(dropdown::Item::link_blank( L10n::l("sample_menus_dev_forum"), @@ -44,14 +41,14 @@ impl Extension for SuperMenu { .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_rust"), - |_| "/dev/sdks/rust".into(), + |cx| cx.route("/dev/sdks/rust"), )) - .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |_| { - "/dev/sdks/js".into() + .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { + cx.route("/dev/sdks/js") })) .add_item(dropdown::Item::link( L10n::l("sample_menus_sdk_python"), - |_| "/dev/sdks/python".into(), + |cx| cx.route("/dev/sdks/python"), )) .add_item(dropdown::Item::divider()) .add_item(dropdown::Item::header(L10n::l( @@ -59,22 +56,22 @@ impl Extension for SuperMenu { ))) .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_auth"), - |_| "/dev/sdks/rust/plugins/auth".into(), + |cx| cx.route("/dev/sdks/rust/plugins/auth"), )) .add_item(dropdown::Item::link( L10n::l("sample_menus_plugin_cache"), - |_| "/dev/sdks/rust/plugins/cache".into(), + |cx| cx.route("/dev/sdks/rust/plugins/cache"), )) .add_item(dropdown::Item::divider()) .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) .add_item(dropdown::Item::link_disabled( L10n::l("sample_menus_item_disabled"), - |_| "#".into(), + |cx| cx.route("#"), )), )) .add_item(nav::Item::link_disabled( L10n::l("sample_menus_item_disabled"), - |_| "#".into(), + |cx| cx.route("#"), )), )) .add_item(navbar::Item::nav( @@ -85,10 +82,10 @@ impl Extension for SuperMenu { ) .add_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), - |_| "/auth/sign-up".into(), + |cx| cx.route("/auth/sign-up"), )) - .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |_| { - "/auth/login".into() + .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { + cx.route("/auth/login") })), )); diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index b293b614..717ec679 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -45,7 +45,7 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("PageTop")) -//! .with_path(Some(|_| "/".into())); +//! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_left(brand) //! .add_item(navbar::Item::nav( @@ -72,7 +72,7 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("Intranet")) -//! .with_path(Some(|_| "/".into())); +//! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_right(brand) //! .with_expand(BreakPoint::LG) @@ -115,7 +115,7 @@ //! # use pagetop_bootsier::prelude::*; //! let brand = navbar::Brand::new() //! .with_title(L10n::n("Main App")) -//! .with_path(Some(|_| "/".into())); +//! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_left(brand) //! .with_position(navbar::Position::FixedTop) diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index 2fc31ef7..2d4eef9e 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -6,7 +6,7 @@ use crate::prelude::*; /// /// Representa la identidad del sitio con una imagen, título y eslogan: /// -/// - Si hay URL ([`with_path()`](Self::with_path)), el bloque completo actúa como enlace. Por +/// - Si hay URL ([`with_route()`](Self::with_route)), el bloque completo actúa como enlace. Por /// defecto enlaza a la raíz del sitio (`/`). /// - Si no hay imagen ([`with_image()`](Self::with_image)) ni título /// ([`with_title()`](Self::with_title)), la marca de identidad no se renderiza. @@ -23,8 +23,8 @@ pub struct Brand { /// Devuelve el eslogan de la marca. slogan: L10n, /// Devuelve la función que resuelve la URL asociada a la marca (si existe). - #[default(_code = "Some(|_| \"/\".into())")] - path: Option<FnPathByContext>, + #[default(_code = "Some(|cx| cx.route(\"/\"))")] + route: Option<FnPathByContext>, } impl Component for Brand { @@ -44,8 +44,8 @@ impl Component for Brand { } let slogan = self.slogan().using(cx); PrepareMarkup::With(html! { - @if let Some(path) = self.path() { - a class="navbar-brand" href=(path(cx)) { (image) (title) (slogan) } + @if let Some(route) = self.route() { + a class="navbar-brand" href=(route(cx)) { (image) (title) (slogan) } } @else { span class="navbar-brand" { (image) (title) (slogan) } } @@ -86,8 +86,8 @@ impl Brand { /// Define la URL de destino. Si es `None`, la marca no será un enlace. #[builder_fn] - pub fn with_path(mut self, path: Option<FnPathByContext>) -> Self { - self.path = path; + pub fn with_route(mut self, route: Option<FnPathByContext>) -> Self { + self.route = route; self } } diff --git a/src/app.rs b/src/app.rs index 6ecff369..dd1f8780 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,9 +4,10 @@ mod figfont; use crate::core::{extension, extension::ExtensionRef}; use crate::html::Markup; +use crate::locale::Locale; use crate::response::page::{ErrorPage, ResultPage}; use crate::service::HttpRequest; -use crate::{global, locale, service, trace, PAGETOP_VERSION}; +use crate::{global, service, trace, PAGETOP_VERSION}; use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; use actix_session::storage::CookieSessionStore; @@ -57,8 +58,8 @@ impl Application { // Inicia gestión de trazas y registro de eventos (logging). LazyLock::force(&trace::TRACING); - // Valida el identificador de idioma por defecto. - LazyLock::force(&locale::DEFAULT_LANGID); + // Inicializa el idioma predeterminado. + Locale::init(); // Registra las extensiones de la aplicación. extension::all::register_extensions(root_extension); diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index c4f8b07f..a3fc3777 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -3,8 +3,7 @@ use crate::prelude::*; /// Página de bienvenida de PageTop. /// /// Esta extensión se instala por defecto si el ajuste de configuración [`global::App::welcome`] es -/// `true`. Muestra una página de bienvenida de PageTop en la ruta raíz (`/`) o en `/lang/{lang}`, -/// siempre que `{lang}` sea un idioma soportado (si no, devuelve una página de error 404). +/// `true`. Muestra una página de bienvenida de PageTop en la ruta raíz (`/`). /// /// No obstante, cualquier extensión puede sobrescribir este comportamiento si utiliza estas mismas /// rutas. @@ -22,33 +21,15 @@ impl Extension for Welcome { } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - scfg.route("/", service::web::get().to(home_page)) - .route("/lang/{lang}", service::web::get().to(home_lang)); + scfg.route("/", service::web::get().to(home)); } } -async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { - let language = Locale::from_request(Some(&request)); - home(request, &language) -} - -async fn home_lang( - request: HttpRequest, - path: service::web::Path<String>, -) -> ResultPage<Markup, ErrorPage> { - let language = Locale::resolve(path.into_inner()); - match language { - Locale::Found(_) => home(request, &language), - _ => Err(ErrorPage::NotFound(request)), - } -} - -fn home(request: HttpRequest, language: &impl LangId) -> ResultPage<Markup, ErrorPage> { +async fn home(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { let app = &global::SETTINGS.app.name; Page::new(request) .with_title(L10n::l("welcome_title")) - .with_langid(language) .add_child( Intro::new() .add_child( diff --git a/src/core/component.rs b/src/core/component.rs index db959cea..ba35fe48 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -16,8 +16,8 @@ pub use context::{Context, ContextError, ContextOp, Contextual}; /// Alias de función (*callback*) para **determinar si un componente se renderiza o no**. /// /// Puede usarse para permitir que una instancia concreta de un tipo de componente dado decida -/// dinámicamente durante el proceso de renderizado ([`Component::is_renderable()`]) si se renderiza -/// o no. +/// dinámicamente durante el proceso de renderizado ([`Component::is_renderable()`]), si se +/// renderiza o no. /// /// # Ejemplo /// @@ -69,28 +69,37 @@ pub type FnIsRenderable = fn(cx: &Context) -> bool; /// Alias de función (*callback*) para **resolver una ruta URL** según el contexto de renderizado. /// /// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, parámetros, -/// etc.). El resultado se devuelve como una [`RoutePath`], que representa un *path* base junto con -/// una lista opcional de parámetros de consulta. +/// etc.). Devuelve una [`RoutePath`], que representa un *path* base junto con una lista opcional de +/// parámetros de consulta. /// -/// Gracias a la implementación de [`RoutePath`] puedes usar rutas estáticas sin asignaciones -/// adicionales: +/// El caso más común es construir rutas relativas dependientes del contexto, normalmente usando +/// [`Context::route`](crate::core::component::Context::route): /// /// ```rust /// # use pagetop::prelude::*; -/// # let static_path: FnPathByContext = -/// |_| "/path/to/resource".into() +/// # let relative_route: FnPathByContext = +/// |cx| cx.route("/path/to/page") /// # ; /// ``` /// -/// O construir rutas dinámicas en tiempo de ejecución: +/// También es posible usar rutas estáticas sin asignaciones adicionales: /// /// ```rust /// # use pagetop::prelude::*; -/// # let dynamic_path: FnPathByContext = +/// # let external_route: FnPathByContext = +/// |_| "https://www.example.com".into() +/// # ; +/// ``` +/// +/// O componer rutas dinámicas en tiempo de ejecución: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # let dynamic_route: FnPathByContext = /// |cx| RoutePath::new("/user").with_param("id", cx.param::<u64>("user_id").unwrap().to_string()) /// # ; /// ``` /// -/// El componente que reciba un [`FnPathByContext`] invocará esta función durante el renderizado -/// para obtener la URL final para asignarla al atributo HTML correspondiente. +/// Los componentes que acepten un [`FnPathByContext`] invocarán esta función durante el renderizado +/// para obtener la URL final que se asignará al atributo HTML correspondiente. pub type FnPathByContext = fn(cx: &Context) -> RoutePath; diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 01b329c1..ec013c14 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -2,13 +2,14 @@ use crate::core::component::ChildOp; use crate::core::theme::all::DEFAULT_THEME; use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef}; use crate::core::TypeInfo; -use crate::html::{html, Markup}; +use crate::html::{html, Markup, RoutePath}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::locale::{LangId, LanguageIdentifier, Locale}; +use crate::locale::{LangId, LanguageIdentifier, RequestLocale}; use crate::service::HttpRequest; use crate::{builder_fn, util}; use std::any::Any; +use std::borrow::Cow; use std::collections::HashMap; /// Operaciones para modificar recursos asociados al [`Context`] de un documento. @@ -204,8 +205,8 @@ pub trait Contextual: LangId { /// ``` #[rustfmt::skip] pub struct Context { - request : Option<HttpRequest>, // Solicitud HTTP de origen. - langid : &'static LanguageIdentifier, // Identificador de idioma. + request : Option<HttpRequest>, // Petición HTTP de origen. + locale : RequestLocale, // Idioma asociado a la petición. theme : ThemeRef, // Referencia al tema usado para renderizar. template : TemplateRef, // Plantilla usada para renderizar. favicon : Option<Favicon>, // Favicon, si se ha definido. @@ -229,10 +230,10 @@ impl Context { /// recursos cargados. #[rustfmt::skip] pub fn new(request: Option<HttpRequest>) -> Self { - let langid = Locale::from_request(request.as_ref()).langid(); + let locale = RequestLocale::from_request(request.as_ref()); Context { request, - langid, + locale, theme : *DEFAULT_THEME, template : DEFAULT_THEME.default_template(), favicon : None, @@ -362,22 +363,40 @@ impl Context { pub fn remove_param(&mut self, key: &'static str) -> bool { self.params.remove(key).is_some() } + + // **< Context HELPERS >************************************************************************ + + /// Construye una ruta aplicada al contexto actual. + /// + /// La ruta resultante se envuelve en un [`RoutePath`], que permite añadir parámetros de + /// consulta de forma tipada. Si la política de negociación de idioma actual + /// [`LangNegotiation`](crate::global::LangNegotiation) indica que debe propagarse el idioma + /// para esta petición, se añade o actualiza el parámetro de *query* `lang=...` con el + /// identificador de idioma efectivo del contexto. + /// + /// Esto garantiza que los enlaces generados desde el contexto preservan la preferencia de + /// idioma del usuario cuando procede. + pub fn route(&self, path: impl Into<Cow<'static, str>>) -> RoutePath { + let mut route = RoutePath::new(path); + if self.locale.needs_lang_query() { + route.alter_param("lang", self.locale.langid().to_string()); + } + route + } } /// Permite a [`Context`](crate::core::component::Context) actuar como proveedor de idioma. /// -/// Devuelve un [`LanguageIdentifier`] siguiendo este orden de prioridad: +/// Internamente delega en [`RequestLocale`], que tiene en cuenta la petición HTTP, la configuración +/// global de idioma de la aplicación, la cabecera `Accept-Language` y/o el idioma de respaldo. /// -/// 1. Un idioma válido establecido explícitamente con [`Context::with_langid`]. -/// 2. El idioma por defecto configurado para la aplicación. -/// 3. Un idioma válido extraído de la cabecera `Accept-Language` del navegador. -/// 4. Y si ninguna de las opciones anteriores aplica, se usa el idioma de respaldo (`"en-US"`). -/// -/// Resulta útil para usar el [`Context`] como fuente de traducción en +/// Todo ello según la negociación indicada en [`global::SETTINGS.app.lang_negotiation`]. Esto +/// permite que el [`Context`] se use como fuente de idioma coherente en /// [`L10n::lookup()`](crate::locale::L10n::lookup) o [`L10n::using()`](crate::locale::L10n::using). impl LangId for Context { + #[inline] fn langid(&self) -> &'static LanguageIdentifier { - self.langid + self.locale.langid() } } @@ -387,12 +406,14 @@ impl Contextual for Context { #[builder_fn] fn with_request(mut self, request: Option<HttpRequest>) -> Self { self.request = request; + // Recalcula el locale según la nueva petición y la política de negociación configurada. + self.locale = RequestLocale::from_request(self.request.as_ref()); self } #[builder_fn] fn with_langid(mut self, language: &impl LangId) -> Self { - self.langid = language.langid(); + self.locale.with_langid(language); self } diff --git a/src/global.rs b/src/global.rs index a40669df..b484731b 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,14 +1,17 @@ //! Opciones de configuración globales. -use crate::include_config; +use crate::{include_config, AutoDefault}; use serde::Deserialize; +// **< SETTINGS >*********************************************************************************** + include_config!(SETTINGS: Settings => [ // [app] "app.name" => "PageTop App", "app.description" => "Developed with the amazing PageTop framework.", "app.theme" => "Basic", + "app.lang_negotiation" => "Full", "app.startup_banner" => "Slant", "app.welcome" => true, @@ -29,6 +32,37 @@ include_config!(SETTINGS: Settings => [ "server.session_lifetime" => 604_800, ]); +// **< LangNegotiation >**************************************************************************** + +/// Modos disponibles para negociar el idioma de una petición HTTP. +/// +/// El ajuste [`global::SETTINGS.app.lang_negotiation`](crate::global::App::lang_negotiation) +/// determina qué fuentes intervienen en la resolución del idioma efectivo utilizado por +/// [`RequestLocale`](crate::locale::RequestLocale) y en la generación de URLs mediante +/// [`Context::route()`](crate::core::component::Context::route). +#[derive(AutoDefault, Clone, Copy, Debug, Deserialize, Eq, PartialEq)] +pub enum LangNegotiation { + /// Usa todas las fuentes disponibles para determinar el idioma, en este orden: comprueba el + /// parámetro `?lang` de la URL; si no está presente o no es válido, usa la cabecera HTTP + /// `Accept-Language`; si tampoco está disponible o no es válido, usa el idioma configurado en + /// [`global::SETTINGS.app.language`](crate::global::App::language) o, en su defecto, el idioma + /// de respaldo. Es el comportamiento por defecto. + #[default] + Full, + + /// Igual que `LangNegotiation::Full`, pero sin tener en cuenta el parámetro `?lang` de la URL. + /// El idioma depende únicamente de la cabecera `Accept-Language` del navegador y, en última + /// instancia, de la configuración o idioma de respaldo. + NoQuery, + + /// Usa sólo la configuración o, en su defecto, el idioma de respaldo; ignora la cabecera + /// `Accept-Language` y el parámetro de la URL. Este modo proporciona un comportamiento estable + /// con idioma fijo. + ConfigOnly, +} + +// **< Settings >*********************************************************************************** + #[derive(Debug, Deserialize)] /// Tipos para las secciones globales [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log) y /// [`[server]`](Server) de [`SETTINGS`]. @@ -48,22 +82,30 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma predeterminado de la aplicación. + /// Idioma predeterminado de la aplicación (p. ej., *"es-ES"* o *"en-US"*). /// - /// Si queda en `None`, el idioma de renderizado se decide intentando usar el asignado con - /// [`Contextual::with_langid()`](crate::core::component::Contextual::with_langid) en el - /// contexto del documento. Si no se ha establecido, prueba el recibido en la cabecera - /// `Accept-Language` enviada por el navegador. Y si ninguno aplica, emplea el idioma de - /// respaldo (`"en-US"`). + /// Cuando tiene un valor validado por [`Locale`](crate::locale::Locale), se usa como candidato + /// para resolver el idioma efectivo de cada petición según la estrategia definida en + /// [`lang_negotiation`](Self::lang_negotiation) y aplicada por + /// [`RequestLocale`](crate::locale::RequestLocale). + /// + /// Si es `None` o no contiene un valor válido, la negociación del idioma pasa a depender de + /// otras fuentes como la cabecera `Accept-Language` de la petición o, en último término, del + /// idioma de respaldo configurado en el sistema. pub language: Option<String>, + /// Estrategia para resolver el idioma usado en la petición: *"Full"*, *"NoQuery"* o + /// *"ConfigOnly"*. + /// + /// Define las fuentes que intervienen en la negociación del idioma para el renderizado de los + /// documentos y la generación de URLs. Ver [`LangNegotiation`] para los modos disponibles. + pub lang_negotiation: LangNegotiation, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. pub startup_banner: String, /// Activa la página de bienvenida de PageTop. /// /// Si está activada, se instala la extensión [`Welcome`](crate::base::extension::Welcome), que - /// ofrece una página de bienvenida predefinida en `"/"` y también en `"/lang/{lang}"`, para - /// mostrar el contenido en el idioma `{lang}`, siempre que esté soportado. + /// ofrece una página de bienvenida predefinida en `"/"`. pub welcome: bool, /// Modo de ejecución, dado por la variable de entorno `PAGETOP_RUN_MODE`, o *"default"* si no /// está definido. @@ -87,18 +129,18 @@ pub struct Dev { #[derive(Debug, Deserialize)] /// Sección `[log]` de la configuración. Forma parte de [`Settings`]. pub struct Log { - /// Gestión de trazas y registro de eventos activado (`true`) o desactivado (`false`). + /// Gestión de trazas y registro de eventos activada (*true*) o desactivada (*false*). pub enabled: bool, /// Opciones, o combinación de opciones separadas por comas, para filtrar las trazas: *"Error"*, /// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*. - /// Ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug". + /// Ejemplo: *"Error,actix_server::builder=Info,tracing_actix_web=Debug"*. pub tracing: String, /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o los vuelca en archivos con /// rotación: *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. pub rolling: String, - /// Directorio para los archivos de traza (si `rolling` ≠ *"Stdout"*). + /// Directorio para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub path: String, - /// Prefijo para los archivos de traza (si `rolling` ≠ *"Stdout"*). + /// Prefijo para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub prefix: String, /// Formato de salida de las trazas. Opciones: *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*. pub format: String, diff --git a/src/html/route.rs b/src/html/route.rs index c7dac096..699706f3 100644 --- a/src/html/route.rs +++ b/src/html/route.rs @@ -1,4 +1,4 @@ -use crate::AutoDefault; +use crate::{builder_fn, AutoDefault}; use std::borrow::Cow; use std::fmt; @@ -56,12 +56,14 @@ impl RoutePath { } /// Añade o sustituye un parámetro `key=value`. Si la clave ya existe, el valor se sobrescribe. + #[builder_fn] pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { self.query.insert(key.into(), value.into()); self } /// Añade o sustituye un *flag* sin valor, por ejemplo `?debug`. + #[builder_fn] pub fn with_flag(mut self, flag: impl Into<String>) -> Self { self.query.insert(flag.into(), String::new()); self diff --git a/src/locale.rs b/src/locale.rs index 1a644e66..742a639f 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -89,215 +89,57 @@ //! Y *voilà*, sólo queda operar con los idiomas soportados por PageTop usando [`Locale`] y traducir //! textos con [`L10n`]. -use crate::html::{Markup, PreEscaped}; -use crate::service::HttpRequest; -use crate::{global, util, AutoDefault}; - pub use fluent_templates; pub use unic_langid::{CharacterDirection, LanguageIdentifier}; use unic_langid::langid; -use fluent_templates::Loader; -use fluent_templates::StaticLoader as Locales; +mod languages; -use std::borrow::Cow; -use std::collections::HashMap; -use std::sync::LazyLock; +mod definition; +pub use definition::{LangId, Locale}; -use std::fmt; +mod request; +pub use request::RequestLocale; -// Asocia cada identificador de idioma (como "en-US") con su respectivo [`LanguageIdentifier`] y la -// clave en *locale/.../languages.ftl* para obtener el nombre del idioma según la localización. -static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = LazyLock::new(|| { - util::kv![ - "en" => ( langid!("en-US"), "english" ), - "en-gb" => ( langid!("en-GB"), "english_british" ), - "en-us" => ( langid!("en-US"), "english_united_states" ), - "es" => ( langid!("es-ES"), "spanish" ), - "es-es" => ( langid!("es-ES"), "spanish_spain" ), - ] -}); +mod l10n; +pub use l10n::L10n; -// Identificador de idioma de **respaldo** (predefinido a `en-US`). -// -// Se usa cuando el valor del identificador de idioma en las traducciones no corresponde con ningún -// idioma soportado por la aplicación. -static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US")); - -// Identificador de idioma **por defecto** para la aplicación. -// -// Se resuelve a partir de [`global::SETTINGS.app.language`](global::SETTINGS). Si el identificador -// de idioma no es válido o no está disponible, se deja sin definir (`None`) y se delega en -// [`Locale::default()`] o [`LangId::langid()`] la aplicación del idioma de respaldo. -pub(crate) static DEFAULT_LANGID: LazyLock<Option<&LanguageIdentifier>> = LazyLock::new(|| { - Locale::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() -}); - -/// Representa la fuente de idioma (`LanguageIdentifier`) asociada a un recurso. +/// Incluye un conjunto de recursos **Fluent** con textos de traducción propios. /// -/// Este *trait* permite que distintas estructuras expongan su fuente de idioma de forma uniforme. -pub trait LangId { - /// Devuelve el identificador de idioma asociado al recurso. - fn langid(&self) -> &'static LanguageIdentifier; -} - -/// Operaciones con los idiomas soportados por PageTop. +/// Esta macro integra en el binario de la aplicación los archivos FTL ubicados en los siguientes +/// directorios opcionales de recursos Fluent: /// -/// Utiliza [`Locale`] para transformar un identificador de idioma en un [`LanguageIdentifier`] -/// soportado por PageTop. +/// - `$dir_locales`, con los subdirectorios de cada idioma. Por ejemplo, `"files/ftl"` o +/// `"assets/translations"`. Si no se indica, se usará el directorio por defecto `"src/locale"`. +/// - `$core_locales`, que añade un conjunto de traducciones que se cargan para **todos** los +/// idiomas. Sirve para definir textos comunes que no tienen por qué duplicarse en cada +/// subdirectorio de idioma. +/// +/// Cada extensión o tema puede definir sus propios recursos de traducción usando esta macro. Para +/// más detalles sobre el sistema de localización consulta el módulo [`locale`](crate::locale). /// /// # Ejemplos /// -/// ```rust -/// # use pagetop::prelude::*; -/// // Coincidencia exacta. -/// let lang = Locale::resolve("es-ES"); -/// assert_eq!(lang.langid().to_string(), "es-ES"); -/// -/// // Coincidencia parcial (retrocede al idioma base si no hay variante regional). -/// let lang = Locale::resolve("es-EC"); -/// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. -/// -/// // Idioma no especificado. -/// let lang = Locale::resolve(""); -/// assert_eq!(lang, Locale::Unspecified); -/// -/// // Idioma no soportado. -/// let lang = Locale::resolve("ja-JP"); -/// assert_eq!(lang, Locale::Unsupported("ja-JP".to_string())); -/// ``` -/// -/// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque -/// resuelve un idioma soportado o porque se aplica el idioma por defecto o, en último caso, el de -/// respaldo (`"en-US"`): +/// Uso básico con el directorio por defecto `"src/locale"`: /// /// ```rust /// # use pagetop::prelude::*; -/// // Idioma por defecto o de respaldo si no resuelve. -/// let lang = Locale::resolve("it-IT"); -/// let langid = lang.langid(); +/// include_locales!(LOCALES_SAMPLE); /// ``` -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Locale { - /// Cuando el identificador de idioma es una cadena vacía. - Unspecified, - /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que - /// coincide exactamente con el identificador de idioma (p. ej. `"es-ES"`), o con el - /// identificador del idioma base (p. ej. `"es"`). - Found(&'static LanguageIdentifier), - /// Si el identificador de idioma no está entre los soportados por PageTop. - Unsupported(String), -} - -impl Default for Locale { - /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`). - fn default() -> Self { - Locale::Found(DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID)) - } -} - -impl Locale { - /// Resuelve `language` y devuelve la variante [`Locale`] apropiada. - pub fn resolve(language: impl AsRef<str>) -> Self { - let language = language.as_ref().trim(); - - // Rechaza cadenas vacías. - if language.is_empty() { - return Self::Unspecified; - } - - // Intenta aplicar coincidencia exacta con el código completo (p. ej. "es-MX"). - let lang = language.to_ascii_lowercase(); - if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) { - return Self::Found(langid); - } - - // Si la variante regional no existe, retrocede al idioma base (p. ej. "es"). - if let Some((base_lang, _)) = lang.split_once('-') { - if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { - return Self::Found(langid); - } - } - - // En caso contrario, indica que el idioma no está soportado. - Self::Unsupported(language.to_string()) - } - - /// Crea un [`Locale`] a partir de una petición HTTP. - /// - /// El orden de resolución del idioma es el siguiente: - /// - /// 1. Idioma por defecto de la aplicación, si se ha definido en la configuración global - /// ([`global::SETTINGS.app.language`]). - /// 2. Si no hay idioma por defecto válido, se intenta extraer el idioma de la cabecera HTTP - /// `Accept-Language` usando [`Locale::resolve`]. - /// 3. Si no hay cabecera o el valor no es legible, se devuelve [`Locale::Unspecified`]. - /// - /// Este método **no aplica** idioma de respaldo. Para obtener siempre un [`LanguageIdentifier`] - /// válido (aplicando idioma por defecto y, en último término, el de respaldo), utiliza - /// [`LangId::langid()`] sobre el valor devuelto. - pub fn from_request(request: Option<&HttpRequest>) -> Self { - // 1) Se usa `DEFAULT_LANGID` si la aplicación tiene un idioma por defecto válido. - if let Some(default) = *DEFAULT_LANGID { - return Locale::Found(default); - } - // 2) Sin idioma por defecto, se evalúa la cabecera `Accept-Language` de la petición HTTP. - request - .and_then(|req| req.headers().get("Accept-Language")) - .and_then(|value| value.to_str().ok()) - // Aplica `resolve()` para devolver `Found`, `Unspecified` o `Unsupported`. - .map(Locale::resolve) - // 3) Si no hay cabecera o no puede leerse, se considera no especificado. - .unwrap_or(Locale::Unspecified) - } - - /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. - /// - /// Solo retorna `Some` si la variante es [`Locale::Found`]. En cualquier otro caso (por - /// ejemplo, si el identificador es vacío o no está soportado), devuelve `None`. - /// - /// Este método es útil cuando se desea acceder directamente al idioma reconocido sin aplicar el - /// idioma por defecto ni el de respaldo. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop::prelude::*; - /// let lang = Locale::resolve("es-ES").as_option(); - /// assert_eq!(lang.unwrap().to_string(), "es-ES"); - /// - /// let lang = Locale::resolve("ja-JP").as_option(); - /// assert!(lang.is_none()); - /// ``` - #[inline] - pub fn as_option(&self) -> Option<&'static LanguageIdentifier> { - match self { - Locale::Found(l) => Some(l), - _ => None, - } - } -} - -/// Permite a [`Locale`] actuar como proveedor de idioma. /// -/// Devuelve el [`LanguageIdentifier`] si la variante es [`Locale::Found`]; en caso contrario, -/// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de -/// respaldo ("en-US"). +/// Uso indicando recursos comunes (además de `"src/locale"`): /// -/// Resulta útil para usar un valor de [`Locale`] como fuente de traducción en [`L10n::lookup()`] -/// o [`L10n::using()`]. -impl LangId for Locale { - fn langid(&self) -> &'static LanguageIdentifier { - match self { - Locale::Found(l) => l, - _ => DEFAULT_LANGID.unwrap_or(&FALLBACK_LANGID), - } - } -} - +/// ```rust,ignore +/// include_locales!(LOCALES_SAMPLE, "src/core-locale"); +/// ``` +/// +/// Uso con un directorio de recursos Fluent alternativo: +/// +/// ```rust,ignore +/// include_locales!(LOCALES_SAMPLE from "ruta/a/las/traducciones"); +/// ``` #[macro_export] -/// Incluye un conjunto de recursos **Fluent** y textos de traducción propios. macro_rules! include_locales { // Se desactiva la inserción de marcas de aislamiento Unicode (FSI/PDI) en los argumentos para // mejorar la legibilidad y la compatibilidad en ciertos contextos de renderizado. @@ -324,184 +166,3 @@ macro_rules! include_locales { } }; } - -include_locales!(LOCALES_PAGETOP); - -// Operación de localización a realizar. -// -// * `None` - No se aplica ninguna localización. -// * `Text` - Con una cadena literal que se devolverá tal cual. -// * `Translate` - Con la clave a resolver en el `Locales` indicado. -#[derive(AutoDefault, Clone, Debug)] -enum L10nOp { - #[default] - None, - Text(Cow<'static, str>), - Translate(Cow<'static, str>), -} - -/// Crea instancias para traducir *textos localizados*. -/// -/// Cada instancia puede representar: -/// -/// - Un texto puro (`n()`) que no requiere traducción. -/// - Una clave para traducir un texto de las traducciones predefinidas de PageTop (`l()`). -/// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). -/// -/// # Ejemplo -/// -/// Los argumentos dinámicos se añaden con `with_arg()` o `with_args()`. -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Texto literal sin traducción. -/// let raw = L10n::n("© 2025 PageTop").get(); -/// -/// // Traducción simple con clave y argumentos. -/// let hello = L10n::l("greeting") -/// .with_arg("name", "Manuel") -/// .get(); -/// ``` -/// -/// También sirve para traducciones contra un conjunto de recursos concreto. -/// -/// ```rust,ignore -/// // Traducción con clave, conjunto de traducciones y fuente de idioma. -/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&Locale::resolve("it")); -/// ``` -#[derive(AutoDefault, Clone)] -pub struct L10n { - op: L10nOp, - #[default(&LOCALES_PAGETOP)] - locales: &'static Locales, - args: HashMap<String, String>, -} - -impl L10n { - /// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción. - pub fn n(text: impl Into<Cow<'static, str>>) -> Self { - L10n { - op: L10nOp::Text(text.into()), - ..Default::default() - } - } - - /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de - /// traducciones predefinidas. - pub fn l(key: impl Into<Cow<'static, str>>) -> Self { - L10n { - op: L10nOp::Translate(key.into()), - ..Default::default() - } - } - - /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de - /// traducciones específico. - pub fn t(key: impl Into<Cow<'static, str>>, locales: &'static Locales) -> Self { - L10n { - op: L10nOp::Translate(key.into()), - locales, - ..Default::default() - } - } - - /// Añade un argumento `{$arg}` => `value` a la traducción. - pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self { - self.args.insert(arg.into(), value.into()); - self - } - - /// Añade varios argumentos a la traducción de una vez (p. ej. usando la macro [`util::kv!`], - /// también vec![("k", "v")], incluso un array de duplas u otras colecciones). - pub fn with_args<I, K, V>(mut self, args: I) -> Self - where - I: IntoIterator<Item = (K, V)>, - K: Into<String>, - V: Into<String>, - { - self.args - .extend(args.into_iter().map(|(k, v)| (k.into(), v.into()))); - self - } - - /// Resuelve la traducción usando el idioma por defecto o, si no procede, el de respaldo de la - /// aplicación. - /// - /// Devuelve `None` si no aplica o no encuentra una traducción válida. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop::prelude::*; - /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); - /// ``` - pub fn get(&self) -> Option<String> { - self.lookup(&Locale::default()) - } - - /// Resuelve la traducción usando la fuente de idioma proporcionada. - /// - /// Devuelve `None` si no aplica o no encuentra una traducción válida. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop::prelude::*; - /// struct ResourceLang; - /// - /// impl LangId for ResourceLang { - /// fn langid(&self) -> &'static LanguageIdentifier { - /// Locale::resolve("es-MX").langid() - /// } - /// } - /// - /// let r = ResourceLang; - /// let text = L10n::l("greeting").with_arg("name", "Usuario").lookup(&r); - /// ``` - pub fn lookup(&self, language: &impl LangId) -> Option<String> { - match &self.op { - L10nOp::None => None, - L10nOp::Text(text) => Some(text.clone().into_owned()), - L10nOp::Translate(key) => { - if self.args.is_empty() { - self.locales.try_lookup(language.langid(), key.as_ref()) - } else { - self.locales.try_lookup_with_args( - language.langid(), - key.as_ref(), - &self - .args - .iter() - .map(|(k, v)| (Cow::Owned(k.clone()), v.clone().into())) - .collect::<HashMap<_, _>>(), - ) - } - } - } - } - - /// Traduce el texto y lo devuelve como [`Markup`] usando la fuente de idioma proporcionada. - /// - /// Si no se encuentra una traducción válida, devuelve una cadena vacía. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop::prelude::*; - /// let html = L10n::l("welcome.message").using(&Locale::resolve("es")); - /// ``` - pub fn using(&self, language: &impl LangId) -> Markup { - PreEscaped(self.lookup(language).unwrap_or_default()) - } -} - -impl fmt::Debug for L10n { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("L10n") - .field("op", &self.op) - .field("args", &self.args) - // No se puede mostrar `locales`. Se representa con un texto fijo. - .field("locales", &"<StaticLoader>") - .finish() - } -} diff --git a/src/locale/definition.rs b/src/locale/definition.rs new file mode 100644 index 00000000..6349b460 --- /dev/null +++ b/src/locale/definition.rs @@ -0,0 +1,210 @@ +use crate::{global, trace}; + +use super::languages::LANGUAGES; +use super::{langid, LanguageIdentifier}; + +use std::sync::LazyLock; + +// Identificador del idioma configurado para la aplicación, si es válido. +static CONFIG_LANGID: LazyLock<Option<&'static LanguageIdentifier>> = LazyLock::new(|| { + Locale::resolve(global::SETTINGS.app.language.as_deref().unwrap_or("")).as_option() +}); + +// Identificador del idioma de respaldo (predefinido a `"en-US"`). +static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US")); + +/// Representa el identificador de idioma [`LanguageIdentifier`] asociado a un recurso. +/// +/// Este *trait* permite que distintas estructuras expongan su idioma de forma uniforme. Las +/// implementaciones deben garantizar que siempre se devuelve un identificador de idioma válido. Si +/// el recurso no tiene uno asignado, se puede devolver, si procede, el identificador de idioma por +/// defecto de la aplicación ([`Locale::default_langid()`]). +pub trait LangId { + /// Devuelve el identificador de idioma asociado al recurso. + fn langid(&self) -> &'static LanguageIdentifier; +} + +/// Resultado de resolver un identificador de idioma. +/// +/// Utiliza [`Locale::resolve()`] para transformar una cadena de idioma en un [`LanguageIdentifier`] +/// soportado por PageTop. +/// +/// # Ejemplos +/// +/// ```rust +/// # use pagetop::prelude::*; +/// // Coincidencia exacta. +/// let lang = Locale::resolve("es-ES"); +/// assert_eq!(lang.langid().to_string(), "es-ES"); +/// +/// // Coincidencia parcial (retrocede al idioma base si no hay variante regional). +/// let lang = Locale::resolve("es-EC"); +/// assert_eq!(lang.langid().to_string(), "es-ES"); // Porque "es-EC" no está soportado. +/// +/// // Idioma no especificado. +/// let lang = Locale::resolve(""); +/// assert_eq!(lang, Locale::Unspecified); +/// +/// // Idioma no soportado. +/// let lang = Locale::resolve("ja-JP"); +/// assert_eq!(lang, Locale::Unsupported("ja-JP".to_string())); +/// ``` +/// +/// Con la siguiente instrucción siempre se obtiene un [`LanguageIdentifier`] válido, ya sea porque +/// resuelve un idioma soportado o porque se aplica el idioma por defecto o, en último término, el +/// de respaldo (`"en-US"`): +/// +/// ```rust +/// # use pagetop::prelude::*; +/// // Idioma por defecto si no resuelve. +/// let lang = Locale::resolve("it-IT"); +/// let langid = lang.langid(); +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Locale { + /// No se ha especificado ningún identificador de idioma. + /// + /// Se usa cuando la cadena de idioma está vacía o no se puede obtener un idioma válido de la + /// petición HTTP. + Unspecified, + /// El identificador se ha resuelto a un idioma soportado por PageTop. + /// + /// Se utiliza cuando se encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados + /// por PageTop que coincide exactamente con el identificador de idioma (p. ej. `"es-ES"`) o + /// con el identificador del idioma base (p. ej. `"es"`). + Resolved(&'static LanguageIdentifier), + /// El identificador de idioma no está soportado por PageTop. + Unsupported(String), +} + +impl Default for Locale { + /// Resuelve al idioma por defecto y, si no está disponible, al idioma de respaldo (`"en-US"`). + fn default() -> Self { + Locale::Resolved(Locale::default_langid()) + } +} + +impl Locale { + /// Resuelve `language` y devuelve la variante [`Locale`] apropiada. + /// + /// - Si la cadena está vacía o contiene solo espacios, devuelve [`Locale::Unspecified`]. + /// - Si el idioma se reconoce (ya sea como código completo o como idioma base), devuelve + /// [`Locale::Resolved`]. + /// - En caso contrario, devuelve [`Locale::Unsupported`] con la cadena original. + pub fn resolve(language: impl AsRef<str>) -> Self { + let language = language.as_ref().trim(); + + // Rechaza cadenas vacías. + if language.is_empty() { + return Self::Unspecified; + } + + // Intenta aplicar coincidencia exacta con el código completo (p. ej. "es-MX"). + let lang = language.to_ascii_lowercase(); + if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) { + return Self::Resolved(langid); + } + + // Si la variante regional no existe, retrocede al idioma base (p. ej. "es"). + if let Some((base_lang, _)) = lang.split_once('-') { + if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { + return Self::Resolved(langid); + } + } + + // En caso contrario, indica que el idioma no está soportado. + Self::Unsupported(language.to_string()) + } + + /// Devuelve el [`LanguageIdentifier`] si el idioma fue reconocido. + /// + /// Solo retorna `Some` si la variante es [`Locale::Resolved`]. En cualquier otro caso (por + /// ejemplo, si el identificador es vacío o no está soportado), devuelve `None`. + /// + /// Este método es útil cuando se desea acceder directamente al idioma reconocido sin aplicar el + /// idioma por defecto ni el de respaldo. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let lang = Locale::resolve("es-ES").as_option(); + /// assert_eq!(lang.unwrap().to_string(), "es-ES"); + /// + /// let lang = Locale::resolve("ja-JP").as_option(); + /// assert!(lang.is_none()); + /// ``` + #[inline] + pub fn as_option(&self) -> Option<&'static LanguageIdentifier> { + match self { + Locale::Resolved(l) => Some(l), + _ => None, + } + } + + // **< Locale HELPERS >************************************************************************* + + /// Inicializa el idioma por defecto que utilizará la aplicación. + /// + /// Debe llamarse durante la inicialización para indicar si el idioma por defecto procede de la + /// configuración, de una configuración no válida o del idioma de respaldo. + pub(crate) fn init() { + match global::SETTINGS.app.language.as_deref() { + Some(raw) if !raw.trim().is_empty() => { + if let Some(langid) = *CONFIG_LANGID { + trace::debug!("Default language \"{langid}\" (from config: \"{raw}\")"); + } else { + trace::debug!( + "Default language \"{}\" (fallback, invalid config: \"{raw}\")", + *FALLBACK_LANGID + ); + } + } + _ => trace::debug!( + "Default language \"{}\" (fallback, no config)", + *FALLBACK_LANGID + ), + } + } + + /// Devuelve el identificador de idioma configurado explícitamente, si es válido. + /// + /// Si no se ha configurado un idioma por defecto o el valor no es válido, devuelve `None`. + pub fn configured_langid() -> Option<&'static LanguageIdentifier> { + *CONFIG_LANGID + } + + /// Devuelve siempre el identificador de idioma de respaldo (`"en-US"`). + /// + /// Es el idioma garantizado incluso cuando no haya configuración de la aplicación o cuando + /// el valor configurado no sea válido. + pub fn fallback_langid() -> &'static LanguageIdentifier { + &*FALLBACK_LANGID + } + + /// Devuelve el identificador de idioma configurado o, en su defecto, el de respaldo. + /// + /// Este es el idioma que utiliza internamente [`Locale::default()`] y resulta útil como idioma + /// base cuando no se dispone de un contexto más específico. + pub fn default_langid() -> &'static LanguageIdentifier { + (*CONFIG_LANGID).unwrap_or(&*FALLBACK_LANGID) + } +} + +/// Permite a [`Locale`] actuar como proveedor de idioma. +/// +/// Devuelve el [`LanguageIdentifier`] si la variante es [`Locale::Resolved`]; en caso contrario, +/// devuelve el idioma por defecto de la aplicación y, si tampoco está disponible, el idioma de +/// respaldo (`"en-US"`). +/// +/// Resulta útil para usar un valor de [`Locale`] como fuente de traducción en +/// [`L10n::lookup()`](crate::locale::L10n::lookup) o [`L10n::using()`](crate::locale::L10n::using). +impl LangId for Locale { + #[inline] + fn langid(&self) -> &'static LanguageIdentifier { + match self { + Locale::Resolved(l) => l, + _ => Locale::default_langid(), + } + } +} diff --git a/src/locale/l10n.rs b/src/locale/l10n.rs new file mode 100644 index 00000000..94c309c9 --- /dev/null +++ b/src/locale/l10n.rs @@ -0,0 +1,194 @@ +use crate::html::{Markup, PreEscaped}; +use crate::{include_locales, AutoDefault}; + +use super::{LangId, Locale}; + +use fluent_templates::Loader; +use fluent_templates::StaticLoader as Locales; + +use std::borrow::Cow; +use std::collections::HashMap; + +use std::fmt; + +include_locales!(LOCALES_PAGETOP); + +/// Operación de localización a realizar. +/// +/// * `None` - No se aplica ninguna localización. +/// * `Text` - Con una cadena literal que se devolverá tal cual. +/// * `Translate` - Con la clave a resolver en el `Locales` indicado. +#[derive(AutoDefault, Clone, Debug)] +enum L10nOp { + #[default] + None, + Text(Cow<'static, str>), + Translate(Cow<'static, str>), +} + +/// Crea instancias para traducir *textos localizados*. +/// +/// Cada instancia puede representar: +/// +/// - Un texto puro (`n()`) que no requiere traducción. +/// - Una clave para traducir un texto de las traducciones predefinidas de PageTop (`l()`). +/// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). +/// +/// # Ejemplo +/// +/// Los argumentos dinámicos se añaden con `with_arg()` o `with_args()`. +/// +/// ```rust +/// # use pagetop::prelude::*; +/// // Texto literal sin traducción. +/// let raw = L10n::n("© 2025 PageTop").get(); +/// +/// // Traducción simple con clave y argumentos. +/// let hello = L10n::l("greeting") +/// .with_arg("name", "Manuel") +/// .get(); +/// ``` +/// +/// También sirve para traducciones contra un conjunto de recursos concreto. +/// +/// ```rust,ignore +/// // Traducción con clave, conjunto de traducciones y fuente de idioma. +/// let bye = L10n::t("goodbye", &LOCALES_CUSTOM).lookup(&Locale::resolve("it")); +/// ``` +#[derive(AutoDefault, Clone)] +pub struct L10n { + op: L10nOp, + #[default(&LOCALES_PAGETOP)] + locales: &'static Locales, + args: HashMap<String, String>, +} + +impl L10n { + /// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción. + pub fn n(text: impl Into<Cow<'static, str>>) -> Self { + L10n { + op: L10nOp::Text(text.into()), + ..Default::default() + } + } + + /// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de + /// traducciones predefinidas. + pub fn l(key: impl Into<Cow<'static, str>>) -> Self { + L10n { + op: L10nOp::Translate(key.into()), + ..Default::default() + } + } + + /// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de + /// traducciones específico. + pub fn t(key: impl Into<Cow<'static, str>>, locales: &'static Locales) -> Self { + L10n { + op: L10nOp::Translate(key.into()), + locales, + ..Default::default() + } + } + + /// Añade un argumento `{$arg}` => `value` a la traducción. + pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self { + self.args.insert(arg.into(), value.into()); + self + } + + /// Añade varios argumentos a la traducción de una vez (p. ej. usando la macro + /// [`util::kv!`](crate::util::kv) o también `vec![("k", "v")]`, incluso un array de duplas u + /// otras colecciones). + pub fn with_args<I, K, V>(mut self, args: I) -> Self + where + I: IntoIterator<Item = (K, V)>, + K: Into<String>, + V: Into<String>, + { + self.args + .extend(args.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } + + /// Resuelve la traducción usando el idioma por defecto o, si no procede, el de respaldo de la + /// aplicación. + /// + /// Devuelve `None` si no aplica o no encuentra una traducción válida. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); + /// ``` + pub fn get(&self) -> Option<String> { + self.lookup(&Locale::default()) + } + + /// Resuelve la traducción usando la fuente de idioma proporcionada. + /// + /// Devuelve `None` si no aplica o no encuentra una traducción válida. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// struct ResourceLang; + /// + /// impl LangId for ResourceLang { + /// fn langid(&self) -> &'static LanguageIdentifier { + /// Locale::resolve("es-MX").langid() + /// } + /// } + /// + /// let r = ResourceLang; + /// let text = L10n::l("greeting").with_arg("name", "Usuario").lookup(&r); + /// ``` + pub fn lookup(&self, language: &impl LangId) -> Option<String> { + match &self.op { + L10nOp::None => None, + L10nOp::Text(text) => Some(text.clone().into_owned()), + L10nOp::Translate(key) => { + if self.args.is_empty() { + self.locales.try_lookup(language.langid(), key.as_ref()) + } else { + self.locales.try_lookup_with_args( + language.langid(), + key.as_ref(), + &self + .args + .iter() + .map(|(k, v)| (Cow::Owned(k.clone()), v.clone().into())) + .collect::<HashMap<_, _>>(), + ) + } + } + } + } + + /// Traduce el texto y lo devuelve como [`Markup`] usando la fuente de idioma proporcionada. + /// + /// Si no se encuentra una traducción válida, devuelve una cadena vacía. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop::prelude::*; + /// let html = L10n::l("welcome.message").using(&Locale::resolve("es")); + /// ``` + pub fn using(&self, language: &impl LangId) -> Markup { + PreEscaped(self.lookup(language).unwrap_or_default()) + } +} + +impl fmt::Debug for L10n { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("L10n") + .field("op", &self.op) + .field("args", &self.args) + // No se puede mostrar `locales`; se representa con un texto fijo. + .field("locales", &"<StaticLoader>") + .finish() + } +} diff --git a/src/locale/languages.rs b/src/locale/languages.rs new file mode 100644 index 00000000..f1962a14 --- /dev/null +++ b/src/locale/languages.rs @@ -0,0 +1,27 @@ +use crate::util; + +use super::{langid, LanguageIdentifier}; + +use std::collections::HashMap; +use std::sync::LazyLock; + +/// Tabla de idiomas soportados por PageTop. +/// +/// Cada entrada asocia un código de idioma en minúsculas (por ejemplo, `"en"` o `"es-es"`) con: +/// +/// - Su [`LanguageIdentifier`] canónico. +/// - La clave de traducción definida en `src/locale/{lang}/languages.ftl` para mostrar su nombre en +/// el idioma activo. +/// +/// Esto permite admitir alias de idioma como `"en"` o `"es"` y, al mismo tiempo, mantener un +/// identificador de idioma canónico (por ejemplo, `langid!("en-US")` o `langid!("es-ES")`). +pub(crate) static LANGUAGES: LazyLock<HashMap<&str, (LanguageIdentifier, &str)>> = + LazyLock::new(|| { + util::kv![ + "en" => ( langid!("en-US"), "english" ), + "en-gb" => ( langid!("en-GB"), "english_british" ), + "en-us" => ( langid!("en-US"), "english_united_states" ), + "es" => ( langid!("es-ES"), "spanish" ), + "es-es" => ( langid!("es-ES"), "spanish_spain" ), + ] + }); diff --git a/src/locale/request.rs b/src/locale/request.rs new file mode 100644 index 00000000..6f3af13d --- /dev/null +++ b/src/locale/request.rs @@ -0,0 +1,178 @@ +use crate::global; +use crate::service::HttpRequest; + +use super::{LangId, LanguageIdentifier, Locale}; + +/// Representa el idioma asociado a una petición HTTP. +/// +/// Determina qué idioma se usará para renderizar la respuesta asociada a una petición. También +/// indica si es necesario propagar ese idioma en los enlaces usando el parámetro de *query* +/// `?lang=...`. El comportamiento concreto depende de la política global +/// [`LangNegotiation`](crate::global::LangNegotiation) configurada en la aplicación. +/// +/// El idioma resultante se expone a través del *trait* [`LangId`], de modo que pueda usarse +/// [`RequestLocale`] como cualquier otra fuente de idioma en PageTop. +pub struct RequestLocale { + // Idioma elegido por la aplicación para esta petición, combinando la configuración, la cabecera + // `Accept-Language` y/o el idioma de respaldo. + base: &'static LanguageIdentifier, + // Idioma finalmente aplicado a la petición (puede coincidir con `base` o no). + effective: &'static LanguageIdentifier, +} + +impl RequestLocale { + /// Construye un `RequestLocale` a partir de una petición HTTP. + /// + /// El idioma de la petición se decide según la estrategia definida por + /// [`LangNegotiation`](crate::global::LangNegotiation): + /// + /// - [`LangNegotiation::Full`](crate::global::LangNegotiation::Full) determina el idioma en + /// este orden: + /// 1. Parámetro de *query* `?lang=...`, si existe y corresponde a un idioma soportado. + /// 2. [`Locale::configured_langid()`], si la aplicación tiene un idioma por defecto válido. + /// 3. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`]. + /// 4. Idioma de respaldo. + /// + /// - [`LangNegotiation::NoQuery`](crate::global::LangNegotiation::NoQuery) descarta el uso del + /// parámetro `?lang=...` y determina el idioma en este orden: + /// 1. [`Locale::configured_langid()`], si la aplicación tiene un idioma por defecto válido. + /// 2. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`]. + /// 3. Idioma de respaldo. + /// + /// - [`LangNegotiation::ConfigOnly`](crate::global::LangNegotiation::ConfigOnly) sólo usa la + /// configuración de la aplicación mediante [`Locale::default_langid()`], sin consultar la + /// cabecera `Accept-Language` ni el parámetro `?lang`. Este modo también aplica el idioma de + /// respaldo si es necesario. + /// + /// En todos los casos, el idioma resultante es siempre un [`LanguageIdentifier`] soportado por + /// la aplicación y será el que PageTop utilice para renderizar la respuesta de la petición. + pub fn from_request(request: Option<&HttpRequest>) -> Self { + let mode = global::SETTINGS.app.lang_negotiation; + + // Idioma elegido por la aplicación para esta petición, antes de considerar ajustes por URL. + let base: &'static LanguageIdentifier = match mode { + global::LangNegotiation::ConfigOnly => { + // Sólo configuración o, en su defecto, idioma de respaldo. + Locale::default_langid() + } + global::LangNegotiation::Full | global::LangNegotiation::NoQuery => { + if let Some(default) = Locale::configured_langid() { + default + } else { + // Sin idioma por defecto, se evalúa la cabecera `Accept-Language`. + request + .and_then(|req| req.headers().get("Accept-Language")) + .and_then(|value| value.to_str().ok()) + .and_then(|header| { + // Puede tener varios idiomas, p. ej. "es-ES,es;q=0.9,en;q=0.8". + // + // Y cada idioma puede aplicar un factor de calidad. Actualmente se + // aplica una estrategia sencilla: usar sólo el primer idioma declarado + // antes de la primera coma e ignorar el resto de entradas y sus + // factores de calidad (`q=...`). + let first = header.split(',').next()?.trim(); + + // En este primer elemento también puede aparecer `;q=...`, así que se + // extrae únicamente la etiqueta de idioma: "es-ES;q=0.9" -> "es-ES". + let tag = first.split(';').next()?.trim(); + + // TODO: Mejorar el soporte de `Accept-Language` en el futuro: + // + // - Parsear todos los idiomas con sus factores de calidad (`q`). + // - Ordenar por `q` descendente y por aparición en caso de empate. + // - Ignorar o tratar explícitamente el comodín `*`. + // - Tener en cuenta rangos de idioma (`es`, `en`, etc.) y variantes + // regionales. + // - Añadir tests unitarios para distintas combinaciones de cabecera. + if tag.is_empty() { + None + } else if let Locale::Resolved(langid) = Locale::resolve(tag) { + Some(langid) + } else { + None + } + }) + // Si no hay cabecera o no puede resolverse, se usa el idioma de respaldo. + .unwrap_or(Locale::fallback_langid()) + } + } + }; + + // Idioma aplicado a la petición tras considerar la *query* `?lang=...`. + let effective: &'static LanguageIdentifier = match mode { + global::LangNegotiation::ConfigOnly | global::LangNegotiation::NoQuery => { + // En estos modos no se permite que la URL modifique el idioma. + base + } + global::LangNegotiation::Full => { + request + // Se obtiene el valor de `lang` de la petición, si existe. + .and_then(|req| { + req.query_string().split('&').find_map(|pair| { + let mut param = pair.splitn(2, '='); + match (param.next(), param.next()) { + (Some("lang"), Some(value)) if !value.is_empty() => Some(value), + _ => None, + } + }) + }) + // Se comprueba si es un idioma soportado. + .and_then(|language| { + if let Locale::Resolved(langid) = Locale::resolve(language) { + Some(langid) + } else { + None + } + }) + // Si no hay `lang` o no es válido, se usa `base`. + .unwrap_or(base) + } + }; + + RequestLocale { base, effective } + } + + /// Fuerza el idioma que se utilizará para las traducciones de esta petición. + /// + /// Este método permite sustituir el idioma calculado (por configuración, cabecera, `?lang`, + /// etc.) por otro idioma. Normalmente se usa cuando quieres que toda la respuesta se genere en + /// un idioma concreto, independientemente de cómo se haya llegado a él. + #[inline] + pub fn with_langid(&mut self, language: &impl LangId) -> &mut Self { + self.effective = language.langid(); + self + } + + /// Indica si conviene propagar `lang=...` en los enlaces generados. + /// + /// El comportamiento depende de la estrategia configurada en + /// [`LangNegotiation`](crate::global::LangNegotiation): + /// + /// - En modo [`LangNegotiation::Full`](crate::global::LangNegotiation::Full) devuelve `true` + /// cuando la respuesta se está generando en un idioma distinto del que la aplicación habría + /// elegido automáticamente a partir de la configuración, el navegador y el idioma de + /// respaldo. En la práctica suele significar que el usuario ha pedido expresamente otro + /// idioma (por ejemplo, con `?lang=...`) o que se ha forzado con + /// [`with_langid()`](Self::with_langid), y por tanto es recomendable propagar `lang=...` en + /// los enlaces para mantener esa preferencia mientras se navega. + /// + /// - En modos [`LangNegotiation::NoQuery`](crate::global::LangNegotiation::NoQuery) y + /// [`LangNegotiation::ConfigOnly`](crate::global::LangNegotiation::ConfigOnly) siempre + /// devuelve `false`, ya que en estas estrategias la aplicación no utiliza el parámetro + /// `?lang=...` para seleccionar ni para propagar el idioma. + #[inline] + pub(crate) fn needs_lang_query(&self) -> bool { + match global::SETTINGS.app.lang_negotiation { + global::LangNegotiation::Full => self.base != self.effective, + global::LangNegotiation::NoQuery | global::LangNegotiation::ConfigOnly => false, + } + } +} + +/// Permite a [`RequestLocale`] actuar como proveedor de idioma. +impl LangId for RequestLocale { + #[inline] + fn langid(&self) -> &'static LanguageIdentifier { + self.effective + } +} diff --git a/src/response/page.rs b/src/response/page.rs index 90ce84e1..41d0a125 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -294,6 +294,7 @@ impl Page { /// Resulta útil para usar [`Page`] directamente como fuente de traducción en [`L10n::lookup()`] o /// [`L10n::using()`]. impl LangId for Page { + #[inline] fn langid(&self) -> &'static LanguageIdentifier { self.context.langid() } From 16d6afbd9847e3b1ba2cb14fa0ac2a97fab0141c Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 15 Dec 2025 17:24:00 +0100 Subject: [PATCH 217/224] =?UTF-8?q?=F0=9F=9A=A7=20(config):=20Nueva=20gest?= =?UTF-8?q?i=C3=B3n=20de=20opciones=20enumeradas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 2 +- src/app/figfont.rs | 22 ++++--------- src/global.rs | 39 +++++------------------ src/global/lang_negotiation.rs | 56 ++++++++++++++++++++++++++++++++++ src/global/startup_banner.rs | 50 ++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 src/global/lang_negotiation.rs create mode 100644 src/global/startup_banner.rs diff --git a/src/app.rs b/src/app.rs index dd1f8780..b49e522e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -78,7 +78,7 @@ impl Application { use colored::Colorize; use terminal_size::{terminal_size, Width}; - if global::SETTINGS.app.startup_banner.to_lowercase() != "off" { + if global::SETTINGS.app.startup_banner != global::StartupBanner::Off { // Nombre de la aplicación, ajustado al ancho del terminal si es necesario. let mut app_ff = String::new(); let app_name = &global::SETTINGS.app.name; diff --git a/src/app/figfont.rs b/src/app/figfont.rs index 6592d813..1b3b2f7d 100644 --- a/src/app/figfont.rs +++ b/src/app/figfont.rs @@ -10,21 +10,11 @@ pub static FIGFONT: LazyLock<FIGfont> = LazyLock::new(|| { let speed = include_str!("speed.flf"); let starwars = include_str!("starwars.flf"); - FIGfont::from_content( - match global::SETTINGS.app.startup_banner.to_lowercase().as_str() { - "off" => slant, - "slant" => slant, - "small" => small, - "speed" => speed, - "starwars" => starwars, - _ => { - println!( - "\n FIGfont \"{}\" not found for banner. Using \"Slant\". Check settings.", - global::SETTINGS.app.startup_banner, - ); - slant - } - }, - ) + FIGfont::from_content(match global::SETTINGS.app.startup_banner { + global::StartupBanner::Off | global::StartupBanner::Slant => slant, + global::StartupBanner::Small => small, + global::StartupBanner::Speed => speed, + global::StartupBanner::Starwars => starwars, + }) .unwrap() }); diff --git a/src/global.rs b/src/global.rs index b484731b..539e15f9 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,9 +1,15 @@ //! Opciones de configuración globales. -use crate::{include_config, AutoDefault}; +use crate::include_config; use serde::Deserialize; +mod lang_negotiation; +pub use lang_negotiation::LangNegotiation; + +mod startup_banner; +pub use startup_banner::StartupBanner; + // **< SETTINGS >*********************************************************************************** include_config!(SETTINGS: Settings => [ @@ -32,35 +38,6 @@ include_config!(SETTINGS: Settings => [ "server.session_lifetime" => 604_800, ]); -// **< LangNegotiation >**************************************************************************** - -/// Modos disponibles para negociar el idioma de una petición HTTP. -/// -/// El ajuste [`global::SETTINGS.app.lang_negotiation`](crate::global::App::lang_negotiation) -/// determina qué fuentes intervienen en la resolución del idioma efectivo utilizado por -/// [`RequestLocale`](crate::locale::RequestLocale) y en la generación de URLs mediante -/// [`Context::route()`](crate::core::component::Context::route). -#[derive(AutoDefault, Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum LangNegotiation { - /// Usa todas las fuentes disponibles para determinar el idioma, en este orden: comprueba el - /// parámetro `?lang` de la URL; si no está presente o no es válido, usa la cabecera HTTP - /// `Accept-Language`; si tampoco está disponible o no es válido, usa el idioma configurado en - /// [`global::SETTINGS.app.language`](crate::global::App::language) o, en su defecto, el idioma - /// de respaldo. Es el comportamiento por defecto. - #[default] - Full, - - /// Igual que `LangNegotiation::Full`, pero sin tener en cuenta el parámetro `?lang` de la URL. - /// El idioma depende únicamente de la cabecera `Accept-Language` del navegador y, en última - /// instancia, de la configuración o idioma de respaldo. - NoQuery, - - /// Usa sólo la configuración o, en su defecto, el idioma de respaldo; ignora la cabecera - /// `Accept-Language` y el parámetro de la URL. Este modo proporciona un comportamiento estable - /// con idioma fijo. - ConfigOnly, -} - // **< Settings >*********************************************************************************** #[derive(Debug, Deserialize)] @@ -101,7 +78,7 @@ pub struct App { pub lang_negotiation: LangNegotiation, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. - pub startup_banner: String, + pub startup_banner: StartupBanner, /// Activa la página de bienvenida de PageTop. /// /// Si está activada, se instala la extensión [`Welcome`](crate::base::extension::Welcome), que diff --git a/src/global/lang_negotiation.rs b/src/global/lang_negotiation.rs new file mode 100644 index 00000000..1e24e6c4 --- /dev/null +++ b/src/global/lang_negotiation.rs @@ -0,0 +1,56 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Modos disponibles para negociar el idioma de una petición HTTP. +/// +/// El ajuste [`global::SETTINGS.app.lang_negotiation`](crate::global::App::lang_negotiation) +/// determina qué fuentes intervienen en la resolución del idioma efectivo utilizado por +/// [`RequestLocale`](crate::locale::RequestLocale) y en la generación de URLs mediante +/// [`Context::route()`](crate::core::component::Context::route). +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LangNegotiation { + /// Usa todas las fuentes disponibles para determinar el idioma, en este orden: comprueba el + /// parámetro `?lang` de la URL; si no está presente o no es válido, usa la cabecera HTTP + /// `Accept-Language`; si tampoco está disponible o no es válido, usa el idioma configurado en + /// [`global::SETTINGS.app.language`](crate::global::App::language) o, en su defecto, el idioma + /// de respaldo. Es el comportamiento por defecto. + #[default] + Full, + + /// Igual que `LangNegotiation::Full`, pero sin tener en cuenta el parámetro `?lang` de la URL. + /// El idioma depende únicamente de la cabecera `Accept-Language` del navegador y, en última + /// instancia, de la configuración o idioma de respaldo. + NoQuery, + + /// Usa sólo la configuración o, en su defecto, el idioma de respaldo; ignora la cabecera + /// `Accept-Language` y el parámetro de la URL. Este modo proporciona un comportamiento estable + /// con idioma fijo. + ConfigOnly, +} + +impl<'de> Deserialize<'de> for LangNegotiation { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "full" => Self::Full, + "noquery" => Self::NoQuery, + "configonly" => Self::ConfigOnly, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [app].lang_negotiation. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/global/startup_banner.rs b/src/global/startup_banner.rs new file mode 100644 index 00000000..d1d10688 --- /dev/null +++ b/src/global/startup_banner.rs @@ -0,0 +1,50 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Opciones para el *banner* ASCII mostrado al arrancar la aplicación. +/// +/// Se obtiene de [`global::SETTINGS.app.startup_banner`](crate::global::App::startup_banner) y +/// controla si se muestra un *banner* en la salida estándar al arrancar la aplicación. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum StartupBanner { + /// No muestra ningún banner de inicio. + Off, + /// Banner en estilo "Slant". Es el comportamiento por defecto. + #[default] + Slant, + /// Banner en estilo "Small". + Small, + /// Banner en estilo "Speed". + Speed, + /// Banner en estilo "Starwars". + Starwars, +} + +impl<'de> Deserialize<'de> for StartupBanner { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "off" => Self::Off, + "slant" => Self::Slant, + "small" => Self::Small, + "speed" => Self::Speed, + "starwars" => Self::Starwars, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [app].startup_banner. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} From b76c4a4d23ba5f09d0e81c069caac18bc9bc3e7e Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Mon, 15 Dec 2025 20:51:23 +0100 Subject: [PATCH 218/224] =?UTF-8?q?=F0=9F=9A=A7=20(config):=20Opciones=20e?= =?UTF-8?q?numeradas=20para=20el=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.rs | 10 ++++++-- src/global/log_format.rs | 51 +++++++++++++++++++++++++++++++++++++++ src/global/log_rolling.rs | 51 +++++++++++++++++++++++++++++++++++++++ src/trace.rs | 45 ++++++++++++---------------------- 4 files changed, 126 insertions(+), 31 deletions(-) create mode 100644 src/global/log_format.rs create mode 100644 src/global/log_rolling.rs diff --git a/src/global.rs b/src/global.rs index 539e15f9..8bf753e3 100644 --- a/src/global.rs +++ b/src/global.rs @@ -10,6 +10,12 @@ pub use lang_negotiation::LangNegotiation; mod startup_banner; pub use startup_banner::StartupBanner; +mod log_rolling; +pub use log_rolling::LogRolling; + +mod log_format; +pub use log_format::LogFormat; + // **< SETTINGS >*********************************************************************************** include_config!(SETTINGS: Settings => [ @@ -114,13 +120,13 @@ pub struct Log { pub tracing: String, /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o los vuelca en archivos con /// rotación: *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. - pub rolling: String, + pub rolling: LogRolling, /// Directorio para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub path: String, /// Prefijo para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub prefix: String, /// Formato de salida de las trazas. Opciones: *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*. - pub format: String, + pub format: LogFormat, } #[derive(Debug, Deserialize)] diff --git a/src/global/log_format.rs b/src/global/log_format.rs new file mode 100644 index 00000000..795d319d --- /dev/null +++ b/src/global/log_format.rs @@ -0,0 +1,51 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Formatos disponibles para mostrar las trazas. +/// +/// El valor se obtiene de [`global::SETTINGS.log.format`](crate::global::Log::format) y determina +/// la representación textual de los eventos registrados por `tracing`. +/// El valor configurado no distingue entre mayúsculas y minúsculas. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LogFormat { + /// Formato JSON estructurado. + Json, + + /// Formato completo con detalles adicionales. Es el valor por defecto. + #[default] + Full, + + /// Formato más conciso y legible. + Compact, + + /// Formato human-friendly con colores y saltos de línea. + Pretty, +} + +impl<'de> Deserialize<'de> for LogFormat { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "json" => Self::Json, + "full" => Self::Full, + "compact" => Self::Compact, + "pretty" => Self::Pretty, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [log].format. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/global/log_rolling.rs b/src/global/log_rolling.rs new file mode 100644 index 00000000..b9b59c79 --- /dev/null +++ b/src/global/log_rolling.rs @@ -0,0 +1,51 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Modos de salida y rotación para el registro de trazas. +/// +/// El valor se obtiene de [`global::SETTINGS.log.rolling`](crate::global::Log::rolling) y +/// determina si las trazas se muestran por pantalla o se vuelcan en archivos con rotación. +/// El valor configurado no distingue entre mayúsculas y minúsculas. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LogRolling { + /// Escribe las trazas en la salida estándar (sin rotación de archivos). + Stdout, + /// Rotación diaria de archivos de traza. + #[default] + Daily, + /// Rotación horaria de archivos de traza. + Hourly, + /// Rotación por minutos de archivos de traza. + Minutely, + /// Archivo de traza "infinito", sin rotación. + Endless, +} + +impl<'de> Deserialize<'de> for LogRolling { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "stdout" => Self::Stdout, + "daily" => Self::Daily, + "hourly" => Self::Hourly, + "minutely" => Self::Minutely, + "endless" => Self::Endless, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [log].rolling. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/trace.rs b/src/trace.rs index 12e428a8..461c2604 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -14,6 +14,7 @@ //! *spans* son estructurados, con la capacidad de registrar tipos de datos y mensajes de texto. use crate::global; +use crate::global::{LogFormat, LogRolling}; pub use tracing::{debug, error, info, trace, warn}; pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span}; @@ -33,7 +34,6 @@ use std::sync::LazyLock; /// Dado que las trazas o eventos registrados poco antes de un fallo suelen ser cruciales para /// diagnosticar la causa, `Lazy<WorkerGuard>` garantiza que todos los registros almacenados se /// envíen antes de finalizar la ejecución. -#[rustfmt::skip] pub(crate) static TRACING: LazyLock<WorkerGuard> = LazyLock::new(|| { if !global::SETTINGS.log.enabled || cfg!(test) || cfg!(feature = "testing") { // Tracing desactivado, se instala un subscriber nulo. @@ -46,25 +46,19 @@ pub(crate) static TRACING: LazyLock<WorkerGuard> = LazyLock::new(|| { let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) .unwrap_or_else(|_| EnvFilter::new("Info")); - let rolling = global::SETTINGS.log.rolling.to_lowercase(); + let rolling = global::SETTINGS.log.rolling; - let (non_blocking, guard) = match rolling.as_str() { - "stdout" => tracing_appender::non_blocking(std::io::stdout()), + let (non_blocking, guard) = match rolling { + LogRolling::Stdout => tracing_appender::non_blocking(std::io::stdout()), _ => tracing_appender::non_blocking({ let path = &global::SETTINGS.log.path; let prefix = &global::SETTINGS.log.prefix; - match rolling.as_str() { - "daily" => tracing_appender::rolling::daily(path, prefix), - "hourly" => tracing_appender::rolling::hourly(path, prefix), - "minutely" => tracing_appender::rolling::minutely(path, prefix), - "endless" => tracing_appender::rolling::never(path, prefix), - _ => { - println!( - "Rolling value \"{}\" not valid. Using \"daily\". Check the settings file.", - global::SETTINGS.log.rolling, - ); - tracing_appender::rolling::daily(path, prefix) - } + match rolling { + LogRolling::Daily => tracing_appender::rolling::daily(path, prefix), + LogRolling::Hourly => tracing_appender::rolling::hourly(path, prefix), + LogRolling::Minutely => tracing_appender::rolling::minutely(path, prefix), + LogRolling::Endless => tracing_appender::rolling::never(path, prefix), + LogRolling::Stdout => unreachable!("Stdout rolling already handled above"), } }), }; @@ -72,20 +66,13 @@ pub(crate) static TRACING: LazyLock<WorkerGuard> = LazyLock::new(|| { let subscriber = tracing_subscriber::fmt() .with_env_filter(env_filter) .with_writer(non_blocking) - .with_ansi(rolling.as_str() == "stdout"); + .with_ansi(matches!(rolling, LogRolling::Stdout)); - match global::SETTINGS.log.format.to_lowercase().as_str() { - "json" => subscriber.json().init(), - "full" => subscriber.init(), - "compact" => subscriber.compact().init(), - "pretty" => subscriber.pretty().init(), - _ => { - println!( - "Tracing format \"{}\" not valid. Using \"Full\". Check the settings file.", - global::SETTINGS.log.format, - ); - subscriber.init(); - } + match global::SETTINGS.log.format { + LogFormat::Json => subscriber.json().init(), + LogFormat::Full => subscriber.init(), + LogFormat::Compact => subscriber.compact().init(), + LogFormat::Pretty => subscriber.pretty().init(), } guard From 700bca7258df46be83baaf5ed46c75a5e6fba58f Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Tue, 16 Dec 2025 09:06:52 +0100 Subject: [PATCH 219/224] =?UTF-8?q?=F0=9F=92=A1=20Repasa=20comentarios=20d?= =?UTF-8?q?e=20la=20cabecera=20de=20funciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/aux/border.rs | 4 +- .../src/theme/aux/breakpoint.rs | 56 +++++++++---------- .../pagetop-bootsier/src/theme/aux/button.rs | 4 +- .../pagetop-bootsier/src/theme/aux/color.rs | 40 ++++++------- .../pagetop-bootsier/src/theme/aux/layout.rs | 26 ++++----- .../pagetop-bootsier/src/theme/aux/rounded.rs | 34 +++++------ .../src/theme/classes/layout.rs | 26 ++++----- .../src/theme/dropdown/props.rs | 18 +++--- .../pagetop-bootsier/src/theme/image/props.rs | 6 +- .../pagetop-bootsier/src/theme/nav/item.rs | 2 +- .../pagetop-bootsier/src/theme/nav/props.rs | 10 ++-- .../src/theme/navbar/props.rs | 6 +- .../src/theme/offcanvas/props.rs | 8 +-- helpers/pagetop-build/src/lib.rs | 2 +- src/app.rs | 6 +- .../component/after_render_component.rs | 2 +- .../component/before_render_component.rs | 2 +- src/base/action/page/after_render_body.rs | 2 +- src/base/action/page/before_render_body.rs | 2 +- .../action/theme/after_render_component.rs | 2 +- .../action/theme/before_render_component.rs | 2 +- src/base/action/theme/prepare_render.rs | 2 +- src/core.rs | 44 +++++++-------- src/core/action/all.rs | 14 ++--- src/core/component/children.rs | 22 ++++---- src/core/theme/all.rs | 2 +- src/html.rs | 2 +- src/html/assets/favicon.rs | 16 +++--- src/response/json.rs | 2 +- 29 files changed, 182 insertions(+), 182 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index 43882767..3bd705e7 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -22,7 +22,7 @@ impl BorderColor { const BORDER: &str = "border"; const BORDER_PREFIX: &str = "border-"; - // Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase. + /// Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -35,7 +35,7 @@ impl BorderColor { } } - // Añade la clase `border-*` a la cadena de clases. + /// Añade la clase `border-*` a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if let Some(suffix) = self.suffix() { diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index 4d9a7626..ce7998e7 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -19,7 +19,7 @@ pub enum BreakPoint { } impl BreakPoint { - // Devuelve la identificación del punto de ruptura. + /// Devuelve la identificación del punto de ruptura. #[rustfmt::skip] #[inline] pub(crate) const fn as_str(self) -> &'static str { @@ -33,11 +33,11 @@ impl BreakPoint { } } - // Añade el punto de ruptura con un prefijo y un sufijo (opcional) separados por un guion `-` a - // la cadena de clases. - // - // - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). - // - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. + /// Añade el punto de ruptura con un prefijo y un sufijo (opcional) separados por un guion `-` a + /// la cadena de clases. + /// + /// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). + /// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. #[inline] pub(crate) fn push_class(self, classes: &mut String, prefix: &str, suffix: &str) { if prefix.is_empty() { @@ -60,28 +60,28 @@ impl BreakPoint { } } - // Devuelve la clase para el punto de ruptura, con un prefijo y un sufijo opcional, separados - // por un guion `-`. - // - // - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). - // - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. - // - Si `prefix` está vacío devuelve `""`. - // - // # Ejemplos - // - // ```rust - // # use pagetop_bootsier::prelude::*; - // let bp = BreakPoint::MD; - // assert_eq!(bp.class_with("col", ""), "col-md"); - // assert_eq!(bp.class_with("col", "6"), "col-md-6"); - // - // let bp = BreakPoint::None; - // assert_eq!(bp.class_with("offcanvas", ""), "offcanvas"); - // assert_eq!(bp.class_with("col", "12"), "col-12"); - // - // let bp = BreakPoint::LG; - // assert_eq!(bp.class_with("", "3"), ""); - // ``` + /// Devuelve la clase para el punto de ruptura, con un prefijo y un sufijo opcional, separados + /// por un guion `-`. + /// + /// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío). + /// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. + /// - Si `prefix` está vacío devuelve `""`. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// let bp = BreakPoint::MD; + /// assert_eq!(bp.class_with("col", ""), "col-md"); + /// assert_eq!(bp.class_with("col", "6"), "col-md-6"); + /// + /// let bp = BreakPoint::None; + /// assert_eq!(bp.class_with("offcanvas", ""), "offcanvas"); + /// assert_eq!(bp.class_with("col", "12"), "col-12"); + /// + /// let bp = BreakPoint::LG; + /// assert_eq!(bp.class_with("", "3"), ""); + /// ``` #[inline] pub(crate) fn class_with(self, prefix: &str, suffix: &str) -> String { if prefix.is_empty() { diff --git a/extensions/pagetop-bootsier/src/theme/aux/button.rs b/extensions/pagetop-bootsier/src/theme/aux/button.rs index 0d1df87d..721c2bec 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/button.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/button.rs @@ -23,7 +23,7 @@ impl ButtonColor { const BTN_OUTLINE_PREFIX: &str = "btn-outline-"; const BTN_LINK: &str = "btn-link"; - // Añade la clase `btn-*` a la cadena de clases. + /// Añade la clase `btn-*` a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if let Self::Default = self { @@ -106,7 +106,7 @@ impl ButtonSize { const BTN_SM: &str = "btn-sm"; const BTN_LG: &str = "btn-lg"; - // Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases. + /// Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if let Self::Default = self { diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index 480ff3d8..b1372104 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -23,7 +23,7 @@ pub enum Color { } impl Color { - // Devuelve el nombre del color. + /// Devuelve el nombre del color. #[rustfmt::skip] #[inline] pub(crate) const fn as_str(self) -> &'static str { @@ -94,7 +94,7 @@ impl Opacity { const OPACITY: &str = "opacity"; const OPACITY_PREFIX: &str = "-opacity"; - // Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase. + /// Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -109,8 +109,8 @@ impl Opacity { } } - // Añade la opacidad a la cadena de clases usando el prefijo dado (`bg`, `border`, `text`, o - // vacío para `opacity-*`). + /// Añade la opacidad a la cadena de clases usando el prefijo dado (`bg`, `border`, `text`, o + /// vacío para `opacity-*`). #[inline] pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { if let Some(suffix) = self.suffix() { @@ -127,18 +127,18 @@ impl Opacity { } } - // Devuelve la clase de opacidad con el prefijo dado (`bg`, `border`, `text`, o vacío para - // `opacity-*`). - // - // # Ejemplos - // - // ```rust - // # use pagetop_bootsier::prelude::*; - // assert_eq!(Opacity::Opaque.class_with(""), "opacity-100"); - // assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50"); - // assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25"); - // assert_eq!(Opacity::Default.class_with("bg"), ""); - // ``` + /// Devuelve la clase de opacidad con el prefijo dado (`bg`, `border`, `text`, o vacío para + /// `opacity-*`). + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100"); + /// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50"); + /// assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25"); + /// assert_eq!(Opacity::Default.class_with("bg"), ""); + /// ``` #[inline] pub(crate) fn class_with(self, prefix: &str) -> String { if let Some(suffix) = self.suffix() { @@ -206,7 +206,7 @@ impl ColorBg { const BG: &str = "bg"; const BG_PREFIX: &str = "bg-"; - // Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase. + /// Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -223,7 +223,7 @@ impl ColorBg { } } - // Añade la clase de fondo `bg-*` a la cadena de clases. + /// Añade la clase de fondo `bg-*` a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if let Some(suffix) = self.suffix() { @@ -305,7 +305,7 @@ impl ColorText { const TEXT: &str = "text"; const TEXT_PREFIX: &str = "text-"; - // Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase. + /// Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -322,7 +322,7 @@ impl ColorText { } } - // Añade la clase de texto `text-*` a la cadena de clases. + /// Añade la clase de texto `text-*` a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if let Some(suffix) = self.suffix() { diff --git a/extensions/pagetop-bootsier/src/theme/aux/layout.rs b/extensions/pagetop-bootsier/src/theme/aux/layout.rs index 1d351582..09edbf91 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/layout.rs @@ -25,8 +25,8 @@ pub enum ScaleSize { } impl ScaleSize { - // Devuelve el sufijo para el tamaño (`"-0"`, `"-1"`, etc.), o `None` si no define ninguna - // clase, o `""` para el tamaño automático. + /// Devuelve el sufijo para el tamaño (`"-0"`, `"-1"`, etc.), o `None` si no define ninguna + /// clase, o `""` para el tamaño automático. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -42,7 +42,7 @@ impl ScaleSize { } } - // Añade el tamaño a la cadena de clases usando el prefijo dado. + /// Añade el tamaño a la cadena de clases usando el prefijo dado. #[inline] pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { if !prefix.is_empty() { @@ -57,16 +57,16 @@ impl ScaleSize { } /* Devuelve la clase del tamaño para el prefijo, o una cadena vacía si no aplica (reservado). - // - // # Ejemplo - // - // ```rust - // # use pagetop_bootsier::prelude::*; - // assert_eq!(ScaleSize::Auto.class_with("border"), "border"); - // assert_eq!(ScaleSize::Zero.class_with("m"), "m-0"); - // assert_eq!(ScaleSize::Three.class_with("p"), "p-3"); - // assert_eq!(ScaleSize::None.class_with("border"), ""); - // ``` + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(ScaleSize::Auto.class_with("border"), "border"); + /// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0"); + /// assert_eq!(ScaleSize::Three.class_with("p"), "p-3"); + /// assert_eq!(ScaleSize::None.class_with("border"), ""); + /// ``` #[inline] pub(crate) fn class_with(self, prefix: &str) -> String { if !prefix.is_empty() { diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index 20e061d6..adf6c261 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -29,8 +29,8 @@ pub enum RoundedRadius { impl RoundedRadius { const ROUNDED: &str = "rounded"; - // Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el - // redondeo por defecto. + /// Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el + /// redondeo por defecto. #[rustfmt::skip] #[inline] const fn suffix(self) -> Option<&'static str> { @@ -48,8 +48,8 @@ impl RoundedRadius { } } - // Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`, - // `rounded-bottom-start`, o vacío para `rounded-*`). + /// Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`, + /// `rounded-bottom-start`, o vacío para `rounded-*`). #[inline] pub(crate) fn push_class(self, classes: &mut String, prefix: &str) { if let Some(suffix) = self.suffix() { @@ -65,19 +65,19 @@ impl RoundedRadius { } } - // Devuelve la clase para el redondeo de esquinas con el prefijo dado (`rounded-top`, - // `rounded-bottom-start`, o vacío para `rounded-*`). - // - // # Ejemplos - // - // ```rust - // # use pagetop_bootsier::prelude::*; - // assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2"); - // assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0"); - // assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3"); - // assert_eq!(RoundedRadius::Circle.class_with(""), "rounded-circle"); - // assert_eq!(RoundedRadius::None.class_with("rounded-bottom-start"), ""); - // ``` + /// Devuelve la clase para el redondeo de esquinas con el prefijo dado (`rounded-top`, + /// `rounded-bottom-start`, o vacío para `rounded-*`). + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::prelude::*; + /// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2"); + /// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0"); + /// assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3"); + /// assert_eq!(RoundedRadius::Circle.class_with(""), "rounded-circle"); + /// assert_eq!(RoundedRadius::None.class_with("rounded-bottom-start"), ""); + /// ``` #[inline] pub(crate) fn class_with(self, prefix: &str) -> String { if let Some(suffix) = self.suffix() { diff --git a/extensions/pagetop-bootsier/src/theme/classes/layout.rs b/extensions/pagetop-bootsier/src/theme/classes/layout.rs index e9d7e248..adb8c3e4 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/layout.rs @@ -48,7 +48,7 @@ impl Margin { // **< Margin HELPERS >************************************************************************* - // Devuelve el prefijo `m*` según el lado. + /// Devuelve el prefijo `m*` según el lado. #[rustfmt::skip] #[inline] const fn side_prefix(&self) -> &'static str { @@ -63,7 +63,7 @@ impl Margin { } } - // Devuelve el sufijo del tamaño (`auto`, `0`..`5`), o `None` si no define clase. + /// Devuelve el sufijo del tamaño (`auto`, `0`..`5`), o `None` si no define clase. #[rustfmt::skip] #[inline] const fn size_suffix(&self) -> Option<&'static str> { @@ -80,8 +80,8 @@ impl Margin { } /* Añade la clase de **margin** a la cadena de clases (reservado). - // - // No añade nada si `size` es `ScaleSize::None`. + /// + /// No añade nada si `size` es `ScaleSize::None`. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let Some(size) = self.size_suffix() else { @@ -148,7 +148,7 @@ impl Padding { // **< Padding HELPERS >************************************************************************ - // Devuelve el prefijo `p*` según el lado. + /// Devuelve el prefijo `p*` según el lado. #[rustfmt::skip] #[inline] const fn prefix(&self) -> &'static str { @@ -163,9 +163,9 @@ impl Padding { } } - // Devuelve el sufijo del tamaño (`0`..`5`), o `None` si no define clase. - // - // Nota: `ScaleSize::Auto` **no aplica** a padding ⇒ devuelve `None`. + /// Devuelve el sufijo del tamaño (`0`..`5`), o `None` si no define clase. + /// + /// Nota: `ScaleSize::Auto` **no aplica** a padding ⇒ devuelve `None`. #[rustfmt::skip] #[inline] const fn suffix(&self) -> Option<&'static str> { @@ -182,8 +182,8 @@ impl Padding { } /* Añade la clase de **padding** a la cadena de clases (reservado). - // - // No añade nada si `size` es `ScaleSize::None` o `ScaleSize::Auto`. + /// + /// No añade nada si `size` es `ScaleSize::None` o `ScaleSize::Auto`. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let Some(size) = self.suffix() else { @@ -192,9 +192,9 @@ impl Padding { self.breakpoint.push_class(classes, self.prefix(), size); } */ - // Devuelve la clase de **padding** como cadena (`"px-2"`, `"pe-sm-4"`, etc.). - // - // Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`. + /// Devuelve la clase de **padding** como cadena (`"px-2"`, `"pe-sm-4"`, etc.). + /// + /// Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`. #[inline] pub fn to_class(self) -> String { let Some(size) = self.suffix() else { diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs index 7571332b..d88f0929 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs @@ -22,7 +22,7 @@ pub enum AutoClose { } impl AutoClose { - // Devuelve el valor para `data-bs-auto-close`, o `None` si es el comportamiento por defecto. + /// Devuelve el valor para `data-bs-auto-close`, o `None` si es el comportamiento por defecto. #[rustfmt::skip] #[inline] pub(crate) const fn as_str(self) -> Option<&'static str> { @@ -59,7 +59,7 @@ pub enum Direction { } impl Direction { - // Mapea la dirección teniendo en cuenta si se agrupa con otros menús [`Dropdown`]. + /// Mapea la dirección teniendo en cuenta si se agrupa con otros menús [`Dropdown`]. #[rustfmt::skip ] #[inline] const fn as_str(self, grouped: bool) -> &'static str { @@ -74,8 +74,8 @@ impl Direction { } } - // Añade la dirección de despliegue a la cadena de clases teniendo en cuenta si se agrupa con - // otros menús [`Dropdown`]. + /// Añade la dirección de despliegue a la cadena de clases teniendo en cuenta si se agrupa con + /// otros menús [`Dropdown`]. #[inline] pub(crate) fn push_class(self, classes: &mut String, grouped: bool) { if grouped { @@ -93,8 +93,8 @@ impl Direction { } } - // Devuelve la clase asociada a la dirección teniendo en cuenta si se agrupa con otros menús - // [`Dropdown`], o `""` si no corresponde ninguna. + /// Devuelve la clase asociada a la dirección teniendo en cuenta si se agrupa con otros menús + /// [`Dropdown`], o `""` si no corresponde ninguna. #[inline] pub(crate) fn class_with(self, grouped: bool) -> String { let mut classes = String::new(); @@ -138,7 +138,7 @@ impl MenuAlign { classes.push_str(class); } - // Añade las clases de alineación a `classes` (sin incluir la base `dropdown-menu`). + /// Añade las clases de alineación a `classes` (sin incluir la base `dropdown-menu`). #[inline] pub(crate) fn push_class(self, classes: &mut String) { match self { @@ -206,7 +206,7 @@ pub enum MenuPosition { } impl MenuPosition { - // Devuelve el valor para `data-bs-offset` o `None` si no aplica. + /// Devuelve el valor para `data-bs-offset` o `None` si no aplica. #[inline] pub(crate) fn data_offset(self) -> Option<String> { match self { @@ -215,7 +215,7 @@ impl MenuPosition { } } - // Devuelve el valor para `data-bs-reference` o `None` si no aplica. + /// Devuelve el valor para `data-bs-reference` o `None` si no aplica. #[inline] pub(crate) fn data_reference(self) -> Option<&'static str> { match self { diff --git a/extensions/pagetop-bootsier/src/theme/image/props.rs b/extensions/pagetop-bootsier/src/theme/image/props.rs index e9b1286a..f31d74c1 100644 --- a/extensions/pagetop-bootsier/src/theme/image/props.rs +++ b/extensions/pagetop-bootsier/src/theme/image/props.rs @@ -31,7 +31,7 @@ pub enum Size { } impl Size { - // Devuelve el valor del atributo `style` en función del tamaño, o `None` si no aplica. + /// Devuelve el valor del atributo `style` en función del tamaño, o `None` si no aplica. #[inline] pub(crate) fn to_style(self) -> Option<String> { match self { @@ -70,7 +70,7 @@ impl Source { const IMG_FLUID: &str = "img-fluid"; const IMG_THUMBNAIL: &str = "img-thumbnail"; - // Devuelve la clase base asociada a la imagen según la fuente. + /// Devuelve la clase base asociada a la imagen según la fuente. #[inline] fn as_str(&self) -> &'static str { match self { @@ -93,7 +93,7 @@ impl Source { classes.push_str(s); } */ - // Devuelve la clase asociada a la imagen según la fuente. + /// Devuelve la clase asociada a la imagen según la fuente. #[inline] pub(crate) fn to_class(&self) -> String { let s = self.as_str(); diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 6c42a76a..41a8b9f2 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -60,7 +60,7 @@ impl ItemKind { classes.push_str(class); } */ - // Devuelve las clases asociadas al tipo de elemento. + /// Devuelve las clases asociadas al tipo de elemento. #[inline] pub(crate) fn to_class(&self) -> String { self.as_str().to_owned() diff --git a/extensions/pagetop-bootsier/src/theme/nav/props.rs b/extensions/pagetop-bootsier/src/theme/nav/props.rs index 46a4e2bc..97304ee4 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/props.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/props.rs @@ -21,7 +21,7 @@ impl Kind { const PILLS: &str = "nav-pills"; const UNDERLINE: &str = "nav-underline"; - // Devuelve la clase base asociada al tipo de menú, o una cadena vacía si no aplica. + /// Devuelve la clase base asociada al tipo de menú, o una cadena vacía si no aplica. #[rustfmt::skip] #[inline] const fn as_str(self) -> &'static str { @@ -33,7 +33,7 @@ impl Kind { } } - // Añade la clase asociada al tipo de menú a la cadena de clases. + /// Añade la clase asociada al tipo de menú a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let class = self.as_str(); @@ -83,7 +83,7 @@ impl Layout { const FILL: &str = "nav-fill"; const JUSTIFIED: &str = "nav-justified"; - // Devuelve la clase base asociada a la distribución y orientación del menú. + /// Devuelve la clase base asociada a la distribución y orientación del menú. #[rustfmt::skip] #[inline] const fn as_str(self) -> &'static str { @@ -98,7 +98,7 @@ impl Layout { } } - // Añade la clase asociada a la distribución y orientación del menú a la cadena de clases. + /// Añade la clase asociada a la distribución y orientación del menú a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let class = self.as_str(); @@ -112,7 +112,7 @@ impl Layout { } /* Devuelve la clase asociada a la distribución y orientación del menú, o una cadena vacía si no - // aplica (reservado). + /// aplica (reservado). #[inline] pub(crate) fn to_class(self) -> String { self.as_str().to_owned() diff --git a/extensions/pagetop-bootsier/src/theme/navbar/props.rs b/extensions/pagetop-bootsier/src/theme/navbar/props.rs index 1aeb6170..59189946 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/props.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/props.rs @@ -64,7 +64,7 @@ pub enum Position { } impl Position { - // Devuelve la clase base asociada a la posición de la barra de navegación. + /// Devuelve la clase base asociada a la posición de la barra de navegación. #[inline] const fn as_str(self) -> &'static str { match self { @@ -76,7 +76,7 @@ impl Position { } } - // Añade la clase asociada a la posición de la barra de navegación a la cadena de clases. + /// Añade la clase asociada a la posición de la barra de navegación a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let class = self.as_str(); @@ -90,7 +90,7 @@ impl Position { } /* Devuelve la clase asociada a la posición de la barra de navegación, o cadena vacía si no - // aplica (reservado). + /// aplica (reservado). #[inline] pub(crate) fn to_class(self) -> String { self.as_str().to_string() diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs index cdfe8623..5a3c1319 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/props.rs @@ -48,7 +48,7 @@ pub enum Placement { } impl Placement { - // Devuelve la clase base asociada a la posición de aparición del panel. + /// Devuelve la clase base asociada a la posición de aparición del panel. #[rustfmt::skip] #[inline] const fn as_str(self) -> &'static str { @@ -60,7 +60,7 @@ impl Placement { } } - // Añade la clase asociada a la posición de aparición del panel a la cadena de clases. + /// Añade la clase asociada a la posición de aparición del panel a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { if !classes.is_empty() { @@ -89,7 +89,7 @@ pub enum Visibility { } impl Visibility { - // Devuelve la clase base asociada al estado inicial del panel. + /// Devuelve la clase base asociada al estado inicial del panel. #[inline] const fn as_str(self) -> &'static str { match self { @@ -98,7 +98,7 @@ impl Visibility { } } - // Añade la clase asociada al estado inicial del panel a la cadena de clases. + /// Añade la clase asociada al estado inicial del panel a la cadena de clases. #[inline] pub(crate) fn push_class(self, classes: &mut String) { let class = self.as_str(); diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 9088ec99..774a4af7 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -104,7 +104,7 @@ use pagetop::prelude::*; pub struct MyExtension; impl Extension for MyExtension { - // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + /// Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { static_files_service!(scfg, guides => "/ruta/a/guides"); } diff --git a/src/app.rs b/src/app.rs index b49e522e..6a266edc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -50,7 +50,7 @@ impl Application { Self::internal_prepare(Some(root_extension)) } - // Método interno para preparar la aplicación, opcionalmente con una extensión. + /// Método interno para preparar la aplicación, opcionalmente con una extensión. fn internal_prepare(root_extension: Option<ExtensionRef>) -> Self { // Al arrancar muestra una cabecera para la aplicación. Self::show_banner(); @@ -73,7 +73,7 @@ impl Application { Self } - // Muestra una cabecera para la aplicación basada en la configuración. + /// Muestra una cabecera para la aplicación basada en la configuración. fn show_banner() { use colored::Colorize; use terminal_size::{terminal_size, Width}; @@ -164,7 +164,7 @@ impl Application { Self::service_app() } - // Configura el servicio web de la aplicación. + /// Configura el servicio web de la aplicación. fn service_app() -> service::App< impl service::Factory< service::Request, diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs index 0cb03347..7178404a 100644 --- a/src/base/action/component/after_render_component.rs +++ b/src/base/action/component/after_render_component.rs @@ -52,7 +52,7 @@ impl<C: Component> AfterRender<C> { self } - // Despacha las acciones. + /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { // Primero despacha las acciones para el tipo de componente. diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs index 46ff9aae..2c86d243 100644 --- a/src/base/action/component/before_render_component.rs +++ b/src/base/action/component/before_render_component.rs @@ -52,7 +52,7 @@ impl<C: Component> BeforeRender<C> { self } - // Despacha las acciones. + /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { // Primero despacha las acciones para el tipo de componente. diff --git a/src/base/action/page/after_render_body.rs b/src/base/action/page/after_render_body.rs index 0bbfeaba..7ecc353a 100644 --- a/src/base/action/page/after_render_body.rs +++ b/src/base/action/page/after_render_body.rs @@ -34,7 +34,7 @@ impl AfterRenderBody { self } - // Despacha las acciones. + /// Despacha las acciones. #[inline(always)] #[allow(clippy::inline_always)] pub(crate) fn dispatch(page: &mut Page) { diff --git a/src/base/action/page/before_render_body.rs b/src/base/action/page/before_render_body.rs index 68f4af7d..d4ae64d0 100644 --- a/src/base/action/page/before_render_body.rs +++ b/src/base/action/page/before_render_body.rs @@ -34,7 +34,7 @@ impl BeforeRenderBody { self } - // Despacha las acciones. + /// Despacha las acciones. #[inline(always)] #[allow(clippy::inline_always)] pub(crate) fn dispatch(page: &mut Page) { diff --git a/src/base/action/theme/after_render_component.rs b/src/base/action/theme/after_render_component.rs index d0beeb45..a31ad56a 100644 --- a/src/base/action/theme/after_render_component.rs +++ b/src/base/action/theme/after_render_component.rs @@ -34,7 +34,7 @@ impl<C: Component> AfterRender<C> { } } - // Despacha las acciones. + /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { dispatch_actions( diff --git a/src/base/action/theme/before_render_component.rs b/src/base/action/theme/before_render_component.rs index ac5ee6b2..c00de8e9 100644 --- a/src/base/action/theme/before_render_component.rs +++ b/src/base/action/theme/before_render_component.rs @@ -34,7 +34,7 @@ impl<C: Component> BeforeRender<C> { } } - // Despacha las acciones. + /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { dispatch_actions( diff --git a/src/base/action/theme/prepare_render.rs b/src/base/action/theme/prepare_render.rs index 4a1a2da6..8e46e8cc 100644 --- a/src/base/action/theme/prepare_render.rs +++ b/src/base/action/theme/prepare_render.rs @@ -41,7 +41,7 @@ impl<C: Component> PrepareRender<C> { } } - // Despacha las acciones. Se detiene en cuanto una renderiza. + /// Despacha las acciones. Se detiene en cuanto una renderiza. #[inline] pub(crate) fn dispatch(component: &C, cx: &mut Context) -> PrepareMarkup { let mut render_component = PrepareMarkup::None; diff --git a/src/core.rs b/src/core.rs index ab4d693b..8a47848e 100644 --- a/src/core.rs +++ b/src/core.rs @@ -30,28 +30,28 @@ impl TypeInfo { } } - // Extrae un rango de segmentos de `type_name` (tokens separados por `::`). - // - // Los argumentos `start` y `end` identifican los índices de los segmentos teniendo en cuenta: - // - // * Los índices positivos cuentan **desde la izquierda**, empezando en `0`. - // * Los índices negativos cuentan **desde la derecha**, `-1` es el último. - // * Si `end` es `None`, el corte llega hasta el último segmento. - // * Si la selección resulta vacía por índices desordenados o segmento inexistente, se devuelve - // la cadena vacía. - // - // Ejemplos (con `type_name = "alloc::vec::Vec<i32>"`): - // - // | Llamada | Resultado | - // |------------------------------|--------------------------| - // | `partial(..., 0, None)` | `"alloc::vec::Vec<i32>"` | - // | `partial(..., 1, None)` | `"vec::Vec<i32>"` | - // | `partial(..., -1, None)` | `"Vec<i32>"` | - // | `partial(..., 0, Some(-2))` | `"alloc::vec"` | - // | `partial(..., -5, None)` | `"alloc::vec::Vec<i32>"` | - // - // La porción devuelta vive tanto como `'static` porque `type_name` es `'static` y sólo se - // presta. + /// Extrae un rango de segmentos de `type_name` (tokens separados por `::`). + /// + /// Los argumentos `start` y `end` identifican los índices de los segmentos teniendo en cuenta: + /// + /// * Los índices positivos cuentan **desde la izquierda**, empezando en `0`. + /// * Los índices negativos cuentan **desde la derecha**, `-1` es el último. + /// * Si `end` es `None`, el corte llega hasta el último segmento. + /// * Si la selección resulta vacía por índices desordenados o segmento inexistente, se devuelve + /// la cadena vacía. + /// + /// Ejemplos (con `type_name = "alloc::vec::Vec<i32>"`): + /// + /// | Llamada | Resultado | + /// |------------------------------|--------------------------| + /// | `partial(..., 0, None)` | `"alloc::vec::Vec<i32>"` | + /// | `partial(..., 1, None)` | `"vec::Vec<i32>"` | + /// | `partial(..., -1, None)` | `"Vec<i32>"` | + /// | `partial(..., 0, Some(-2))` | `"alloc::vec"` | + /// | `partial(..., -5, None)` | `"alloc::vec::Vec<i32>"` | + /// + /// La porción devuelta vive tanto como `'static` porque `type_name` es `'static` y sólo se + /// presta. fn partial(type_name: &'static str, start: isize, end: Option<isize>) -> &'static str { let maxlen = type_name.len(); diff --git a/src/core/action/all.rs b/src/core/action/all.rs index 2a3dfd2d..7e7305b1 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -12,13 +12,13 @@ static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> = // **< AÑADIR ACCIONES >**************************************************************************** -// Registra una nueva acción en el sistema. -// -// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la misma lista. Si no, se -// crea una nueva lista. -// -// Las extensiones llamarán a esta función durante su inicialización para instalar acciones -// personalizadas que modifiquen el comportamiento del *core* o de otros componentes. +/// Registra una nueva acción en el sistema. +/// +/// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la misma lista. Si no, se +/// crea una nueva lista. +/// +/// Las extensiones llamarán a esta función durante su inicialización para instalar acciones +/// personalizadas que modifiquen el comportamiento del *core* o de otros componentes. pub(crate) fn add_action(action: ActionBox) { let key = ActionKey::new( action.type_id(), diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 15a6de22..08e7e5b6 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -55,7 +55,7 @@ impl Child { // **< Child HELPERS >************************************************************************** - // Devuelve el [`UniqueId`] del tipo del componente, si existe. + /// Devuelve el [`UniqueId`] del tipo del componente, si existe. #[inline] fn type_id(&self) -> Option<UniqueId> { self.0.as_ref().map(|c| c.read().type_id()) @@ -156,7 +156,7 @@ impl<C: Component> Typed<C> { // **< Typed HELPERS >************************************************************************** - // Método interno para convertir un componente tipado en un [`Child`]. + /// Método interno para convertir un componente tipado en un [`Child`]. #[inline] fn into(self) -> Child { if let Some(c) = &self.0 { @@ -216,7 +216,7 @@ impl Children { Children::default().with_child(ChildOp::Add(child)) } - // Fusiona varias listas de `Children` en una sola. + /// Fusiona varias listas de `Children` en una sola. pub(crate) fn merge(mixes: &[Option<&Children>]) -> Self { let mut opt = Children::default(); for m in mixes.iter().flatten() { @@ -321,7 +321,7 @@ impl Children { // **< Children HELPERS >*********************************************************************** - // Añade más de un componente hijo al final de la lista (en el orden recibido). + /// Añade más de un componente hijo al final de la lista (en el orden recibido). #[inline] fn add_many<I>(&mut self, iter: I) -> &mut Self where @@ -331,7 +331,7 @@ impl Children { self } - // Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra. + /// Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra. #[inline] fn insert_after_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self { let id = Some(id.as_ref()); @@ -342,7 +342,7 @@ impl Children { self } - // Inserta un hijo antes del componente con el `id` dado, o al principio si no se encuentra. + /// Inserta un hijo antes del componente con el `id` dado, o al principio si no se encuentra. #[inline] fn insert_before_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self { let id = Some(id.as_ref()); @@ -353,14 +353,14 @@ impl Children { self } - // Inserta un hijo al principio de la colección. + /// Inserta un hijo al principio de la colección. #[inline] fn prepend(&mut self, child: Child) -> &mut Self { self.0.insert(0, child); self } - // Inserta más de un componente hijo al principio de la lista (manteniendo el orden recibido). + /// Inserta más de un componente hijo al principio de la lista (manteniendo el orden recibido). #[inline] fn prepend_many<I>(&mut self, iter: I) -> &mut Self where @@ -371,7 +371,7 @@ impl Children { self } - // Elimina el primer hijo con el `id` dado. + /// Elimina el primer hijo con el `id` dado. #[inline] fn remove_by_id(&mut self, id: impl AsRef<str>) -> &mut Self { let id = Some(id.as_ref()); @@ -381,7 +381,7 @@ impl Children { self } - // Sustituye el primer hijo con el `id` dado por otro componente. + /// Sustituye el primer hijo con el `id` dado por otro componente. #[inline] fn replace_by_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self { let id = Some(id.as_ref()); @@ -394,7 +394,7 @@ impl Children { self } - // Elimina todos los componentes hijo de la lista. + /// Elimina todos los componentes hijo de la lista. #[inline] fn reset(&mut self) -> &mut Self { self.0.clear(); diff --git a/src/core/theme/all.rs b/src/core/theme/all.rs index 5e6b65b0..4774ee6e 100644 --- a/src/core/theme/all.rs +++ b/src/core/theme/all.rs @@ -19,7 +19,7 @@ pub static DEFAULT_THEME: LazyLock<ThemeRef> = // **< TEMA POR NOMBRE >**************************************************************************** -// Devuelve el tema identificado por su [`short_name()`](AnyInfo::short_name). +/// Devuelve el tema identificado por su [`short_name()`](AnyInfo::short_name). pub fn theme_by_short_name(short_name: &'static str) -> Option<ThemeRef> { let short_name = short_name.to_lowercase(); match THEMES diff --git a/src/html.rs b/src/html.rs index e0725dde..16a6c9e4 100644 --- a/src/html.rs +++ b/src/html.rs @@ -106,7 +106,7 @@ impl PrepareMarkup { self.render().into_string() } - // Integra el renderizado fácilmente en la macro [`html!`]. + /// Integra el renderizado fácilmente en la macro [`html!`]. pub(crate) fn render(&self) -> Markup { match self { PrepareMarkup::None => html! {}, diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 7598603c..704e2d6b 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -113,14 +113,14 @@ impl Favicon { self } - // Función interna que centraliza la creación de las etiquetas `<link>`. - // - // - `icon_rel`: indica el tipo de recurso (`"icon"`, `"apple-touch-icon"`, etc.). - // - `icon_source`: URL del recurso. - // - `icon_sizes`: tamaños opcionales. - // - `icon_color`: color opcional (solo relevante para `mask-icon`). - // - // También infiere automáticamente el tipo MIME (`type`) según la extensión del archivo. + /// Función interna que centraliza la creación de las etiquetas `<link>`. + /// + /// - `icon_rel`: indica el tipo de recurso (`"icon"`, `"apple-touch-icon"`, etc.). + /// - `icon_source`: URL del recurso. + /// - `icon_sizes`: tamaños opcionales. + /// - `icon_color`: color opcional (solo relevante para `mask-icon`). + /// + /// También infiere automáticamente el tipo MIME (`type`) según la extensión del archivo. fn add_icon_item( mut self, icon_rel: &str, diff --git a/src/response/json.rs b/src/response/json.rs index 0878999f..23b8ab2c 100644 --- a/src/response/json.rs +++ b/src/response/json.rs @@ -10,7 +10,7 @@ //! #[derive(serde::Deserialize)] //! struct NuevoUsuario { nombre: String, email: String } //! -//! // Manejador configurado para la ruta POST "/usuarios". +//! /// Manejador configurado para la ruta POST "/usuarios". //! async fn crear_usuario(payload: Json<NuevoUsuario>) -> HttpResponse { //! // `payload` ya es `NuevoUsuario`; si la deserialización falla, //! // devolverá automáticamente 400 Bad Request con un cuerpo JSON que describe el error. From 11763d097dacc18626d65a55a64bf77fd7652525 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 17 Dec 2025 12:35:21 +0100 Subject: [PATCH 220/224] =?UTF-8?q?=F0=9F=94=A5=20Elimina=20anotaciones=20?= =?UTF-8?q?#[inline]=20para=20evitar=20abuso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/theme/aux/border.rs | 1 - extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs | 4 ++-- extensions/pagetop-bootsier/src/theme/aux/button.rs | 2 -- extensions/pagetop-bootsier/src/theme/aux/color.rs | 6 ++---- extensions/pagetop-bootsier/src/theme/aux/layout.rs | 4 ++-- extensions/pagetop-bootsier/src/theme/aux/rounded.rs | 4 ++-- extensions/pagetop-bootsier/src/theme/classes/border.rs | 1 - extensions/pagetop-bootsier/src/theme/classes/color.rs | 2 -- extensions/pagetop-bootsier/src/theme/classes/layout.rs | 2 -- extensions/pagetop-bootsier/src/theme/classes/rounded.rs | 1 - extensions/pagetop-bootsier/src/theme/container/props.rs | 1 - extensions/pagetop-bootsier/src/theme/dropdown/props.rs | 7 +++---- extensions/pagetop-bootsier/src/theme/image/props.rs | 3 +-- 13 files changed, 12 insertions(+), 26 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index 3bd705e7..44d24b0e 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -64,7 +64,6 @@ impl BorderColor { /// assert_eq!(BorderColor::Black.to_class(), "border-black"); /// assert_eq!(BorderColor::Default.to_class(), ""); /// ``` - #[inline] pub fn to_class(self) -> String { if let Some(suffix) = self.suffix() { let base_len = match self { diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index ce7998e7..992f8525 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -82,8 +82,8 @@ impl BreakPoint { /// let bp = BreakPoint::LG; /// assert_eq!(bp.class_with("", "3"), ""); /// ``` - #[inline] - pub(crate) fn class_with(self, prefix: &str, suffix: &str) -> String { + #[doc(hidden)] + pub fn class_with(self, prefix: &str, suffix: &str) -> String { if prefix.is_empty() { return String::new(); } diff --git a/extensions/pagetop-bootsier/src/theme/aux/button.rs b/extensions/pagetop-bootsier/src/theme/aux/button.rs index 721c2bec..b32bd17d 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/button.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/button.rs @@ -65,7 +65,6 @@ impl ButtonColor { /// assert_eq!(ButtonColor::Link.to_class(), "btn-link"); /// assert_eq!(ButtonColor::Default.to_class(), ""); /// ``` - #[inline] pub fn to_class(self) -> String { match self { Self::Default => String::new(), @@ -132,7 +131,6 @@ impl ButtonSize { /// assert_eq!(ButtonSize::Large.to_class(), "btn-lg"); /// assert_eq!(ButtonSize::Default.to_class(), ""); /// ``` - #[inline] pub fn to_class(self) -> String { match self { Self::Default => String::new(), diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index b1372104..f49c36b6 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -139,8 +139,8 @@ impl Opacity { /// assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25"); /// assert_eq!(Opacity::Default.class_with("bg"), ""); /// ``` - #[inline] - pub(crate) fn class_with(self, prefix: &str) -> String { + #[doc(hidden)] + pub fn class_with(self, prefix: &str) -> String { if let Some(suffix) = self.suffix() { let base_len = if prefix.is_empty() { Self::OPACITY.len() @@ -253,7 +253,6 @@ impl ColorBg { /// assert_eq!(ColorBg::Transparent.to_class(), "bg-transparent"); /// assert_eq!(ColorBg::Default.to_class(), ""); /// ``` - #[inline] pub fn to_class(self) -> String { if let Some(suffix) = self.suffix() { let base_len = match self { @@ -352,7 +351,6 @@ impl ColorText { /// assert_eq!(ColorText::Black.to_class(), "text-black"); /// assert_eq!(ColorText::Default.to_class(), ""); /// ``` - #[inline] pub fn to_class(self) -> String { if let Some(suffix) = self.suffix() { let base_len = match self { diff --git a/extensions/pagetop-bootsier/src/theme/aux/layout.rs b/extensions/pagetop-bootsier/src/theme/aux/layout.rs index 09edbf91..a1255dc0 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/layout.rs @@ -67,8 +67,8 @@ impl ScaleSize { /// assert_eq!(ScaleSize::Three.class_with("p"), "p-3"); /// assert_eq!(ScaleSize::None.class_with("border"), ""); /// ``` - #[inline] - pub(crate) fn class_with(self, prefix: &str) -> String { + #[doc(hidden)] + pub fn class_with(self, prefix: &str) -> String { if !prefix.is_empty() { if let Some(suffix) = self.suffix() { let mut class = String::with_capacity(prefix.len() + suffix.len()); diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index adf6c261..69976142 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -78,8 +78,8 @@ impl RoundedRadius { /// assert_eq!(RoundedRadius::Circle.class_with(""), "rounded-circle"); /// assert_eq!(RoundedRadius::None.class_with("rounded-bottom-start"), ""); /// ``` - #[inline] - pub(crate) fn class_with(self, prefix: &str) -> String { + #[doc(hidden)] + pub fn class_with(self, prefix: &str) -> String { if let Some(suffix) = self.suffix() { let base_len = if prefix.is_empty() { Self::ROUNDED.len() diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs index 3095498c..2da7bfbb 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/border.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -145,7 +145,6 @@ impl Border { /// `"border border-top-0 border-end-3 border-primary border-opacity-50"`, etc.). /// /// Si no se define ningún tamaño, color ni opacidad, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let mut classes = String::new(); self.push_class(&mut classes); diff --git a/extensions/pagetop-bootsier/src/theme/classes/color.rs b/extensions/pagetop-bootsier/src/theme/classes/color.rs index 162b7849..4f5b4650 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/color.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/color.rs @@ -76,7 +76,6 @@ impl Background { /// Devuelve las clases de fondo como cadena (`"bg-primary"`, `"bg-body-secondary bg-opacity-50"`, etc.). /// /// Si no se define ni color ni opacidad, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let mut classes = String::new(); self.push_class(&mut classes); @@ -189,7 +188,6 @@ impl Text { /// etc.). /// /// Si no se define ni color ni opacidad, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let mut classes = String::new(); self.push_class(&mut classes); diff --git a/extensions/pagetop-bootsier/src/theme/classes/layout.rs b/extensions/pagetop-bootsier/src/theme/classes/layout.rs index adb8c3e4..1f388450 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/layout.rs @@ -94,7 +94,6 @@ impl Margin { /// Devuelve la clase de **margin** como cadena (`"mt-3"`, `"ms-lg-auto"`, etc.). /// /// Si `size` es `ScaleSize::None`, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let Some(size) = self.size_suffix() else { return String::new(); @@ -195,7 +194,6 @@ impl Padding { /// Devuelve la clase de **padding** como cadena (`"px-2"`, `"pe-sm-4"`, etc.). /// /// Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let Some(size) = self.suffix() else { return String::new(); diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs index 58d50b86..077740e1 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -160,7 +160,6 @@ impl Rounded { /// `"rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"`, etc.). /// /// Si no se define ningún radio, devuelve `""`. - #[inline] pub fn to_class(self) -> String { let mut classes = String::new(); self.push_class(&mut classes); diff --git a/extensions/pagetop-bootsier/src/theme/container/props.rs b/extensions/pagetop-bootsier/src/theme/container/props.rs index 2010ba8e..209773b9 100644 --- a/extensions/pagetop-bootsier/src/theme/container/props.rs +++ b/extensions/pagetop-bootsier/src/theme/container/props.rs @@ -59,7 +59,6 @@ impl Width { } */ /// Devuelve la clase asociada al comportamiento del contenedor según el ajuste de su ancho. - #[inline] pub fn to_class(self) -> String { match self { Self::Default => BreakPoint::None.class_with(Self::CONTAINER, ""), diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs index d88f0929..fd315508 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs @@ -95,8 +95,8 @@ impl Direction { /// Devuelve la clase asociada a la dirección teniendo en cuenta si se agrupa con otros menús /// [`Dropdown`], o `""` si no corresponde ninguna. - #[inline] - pub(crate) fn class_with(self, grouped: bool) -> String { + #[doc(hidden)] + pub fn class_with(self, grouped: bool) -> String { let mut classes = String::new(); self.push_class(&mut classes, grouped); classes @@ -179,8 +179,7 @@ impl MenuAlign { } /* Devuelve las clases de alineación sin incluir `dropdown-menu` (reservado). - #[inline] - pub(crate) fn to_class(self) -> String { + pub fn to_class(self) -> String { let mut classes = String::new(); self.push_class(&mut classes); classes diff --git a/extensions/pagetop-bootsier/src/theme/image/props.rs b/extensions/pagetop-bootsier/src/theme/image/props.rs index f31d74c1..2041b2fd 100644 --- a/extensions/pagetop-bootsier/src/theme/image/props.rs +++ b/extensions/pagetop-bootsier/src/theme/image/props.rs @@ -94,8 +94,7 @@ impl Source { } */ /// Devuelve la clase asociada a la imagen según la fuente. - #[inline] - pub(crate) fn to_class(&self) -> String { + pub fn to_class(&self) -> String { let s = self.as_str(); if s.is_empty() { String::new() From 3f00b699026f8c201c908fd7814d5fe550577e54 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 17 Dec 2025 12:36:26 +0100 Subject: [PATCH 221/224] =?UTF-8?q?=F0=9F=9A=A8=20Ajuste=20menor=20sugerid?= =?UTF-8?q?o=20por=20clippy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locale/definition.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locale/definition.rs b/src/locale/definition.rs index 6349b460..06a07c49 100644 --- a/src/locale/definition.rs +++ b/src/locale/definition.rs @@ -179,7 +179,7 @@ impl Locale { /// Es el idioma garantizado incluso cuando no haya configuración de la aplicación o cuando /// el valor configurado no sea válido. pub fn fallback_langid() -> &'static LanguageIdentifier { - &*FALLBACK_LANGID + &FALLBACK_LANGID } /// Devuelve el identificador de idioma configurado o, en su defecto, el de respaldo. @@ -187,7 +187,7 @@ impl Locale { /// Este es el idioma que utiliza internamente [`Locale::default()`] y resulta útil como idioma /// base cuando no se dispone de un contexto más específico. pub fn default_langid() -> &'static LanguageIdentifier { - (*CONFIG_LANGID).unwrap_or(&*FALLBACK_LANGID) + (*CONFIG_LANGID).unwrap_or(&FALLBACK_LANGID) } } From 57f2fa64f4fd3febfa1541af3f77d7b889978153 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Wed, 17 Dec 2025 12:40:53 +0100 Subject: [PATCH 222/224] =?UTF-8?q?=F0=9F=8E=A8=20Mejora=20gesti=C3=B3n=20?= =?UTF-8?q?de=20errores=20para=20403,=20404=20y=20otros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/theme/definition.rs | 78 +++++++++++++++++++++---- src/locale/en-US/theme.ftl | 31 +++++++++- src/locale/es-ES/theme.ftl | 31 +++++++++- src/response/page/error.rs | 108 ++++++++++++++++++++--------------- 4 files changed, 185 insertions(+), 63 deletions(-) diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index a0edbd9a..81e47756 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,10 +1,12 @@ -use crate::core::component::Contextual; +use crate::base::component::{Html, Intro, IntroOpening}; +use crate::core::component::{Child, ChildOp, Component, Contextual}; use crate::core::extension::Extension; +use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef}; use crate::global; use crate::html::{html, Markup}; use crate::locale::L10n; -use crate::prelude::{DefaultTemplate, TemplateRef}; use crate::response::page::Page; +use crate::service::http::StatusCode; /// Interfaz común que debe implementar cualquier tema de PageTop. /// @@ -155,20 +157,76 @@ pub trait Theme: Extension + Send + Sync { } } - /// Contenido predeterminado para la página de error "*403 - Forbidden*". + /// Contenido predefinido para la página de error "*403 - Forbidden*" (acceso denegado). /// /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la - /// página de error, manteniendo o no el mensaje de los *textos localizados*. - fn error403(&self, page: &mut Page) -> Markup { - html! { div { h1 { (L10n::l("error403_notice").using(page)) } } } + /// página de error. + fn error_403(&self, page: &mut Page) { + page.alter_title(L10n::l("error403_title")) + .alter_template(&DefaultTemplate::Error) + .alter_child_in( + &DefaultRegion::Content, + ChildOp::Prepend(Child::with(Html::with(move |cx| { + html! { + div { + h1 { (L10n::l("error403_alert").using(cx)) } + p { (L10n::l("error403_help").using(cx)) } + } + } + }))), + ); } - /// Contenido predeterminado para la página de error "*404 - Not Found*". + /// Contenido predefinido para la página de error "*404 - Not Found*" (recurso no encontrado). /// /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la - /// página de error, manteniendo o no el mensaje de los *textos localizados*. - fn error404(&self, page: &mut Page) -> Markup { - html! { div { h1 { (L10n::l("error404_notice").using(page)) } } } + /// página de error. + fn error_404(&self, page: &mut Page) { + page.alter_title(L10n::l("error404_title")) + .alter_template(&DefaultTemplate::Error) + .alter_child_in( + &DefaultRegion::Content, + ChildOp::Prepend(Child::with(Html::with(move |cx| { + html! { + div { + h1 { (L10n::l("error404_alert").using(cx)) } + p { (L10n::l("error404_help").using(cx)) } + } + } + }))), + ); + } + + /// Permite al tema preparar y componer una página de error fatal. + /// + /// Por defecto, asigna el título al documento (`title`) y muestra un componente [`Intro`] con + /// el código HTTP del error (`code`) y los mensajes proporcionados (`alert` y `help`) como + /// descripción del error. + /// + /// Este método no se utiliza en las implementaciones predefinidas de [`Self::error_403()`] ni + /// [`Self::error_404()`], que definen su propio contenido específico. + /// + /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la + /// página de error. + fn error_fatal(&self, page: &mut Page, code: StatusCode, title: L10n, alert: L10n, help: L10n) { + page.alter_title(title) + .alter_template(&DefaultTemplate::Error) + .alter_child_in( + &DefaultRegion::Content, + ChildOp::Prepend(Child::with( + Intro::new() + .with_title(L10n::l("error_code").with_arg("code", code.as_str())) + .with_slogan(L10n::n(code.to_string())) + .with_button(None) + .with_opening(IntroOpening::Custom) + .add_child(Html::with(move |cx| { + html! { + h1 { (alert.using(cx)) } + p { (help.using(cx)) } + } + })), + )), + ); } } diff --git a/src/locale/en-US/theme.ftl b/src/locale/en-US/theme.ftl index f766766d..3f4c0064 100644 --- a/src/locale/en-US/theme.ftl +++ b/src/locale/en-US/theme.ftl @@ -3,7 +3,32 @@ region_header = Header region_content = Content region_footer = Footer -error403_notice = FORBIDDEN ACCESS -error404_notice = RESOURCE NOT FOUND - +# Logo. pagetop_logo = PageTop Logo + +# Error Messages. +error_code = Error { $code } + +error400_title = Error BAD REQUEST +error400_alert = The request could not be processed. +error400_help = The server could not understand your request. The address may be incorrect or some required data may be missing. + +error403_title = Error FORBIDDEN +error403_alert = You do not have permission to access this resource. +error403_help = Your account does not have the necessary privileges to view this page. If you believe this is an error, please contact the system administrator. + +error404_title = Error RESOURCE NOT FOUND +error404_alert = The requested page could not be found. +error404_help = The address may be incorrect, or the document may have been moved or deleted. Check the URL or use the navigation links to return to a known place. + +error500_title = Error INTERNAL ERROR +error500_alert = An unexpected error occurred on the server. +error500_help = We could not complete your request due to an internal problem. Please try again in a few minutes. If the error persists, contact the system administrator. + +error503_title = Error SERVICE UNAVAILABLE +error503_alert = The service is temporarily unavailable. +error503_help = The server is currently unable to handle your request due to maintenance or high load. Please try again in a few minutes. If the problem persists, contact the system administrator. + +error504_title = Error GATEWAY TIMEOUT +error504_alert = The server took too long to respond. +error504_help = The service is temporarily unavailable or overloaded. Please try again in a few minutes. If the problem continues, notify the system administrator. diff --git a/src/locale/es-ES/theme.ftl b/src/locale/es-ES/theme.ftl index b8b91449..7d4abcf6 100644 --- a/src/locale/es-ES/theme.ftl +++ b/src/locale/es-ES/theme.ftl @@ -3,7 +3,32 @@ region_header = Cabecera region_content = Contenido region_footer = Pie de página -error403_notice = ACCESO NO PERMITIDO -error404_notice = RECURSO NO ENCONTRADO - +# Logo. pagetop_logo = Logotipo de PageTop + +# Error Messages. +error_code = Error { $code } + +error400_title = Error PETICIÓN INCORRECTA +error400_alert = No se ha podido procesar la petición. +error400_help = El servidor no ha podido interpretar su petición. Es posible que la dirección sea incorrecta o que falten datos obligatorios. Revise la información introducida e inténtelo de nuevo. + +error403_title = Error ACCESO PROHIBIDO +error403_alert = No dispone de permisos para acceder a este recurso. +error403_help = Su cuenta no tiene los privilegios necesarios para visualizar esta página. Si considera que se trata de un error, póngase en contacto con el administrador del sistema. + +error404_title = Error RECURSO NO ENCONTRADO +error404_alert = No se ha podido encontrar el recurso solicitado. +error404_help = Es posible que la dirección sea incorrecta o que el documento haya sido movido o eliminado. Compruebe la URL o utilice los enlaces de navegación para volver a una ubicación conocida. + +error500_title = Error INTERNO DEL SERVIDOR +error500_alert = Se ha producido un error interno en el servidor. +error500_help = No hemos podido completar su petición debido a un problema interno. Inténtelo de nuevo pasados unos minutos. Si el error persiste, póngase en contacto con el administrador del sistema. + +error503_title = Error SERVICIO NO DISPONIBLE +error503_alert = El servicio no está disponible temporalmente. +error503_help = En este momento el servidor no puede atender su petición debido a tareas de mantenimiento o a una alta carga de trabajo. Inténtelo de nuevo en unos minutos. Si el problema persiste, póngase en contacto con el administrador del sistema. + +error504_title = Error TIEMPO DE ESPERA AGOTADO +error504_alert = El servidor ha tardado demasiado en responder. +error504_help = El servicio no está disponible temporalmente o está experimentando una alta carga. Inténtelo de nuevo en unos minutos. Si el problema continúa, notifique la incidencia al administrador del sistema. diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 7d6cf33b..fd9959c2 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,10 +1,9 @@ -use crate::base::component::Html; use crate::core::component::Contextual; -use crate::core::theme::DefaultTemplate; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode}; use crate::service::{HttpRequest, HttpResponse}; +use crate::util; use super::Page; @@ -12,71 +11,87 @@ use std::fmt; /// Página de error asociada a un código de estado HTTP. /// -/// Este enumerado agrupa los distintos tipos de error que pueden devolverse como página HTML -/// completa. Cada variante encapsula la solicitud original ([`HttpRequest`]) y se corresponde con -/// un código de estado concreto. +/// Este enumerado agrupa tipos esenciales de error que pueden devolverse como página HTML completa. +/// Cada variante encapsula la solicitud original ([`HttpRequest`]) y se corresponde con un código +/// de estado concreto. /// -/// Para algunos errores (como [`ErrorPage::AccessDenied`] y [`ErrorPage::NotFound`]) se construye -/// una [`Page`] usando la plantilla de error del tema activo ([`DefaultTemplate::Error`]), lo que -/// permite personalizar el contenido del mensaje. En el resto de casos se devuelve un cuerpo HTML -/// mínimo basado en una descripción genérica del error. -/// -/// `ErrorPage` implementa [`ResponseError`], por lo que puede utilizarse directamente como tipo de -/// error en los controladores HTTP. +/// Para cada error se construye una [`Page`] usando el tema activo, lo que permite personalizar +/// la plantilla y el contenido del mensaje mediante los métodos específicos del tema +/// (por ejemplo, [`Theme::error_403()`](crate::core::theme::Theme::error_403), +/// [`Theme::error_404()`](crate::core::theme::Theme::error_404) o +/// [`Theme::error_fatal()`](crate::core::theme::Theme::error_fatal)). #[derive(Debug)] pub enum ErrorPage { - NotModified(HttpRequest), BadRequest(HttpRequest), AccessDenied(HttpRequest), NotFound(HttpRequest), - PreconditionFailed(HttpRequest), InternalError(HttpRequest), - Timeout(HttpRequest), + ServiceUnavailable(HttpRequest), + GatewayTimeout(HttpRequest), +} + +impl ErrorPage { + /// Función auxiliar para renderizar una página de error genérica usando el tema activo. + /// + /// Construye una [`Page`] a partir de la petición y un prefijo de clave basado en el código de + /// estado (`error<code>`), del que se derivan los textos localizados `error<code>_title`, + /// `error<code>_alert` y `error<code>_help`. + /// + /// Si el renderizado falla, escribe en su lugar el texto plano asociado al código de estado. + fn display_error_page(&self, f: &mut fmt::Formatter<'_>, request: &HttpRequest) -> fmt::Result { + let mut page = Page::new(request.clone()); + let code = self.status_code(); + page.theme().error_fatal( + &mut page, + code, + L10n::l(util::join!("error", code.as_str(), "_title")), + L10n::l(util::join!("error", code.as_str(), "_alert")), + L10n::l(util::join!("error", code.as_str(), "_help")), + ); + if let Ok(rendered) = page.render() { + write!(f, "{}", rendered.into_string()) + } else { + f.write_str(&code.to_string()) + } + } } impl fmt::Display for ErrorPage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - // Error 304. - ErrorPage::NotModified(_) => f.write_str("Not Modified"), // Error 400. - ErrorPage::BadRequest(_) => f.write_str("Bad Client Data"), + Self::BadRequest(request) => self.display_error_page(f, request), + // Error 403. - ErrorPage::AccessDenied(request) => { - let mut error_page = Page::new(request.clone()); - let error403 = error_page.theme().error403(&mut error_page); - if let Ok(page) = error_page - .with_title(L10n::n("Error FORBIDDEN")) - .with_template(&DefaultTemplate::Error) - .add_child(Html::with(move |_| error403.clone())) - .render() - { - write!(f, "{}", page.into_string()) + Self::AccessDenied(request) => { + let mut page = Page::new(request.clone()); + page.theme().error_403(&mut page); + if let Ok(rendered) = page.render() { + write!(f, "{}", rendered.into_string()) } else { - f.write_str("Access Denied") + f.write_str(&self.status_code().to_string()) } } + // Error 404. - ErrorPage::NotFound(request) => { - let mut error_page = Page::new(request.clone()); - let error404 = error_page.theme().error404(&mut error_page); - if let Ok(page) = error_page - .with_title(L10n::n("Error RESOURCE NOT FOUND")) - .with_template(&DefaultTemplate::Error) - .add_child(Html::with(move |_| error404.clone())) - .render() - { - write!(f, "{}", page.into_string()) + Self::NotFound(request) => { + let mut page = Page::new(request.clone()); + page.theme().error_404(&mut page); + if let Ok(rendered) = page.render() { + write!(f, "{}", rendered.into_string()) } else { - f.write_str("Not Found") + f.write_str(&self.status_code().to_string()) } } - // Error 412. - ErrorPage::PreconditionFailed(_) => f.write_str("Precondition Failed"), + // Error 500. - ErrorPage::InternalError(_) => f.write_str("Internal Error"), + Self::InternalError(request) => self.display_error_page(f, request), + + // Error 503. + Self::ServiceUnavailable(request) => self.display_error_page(f, request), + // Error 504. - ErrorPage::Timeout(_) => f.write_str("Timeout"), + Self::GatewayTimeout(request) => self.display_error_page(f, request), } } } @@ -91,13 +106,12 @@ impl ResponseError for ErrorPage { #[rustfmt::skip] fn status_code(&self) -> StatusCode { match self { - ErrorPage::NotModified(_) => StatusCode::NOT_MODIFIED, ErrorPage::BadRequest(_) => StatusCode::BAD_REQUEST, ErrorPage::AccessDenied(_) => StatusCode::FORBIDDEN, ErrorPage::NotFound(_) => StatusCode::NOT_FOUND, - ErrorPage::PreconditionFailed(_) => StatusCode::PRECONDITION_FAILED, ErrorPage::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - ErrorPage::Timeout(_) => StatusCode::GATEWAY_TIMEOUT, + ErrorPage::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE, + ErrorPage::GatewayTimeout(_) => StatusCode::GATEWAY_TIMEOUT, } } } From 3db798ad3bbb5a3ff9d2aaff1d4470e01913aaff Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Fri, 19 Dec 2025 11:15:49 +0100 Subject: [PATCH 223/224] =?UTF-8?q?=F0=9F=93=9D=20A=C3=B1ade=20gu=C3=ADas?= =?UTF-8?q?=20de=20contribuci=C3=B3n=20y=20revisa=20estilos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 163 ++++++++++++++++++++ MAINTAINERS.md | 213 ++++++++++++++++++++++++++ README.md | 37 ++++- extensions/pagetop-aliner/README.md | 8 +- extensions/pagetop-bootsier/README.md | 8 +- helpers/pagetop-build/README.md | 14 +- helpers/pagetop-macros/README.md | 9 +- helpers/pagetop-minimal/README.md | 12 +- helpers/pagetop-statics/README.md | 12 +- 9 files changed, 439 insertions(+), 37 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 MAINTAINERS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..c2e2a263 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,163 @@ +# Guía de contribución a PageTop + +Gracias por tu interés en contribuir a **PageTop** 🎉 + +Este documento describe **cómo participar en el desarrollo del proyecto**, el flujo de trabajo y las +normas que permitan garantizar un historial limpio, trazable y sostenible a largo plazo. + +Por favor, léelo completo antes de abrir una *issue* o una *pull request*. + + +## 1. Repositorios + +PageTop mantiene **un único repositorio oficial**: + + * **Repositorio oficial:** https://git.cillero.es/manuelcillero/pagetop + * **Repositorio espejo:** https://github.com/manuelcillero/pagetop + +El repositorio de GitHub actúa como espejo y punto de entrada para: + + * dar mayor visibilidad al proyecto, + * facilitar la participación de la comunidad, + * centralizar *issues* y *pull requests* externas. + +> ⚠️ **Importante** +> Aunque GitHub permite abrir *pull requests*, **la integración del código se realiza únicamente en +> el repositorio oficial**. El repositorio de GitHub se sincroniza posteriormente para reflejar el +> mismo estado. + +En todos los casos, se respeta la **autoría original** de las contribuciones integradas, tanto en el +historial como en la documentación asociada al cambio. + + +## 2. Issues (incidencias, propuestas, preguntas) + +Antes de abrir una *issue* **en GitHub**: + + * comprueba que no exista ya una similar, + * describe claramente el problema o propuesta, + * incluye pasos de reproducción si se trata de un *bug*, + * indica versión, entorno y contexto cuando sea relevante. + +Las *issues* se usan para: + + * informar de errores, + * propuestas de mejora, + * discusión técnica previa a cambios relevantes. + + +## 3. Pull Requests (PRs) + +### 3.1 Dónde abrirlas + +Las *pull requests* se abren **en GitHub**, contra la rama `main`. GitHub es el punto de entrada +recomendado para contribuciones externas. + +### 3.2 Reglas generales para PRs + + * Cada PR debe abordar **un único objetivo claro**. + * Mantén el alcance lo más acotado posible. + * Incluye descripción clara del cambio. + * Si el PR corrige una *issue*, enlázala explícitamente. + * Asegúrate de que el código compila y pasa las pruebas. + +### 3.3 Revisión y aceptación + +Todas las PRs: + + * serán **revisadas manualmente**, + * pueden recibir comentarios o solicitudes de cambios, + * **no se integran directamente en GitHub**, ya que la integración se realiza en el repositorio + oficial para mantener coherencia y trazabilidad. + +Una PR aceptada: + + * se integra en el repositorio oficial (Forgejo), + * respetando **la autoría original del contribuidor**, + * normalmente mediante **squash merge** para mantener un historial limpio. + + +## 4. Autoría y atribución + +PageTop cuida especialmente la atribución de contribuciones. + + * El **autor original del código se conserva** en el commit final integrado en Forgejo. + * Aunque el autor no tenga cuenta en Forgejo, su nombre y email quedarán reflejados. + * En GitHub, cuando es posible, la contribución quedará asociada al usuario original. + +Adicionalmente, el mensaje del commit puede incluir líneas `Co-authored-by` cuando proceda. + + +## 5. Cierre de Pull Requests en GitHub + +Una vez que el cambio ha sido integrado en Forgejo: + + * La PR en GitHub se **cerrará manualmente** (no se mergea). + * Se añadirá un **mensaje estándar de cierre**, indicando: + * que el cambio ha sido integrado, + * la referencia al commit o versión, + * que GitHub es un repositorio espejo. + +Ejemplo de mensaje de cierre: + +> Este cambio ha sido integrado en el repositorio oficial (Forgejo). +> GitHub actúa como repositorio espejo, por lo que la PR se cierra sin merge. +> Gracias por tu contribución. + +Esto garantiza: + + * transparencia, + * trazabilidad, + * coherencia entre repositorios. + + +## 6. Sincronización entre Forgejo y GitHub + +Tras integrar cambios en Forgejo: + + * el repositorio de GitHub se **actualiza para reflejar el estado de Forgejo**, + * el historial de GitHub puede reescribirse para mantener coherencia. + +Por este motivo: + + * **no se deben hacer merges “definitivos” en GitHub**, + * GitHub no debe considerarse fuente de verdad del historial. + + +## 7. Estilo de código y calidad + + * Sigue el estilo existente del proyecto. + * Mantén los comentarios claros y precisos. + * La documentación es parte del código: actualízala cuando sea necesario. + * Cambios públicos o estructurales deben ir acompañados de documentación. + + +## 8. Commits + +Recomendaciones generales: + + * Mensajes claros y descriptivos. + * Un commit debe representar una unidad lógica de cambio. + * En contribuciones externas, el formato exacto del commit puede ajustarse durante la integración. + +Durante la integración, los commits pueden ajustarse (rebase, squash o edición de mensajes) para +adaptarse al historial del proyecto. + +## 9. Comunicación y respeto + +PageTop sigue un enfoque profesional y colaborativo: + + * Sé respetuoso en revisiones y discusiones. + * Acepta sugerencias técnicas como parte del proceso. + * Recuerda que todas las contribuciones son revisadas con el objetivo de mejorar el proyecto. + + +## 10. Dudas + +Si tienes dudas sobre el proceso: + + * abre una *issue* de tipo pregunta, + * o inicia una discusión (si está habilitada). + +Gracias por contribuir a **PageTop** 🚀 Cada aportación, grande o pequeña, ayuda a que el proyecto +mejore. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000..fbf69db3 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,213 @@ +# MAINTAINERS.md + +## Guía para mantenedores de PageTop + +Este documento describe **el flujo técnico interno de revisión e integración de contribuciones** en +**PageTop**. + +Está dirigido a **mantenedores del proyecto** y **no forma parte de la guía de contribución para +usuarios externos**. Su objetivo es servir como **referencia operativa**, garantizando coherencia, +trazabilidad y preservación de la autoría en un entorno con repositorios espejo. + + +## 1. Repositorios y roles + +PageTop mantiene **un único repositorio oficial**: + + * **Repositorio oficial:** https://git.cillero.es/manuelcillero/pagetop + * **Repositorio espejo:** https://github.com/manuelcillero/pagetop + +El repositorio de GitHub actúa como espejo y punto de entrada para: + + * dar mayor visibilidad al proyecto, + * facilitar la participación de la comunidad, + * centralizar *issues* y *pull requests* externas. + +### Principios clave + + * El repositorio oficial **es la única fuente de verdad** del historial. + * **Nunca se realizan *merges* en GitHub**. + + +## 2. Configuración local recomendada + +Configuración típica de *remotes*: + +```bash +git remote -v +``` + +Ejemplo esperado: + +```text +origin git@git.cillero.es:manuelcillero/pagetop.git (fetch) +origin git@git.cillero.es:manuelcillero/pagetop.git (push) +github git@github.com:manuelcillero/pagetop.git (fetch) +github git@github.com:manuelcillero/pagetop.git (push) +``` + +Convenciones usadas en este documento: + +* `origin` -> Oficial +* `github` -> GitHub (espejo) + + +## 3. Recepción de Pull Requests desde GitHub + +Las contribuciones externas llegan como *pull requests* en GitHub, normalmente contra `main`. + +### 3.1 Obtener la PR en local + +Opción habitual (ejemplo con PR #123): + +```bash +git fetch github pull/123/head:pr-123 +git checkout pr-123 +``` + +Alternativamente, si la rama del contribuidor es accesible directamente como referencia remota: + +```bash +git fetch github +git checkout nombre-de-la-rama +``` + + +## 4. Revisión local + +Antes de integrar cualquier cambio: + +* Revisar el código manualmente. +* Verificar compilación y pruebas: + +```bash +cargo build +cargo test +``` + +* Comprobar impacto en documentación. +* Evaluar coherencia con la arquitectura y el estilo del proyecto. + +Si se requieren cambios: + +* comentar en la PR, +* solicitar ajustes, +* o realizar modificaciones locales explicadas claramente. + + +## 5. Estrategia de integración + +La integración **se realiza siempre en el repositorio oficial**. + +### 5.1 Estrategia por defecto: *squash merge* + +Usada cuando: + +* la PR tiene varios commits intermedios, +* los commits no siguen el estilo del proyecto, +* se desea un historial compacto. + +Procedimiento típico: + +```bash +git checkout main +git pull origin main +git merge --squash pr-123 +``` + +Crear el commit final **preservando la autoría** (ver sección 6). + +### 5.2 Cherry-pick selectivo + +Usado cuando: + +* uno o varios commits son claros y autocontenidos, +* interesa conservar referencias explícitas. + +Ejemplo: + +```bash +git checkout main +git pull origin main +git cherry-pick -x <commit-sha> +``` + + +## 6. Preservación de la autoría + +La autoría original **debe conservarse siempre**. + +### 6.1 Commit con autor explícito + +Ejemplo: + +```bash +git commit --author="Nombre Apellido <email@ejemplo.com>" +``` + +El mantenedor figura como *committer*; el contribuidor como *author*. + +### 6.2 Co-authored-by + +Cuando procede, puede añadirse al mensaje del commit: + +```text +Co-authored-by: Nombre Apellido <email@ejemplo.com> +``` + + +## 7. Push al repositorio oficial + +Una vez integrado: + +```bash +git push origin main +``` + +Este push representa **la integración definitiva**. + + +## 8. Cierre de la Pull Request en GitHub + +Tras integrar el cambio en el repositorio oficial: + +* **No se mergea la PR en GitHub**. +* Se cierra manualmente con un mensaje estándar. + +Ejemplo recomendado: + +```text +Este cambio ha sido integrado en el repositorio oficial. +GitHub actúa como repositorio espejo, por lo que la PR se cierra sin merge. +Gracias por tu contribución. +``` + + +## 9. Sincronización del repositorio oficial a GitHub + +El repositorio de GitHub se mantiene como **espejo automático** del repositorio oficial +mediante un **push mirror configurado**. + +No se realizan sincronizaciones manuales desde clones locales. + +### Consideraciones + + * El repositorio oficial es siempre la **fuente de verdad**. + * El historial de GitHub puede **reescribirse automáticamente** para reflejar el estado del + repositorio oficial. + * Todas las ramas que deban preservarse en GitHub **deben existir también en el repositorio + oficial**. + * GitHub no debe usarse como referencia del historial real. + + +## 10. Principios de mantenimiento + +* Priorizar **claridad y trazabilidad** frente a rapidez. +* Mantener un historial legible y significativo. +* Documentar cambios estructurales o públicos. +* Tratar las contribuciones externas con respeto y transparencia. + +--- + +Este documento puede evolucionar con el proyecto. +Su objetivo no es imponer rigidez, sino **capturar el conocimiento operativo real** de PageTop. diff --git a/README.md b/README.md index eceb5058..4c421edd 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ según las necesidades de cada proyecto, incluyendo: componentes sin comprometer su funcionalidad. -# ⚡️ Guía rápida +## ⚡️ Guía rápida La aplicación más sencilla de PageTop se ve así: @@ -74,7 +74,7 @@ Este programa implementa una extensión llamada `HelloWorld` que sirve una pági (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `<h1>`. -# 📂 Repositorio +## 📂 Proyecto El código se organiza en un *workspace* donde actualmente se incluyen los siguientes subproyectos: @@ -82,7 +82,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui fuente de la librería principal. Reúne algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar APIs y recursos para la creación avanzada de soluciones web. -## Auxiliares +### Auxiliares * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las @@ -100,7 +100,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo de compilación. -## Extensiones +### Extensiones * **[pagetop-aliner](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner)**, es un tema para demos y pruebas que muestra esquemáticamente la composición de las páginas HTML. @@ -110,7 +110,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui componentes flexibles. -# 🧪 Pruebas +## 🧪 Pruebas Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de Cargo** declarados en `.cargo/config.toml`. Basta con ejecutarlos desde la raíz del proyecto: @@ -127,14 +127,14 @@ Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de C > Si quieres **activar** las trazas del registro de eventos entonces usa simplemente `cargo test`. -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: @@ -148,7 +148,28 @@ Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el es el ecosistema Rust. -# ✨ Contribuir +## ✨ Contribuir + +PageTop mantiene **un único repositorio oficial**: + + * **Repositorio oficial:** https://git.cillero.es/manuelcillero/pagetop + * **Repositorio espejo:** https://github.com/manuelcillero/pagetop + +El repositorio de GitHub actúa como espejo y punto de entrada para: + + * dar mayor visibilidad al proyecto, + * facilitar la participación de la comunidad, + * centralizar *issues* y *pull requests* externas. + +Aunque GitHub permite abrir *pull requests*, **la integración del código se realiza únicamente en el +repositorio oficial**. El repositorio de GitHub se sincroniza posteriormente para reflejar el mismo +estado. + +En todos los casos, se respeta la **autoría original** de las contribuciones integradas, tanto en el +historial como en la documentación asociada al cambio. + +Para conocer el proceso completo de participación, revisión e integración de cambios, consulta el +archivo [`CONTRIBUTING.md`](CONTRIBUTING.md). Cualquier contribución para añadir al proyecto se considerará automáticamente bajo la doble licencia indicada arriba (MIT o Apache v2.0), sin términos o condiciones adicionales, tal y como permite la diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index f4670aae..f3849122 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -12,14 +12,14 @@ <br> </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -# ⚡️ Guía rápida +## ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -80,14 +80,14 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { ``` -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index d6e1666a..e7a3ea79 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -12,14 +12,14 @@ <br> </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -# ⚡️ Guía rápida +## ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -80,14 +80,14 @@ async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { ``` -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 875acbd9..c5d9c5bd 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -11,14 +11,14 @@ </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -# ⚡️ Guía rápida +## ⚡️ Guía rápida Añadir en el archivo `Cargo.toml` del proyecto: @@ -30,7 +30,7 @@ pagetop-build = { ... } Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: -## Incluir archivos estáticos desde un directorio +### Incluir archivos estáticos desde un directorio Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: @@ -64,7 +64,7 @@ fn main() -> std::io::Result<()> { } ``` -## Compilar archivos SCSS a CSS +### Compilar archivos SCSS a CSS Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con el archivo CSS minificado obtenido. Por ejemplo: @@ -83,7 +83,7 @@ Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. -# 📦 Archivos generados +## 📦 Archivos generados Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) @@ -111,14 +111,14 @@ impl Extension for MyExtension { ``` -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index b7f1ad5e..9b0174a6 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -11,13 +11,14 @@ </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Créditos + +## 📚 Créditos Este *crate* incluye entre sus macros una adaptación de [maud-macros](https://crates.io/crates/maud_macros) @@ -29,14 +30,14 @@ necesidad de referenciar `maud` o `smart_default` en las dependencias del archiv cada proyecto PageTop. -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-minimal/README.md b/helpers/pagetop-minimal/README.md index 16f73963..1f8ec148 100644 --- a/helpers/pagetop-minimal/README.md +++ b/helpers/pagetop-minimal/README.md @@ -11,19 +11,21 @@ </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Descripción general + +## 🗺️ Descripción general Este *crate* proporciona un conjunto básico de macros que se integran en las utilidades de PageTop para optimizar operaciones habituales relacionadas con la composición estructurada de texto, la concatenación de cadenas y el uso rápido de colecciones clave-valor. -## Créditos + +## 📚 Créditos Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc!`** se reexportan del *crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). @@ -38,14 +40,14 @@ La macro para generar identificadores dinámicos **`paste!`** se reexporta del * `paste!` de [David Tolnay](https://crates.io/users/dtolnay). -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index 99e24b4d..7466dc1f 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -11,19 +11,21 @@ </div> -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Descripción general + +## 🗺️ Descripción general Este *crate* permite incluir archivos estáticos en el ejecutable de las aplicaciones PageTop para servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de compilación. -## Créditos + +## 📚 Créditos Para ello, adapta el código de los *crates* [static-files](https://crates.io/crates/static_files) (versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y @@ -35,14 +37,14 @@ Estas implementaciones se integran en PageTop para evitar que cada proyecto teng `static-files` manualmente como dependencia en su `Cargo.toml`. -# 🚧 Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -# 📜 Licencia +## 📜 Licencia El código está disponible bajo una doble licencia: From dd5cdb19cf4250a37ba47530937f4c2e09050c63 Mon Sep 17 00:00:00 2001 From: Manuel Cillero <manuel@cillero.es> Date: Sun, 21 Dec 2025 09:47:35 +0100 Subject: [PATCH 224/224] =?UTF-8?q?=F0=9F=93=9D=20Actualiza=20las=20gu?= =?UTF-8?q?=C3=ADas=20de=20contribuci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 121 +++++++++------------------ MAINTAINERS.md | 211 ++++++++++++++++++------------------------------ 2 files changed, 113 insertions(+), 219 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2e2a263..e0e275a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,19 +15,10 @@ PageTop mantiene **un único repositorio oficial**: * **Repositorio oficial:** https://git.cillero.es/manuelcillero/pagetop * **Repositorio espejo:** https://github.com/manuelcillero/pagetop -El repositorio de GitHub actúa como espejo y punto de entrada para: - - * dar mayor visibilidad al proyecto, - * facilitar la participación de la comunidad, - * centralizar *issues* y *pull requests* externas. - > ⚠️ **Importante** -> Aunque GitHub permite abrir *pull requests*, **la integración del código se realiza únicamente en -> el repositorio oficial**. El repositorio de GitHub se sincroniza posteriormente para reflejar el -> mismo estado. - -En todos los casos, se respeta la **autoría original** de las contribuciones integradas, tanto en el -historial como en la documentación asociada al cambio. +> Aunque GitHub permite abrir *issues* y *pull requests*, **la integración del código se realiza +> únicamente en el repositorio oficial**. GitHub actúa como repositorio espejo que se sincroniza +> automáticamente para reflejar el mismo estado. ## 2. Issues (incidencias, propuestas, preguntas) @@ -50,8 +41,8 @@ Las *issues* se usan para: ### 3.1 Dónde abrirlas -Las *pull requests* se abren **en GitHub**, contra la rama `main`. GitHub es el punto de entrada -recomendado para contribuciones externas. +Las *pull requests* se abren **en GitHub**, normalmente contra la rama `main`. GitHub es el punto de +entrada recomendado para contribuciones externas. ### 3.2 Reglas generales para PRs @@ -63,68 +54,22 @@ recomendado para contribuciones externas. ### 3.3 Revisión y aceptación -Todas las PRs: +Todas las PRs son **revisadas manualmente** y pueden recibir comentarios o solicitudes de cambios. - * serán **revisadas manualmente**, - * pueden recibir comentarios o solicitudes de cambios, - * **no se integran directamente en GitHub**, ya que la integración se realiza en el repositorio - oficial para mantener coherencia y trazabilidad. - -Una PR aceptada: - - * se integra en el repositorio oficial (Forgejo), - * respetando **la autoría original del contribuidor**, - * normalmente mediante **squash merge** para mantener un historial limpio. +Las PRs aceptadas se integran en el repositorio oficial, nunca directamente en GitHub, preservando +siempre la **autoría original** del contribuidor. -## 4. Autoría y atribución +### 3.4. Cierre de Pull Requests y sincronización -PageTop cuida especialmente la atribución de contribuciones. +Una vez que el cambio ha sido integrado en el repositorio oficial: - * El **autor original del código se conserva** en el commit final integrado en Forgejo. - * Aunque el autor no tenga cuenta en Forgejo, su nombre y email quedarán reflejados. - * En GitHub, cuando es posible, la contribución quedará asociada al usuario original. - -Adicionalmente, el mensaje del commit puede incluir líneas `Co-authored-by` cuando proceda. + * La PR en GitHub se **cierra manualmente**. + * Se añade un **mensaje estándar de cierre** indicando que el cambio ha sido integrado. + * El repositorio de GitHub **se sincroniza automáticamente** como espejo. -## 5. Cierre de Pull Requests en GitHub - -Una vez que el cambio ha sido integrado en Forgejo: - - * La PR en GitHub se **cerrará manualmente** (no se mergea). - * Se añadirá un **mensaje estándar de cierre**, indicando: - * que el cambio ha sido integrado, - * la referencia al commit o versión, - * que GitHub es un repositorio espejo. - -Ejemplo de mensaje de cierre: - -> Este cambio ha sido integrado en el repositorio oficial (Forgejo). -> GitHub actúa como repositorio espejo, por lo que la PR se cierra sin merge. -> Gracias por tu contribución. - -Esto garantiza: - - * transparencia, - * trazabilidad, - * coherencia entre repositorios. - - -## 6. Sincronización entre Forgejo y GitHub - -Tras integrar cambios en Forgejo: - - * el repositorio de GitHub se **actualiza para reflejar el estado de Forgejo**, - * el historial de GitHub puede reescribirse para mantener coherencia. - -Por este motivo: - - * **no se deben hacer merges “definitivos” en GitHub**, - * GitHub no debe considerarse fuente de verdad del historial. - - -## 7. Estilo de código y calidad +## 4. Estilo de código y calidad * Sigue el estilo existente del proyecto. * Mantén los comentarios claros y precisos. @@ -132,18 +77,28 @@ Por este motivo: * Cambios públicos o estructurales deben ir acompañados de documentación. -## 8. Commits +## 5. Commits -Recomendaciones generales: +PageTop usa la especificación **gitmoji** para los mensajes de *commit*. El formato recomendado es: - * Mensajes claros y descriptivos. - * Un commit debe representar una unidad lógica de cambio. - * En contribuciones externas, el formato exacto del commit puede ajustarse durante la integración. + `<propósito> (ámbito opcional): <mensaje>` -Durante la integración, los commits pueden ajustarse (rebase, squash o edición de mensajes) para -adaptarse al historial del proyecto. +Ejemplos: -## 9. Comunicación y respeto + * 📝 Actualiza la guía de contribución + * ✨ (locale): Refactoriza sistema de localización + * ♻️ (bootsier): Simplifica asignación de clases + +El emoji puede usarse en formato Unicode o como *shortcode*, por ejemplo `:sparkles:` en vez de ✨. + +Consulta la especificación oficial en https://gitmoji.dev/specification + +Durante la integración, los *commits* pueden ajustarse para adaptarse al historial del proyecto. + +Un *commit* debe representar una unidad lógica de cambio. Usa mensajes claros y descriptivos. + + +## 6. Comunicación y respeto PageTop sigue un enfoque profesional y colaborativo: @@ -151,13 +106,9 @@ PageTop sigue un enfoque profesional y colaborativo: * Acepta sugerencias técnicas como parte del proceso. * Recuerda que todas las contribuciones son revisadas con el objetivo de mejorar el proyecto. +Si tienes dudas sobre el proceso, abre una *issue* de tipo pregunta para tratar la cuestión en +comunidad. -## 10. Dudas +--- -Si tienes dudas sobre el proceso: - - * abre una *issue* de tipo pregunta, - * o inicia una discusión (si está habilitada). - -Gracias por contribuir a **PageTop** 🚀 Cada aportación, grande o pequeña, ayuda a que el proyecto -mejore. +Gracias por contribuir a **PageTop** 🚀 Cada aportación contribuye a mejorar el proyecto. diff --git a/MAINTAINERS.md b/MAINTAINERS.md index fbf69db3..25559841 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -10,204 +10,147 @@ usuarios externos**. Su objetivo es servir como **referencia operativa**, garant trazabilidad y preservación de la autoría en un entorno con repositorios espejo. -## 1. Repositorios y roles +## 1. Repositorios y principios PageTop mantiene **un único repositorio oficial**: * **Repositorio oficial:** https://git.cillero.es/manuelcillero/pagetop * **Repositorio espejo:** https://github.com/manuelcillero/pagetop -El repositorio de GitHub actúa como espejo y punto de entrada para: - - * dar mayor visibilidad al proyecto, - * facilitar la participación de la comunidad, - * centralizar *issues* y *pull requests* externas. - ### Principios clave * El repositorio oficial **es la única fuente de verdad** del historial. * **Nunca se realizan *merges* en GitHub**. + * Toda integración definitiva se realiza en el repositorio oficial. + * La autoría original debe preservarse siempre. ## 2. Configuración local recomendada -Configuración típica de *remotes*: +El remoto `github` debe configurarse únicamente para operaciones de lectura (*fetch*), con la URL de +*push* deshabilitada para evitar publicaciones accidentales en el repositorio espejo. -```bash -git remote -v -``` - -Ejemplo esperado: +Estado esperado de `git remote -v`: ```text origin git@git.cillero.es:manuelcillero/pagetop.git (fetch) origin git@git.cillero.es:manuelcillero/pagetop.git (push) -github git@github.com:manuelcillero/pagetop.git (fetch) -github git@github.com:manuelcillero/pagetop.git (push) +github git@github.com:manuelcillero/pagetop.git (fetch) +github DISABLED (push) ``` Convenciones usadas en este documento: -* `origin` -> Oficial -* `github` -> GitHub (espejo) + * `origin` -> Repositorio oficial + * `github` -> Repositorio espejo -## 3. Recepción de Pull Requests desde GitHub +## 3. Recepción y revisión de Pull Requests -Las contribuciones externas llegan como *pull requests* en GitHub, normalmente contra `main`. +Las PRs externas llegan por GitHub, normalmente contra la rama `main`. -### 3.1 Obtener la PR en local - -Opción habitual (ejemplo con PR #123): +Se asume que el repositorio local está configurado para recuperar PRs de GitHub como referencias +remotas (`refs/pull/<N>/head`): ```bash -git fetch github pull/123/head:pr-123 -git checkout pr-123 +git fetch github --prune +git checkout -b pr-123 github/pr/123 ``` -Alternativamente, si la rama del contribuidor es accesible directamente como referencia remota: +Antes de integrar: -```bash -git fetch github -git checkout nombre-de-la-rama -``` + * Revisar el código manualmente. + * Verificar formato, análisis y pruebas: + + ```bash + cargo fmt + cargo clippy + cargo test + ``` + + * Comprobar impacto en documentación. + * Evaluar coherencia con la arquitectura y el estilo del proyecto. + +Los cambios adicionales se solicitan o se aplican explicando claramente el motivo. -## 4. Revisión local +## 4. Estrategia de integración -Antes de integrar cualquier cambio: +La integración **se realiza siempre en el repositorio oficial** (`origin`). -* Revisar el código manualmente. -* Verificar compilación y pruebas: +### 4.1 Estrategia por defecto: *rebase* + *fast-forward* -```bash -cargo build -cargo test -``` +Esta es la **estrategia estándar y recomendada** en PageTop. Ventajas: -* Comprobar impacto en documentación. -* Evaluar coherencia con la arquitectura y el estilo del proyecto. - -Si se requieren cambios: - -* comentar en la PR, -* solicitar ajustes, -* o realizar modificaciones locales explicadas claramente. - - -## 5. Estrategia de integración - -La integración **se realiza siempre en el repositorio oficial**. - -### 5.1 Estrategia por defecto: *squash merge* - -Usada cuando: - -* la PR tiene varios commits intermedios, -* los commits no siguen el estilo del proyecto, -* se desea un historial compacto. + * conserva los commits originales, + * preserva la autoría real de cada cambio, + * mantiene un historial lineal y trazable, + * facilita auditoría y depuración. Procedimiento típico: +```bash +git checkout pr-123 +git rebase main + +# Resolver conflictos si los hay + +git checkout main +git merge --ff-only pr-123 +``` + +Si `merge --ff-only` falla, **no se debe continuar**, indica divergencias que deben resolverse antes +de integrar la PR. + +### 4.2 Estrategia excepcional: *Squash* + +Sólo debe usarse cuando esté justificado: + + * la PR contiene múltiples commits de prueba o ruido, + * el historial aportado no es significativo, + * el cambio es pequeño y autocontenido. + +En este caso, se debe **preservar explícitamente la autoría**: + ```bash git checkout main -git pull origin main git merge --squash pr-123 -``` - -Crear el commit final **preservando la autoría** (ver sección 6). - -### 5.2 Cherry-pick selectivo - -Usado cuando: - -* uno o varios commits son claros y autocontenidos, -* interesa conservar referencias explícitas. - -Ejemplo: - -```bash -git checkout main -git pull origin main -git cherry-pick -x <commit-sha> -``` - - -## 6. Preservación de la autoría - -La autoría original **debe conservarse siempre**. - -### 6.1 Commit con autor explícito - -Ejemplo: - -```bash git commit --author="Nombre Apellido <email@ejemplo.com>" ``` -El mantenedor figura como *committer*; el contribuidor como *author*. -### 6.2 Co-authored-by - -Cuando procede, puede añadirse al mensaje del commit: - -```text -Co-authored-by: Nombre Apellido <email@ejemplo.com> -``` - - -## 7. Push al repositorio oficial - -Una vez integrado: +### 4.3. Publicación en el repositorio oficial ```bash git push origin main ``` -Este push representa **la integración definitiva**. +Este *push* representa la **integración definitiva** del cambio en la rama `main`. -## 8. Cierre de la Pull Request en GitHub +## 5. Cierre de la PR y sincronización -Tras integrar el cambio en el repositorio oficial: - -* **No se mergea la PR en GitHub**. -* Se cierra manualmente con un mensaje estándar. - -Ejemplo recomendado: +Tras integrar el cambio en el repositorio oficial, se cierra manualmente la PR en GitHub con un +mensaje estándar: ```text -Este cambio ha sido integrado en el repositorio oficial. -GitHub actúa como repositorio espejo, por lo que la PR se cierra sin merge. Gracias por tu contribución. + +Este cambio ha sido integrado en el repositorio oficial en `main` (`<hash>`). +GitHub actúa como repositorio espejo sincronizado. ``` -## 9. Sincronización del repositorio oficial a GitHub +## 6. Principios de mantenimiento -El repositorio de GitHub se mantiene como **espejo automático** del repositorio oficial -mediante un **push mirror configurado**. - -No se realizan sincronizaciones manuales desde clones locales. - -### Consideraciones - - * El repositorio oficial es siempre la **fuente de verdad**. - * El historial de GitHub puede **reescribirse automáticamente** para reflejar el estado del - repositorio oficial. - * Todas las ramas que deban preservarse en GitHub **deben existir también en el repositorio - oficial**. - * GitHub no debe usarse como referencia del historial real. - - -## 10. Principios de mantenimiento - -* Priorizar **claridad y trazabilidad** frente a rapidez. -* Mantener un historial legible y significativo. -* Documentar cambios estructurales o públicos. -* Tratar las contribuciones externas con respeto y transparencia. + * Priorizar **claridad y trazabilidad** frente a rapidez. + * Mantener un historial legible y significativo. + * Documentar cambios estructurales o públicos. + * Tratar las contribuciones externas con respeto y transparencia. --- Este documento puede evolucionar con el proyecto. -Su objetivo no es imponer rigidez, sino **capturar el conocimiento operativo real** de PageTop. + +No se trata de imponer rigidez, sino de **capturar el conocimiento operativo real** de PageTop como +guía práctica para el mantenimiento.