From 5d05ec58be9e6dcb028d69a4280a1d9e9d99f20e Mon Sep 17 00:00:00 2001 From: BlackDex Date: Wed, 8 Jun 2022 19:46:33 +0200 Subject: [PATCH] Updated deps and misc fixes and updates - Updated some Rust dependencies - Fixed an issue with CSP header, this was not configured correctly - Prevent sending CSP and Frame headers for the MFA connector.html files. Else some clients will fail to handle these protocols. - Add `unsafe-inline` for `script-src` only to the CSP for the Admin Interface - Updated JavaScript and CSS files for the Admin interface - Changed the layout for showing overridden settings, better visible now. - Made the version check cachable to prevent hitting the Github API rate limits - Hide the `database_url` as if it is a password in the Admin Interface Else for MariaDB/MySQL or PostgreSQL this was plain text. - Fixed an issue that pressing enter on the SMTP Test would save the config. resolves #2542 - Prevent user names larger then 50 characters resolves #2419 --- .github/workflows/build.yml | 2 - .pre-commit-config.yaml | 8 +- Cargo.lock | 310 +- Cargo.toml | 12 +- src/api/admin.rs | 77 +- src/api/core/accounts.rs | 14 + src/config.rs | 5 +- src/db/models/org_policy.rs | 2 +- src/static/scripts/bootstrap-native.js | 1619 ++-- src/static/scripts/bootstrap.css | 3674 ++++--- src/static/scripts/datatables.css | 302 +- src/static/scripts/datatables.js | 11112 +++++++++++----------- src/static/templates/admin/base.hbs | 11 +- src/static/templates/admin/settings.hbs | 63 +- src/util.rs | 49 +- 15 files changed, 9177 insertions(+), 8083 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7857fa8a..674e37c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,6 @@ on: - "migrations/**" - "Cargo.*" - "build.rs" - - "diesel.toml" - "rust-toolchain" pull_request: paths: @@ -17,7 +16,6 @@ on: - "migrations/**" - "Cargo.*" - "build.rs" - - "diesel.toml" - "rust-toolchain" jobs: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c545317..8f31035a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-yaml - id: check-json @@ -26,7 +26,8 @@ repos: entry: cargo test language: system args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--"] - types: [rust] + types_or: [file, rust] + files: (Cargo.toml|Cargo.lock) pass_filenames: false - id: cargo-clippy name: cargo clippy @@ -34,5 +35,6 @@ repos: entry: cargo clippy language: system args: ["--features", "sqlite,mysql,postgresql,enable_mimalloc", "--", "-D", "warnings"] - types: [rust] + types_or: [file, rust] + files: (Cargo.toml|Cargo.lock) pass_filenames: false diff --git a/Cargo.lock b/Cargo.lock index 43ffab21..007b355d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,12 +178,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base-x" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" - [[package]] name = "base64" version = "0.13.0" @@ -279,9 +273,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cached" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aadf76ddea74bab35ebeb8f1eb115b9bc04eaee42d8acc0d5f477dee6b176c9a" +checksum = "12f5cd208ba696f870238022d81ca1d80ed9d696fd62341c747f2d8f6ecdd9fe" dependencies = [ "async-trait", "async_once", @@ -370,23 +364,6 @@ dependencies = [ "generic-array 0.14.5", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - -[[package]] -name = "cookie" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" -dependencies = [ - "percent-encoding 2.1.0", - "time 0.2.27", - "version_check", -] - [[package]] name = "cookie" version = "0.16.0" @@ -405,29 +382,13 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie_store" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f7034c0932dc36f5bd8ec37368d971346809435824f277cb3b8299fc56167c" -dependencies = [ - "cookie 0.15.1", - "idna 0.2.3", - "log", - "publicsuffix", - "serde", - "serde_json", - "time 0.2.27", - "url 2.2.2", -] - [[package]] name = "cookie_store" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" dependencies = [ - "cookie 0.16.0", + "cookie", "idna 0.2.3", "log", "publicsuffix", @@ -694,12 +655,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "dotenvy" version = "0.15.1" @@ -717,9 +672,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "email-encoding" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b91dddc343e7eaa27f9764e5bffe57370d957017fdd75244f5045e829a8441" +checksum = "827e1fb86d24d558ab0454ca3fa084f8a6144ade1e3e6982f697c586bf96b41b" dependencies = [ "base64", "memchr", @@ -742,9 +697,9 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ "heck", "proc-macro2", @@ -970,13 +925,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -1033,7 +988,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.3", "tracing", ] @@ -1045,9 +1000,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d113a9853e5accd30f43003560b5563ffbb007e3f325e8b103fa0d0029c6e6df" +checksum = "b66d0c1b6e3abfd1e72818798925e16e02ed77e1b47f6c25a95a23b377ee4299" dependencies = [ "log", "pest", @@ -1125,9 +1080,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", @@ -1174,7 +1129,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.4", + "socket2", "tokio", "tower-service", "tracing", @@ -1250,14 +1205,14 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.3.19", + "socket2", "widestring", "winapi", - "winreg 0.6.2", + "winreg 0.7.0", ] [[package]] @@ -1337,7 +1292,7 @@ dependencies = [ "once_cell", "quoted_printable", "serde", - "socket2 0.4.4", + "socket2", "tracing", ] @@ -2194,15 +2149,15 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ "async-compression", "base64", "bytes", - "cookie 0.15.1", - "cookie_store 0.15.1", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -2226,7 +2181,8 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-socks", - "tokio-util 0.6.10", + "tokio-util 0.7.3", + "tower-service", "trust-dns-resolver", "url 2.2.2", "wasm-bindgen", @@ -2314,7 +2270,7 @@ dependencies = [ "time 0.3.9", "tokio", "tokio-stream", - "tokio-util 0.7.2", + "tokio-util 0.7.3", "ubyte", "version_check", "yansi", @@ -2342,7 +2298,7 @@ version = "0.5.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" dependencies = [ - "cookie 0.16.0", + "cookie", "either", "futures", "http", @@ -2372,15 +2328,6 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "rustls" version = "0.20.6" @@ -2487,21 +2434,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.137" @@ -2578,15 +2510,6 @@ dependencies = [ "digest 0.10.3", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - [[package]] name = "sha1" version = "0.10.1" @@ -2598,12 +2521,6 @@ dependencies = [ "digest 0.10.3", ] -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.10.2" @@ -2663,17 +2580,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if", - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.4.4" @@ -2705,15 +2611,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - [[package]] name = "state" version = "0.5.3" @@ -2723,55 +2620,6 @@ dependencies = [ "loom", ] -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1 0.6.1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strsim" version = "0.10.0" @@ -2870,21 +2718,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros 0.1.1", - "version_check", - "winapi", -] - [[package]] name = "time" version = "0.3.9" @@ -2894,17 +2727,7 @@ dependencies = [ "itoa", "libc", "num_threads", - "time-macros 0.2.4", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", + "time-macros", ] [[package]] @@ -2913,19 +2736,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -2943,9 +2753,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.0" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f392c8f16bda3456c0b00c6de39cb100449b98de55ac41c6cdd2bfcf53a1245" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes", "libc", @@ -2956,16 +2766,16 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -3007,9 +2817,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", "pin-project-lite", @@ -3044,9 +2854,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", @@ -3085,9 +2895,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if", "log", @@ -3109,11 +2919,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -3148,9 +2958,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.4" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" dependencies = [ "async-trait", "cfg-if", @@ -3173,9 +2983,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.4" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" dependencies = [ "cfg-if", "futures-util", @@ -3183,7 +2993,7 @@ dependencies = [ "lazy_static", "log", "lru-cache", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "resolv-conf", "smallvec", "thiserror", @@ -3255,9 +3065,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -3322,9 +3132,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d5d669b51467dcf7b2f1a796ce0f955f05f01cafda6c19d6e95f730df29238" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" dependencies = [ "getrandom", ] @@ -3344,8 +3154,8 @@ dependencies = [ "cached", "chrono", "chrono-tz", - "cookie 0.16.0", - "cookie_store 0.16.1", + "cookie", + "cookie_store", "ctrlc", "dashmap", "data-encoding", @@ -3543,9 +3353,9 @@ dependencies = [ [[package]] name = "widestring" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" [[package]] name = "winapi" @@ -3623,9 +3433,9 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi", ] @@ -3657,6 +3467,6 @@ dependencies = [ "hmac", "rand", "reqwest", - "sha1 0.10.1", + "sha1", "threadpool", ] diff --git a/Cargo.toml b/Cargo.toml index 25b84a07..dbc1fc1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ syslog = "6.0.1" # Needs to be v4 until fern is updated # Logging log = "0.4.17" fern = { version = "0.6.1", features = ["syslog-6"] } -tracing = { version = "0.1.34", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work +tracing = { version = "0.1.35", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work backtrace = "0.3.65" # Logging panics to logfile instead stderr only @@ -61,7 +61,7 @@ dashmap = "5.3.4" # Concurrent hashmap implementation # Async futures futures = "0.3.21" -tokio = { version = "1.19.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] } +tokio = { version = "1.19.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] } # A generic serialization/deserialization framework serde = { version = "1.0.137", features = ["derive"] } @@ -79,7 +79,7 @@ rand = { version = "0.8.5", features = ["small_rng"] } ring = "0.16.20" # UUID generation -uuid = { version = "1.1.1", features = ["v4"] } +uuid = { version = "1.1.2", features = ["v4"] } # Date and time libraries chrono = { version = "0.4.19", features = ["clock", "serde"], default-features = false } @@ -112,17 +112,17 @@ lettre = { version = "0.10.0-rc.7", features = ["smtp-transport", "builder", "se percent-encoding = "2.1.0" # URL encoding library used for URL's in the emails # Template library -handlebars = { version = "4.3.0", features = ["dir_source"] } +handlebars = { version = "4.3.1", features = ["dir_source"] } # HTTP client -reqwest = { version = "0.11.10", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } +reqwest = { version = "0.11.11", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } # For favicon extraction from main website html5gum = "0.4.0" regex = { version = "1.5.6", features = ["std", "perf", "unicode-perl"], default-features = false } data-url = "0.1.1" bytes = "1.1.0" -cached = "0.34.0" +cached = "0.34.1" # Used for custom short lived cookie jar during favicon extraction cookie = "0.16.0" diff --git a/src/api/admin.rs b/src/api/admin.rs index 37b169ed..63edde66 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -491,41 +491,14 @@ async fn has_http_access() -> bool { } } -#[get("/diagnostics")] -async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult> { - use crate::util::read_file_string; - use chrono::prelude::*; - use std::net::ToSocketAddrs; - - // Get current running versions - let web_vault_version: WebVaultVersion = - match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "vw-version.json")) { - Ok(s) => serde_json::from_str(&s)?, - _ => match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) { - Ok(s) => serde_json::from_str(&s)?, - _ => WebVaultVersion { - version: String::from("Version file missing"), - }, - }, - }; - - // Execute some environment checks - let running_within_docker = is_running_in_docker(); - let has_http_access = has_http_access().await; - let uses_proxy = env::var_os("HTTP_PROXY").is_some() - || env::var_os("http_proxy").is_some() - || env::var_os("HTTPS_PROXY").is_some() - || env::var_os("https_proxy").is_some(); - - // Check if we are able to resolve DNS entries - let dns_resolved = match ("github.com", 0).to_socket_addrs().map(|mut i| i.next()) { - Ok(Some(a)) => a.ip().to_string(), - _ => "Could not resolve domain name.".to_string(), - }; - +use cached::proc_macro::cached; +/// Cache this function to prevent API call rate limit. Github only allows 60 requests per hour, and we use 3 here already. +/// It will cache this function for 300 seconds (5 minutes) which should prevent the exhaustion of the rate limit. +#[cached(time = 300, sync_writes = true)] +async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) { // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. - // TODO: Maybe we need to cache this using a LazyStatic or something. Github only allows 60 requests per hour, and we use 3 here already. - let (latest_release, latest_commit, latest_web_build) = if has_http_access { + if has_http_access { + info!("Running get_release_info!!"); ( match get_github_api::("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest") .await @@ -558,8 +531,44 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> A ) } else { ("-".to_string(), "-".to_string(), "-".to_string()) + } +} + +#[get("/diagnostics")] +async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult> { + use crate::util::read_file_string; + use chrono::prelude::*; + use std::net::ToSocketAddrs; + + // Get current running versions + let web_vault_version: WebVaultVersion = + match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "vw-version.json")) { + Ok(s) => serde_json::from_str(&s)?, + _ => match read_file_string(&format!("{}/{}", CONFIG.web_vault_folder(), "version.json")) { + Ok(s) => serde_json::from_str(&s)?, + _ => WebVaultVersion { + version: String::from("Version file missing"), + }, + }, + }; + + // Execute some environment checks + let running_within_docker = is_running_in_docker(); + let has_http_access = has_http_access().await; + let uses_proxy = env::var_os("HTTP_PROXY").is_some() + || env::var_os("http_proxy").is_some() + || env::var_os("HTTPS_PROXY").is_some() + || env::var_os("https_proxy").is_some(); + + // Check if we are able to resolve DNS entries + let dns_resolved = match ("github.com", 0).to_socket_addrs().map(|mut i| i.next()) { + Ok(Some(a)) => a.ip().to_string(), + _ => "Could not resolve domain name.".to_string(), }; + let (latest_release, latest_commit, latest_web_build) = + get_release_info(has_http_access, running_within_docker).await; + let ip_header_name = match &ip_header.0 { Some(h) => h, _ => "", diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index d0a9b37a..61299de6 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -67,6 +67,14 @@ async fn register(data: JsonUpcase, conn: DbConn) -> EmptyResult { let data: RegisterData = data.into_inner().data; let email = data.Email.to_lowercase(); + // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) + // This also prevents issues with very long usernames causing to large JWT's. See #2419 + if let Some(ref name) = data.Name { + if name.len() > 50 { + err!("The field Name must be a string with a maximum length of 50."); + } + } + let mut user = match User::find_by_mail(&email, &conn).await { Some(user) => { if !user.password_hash.is_empty() { @@ -176,6 +184,12 @@ async fn put_profile(data: JsonUpcase, headers: Headers, conn: DbCo async fn post_profile(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { let data: ProfileData = data.into_inner().data; + // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) + // This also prevents issues with very long usernames causing to large JWT's. See #2419 + if data.Name.len() > 50 { + err!("The field Name must be a string with a maximum length of 50."); + } + let mut user = headers.user; user.name = data.Name; diff --git a/src/config.rs b/src/config.rs index 847382cd..a0fad9d9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1058,12 +1058,11 @@ fn js_escape_helper<'reg, 'rc>( _rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> HelperResult { - let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?; + let param = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"jsesc\""))?; let no_quote = h.param(1).is_some(); - let value = - param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?; + let value = param.value().as_str().ok_or_else(|| RenderError::new("Param for helper \"jsesc\" is not a String"))?; let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27"); if !no_quote { diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 95045f3e..65ec0fd8 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -21,7 +21,7 @@ db_object! { } } -#[derive(Copy, Clone, PartialEq, num_derive::FromPrimitive)] +#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)] pub enum OrgPolicyType { TwoFactorAuthentication = 0, MasterPassword = 1, diff --git a/src/static/scripts/bootstrap-native.js b/src/static/scripts/bootstrap-native.js index fa266298..bf26cef8 100644 --- a/src/static/scripts/bootstrap-native.js +++ b/src/static/scripts/bootstrap-native.js @@ -1,5 +1,5 @@ /*! - * Native JavaScript for Bootstrap v4.1.2 (https://thednp.github.io/bootstrap.native/) + * Native JavaScript for Bootstrap v4.2.0 (https://thednp.github.io/bootstrap.native/) * Copyright 2015-2022 © dnp_theme * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) */ @@ -15,27 +15,26 @@ /** * The global event listener. * - * @this {Element | HTMLElement | Window | Document} - * @param {Event} e - * @returns {void} + * @type {EventListener} + * @this {EventTarget} */ function globalListener(e) { const that = this; const { type } = e; - const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : []; - oneEvMap.forEach((elementsMap) => { + [...EventRegistry[type]].forEach((elementsMap) => { const [element, listenersMap] = elementsMap; - [...listenersMap].forEach((listenerMap) => { - if (element === that) { + /* istanbul ignore else */ + if (element === that) { + [...listenersMap].forEach((listenerMap) => { const [listener, options] = listenerMap; listener.apply(element, [e]); if (options && options.once) { removeListener(element, type, listener, options); } - } - }); + }); + } }); } @@ -43,10 +42,7 @@ * Register a new listener with its options and attach the `globalListener` * to the target if this is the first listener. * - * @param {Element | HTMLElement | Window | Document} element - * @param {string} eventType - * @param {EventListenerObject['handleEvent']} listener - * @param {AddEventListenerOptions=} options + * @type {Listener.ListenerAction} */ const addListener = (element, eventType, listener, options) => { // get element listeners first @@ -64,9 +60,7 @@ const { size } = oneElementMap; // register listener with its options - if (oneElementMap) { - oneElementMap.set(listener, options); - } + oneElementMap.set(listener, options); // add listener last if (!size) { @@ -78,10 +72,7 @@ * Remove a listener from registry and detach the `globalListener` * if no listeners are found in the registry. * - * @param {Element | HTMLElement | Window | Document} element - * @param {string} eventType - * @param {EventListenerObject['handleEvent']} listener - * @param {AddEventListenerOptions=} options + * @type {Listener.ListenerAction} */ const removeListener = (element, eventType, listener, options) => { // get listener first @@ -100,6 +91,7 @@ if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType]; // remove listener last + /* istanbul ignore else */ if (!oneElementMap || !oneElementMap.size) { element.removeEventListener(eventType, globalListener, eventOptions); } @@ -111,7 +103,7 @@ * @see https://gist.github.com/shystruk/d16c0ee7ac7d194da9644e5d740c8338#file-subpub-js * @see https://hackernoon.com/do-you-still-register-window-event-listeners-in-each-component-react-in-example-31a4b1f6f1c8 */ - const EventListener = { + const Listener = { on: addListener, off: removeListener, globalListener, @@ -150,34 +142,35 @@ * * If `element` parameter is not an `HTMLElement`, `getComputedStyle` * throws a `ReferenceError`. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {string} property the css property * @return {string} the css property value */ function getElementStyle(element, property) { const computedStyle = getComputedStyle(element); - // @ts-ignore -- must use camelcase strings, + // must use camelcase strings, // or non-camelcase strings with `getPropertyValue` - return property in computedStyle ? computedStyle[property] : ''; + return property.includes('--') + ? computedStyle.getPropertyValue(property) + : computedStyle[property]; } /** * Utility to get the computed `transitionDelay` * from Element in miliseconds. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @return {number} the value in miliseconds */ function getElementTransitionDelay(element) { const propertyValue = getElementStyle(element, transitionProperty); const delayValue = getElementStyle(element, transitionDelay); - - const delayScale = delayValue.includes('ms') ? 1 : 1000; + const delayScale = delayValue.includes('ms') ? /* istanbul ignore next */1 : 1000; const duration = propertyValue && propertyValue !== 'none' ? parseFloat(delayValue) * delayScale : 0; - return !Number.isNaN(duration) ? duration : 0; + return !Number.isNaN(duration) ? duration : /* istanbul ignore next */0; } /** @@ -190,24 +183,32 @@ * Utility to get the computed `transitionDuration` * from Element in miliseconds. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @return {number} the value in miliseconds */ function getElementTransitionDuration(element) { const propertyValue = getElementStyle(element, transitionProperty); const durationValue = getElementStyle(element, transitionDuration); - const durationScale = durationValue.includes('ms') ? 1 : 1000; + const durationScale = durationValue.includes('ms') ? /* istanbul ignore next */1 : 1000; const duration = propertyValue && propertyValue !== 'none' ? parseFloat(durationValue) * durationScale : 0; - return !Number.isNaN(duration) ? duration : 0; + return !Number.isNaN(duration) ? duration : /* istanbul ignore next */0; } + /** + * Shortcut for the `Element.dispatchEvent(Event)` method. + * + * @param {HTMLElement} element is the target + * @param {Event} event is the `Event` object + */ + const dispatchEvent = (element, event) => element.dispatchEvent(event); + /** * Utility to make sure callbacks are consistently * called when transition ends. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {EventListener} handler `transitionend` callback */ function emulateTransitionEnd(element, handler) { @@ -222,6 +223,7 @@ * @type {EventListener} e Event object */ const transitionEndWrapper = (e) => { + /* istanbul ignore else */ if (e.target === element) { handler.apply(element, [e]); element.removeEventListener(transitionEndEvent, transitionEndWrapper); @@ -230,7 +232,8 @@ }; element.addEventListener(transitionEndEvent, transitionEndWrapper); setTimeout(() => { - if (!called) element.dispatchEvent(endEvent); + /* istanbul ignore next */ + if (!called) dispatchEvent(element, endEvent); }, duration + delay + 17); } else { handler.apply(element, [endEvent]); @@ -238,43 +241,64 @@ } /** - * Returns the `document` or the `#document` element. - * @see https://github.com/floating-ui/floating-ui - * @param {(Node | HTMLElement | Element | globalThis)=} node - * @returns {Document} + * Checks if an object is a `Node`. + * + * @param {any} node the target object + * @returns {boolean} the query result */ - function getDocument(node) { - if (node instanceof HTMLElement) return node.ownerDocument; - if (node instanceof Window) return node.document; - return window.document; - } + const isNode = (element) => (element && [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + .some((x) => +element.nodeType === x)) || false; /** - * A global array of possible `ParentNode`. + * Check if a target object is `Window`. + * => equivalent to `object instanceof Window` + * + * @param {any} object the target object + * @returns {boolean} the query result */ - const parentNodes = [Document, Element, HTMLElement]; + const isWindow = (object) => (object && object.constructor.name === 'Window') || false; /** - * A global array with `Element` | `HTMLElement`. + * Checks if an object is a `Document`. + * @see https://dom.spec.whatwg.org/#node + * + * @param {any} object the target object + * @returns {boolean} the query result */ - const elementNodes = [Element, HTMLElement]; + const isDocument = (object) => (object && object.nodeType === 9) || false; + + /** + * Returns the `document` or the `#document` element. + * @see https://github.com/floating-ui/floating-ui + * @param {(Node | Window)=} node + * @returns {Document} + */ + function getDocument(node) { + // node instanceof Document + if (isDocument(node)) return node; + // node instanceof Node + if (isNode(node)) return node.ownerDocument; + // node instanceof Window + if (isWindow(node)) return node.document; + // node is undefined | NULL + return window.document; + } /** * Utility to check if target is typeof `HTMLElement`, `Element`, `Node` * or find one that matches a selector. * - * @param {HTMLElement | Element | string} selector the input selector or target element - * @param {(HTMLElement | Element | Document)=} parent optional node to look into - * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result + * @param {Node | string} selector the input selector or target element + * @param {ParentNode=} parent optional node to look into + * @return {HTMLElement?} the `HTMLElement` or `querySelector` result */ function querySelector(selector, parent) { - const lookUp = parentNodes.some((x) => parent instanceof x) - ? parent : getDocument(); + if (isNode(selector)) { + return selector; + } + const lookUp = isNode(parent) ? parent : getDocument(); - // @ts-ignore - return elementNodes.some((x) => selector instanceof x) - // @ts-ignore - ? selector : lookUp.querySelector(selector); + return lookUp.querySelector(selector); } /** @@ -284,13 +308,13 @@ * * @see https://stackoverflow.com/q/54520554/803358 * - * @param {HTMLElement | Element} element Element to look into + * @param {HTMLElement} element Element to look into * @param {string} selector the selector name - * @return {(HTMLElement | Element)?} the query result + * @return {HTMLElement?} the query result */ function closest(element, selector) { return element ? (element.closest(selector) - // @ts-ignore -- break out of `ShadowRoot` + // break out of `ShadowRoot` || closest(element.getRootNode().host, selector)) : null; } @@ -304,7 +328,7 @@ /** * Check class in `HTMLElement.classList`. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {string} classNAME to check * @returns {boolean} */ @@ -315,7 +339,7 @@ /** * Remove class from `HTMLElement.classList`. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {string} classNAME to remove * @returns {void} */ @@ -324,14 +348,15 @@ } /** - * Shortcut for the `Element.dispatchEvent(Event)` method. + * Checks if an element is an `HTMLElement`. + * @see https://dom.spec.whatwg.org/#node * - * @param {HTMLElement | Element} element is the target - * @param {Event} event is the `Event` object + * @param {any} element the target object + * @returns {boolean} the query result */ - const dispatchEvent = (element, event) => element.dispatchEvent(event); + const isHTMLElement = (element) => (element && element.nodeType === 1) || false; - /** @type {Map>>} */ + /** @type {Map>>} */ const componentData = new Map(); /** * An interface for web components background data. @@ -340,27 +365,27 @@ const Data = { /** * Sets web components data. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {string} component the component's name or a unique key * @param {Record} instance the component instance */ - set: (target, component, instance) => { - const element = querySelector(target); - if (!element) return; + set: (element, component, instance) => { + if (!isHTMLElement(element)) return; + /* istanbul ignore else */ if (!componentData.has(component)) { componentData.set(component, new Map()); } const instanceMap = componentData.get(component); - // @ts-ignore - not undefined, but defined right above + // not undefined, but defined right above instanceMap.set(element, instance); }, /** * Returns all instances for specified component. * @param {string} component the component's name or a unique key - * @returns {Map>?} all the component instances + * @returns {Map>?} all the component instances */ getAllFor: (component) => { const instanceMap = componentData.get(component); @@ -370,12 +395,12 @@ /** * Returns the instance associated with the target. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {string} component the component's name or a unique key * @returns {Record?} the instance */ - get: (target, component) => { - const element = querySelector(target); + get: (element, component) => { + if (!isHTMLElement(element) || !component) return null; const allForC = Data.getAllFor(component); const instance = element && allForC && allForC.get(element); @@ -384,16 +409,16 @@ /** * Removes web components data. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {string} component the component's name or a unique key */ - remove: (target, component) => { - const element = querySelector(target); + remove: (element, component) => { const instanceMap = componentData.get(component); - if (!instanceMap || !element) return; + if (!instanceMap || !isHTMLElement(element)) return; instanceMap.delete(element); + /* istanbul ignore else */ if (instanceMap.size === 0) { componentData.delete(component); } @@ -402,22 +427,31 @@ /** * An alias for `Data.get()`. - * @type {SHORTER.getInstance} + * @type {SHORTY.getInstance} */ const getInstance = (target, component) => Data.get(target, component); + /** + * Checks if an object is an `Object`. + * + * @param {any} obj the target object + * @returns {boolean} the query result + */ + const isObject = (obj) => (typeof obj === 'object') || false; + /** * Returns a namespaced `CustomEvent` specific to each component. * @param {string} EventType Event.type * @param {Record=} config Event.options | Event.properties - * @returns {SHORTER.OriginalEvent} a new namespaced event + * @returns {SHORTY.OriginalEvent} a new namespaced event */ function OriginalEvent(EventType, config) { const OriginalCustomEvent = new CustomEvent(EventType, { cancelable: true, bubbles: true, }); - if (config instanceof Object) { + /* istanbul ignore else */ + if (isObject(config)) { ObjectAssign(OriginalCustomEvent, config); } return OriginalCustomEvent; @@ -446,7 +480,7 @@ /** * Shortcut for `HTMLElement.getAttribute()` method. - * @param {HTMLElement | Element} element target element + * @param {HTMLElement} element target element * @param {string} attribute attribute name * @returns {string?} attribute value */ @@ -465,22 +499,24 @@ * @return {niceValue} the normalized value */ function normalizeValue(value) { - if (value === 'true') { // boolean + if (['true', true].includes(value)) { // boolean + // if ('true' === value) { // boolean return true; } - if (value === 'false') { // boolean + if (['false', false].includes(value)) { // boolean + // if ('false' === value) { // boolean return false; } - if (!Number.isNaN(+value)) { // number - return +value; - } - if (value === '' || value === 'null') { // null return null; } + if (value !== '' && !Number.isNaN(+value)) { // number + return +value; + } + // string / function / HTMLElement / object return value; } @@ -503,14 +539,13 @@ /** * Utility to normalize component options. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {Record} defaultOps component default options * @param {Record} inputOps component instance options * @param {string=} ns component namespace * @return {Record} normalized component options object */ function normalizeOptions(element, defaultOps, inputOps, ns) { - // @ts-ignore -- our targets are always `HTMLElement` const data = { ...element.dataset }; /** @type {Record} */ const normalOps = {}; @@ -531,6 +566,7 @@ }); ObjectKeys(defaultOps).forEach((k) => { + /* istanbul ignore else */ if (k in inputOps) { normalOps[k] = inputOps[k]; } else if (k in dataOps) { @@ -545,7 +581,7 @@ return normalOps; } - var version = "4.1.2"; + var version = "4.2.0"; const Version = version; @@ -555,7 +591,7 @@ /** Returns a new `BaseComponent` instance. */ class BaseComponent { /** - * @param {HTMLElement | Element | string} target `Element` or selector string + * @param {HTMLElement | string} target `Element` or selector string * @param {BSN.ComponentOptions=} config component instance options */ constructor(target, config) { @@ -572,10 +608,11 @@ const prevInstance = Data.get(element, self.name); if (prevInstance) prevInstance.dispose(); - /** @type {HTMLElement | Element} */ + /** @type {HTMLElement} */ self.element = element; - if (self.defaults && Object.keys(self.defaults).length) { + /* istanbul ignore else */ + if (self.defaults && ObjectKeys(self.defaults).length) { self.options = normalizeOptions(element, self.defaults, (config || {}), 'bs'); } @@ -583,15 +620,17 @@ } /* eslint-disable */ + /* istanbul ignore next */ /** @static */ get version() { return Version; } - /* eslint-enable */ + /* eslint-enable */ + /* istanbul ignore next */ /** @static */ get name() { return this.constructor.name; } + /* istanbul ignore next */ /** @static */ - // @ts-ignore get defaults() { return this.constructor.defaults; } /** @@ -600,7 +639,6 @@ dispose() { const self = this; Data.remove(self.element, self.name); - // @ts-ignore ObjectKeys(self).forEach((prop) => { self[prop] = null; }); } } @@ -658,6 +696,7 @@ function toggleAlertHandler(self, add) { const action = add ? addListener : removeListener; const { dismiss } = self; + /* istanbul ignore else */ if (dismiss) action(dismiss, mouseclickEvent, self.close); } @@ -665,7 +704,7 @@ // ================ /** Creates a new Alert instance. */ class Alert extends BaseComponent { - /** @param {HTMLElement | Element | string} target element or selector */ + /** @param {HTMLElement | string} target element or selector */ constructor(target) { super(target); // bind @@ -675,7 +714,7 @@ const { element } = self; // the dismiss button - /** @static @type {(HTMLElement | Element)?} */ + /** @static @type {HTMLElement?} */ self.dismiss = querySelector(alertDismissSelector, element); // add event listener @@ -685,7 +724,6 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return alertComponent; } /* eslint-enable */ @@ -701,12 +739,11 @@ * @this {Alert} the `Alert` instance or `EventTarget` */ close(e) { - // @ts-ignore const self = e ? getAlertInstance(closest(this, alertSelector)) : this; - if (!self) return; const { element } = self; - if (hasClass(element, showClass)) { + /* istanbul ignore else */ + if (element && hasClass(element, showClass)) { dispatchEvent(element, closeAlertEvent); if (closeAlertEvent.defaultPrevented) return; @@ -739,7 +776,7 @@ /** * Shortcut for `HTMLElement.setAttribute()` method. - * @param {HTMLElement | Element} element target element + * @param {HTMLElement} element target element * @param {string} attribute attribute name * @param {string} value attribute value * @returns {void} @@ -749,7 +786,7 @@ /** * Add class to `HTMLElement.classList`. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @param {string} classNAME to add * @returns {void} */ @@ -811,7 +848,7 @@ /** Creates a new `Button` instance. */ class Button extends BaseComponent { /** - * @param {HTMLElement | Element | string} target usually a `.btn` element + * @param {HTMLElement | string} target usually a `.btn` element */ constructor(target) { super(target); @@ -832,7 +869,6 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return buttonComponent; } /* eslint-enable */ @@ -845,19 +881,16 @@ */ toggle(e) { if (e) e.preventDefault(); - // @ts-ignore const self = e ? getButtonInstance(this) : this; - if (!self) return; - const { element } = self; + if (!self.element) return; + const { element, isActive } = self; if (hasClass(element, 'disabled')) return; - self.isActive = hasClass(element, activeClass); - const { isActive } = self; const action = isActive ? removeClass : addClass; - action(element, activeClass); setAttribute(element, ariaPressed, isActive ? 'false' : 'true'); + self.isActive = hasClass(element, activeClass); } /** Removes the `Button` component from the target element. */ @@ -891,24 +924,6 @@ */ const keydownEvent = 'keydown'; - /** - * A global namespace for `touchmove` event. - * @type {string} - */ - const touchmoveEvent = 'touchmove'; - - /** - * A global namespace for `touchend` event. - * @type {string} - */ - const touchendEvent = 'touchend'; - - /** - * A global namespace for `touchstart` event. - * @type {string} - */ - const touchstartEvent = 'touchstart'; - /** * A global namespace for `ArrowLeft` key. * @type {string} e.which = 37 equivalent @@ -922,34 +937,31 @@ const keyArrowRight = 'ArrowRight'; /** - * Returns the `Window` object of a target node. - * @see https://github.com/floating-ui/floating-ui - * - * @param {(Node | HTMLElement | Element | Window)=} node target node - * @returns {globalThis} + * A global namespace for `pointerdown` event. + * @type {string} */ - function getWindow(node) { - if (node == null) { - return window; - } + const pointerdownEvent = 'pointerdown'; - if (!(node instanceof Window)) { - const { ownerDocument } = node; - return ownerDocument ? ownerDocument.defaultView || window : window; - } + /** + * A global namespace for `pointermove` event. + * @type {string} + */ + const pointermoveEvent = 'pointermove'; - // @ts-ignore - return node; - } + /** + * A global namespace for `pointerup` event. + * @type {string} + */ + const pointerupEvent = 'pointerup'; /** * Returns the bounding client rect of a target `HTMLElement`. * * @see https://github.com/floating-ui/floating-ui * - * @param {HTMLElement | Element} element event.target + * @param {HTMLElement} element event.target * @param {boolean=} includeScale when *true*, the target scale is also computed - * @returns {SHORTER.BoundingClientRect} the bounding client rect object + * @returns {SHORTY.BoundingClientRect} the bounding client rect object */ function getBoundingClientRect(element, includeScale) { const { @@ -958,10 +970,12 @@ let scaleX = 1; let scaleY = 1; - if (includeScale && element instanceof HTMLElement) { + if (includeScale && isHTMLElement(element)) { const { offsetWidth, offsetHeight } = element; - scaleX = offsetWidth > 0 ? Math.round(width) / offsetWidth || 1 : 1; - scaleY = offsetHeight > 0 ? Math.round(height) / offsetHeight || 1 : 1; + scaleX = offsetWidth > 0 ? Math.round(width) / offsetWidth + : /* istanbul ignore next */1; + scaleY = offsetHeight > 0 ? Math.round(height) / offsetHeight + : /* istanbul ignore next */1; } return { @@ -979,8 +993,8 @@ /** * Returns the `document.documentElement` or the `` element. * - * @param {(Node | HTMLElement | Element | globalThis)=} node - * @returns {HTMLElement | HTMLHtmlElement} + * @param {(Node | Window)=} node + * @returns {HTMLHtmlElement} */ function getDocumentElement(node) { return getDocument(node).documentElement; @@ -990,19 +1004,20 @@ * Utility to determine if an `HTMLElement` * is partially visible in viewport. * - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @return {boolean} the query result */ const isElementInScrollRange = (element) => { + if (!element || !isNode(element)) return false; + const { top, bottom } = getBoundingClientRect(element); const { clientHeight } = getDocumentElement(element); - // checks bottom && top return top <= clientHeight && bottom >= 0; }; /** * Checks if a page is Right To Left. - * @param {(HTMLElement | Element)=} node the target + * @param {HTMLElement=} node the target * @returns {boolean} the query result */ const isRTL = (node) => getDocumentElement(node).dir === 'rtl'; @@ -1011,13 +1026,11 @@ * A shortcut for `(document|Element).querySelectorAll`. * * @param {string} selector the input selector - * @param {(HTMLElement | Element | Document | Node)=} parent optional node to look into - * @return {NodeListOf} the query result + * @param {ParentNode=} parent optional node to look into + * @return {NodeListOf} the query result */ function querySelectorAll(selector, parent) { - const lookUp = parent && parentNodes - .some((x) => parent instanceof x) ? parent : getDocument(); - // @ts-ignore -- `ShadowRoot` is also a node + const lookUp = isNode(parent) ? parent : getDocument(); return lookUp.querySelectorAll(selector); } @@ -1026,16 +1039,15 @@ * like `ShadowRoot` do not support `getElementsByClassName`. * * @param {string} selector the class name - * @param {(HTMLElement | Element | Document)=} parent optional Element to look into - * @return {HTMLCollectionOf} the 'HTMLCollection' + * @param {ParentNode=} parent optional Element to look into + * @return {HTMLCollectionOf} the 'HTMLCollection' */ function getElementsByClassName(selector, parent) { - const lookUp = parent && parentNodes.some((x) => parent instanceof x) - ? parent : getDocument(); + const lookUp = isNode(parent) ? parent : getDocument(); return lookUp.getElementsByClassName(selector); } - /** @type {Map} */ + /** @type {Map} */ const TimeCache = new Map(); /** * An interface for one or more `TimerHandler`s per `Element`. @@ -1044,17 +1056,17 @@ const Timer = { /** * Sets a new timeout timer for an element, or element -> key association. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {ReturnType} callback the callback * @param {number} delay the execution delay * @param {string=} key a unique key */ - set: (target, callback, delay, key) => { - const element = querySelector(target); - - if (!element) return; + set: (element, callback, delay, key) => { + if (!isHTMLElement(element)) return; + /* istanbul ignore else */ if (key && key.length) { + /* istanbul ignore else */ if (!TimeCache.has(element)) { TimeCache.set(element, new Map()); } @@ -1067,38 +1079,35 @@ /** * Returns the timer associated with the target. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {string=} key a unique * @returns {number?} the timer */ - get: (target, key) => { - const element = querySelector(target); - - if (!element) return null; + get: (element, key) => { + if (!isHTMLElement(element)) return null; const keyTimers = TimeCache.get(element); if (key && key.length && keyTimers && keyTimers.get) { - return keyTimers.get(key) || null; + return keyTimers.get(key) || /* istanbul ignore next */null; } return keyTimers || null; }, /** * Clears the element's timer. - * @param {HTMLElement | Element | string} target target element + * @param {HTMLElement} element target element * @param {string=} key a unique key */ - clear: (target, key) => { - const element = querySelector(target); - - if (!element) return; + clear: (element, key) => { + if (!isHTMLElement(element)) return; if (key && key.length) { const keyTimers = TimeCache.get(element); - + /* istanbul ignore else */ if (keyTimers && keyTimers.get) { clearTimeout(keyTimers.get(key)); keyTimers.delete(key); + /* istanbul ignore else */ if (keyTimers.size === 0) { TimeCache.delete(element); } @@ -1113,10 +1122,9 @@ /** * Utility to force re-paint of an `HTMLElement` target. * - * @param {HTMLElement | Element} element is the target + * @param {HTMLElement} element is the target * @return {number} the `Element.offsetHeight` value */ - // @ts-ignore const reflow = (element) => element.offsetHeight; /** @@ -1150,8 +1158,8 @@ * Returns the `Element` that THIS one targets * via `data-bs-target`, `href`, `data-bs-parent` or `data-bs-container`. * - * @param {HTMLElement | Element} element the target element - * @returns {(HTMLElement | Element)?} the query result + * @param {HTMLElement} element the target element + * @returns {HTMLElement?} the query result */ function getTargetElement(element) { const targetAttr = [dataBsTarget, dataBsParent, dataBsContainer, 'href']; @@ -1219,6 +1227,7 @@ } = self; // discontinue disposed instances + /* istanbul ignore else */ if (self.isAnimating && getCarouselInstance(element)) { const activeItem = getActiveIndex(self); const orientation = direction === 'left' ? 'next' : 'prev'; @@ -1243,30 +1252,30 @@ } /** - * Handles the `mouseenter` / `touchstart` events when *options.pause* + * Handles the `mouseenter` events when *options.pause* * is set to `hover`. * - * @this {HTMLElement | Element} + * @this {HTMLElement} */ function carouselPauseHandler() { const element = this; const self = getCarouselInstance(element); - + /* istanbul ignore else */ if (self && !self.isPaused && !Timer.get(element, pausedClass)) { addClass(element, pausedClass); } } /** - * Handles the `mouseleave` / `touchend` events when *options.pause* + * Handles the `mouseleave` events when *options.pause* * is set to `hover`. * - * @this {HTMLElement | Element} + * @this {HTMLElement} */ function carouselResumeHandler() { const element = this; const self = getCarouselInstance(element); - + /* istanbul ignore else */ if (self && self.isPaused && !Timer.get(element, pausedClass)) { self.cycle(); } @@ -1282,12 +1291,10 @@ e.preventDefault(); const indicator = this; const element = closest(indicator, carouselSelector) || getTargetElement(indicator); - if (!element) return; const self = getCarouselInstance(element); if (!self || self.isAnimating) return; - // @ts-ignore const newIndex = +getAttribute(indicator, dataBsSlideTo); if (indicator && !hasClass(indicator, activeClass) // event target is not active @@ -1306,10 +1313,12 @@ e.preventDefault(); const control = this; const element = closest(control, carouselSelector) || getTargetElement(control); - const self = element && getCarouselInstance(element); + const self = getCarouselInstance(element); + if (!self || self.isAnimating) return; const orientation = getAttribute(control, dataBsSlide); + /* istanbul ignore else */ if (orientation === 'next') { self.next(); } else if (orientation === 'prev') { @@ -1322,16 +1331,19 @@ * * @param {KeyboardEvent} e the `Event` object */ - function carouselKeyHandler({ code }) { - const [element] = [...querySelectorAll(carouselSelector)] + function carouselKeyHandler({ code, target }) { + const doc = getDocument(target); + const [element] = [...querySelectorAll(carouselSelector, doc)] .filter((x) => isElementInScrollRange(x)); - const self = getCarouselInstance(element); - if (!self) return; - const RTL = isRTL(); + + /* istanbul ignore next */ + if (!self || self.isAnimating || /textarea|input/i.test(target.tagName)) return; + const RTL = isRTL(element); const arrowKeyNext = !RTL ? keyArrowRight : keyArrowLeft; const arrowKeyPrev = !RTL ? keyArrowLeft : keyArrowRight; + /* istanbul ignore else */ if (code === arrowKeyPrev) self.prev(); else if (code === arrowKeyNext) self.next(); } @@ -1339,80 +1351,95 @@ // CAROUSEL TOUCH HANDLERS // ======================= /** - * Handles the `touchdown` event for the `Carousel` element. + * Handles the `pointerdown` event for the `Carousel` element. * - * @this {HTMLElement | Element} - * @param {TouchEvent} e the `Event` object + * @this {HTMLElement} + * @param {PointerEvent} e the `Event` object */ - function carouselTouchDownHandler(e) { + function carouselPointerDownHandler(e) { const element = this; + const { target } = e; const self = getCarouselInstance(element); - if (!self || self.isTouch) { return; } + // filter pointer event on controls & indicators + const { controls, indicators } = self; + if ([...controls, ...indicators].some((el) => (el === target || el.contains(target)))) { + return; + } + + if (!self || self.isAnimating || self.isTouch) { return; } - startX = e.changedTouches[0].pageX; + startX = e.pageX; - // @ts-ignore - if (element.contains(e.target)) { + /* istanbul ignore else */ + if (element.contains(target)) { self.isTouch = true; toggleCarouselTouchHandlers(self, true); } } /** - * Handles the `touchmove` event for the `Carousel` element. + * Handles the `pointermove` event for the `Carousel` element. * - * @this {HTMLElement | Element} - * @param {TouchEvent} e + * @this {HTMLElement} + * @param {PointerEvent} e */ - function carouselTouchMoveHandler(e) { - const { changedTouches, type } = e; - const self = getCarouselInstance(this); - - if (!self || !self.isTouch) { return; } + function carouselPointerMoveHandler(e) { + // const self = getCarouselInstance(this); - currentX = changedTouches[0].pageX; + // if (!self || !self.isTouch) { return; } - // cancel touch if more than one changedTouches detected - if (type === touchmoveEvent && changedTouches.length > 1) { - e.preventDefault(); - } + currentX = e.pageX; } /** - * Handles the `touchend` event for the `Carousel` element. + * Handles the `pointerup` event for the `Carousel` element. * - * @this {HTMLElement | Element} + * @this {HTMLElement} - * @param {TouchEvent} e + * @param {PointerEvent} e */ - function carouselTouchEndHandler(e) { - const element = this; - const self = getCarouselInstance(element); + function carouselPointerUpHandler(e) { + const { target } = e; + const doc = getDocument(target); + const self = [...querySelectorAll(carouselSelector, doc)] + .map((c) => getCarouselInstance(c)).find((i) => i.isTouch); - if (!self || !self.isTouch) { return; } + // impossible to satisfy + /* istanbul ignore next */ + if (!self) { return; } - endX = currentX || e.changedTouches[0].pageX; + const { element, index } = self; + const RTL = isRTL(target); - if (self.isTouch) { - // the event target is outside the carousel OR carousel doens't include the related target - // @ts-ignore - if ((!element.contains(e.target) || !element.contains(e.relatedTarget)) - && Math.abs(startX - endX) < 75) { // AND swipe distance is less than 75px - // when the above conditions are satisfied, no need to continue - return; - } // OR determine next index to slide to - if (currentX < startX) { - self.index += 1; - } else if (currentX > startX) { - self.index -= 1; - } + self.isTouch = false; + toggleCarouselTouchHandlers(self); - self.isTouch = false; - self.to(self.index); // do the slide + if (doc.getSelection().toString().length) { + // reset pointer position + startX = 0; currentX = 0; endX = 0; + return; + } - toggleCarouselTouchHandlers(self); // remove touch events handlers + endX = e.pageX; + + // the event target is outside the carousel context + // OR swipe distance is less than 120px + /* istanbul ignore else */ + if (!element.contains(target) || Math.abs(startX - endX) < 120) { + // reset pointer position + startX = 0; currentX = 0; endX = 0; + return; } + // OR determine next index to slide to + /* istanbul ignore else */ + if (currentX < startX) { + self.to(index + (RTL ? -1 : 1)); + } else if (currentX > startX) { + self.to(index + (RTL ? 1 : -1)); + } + // reset pointer position + startX = 0; currentX = 0; endX = 0; } // CAROUSEL PRIVATE METHODS @@ -1426,19 +1453,20 @@ const { indicators } = self; [...indicators].forEach((x) => removeClass(x, activeClass)); + /* istanbul ignore else */ if (self.indicators[pageIndex]) addClass(indicators[pageIndex], activeClass); } /** - * Toggles the touch event listeners for a given `Carousel` instance. + * Toggles the pointer event listeners for a given `Carousel` instance. * @param {Carousel} self the `Carousel` instance * @param {boolean=} add when `TRUE` event listeners are added */ function toggleCarouselTouchHandlers(self, add) { const { element } = self; const action = add ? addListener : removeListener; - action(element, touchmoveEvent, carouselTouchMoveHandler, passiveHandler); - action(element, touchendEvent, carouselTouchEndHandler, passiveHandler); + action(getDocument(element), pointermoveEvent, carouselPointerMoveHandler, passiveHandler); + action(getDocument(element), pointerupEvent, carouselPointerUpHandler, passiveHandler); } /** @@ -1458,27 +1486,28 @@ if (pause && interval) { action(element, mouseenterEvent, carouselPauseHandler); action(element, mouseleaveEvent, carouselResumeHandler); - action(element, touchstartEvent, carouselPauseHandler, passiveHandler); - action(element, touchendEvent, carouselResumeHandler, passiveHandler); } - if (touch && slides.length > 1) { - action(element, touchstartEvent, carouselTouchDownHandler, passiveHandler); + if (touch && slides.length > 2) { + action(element, pointerdownEvent, carouselPointerDownHandler, passiveHandler); } + /* istanbul ignore else */ if (controls.length) { controls.forEach((arrow) => { + /* istanbul ignore else */ if (arrow) action(arrow, mouseclickEvent, carouselControlsHandler); }); } + /* istanbul ignore else */ if (indicators.length) { indicators.forEach((indicator) => { action(indicator, mouseclickEvent, carouselIndicatorHandler); }); } - // @ts-ignore - if (keyboard) action(getWindow(element), keydownEvent, carouselKeyHandler); + + if (keyboard) action(getDocument(element), keydownEvent, carouselKeyHandler); } /** @@ -1489,7 +1518,6 @@ function getActiveIndex(self) { const { slides, element } = self; const activeItem = querySelector(`.${carouselItem}.${activeClass}`, element); - // @ts-ignore return [...slides].indexOf(activeItem); } @@ -1498,24 +1526,24 @@ /** Creates a new `Carousel` instance. */ class Carousel extends BaseComponent { /** - * @param {HTMLElement | Element | string} target mostly a `.carousel` element + * @param {HTMLElement | string} target mostly a `.carousel` element * @param {BSN.Options.Carousel=} config instance options */ constructor(target, config) { super(target, config); // bind const self = this; + // initialization element + const { element } = self; // additional properties /** @type {string} */ - self.direction = isRTL() ? 'right' : 'left'; + self.direction = isRTL(element) ? 'right' : 'left'; /** @type {number} */ self.index = 0; /** @type {boolean} */ self.isTouch = false; - // initialization element - const { element } = self; // carousel elements // a LIVE collection is prefferable self.slides = getElementsByClassName(carouselItem, element); @@ -1524,20 +1552,22 @@ // invalidate when not enough items // no need to go further if (slides.length < 2) { return; } + // external controls must be within same document context + const doc = getDocument(element); self.controls = [ ...querySelectorAll(`[${dataBsSlide}]`, element), - ...querySelectorAll(`[${dataBsSlide}][${dataBsTarget}="#${element.id}"]`), + ...querySelectorAll(`[${dataBsSlide}][${dataBsTarget}="#${element.id}"]`, doc), ]; - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.indicator = querySelector(`.${carouselString}-indicators`, element); // a LIVE collection is prefferable - /** @type {(HTMLElement | Element)[]} */ + /** @type {HTMLElement[]} */ self.indicators = [ ...(self.indicator ? querySelectorAll(`[${dataBsSlideTo}]`, self.indicator) : []), - ...querySelectorAll(`[${dataBsSlideTo}][${dataBsTarget}="#${element.id}"]`), + ...querySelectorAll(`[${dataBsSlideTo}][${dataBsTarget}="#${element.id}"]`, doc), ]; // set JavaScript and DATA API options @@ -1549,8 +1579,10 @@ : options.interval; // set first slide active if none + /* istanbul ignore else */ if (getActiveIndex(self) < 0) { - if (slides.length) addClass(slides[0], activeClass); + addClass(slides[0], activeClass); + /* istanbul ignore else */ if (self.indicators.length) activateCarouselIndicator(self, 0); } @@ -1564,12 +1596,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return carouselComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return carouselDefaults; } /* eslint-enable */ @@ -1595,7 +1625,9 @@ /** Slide automatically through items. */ cycle() { const self = this; - const { element, options, isPaused } = self; + const { + element, options, isPaused, index, + } = self; Timer.clear(element, carouselString); if (isPaused) { @@ -1604,9 +1636,12 @@ } Timer.set(element, () => { - if (!self.isPaused && isElementInScrollRange(element)) { - self.index += 1; - self.to(self.index); + // it's very important to check self.element + // where instance might have been disposed + /* istanbul ignore else */ + if (self.element && !self.isPaused && !self.isTouch + && isElementInScrollRange(element)) { + self.to(index + 1); } }, options.interval, carouselString); } @@ -1615,6 +1650,7 @@ pause() { const self = this; const { element, options } = self; + /* istanbul ignore else */ if (!self.isPaused && options.interval) { addClass(element, pausedClass); Timer.set(element, () => {}, 1, pausedClass); @@ -1624,13 +1660,15 @@ /** Slide to the next item. */ next() { const self = this; - if (!self.isAnimating) { self.index += 1; self.to(self.index); } + /* istanbul ignore else */ + if (!self.isAnimating) { self.to(self.index + 1); } } /** Slide to the previous item. */ prev() { const self = this; - if (!self.isAnimating) { self.index -= 1; self.to(self.index); } + /* istanbul ignore else */ + if (!self.isAnimating) { self.to(self.index - 1); } } /** @@ -1643,14 +1681,16 @@ element, slides, options, } = self; const activeItem = getActiveIndex(self); - const RTL = isRTL(); + const RTL = isRTL(element); let next = idx; // when controled via methods, make sure to check again // first return if we're on the same item #227 - if (self.isAnimating || activeItem === next) return; + // `to()` must be SPAM protected by Timer + if (self.isAnimating || activeItem === next || Timer.get(element, dataBsSlide)) return; // determine transition direction + /* istanbul ignore else */ if ((activeItem < next) || (activeItem === 0 && next === slides.length - 1)) { self.direction = RTL ? 'right' : 'left'; // next } else if ((activeItem > next) || (activeItem === slides.length - 1 && next === 0)) { @@ -1692,7 +1732,7 @@ addClass(slides[activeItem], `${carouselItem}-${directionClass}`); emulateTransitionEnd(slides[next], () => carouselTransitionEndHandler(self)); - }, 17, dataBsSlide); + }, 0, dataBsSlide); } else { addClass(slides[next], activeClass); removeClass(slides[activeItem], activeClass); @@ -1700,12 +1740,13 @@ Timer.set(element, () => { Timer.clear(element, dataBsSlide); // check for element, might have been disposed + /* istanbul ignore else */ if (element && options.interval && !self.isPaused) { self.cycle(); } dispatchEvent(element, carouselSlidEvent); - }, 17, dataBsSlide); + }, 0, dataBsSlide); } } @@ -1737,6 +1778,29 @@ */ const ariaExpanded = 'aria-expanded'; + /** + * Shortcut for `Object.entries()` static method. + * @param {Record} obj a target object + * @returns {[string, any][]} + */ + const ObjectEntries = (obj) => Object.entries(obj); + + /** + * Shortcut for multiple uses of `HTMLElement.style.propertyName` method. + * @param {HTMLElement} element target element + * @param {Partial} styles attribute value + */ + const setElementStyle = (element, styles) => { + ObjectEntries(styles).forEach(([key, value]) => { + if (key.includes('--')) { + element.style.setProperty(key, value); + } else { + const propObject = {}; propObject[key] = value; + ObjectAssign(element.style, propObject); + } + }); + }; + /** * Global namespace for most components `collapsing` class. * As used by `Collapse` / `Tab`. @@ -1799,8 +1863,7 @@ addClass(element, collapsingClass); removeClass(element, collapseString); - // @ts-ignore - element.style.height = `${element.scrollHeight}px`; + setElementStyle(element, { height: `${element.scrollHeight}px` }); emulateTransitionEnd(element, () => { Timer.clear(element); @@ -1812,8 +1875,7 @@ addClass(element, collapseString); addClass(element, showClass); - // @ts-ignore - element.style.height = ''; + setElementStyle(element, { height: '' }); dispatchEvent(element, shownCollapseEvent); }); @@ -1825,7 +1887,6 @@ */ function collapseContent(self) { const { - // @ts-ignore element, parent, triggers, } = self; @@ -1836,19 +1897,18 @@ Timer.set(element, () => {}, 17); if (parent) Timer.set(parent, () => {}, 17); - // @ts-ignore - element.style.height = `${element.scrollHeight}px`; + setElementStyle(element, { height: `${element.scrollHeight}px` }); removeClass(element, collapseString); removeClass(element, showClass); addClass(element, collapsingClass); reflow(element); - // @ts-ignore - element.style.height = '0px'; + setElementStyle(element, { height: '0px' }); emulateTransitionEnd(element, () => { Timer.clear(element); + /* istanbul ignore else */ if (parent) Timer.clear(parent); triggers.forEach((btn) => setAttribute(btn, ariaExpanded, 'false')); @@ -1856,8 +1916,7 @@ removeClass(element, collapsingClass); addClass(element, collapseString); - // @ts-ignore - element.style.height = ''; + setElementStyle(element, { height: '' }); dispatchEvent(element, hiddenCollapseEvent); }); @@ -1872,6 +1931,7 @@ const action = add ? addListener : removeListener; const { triggers } = self; + /* istanbul ignore else */ if (triggers.length) { triggers.forEach((btn) => action(btn, mouseclickEvent, collapseClickHandler)); } @@ -1884,10 +1944,11 @@ * @param {MouseEvent} e the `Event` object */ function collapseClickHandler(e) { - const { target } = e; // @ts-ignore - our target is `HTMLElement` + const { target } = e; // our target is `HTMLElement` const trigger = target && closest(target, collapseToggleSelector); const element = trigger && getTargetElement(trigger); const self = element && getCollapseInstance(element); + /* istanbul ignore else */ if (self) self.toggle(); // event target is anchor link #398 @@ -1900,7 +1961,7 @@ /** Returns a new `Colapse` instance. */ class Collapse extends BaseComponent { /** - * @param {HTMLElement | Element | string} target and `Element` that matches the selector + * @param {HTMLElement | string} target and `Element` that matches the selector * @param {BSN.Options.Collapse=} config instance options */ constructor(target, config) { @@ -1910,15 +1971,17 @@ // initialization element const { element, options } = self; + const doc = getDocument(element); // set triggering elements - /** @type {(HTMLElement | Element)[]} */ - self.triggers = [...querySelectorAll(collapseToggleSelector)] + /** @type {HTMLElement[]} */ + self.triggers = [...querySelectorAll(collapseToggleSelector, doc)] .filter((btn) => getTargetElement(btn) === element); // set parent accordion - /** @type {(HTMLElement | Element)?} */ - self.parent = querySelector(options.parent); + /** @type {HTMLElement?} */ + self.parent = querySelector(options.parent, doc) + || getTargetElement(element) || null; // add event listeners toggleCollapseHandler(self, true); @@ -1927,12 +1990,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return collapseComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return collapseDefaults; } /* eslint-enable */ @@ -1953,6 +2014,7 @@ if (Timer.get(element)) return; collapseContent(self); + /* istanbul ignore else */ if (triggers.length) { triggers.forEach((btn) => addClass(btn, `${collapseString}d`)); } @@ -1973,7 +2035,7 @@ activeCollapseInstance = activeCollapse && getCollapseInstance(activeCollapse); } - if ((!parent || (parent && !Timer.get(parent))) && !Timer.get(element)) { + if ((!parent || !Timer.get(parent)) && !Timer.get(element)) { if (activeCollapseInstance && activeCollapse !== element) { collapseContent(activeCollapseInstance); activeCollapseInstance.triggers.forEach((btn) => { @@ -1982,6 +2044,7 @@ } expandCollapse(self); + /* istanbul ignore else */ if (triggers.length) { triggers.forEach((btn) => removeClass(btn, `${collapseString}d`)); } @@ -2047,27 +2110,36 @@ /** * Shortcut for `HTMLElement.hasAttribute()` method. - * @param {HTMLElement | Element} element target element + * @param {HTMLElement} element target element * @param {string} attribute attribute name * @returns {boolean} the query result */ const hasAttribute = (element, attribute) => element.hasAttribute(attribute); /** - * Shortcut for multiple uses of `HTMLElement.style.propertyName` method. - * @param {HTMLElement | Element} element target element - * @param {Partial} styles attribute value + * Utility to focus an `HTMLElement` target. + * + * @param {HTMLElement} element is the target */ - // @ts-ignore - const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); }; + const focus = (element) => element.focus(); /** - * Utility to focus an `HTMLElement` target. + * Returns the `Window` object of a target node. + * @see https://github.com/floating-ui/floating-ui * - * @param {HTMLElement | Element} element is the target + * @param {(Node | Window)=} node target node + * @returns {Window} the `Window` object */ - // @ts-ignore -- `Element`s resulted from querySelector can focus too - const focus = (element) => element.focus(); + function getWindow(node) { + // node is undefined | NULL + if (!node) return window; + // node instanceof Document + if (isDocument(node)) return node.defaultView; + // node instanceof Node + if (isNode(node)) return node.ownerDocument.defaultView; + // node is instanceof Window + return node; + } /** * Global namespace for `Dropdown` types / classes. @@ -2086,16 +2158,18 @@ * Checks if an *event.target* or its parent has an `href="#"` value. * We need to prevent jumping around onclick, don't we? * - * @param {HTMLElement | HTMLAnchorElement | EventTarget} element the target element + * @param {Node} element the target element * @returns {boolean} the query result */ function isEmptyAnchor(element) { - // @ts-ignore -- `EventTarget` must be `HTMLElement` + // `EventTarget` must be `HTMLElement` const parentAnchor = closest(element, 'A'); - // @ts-ignore -- anchor href starts with # - return element && ((hasAttribute(element, 'href') && element.href.slice(-1) === '#') - // @ts-ignore -- OR a child of an anchor with href starts with # - || (parentAnchor && hasAttribute(parentAnchor, 'href') && parentAnchor.href.slice(-1) === '#')); + return isHTMLElement(element) + // anchor href starts with # + && ((hasAttribute(element, 'href') && element.href.slice(-1) === '#') + // OR a child of an anchor with href starts with # + || (parentAnchor && hasAttribute(parentAnchor, 'href') + && parentAnchor.href.slice(-1) === '#')); } /* Native JavaScript for Bootstrap 5 | Dropdown @@ -2127,6 +2201,7 @@ // DROPDOWN PRIVATE GC // =================== + // const dropdownMenuStartClass = `${dropdownMenuClass}-start`; const dropdownMenuEndClass = `${dropdownMenuClass}-end`; const verticalClass = [dropdownString, dropupString]; const horizontalClass = [dropstartString, dropendString]; @@ -2159,19 +2234,22 @@ const { offset } = options; // don't apply any style on mobile view + /* istanbul ignore next: this test requires a navbar */ if (getElementStyle(menu, 'position') === 'static') return; const RTL = isRTL(element); - const menuEnd = hasClass(parentElement, dropdownMenuEndClass); + // const menuStart = hasClass(menu, dropdownMenuStartClass); + const menuEnd = hasClass(menu, dropdownMenuEndClass); // reset menu offset and position const resetProps = ['margin', 'top', 'bottom', 'left', 'right']; - // @ts-ignore resetProps.forEach((p) => { menu.style[p] = ''; }); // set initial position class // take into account .btn-group parent as .dropdown - let positionClass = dropdownMenuClasses.find((c) => hasClass(parentElement, c)) || dropdownString; + // this requires navbar/btn-group/input-group + let positionClass = dropdownMenuClasses.find((c) => hasClass(parentElement, c)) + || /* istanbul ignore next: fallback position */ dropdownString; /** @type {Record>} */ let dropdownMargin = { @@ -2187,10 +2265,10 @@ dropup: { top: 'auto', bottom: '100%' }, dropstart: RTL ? { left: '100%', right: 'auto' } : { left: 'auto', right: '100%' }, dropend: RTL ? { left: 'auto', right: '100%' } : { left: '100%', right: 'auto' }, + menuStart: RTL ? { right: 0, left: 'auto' } : { right: 'auto', left: 0 }, menuEnd: RTL ? { right: 'auto', left: 0 } : { right: 0, left: 'auto' }, }; - // @ts-ignore const { offsetWidth: menuWidth, offsetHeight: menuHeight } = menu; const { clientWidth, clientHeight } = getDocumentElement(element); @@ -2232,41 +2310,47 @@ if (positionClass === dropdownString && bottomFullExceed && !topExceed) { positionClass = dropupString; } + // override position for horizontal classes if (horizontalClass.includes(positionClass) && bottomExceed) { ObjectAssign(dropdownPosition[positionClass], { top: 'auto', bottom: 0, }); } + // override position for vertical classes if (verticalClass.includes(positionClass) && (leftExceed || rightExceed)) { // don't realign when menu is wider than window // in both RTL and non-RTL readability is KING - if (targetLeft + targetWidth + Math.abs(menuWidth - targetWidth) + offset < clientWidth) { - ObjectAssign(dropdownPosition[positionClass], - leftExceed ? { left: 0, right: 'auto' } : { left: 'auto', right: 0 }); - } + let posAjust; + if (!leftExceed && rightExceed && !RTL) posAjust = { left: 'auto', right: 0 }; + if (leftExceed && !rightExceed && RTL) posAjust = { left: 0, right: 'auto' }; + if (posAjust) ObjectAssign(dropdownPosition[positionClass], posAjust); } dropdownMargin = dropdownMargin[positionClass]; - // @ts-ignore - menu.style.margin = `${dropdownMargin.map((x) => (x ? `${x}px` : x)).join(' ')}`; - - setElementStyle(menu, dropdownPosition[positionClass]); + setElementStyle(menu, { + ...dropdownPosition[positionClass], + margin: `${dropdownMargin.map((x) => (x ? `${x}px` : x)).join(' ')}`, + }); - // update dropdown-menu-end - if (hasClass(menu, dropdownMenuEndClass)) { - setElementStyle(menu, dropdownPosition.menuEnd); + // override dropdown-menu-start | dropdown-menu-end + if (verticalClass.includes(positionClass) && menuEnd) { + /* istanbul ignore else */ + if (menuEnd) { + const endAdjust = (!RTL && leftExceed) || (RTL && rightExceed) + ? 'menuStart' : /* istanbul ignore next */'menuEnd'; + setElementStyle(menu, dropdownPosition[endAdjust]); + } } } /** * Returns an `Array` of focusable items in the given dropdown-menu. - * @param {HTMLElement | Element} menu - * @returns {(HTMLElement | Element)[]} + * @param {HTMLElement} menu + * @returns {HTMLElement[]} */ function getMenuItems(menu) { - // @ts-ignore return [...menu.children].map((c) => { if (c && menuFocusTags.includes(c.tagName)) return c; const { firstElementChild } = c; @@ -2284,7 +2368,7 @@ * @param {Dropdown} self the `Dropdown` instance */ function toggleDropdownDismiss(self) { - const { element } = self; + const { element, options } = self; const action = self.open ? addListener : removeListener; const doc = getDocument(element); @@ -2293,9 +2377,9 @@ action(doc, keydownEvent, dropdownPreventScroll); action(doc, keyupEvent, dropdownKeyHandler); - if (self.options.display === 'dynamic') { + /* istanbul ignore else */ + if (options.display === 'dynamic') { [scrollEvent, resizeEvent].forEach((ev) => { - // @ts-ignore action(getWindow(element), ev, dropdownLayoutHandler, passiveHandler); }); } @@ -2315,16 +2399,15 @@ /** * Returns the currently open `.dropdown` element. * - * @param {(Document | HTMLElement | Element | globalThis)=} element target + * @param {(Node | Window)=} element target * @returns {HTMLElement?} the query result */ function getCurrentOpenDropdown(element) { const currentParent = [...dropdownMenuClasses, 'btn-group', 'input-group'] - .map((c) => getElementsByClassName(`${c} ${showClass}`), getDocument(element)) + .map((c) => getElementsByClassName(`${c} ${showClass}`, getDocument(element))) .find((x) => x.length); if (currentParent && currentParent.length) { - // @ts-ignore -- HTMLElement is also Element return [...currentParent[0].children] .find((x) => hasAttribute(x, dataBsToggle)); } @@ -2341,34 +2424,31 @@ */ function dropdownDismissHandler(e) { const { target, type } = e; - // @ts-ignore + + /* istanbul ignore next: impossible to satisfy */ if (!target || !target.closest) return; // some weird FF bug #409 - // @ts-ignore const element = getCurrentOpenDropdown(target); - if (!element) return; - const self = getDropdownInstance(element); + + /* istanbul ignore next */ if (!self) return; const { parentElement, menu } = self; - // @ts-ignore const hasData = closest(target, dropdownSelector) !== null; - // @ts-ignore const isForm = parentElement && parentElement.contains(target) - // @ts-ignore && (target.tagName === 'form' || closest(target, 'form') !== null); - // @ts-ignore if (type === mouseclickEvent && isEmptyAnchor(target)) { e.preventDefault(); } - if (type === focusEvent // @ts-ignore + if (type === focusEvent && (target === element || target === menu || menu.contains(target))) { return; } + /* istanbul ignore else */ if (isForm || hasData) ; else if (self) { self.hide(); } @@ -2376,7 +2456,7 @@ /** * Handles `click` event listener for `Dropdown`. - * @this {HTMLElement | Element} + * @this {HTMLElement} * @param {MouseEvent} e event object */ function dropdownClickHandler(e) { @@ -2384,8 +2464,10 @@ const { target } = e; const self = getDropdownInstance(element); + /* istanbul ignore else */ if (self) { self.toggle(); + /* istanbul ignore else */ if (target && isEmptyAnchor(target)) e.preventDefault(); } } @@ -2395,6 +2477,7 @@ * @param {KeyboardEvent} e event object */ function dropdownPreventScroll(e) { + /* istanbul ignore else */ if ([keyArrowDown, keyArrowUp].includes(e.code)) e.preventDefault(); } @@ -2407,21 +2490,24 @@ const { code } = e; const element = getCurrentOpenDropdown(this); const self = element && getDropdownInstance(element); - const activeItem = element && getDocument(element).activeElement; - if (!self || !activeItem) return; + const { activeElement } = element && getDocument(element); + /* istanbul ignore next: impossible to satisfy */ + if (!self || !activeElement) return; const { menu, open } = self; const menuItems = getMenuItems(menu); // arrow up & down if (menuItems && menuItems.length && [keyArrowDown, keyArrowUp].includes(code)) { - let idx = menuItems.indexOf(activeItem); - if (activeItem === element) { + let idx = menuItems.indexOf(activeElement); + /* istanbul ignore else */ + if (activeElement === element) { idx = 0; } else if (code === keyArrowUp) { idx = idx > 1 ? idx - 1 : 0; } else if (code === keyArrowDown) { idx = idx < menuItems.length - 1 ? idx + 1 : idx; } + /* istanbul ignore else */ if (menuItems[idx]) focus(menuItems[idx]); } @@ -2439,6 +2525,7 @@ const element = getCurrentOpenDropdown(this); const self = element && getDropdownInstance(element); + /* istanbul ignore else */ if (self && self.open) styleDropdown(self); } @@ -2447,7 +2534,7 @@ /** Returns a new Dropdown instance. */ class Dropdown extends BaseComponent { /** - * @param {HTMLElement | Element | string} target Element or string selector + * @param {HTMLElement | string} target Element or string selector * @param {BSN.Options.Dropdown=} config the instance options */ constructor(target, config) { @@ -2461,10 +2548,8 @@ // set targets /** @type {(Element | HTMLElement)} */ - // @ts-ignore self.parentElement = parentElement; /** @type {(Element | HTMLElement)} */ - // @ts-ignore self.menu = querySelector(`.${dropdownMenuClass}`, parentElement); // set initial state to closed @@ -2478,12 +2563,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return dropdownComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return dropdownDefaults; } /* eslint-enable */ @@ -2505,12 +2588,17 @@ element, open, menu, parentElement, } = self; + /* istanbul ignore next */ + if (open) return; + const currentElement = getCurrentOpenDropdown(element); const currentInstance = currentElement && getDropdownInstance(currentElement); if (currentInstance) currentInstance.hide(); - // dispatch - [showDropdownEvent, shownDropdownEvent].forEach((e) => { e.relatedTarget = element; }); + // dispatch event + [showDropdownEvent, shownDropdownEvent].forEach((e) => { + e.relatedTarget = element; + }); dispatchEvent(parentElement, showDropdownEvent); if (showDropdownEvent.defaultPrevented) return; @@ -2523,11 +2611,9 @@ self.open = !open; - setTimeout(() => { - focus(element); // focus the element - toggleDropdownDismiss(self); - dispatchEvent(parentElement, shownDropdownEvent); - }, 1); + focus(element); // focus the element + toggleDropdownDismiss(self); + dispatchEvent(parentElement, shownDropdownEvent); } /** Hides the dropdown menu from the user. */ @@ -2536,8 +2622,13 @@ const { element, open, menu, parentElement, } = self; - [hideDropdownEvent, hiddenDropdownEvent].forEach((e) => { e.relatedTarget = element; }); + /* istanbul ignore next */ + if (!open) return; + + [hideDropdownEvent, hiddenDropdownEvent].forEach((e) => { + e.relatedTarget = element; + }); dispatchEvent(parentElement, hideDropdownEvent); if (hideDropdownEvent.defaultPrevented) return; @@ -2546,19 +2637,15 @@ setAttribute(element, ariaExpanded, 'false'); self.open = !open; - // only re-attach handler if the instance is not disposed - setTimeout(() => toggleDropdownDismiss(self), 1); - + toggleDropdownDismiss(self); dispatchEvent(parentElement, hiddenDropdownEvent); } /** Removes the `Dropdown` component from the target element. */ dispose() { const self = this; - const { parentElement } = self; - - if (hasClass(parentElement, showClass) && self.open) self.hide(); + if (self.open) self.hide(); toggleDropdownHandler(self); @@ -2586,7 +2673,7 @@ /** * Shortcut for `HTMLElement.removeAttribute()` method. - * @param {HTMLElement | Element} element target element + * @param {HTMLElement} element target element * @param {string} attribute attribute name * @returns {void} */ @@ -2595,8 +2682,8 @@ /** * Returns the `document.body` or the `` element. * - * @param {(Node | HTMLElement | Element | globalThis)=} node - * @returns {HTMLElement | HTMLBodyElement} + * @param {(Node | Window)=} node + * @returns {HTMLBodyElement} */ function getDocumentBody(node) { return getDocument(node).body; @@ -2614,17 +2701,15 @@ * @param {any} element target * @returns {boolean} the query result */ - const isShadowRoot = (element) => { - const OwnElement = getWindow(element).ShadowRoot; - return element instanceof OwnElement || element instanceof ShadowRoot; - }; + const isShadowRoot = (element) => (element && element.constructor.name === 'ShadowRoot') + || false; /** * Returns the `parentNode` also going through `ShadowRoot`. * @see https://github.com/floating-ui/floating-ui * - * @param {Node | HTMLElement | Element} node the target node - * @returns {Node | HTMLElement | Element} the apropriate parent node + * @param {Node} node the target node + * @returns {Node} the apropriate parent node */ function getParentNode(node) { if (node.nodeName === 'HTML') { @@ -2633,28 +2718,23 @@ // this is a quicker (but less type safe) way to save quite some bytes from the bundle return ( - // @ts-ignore node.assignedSlot // step into the shadow DOM of the parent of a slotted node - || node.parentNode // @ts-ignore DOM Element detected - || (isShadowRoot(node) ? node.host : null) // ShadowRoot detected + || node.parentNode // DOM Element detected + || (isShadowRoot(node) && node.host) // ShadowRoot detected || getDocumentElement(node) // fallback ); } /** * Check if a target element is a ``, `
` or ``. - * @param {any} element the target element - * @returns {boolean} the query result - */ - const isTableElement = (element) => ['TABLE', 'TD', 'TH'].includes(element.tagName); - - /** - * Checks if an element is an `HTMLElement`. + * This specific check is important for determining + * the `offsetParent` of a given element. * - * @param {any} element the target object + * @param {any} element the target element * @returns {boolean} the query result */ - const isHTMLElement = (element) => element instanceof HTMLElement; + const isTableElement = (element) => (element && ['TABLE', 'TD', 'TH'].includes(element.tagName)) + || false; /** * Returns an `HTMLElement` to be used as default value for *options.container* @@ -2664,9 +2744,9 @@ * offsets computation similar to **floating-ui**. * @see https://github.com/floating-ui/floating-ui * - * @param {HTMLElement | Element} element the target + * @param {HTMLElement} element the target * @param {boolean=} getOffset when *true* it will return an `offsetParent` - * @returns {HTMLElement | HTMLBodyElement | Window | globalThis} the query result + * @returns {ParentNode | Window} the query result */ function getElementContainer(element, getOffset) { const majorBlockTags = ['HTML', 'BODY']; @@ -2675,7 +2755,6 @@ /** @type {any} */ let { offsetParent } = element; const win = getWindow(element); - // const { innerWidth } = getDocumentElement(element); while (offsetParent && (isTableElement(offsetParent) || (isHTMLElement(offsetParent) @@ -2684,21 +2763,21 @@ offsetParent = offsetParent.offsetParent; } - if (!offsetParent || (offsetParent - && (majorBlockTags.includes(offsetParent.tagName) - || getElementStyle(offsetParent, 'position') === 'static'))) { + if (!offsetParent || (majorBlockTags.includes(offsetParent.tagName) + || getElementStyle(offsetParent, 'position') === 'static')) { offsetParent = win; } return offsetParent; } - /** @type {(HTMLElement)[]} */ + /** @type {ParentNode[]} */ const containers = []; - /** @type {any} */ + /** @type {ParentNode} */ let { parentNode } = element; while (parentNode && !majorBlockTags.includes(parentNode.nodeName)) { parentNode = getParentNode(parentNode); + /* istanbul ignore else */ if (!(isShadowRoot(parentNode) || !!parentNode.shadowRoot || isTableElement(parentNode))) { containers.push(parentNode); @@ -2734,7 +2813,7 @@ */ const positionStickyClass = 'position-sticky'; - /** @param {(HTMLElement | Element | Document)=} parent */ + /** @param {(HTMLElement | Document)=} parent */ const getFixedItems = (parent) => [ ...getElementsByClassName(fixedTopClass, parent), ...getElementsByClassName(fixedBottomClass, parent), @@ -2746,7 +2825,7 @@ /** * Removes *padding* and *overflow* from the `` * and all spacing from fixed items. - * @param {(HTMLElement | Element)=} element the target modal/offcanvas + * @param {HTMLElement=} element the target modal/offcanvas */ function resetScrollbar(element) { const bd = getDocumentBody(element); @@ -2770,7 +2849,7 @@ /** * Returns the scrollbar width if the body does overflow * the window. - * @param {(HTMLElement | Element)=} element + * @param {HTMLElement=} element * @returns {number} the value */ function measureScrollbar(element) { @@ -2783,7 +2862,7 @@ * Sets the `` and fixed items style when modal / offcanvas * is shown to the user. * - * @param {HTMLElement | Element} element the target modal/offcanvas + * @param {HTMLElement} element the target modal/offcanvas * @param {boolean=} overflow body does overflow or not */ function setScrollbar(element, overflow) { @@ -2793,20 +2872,21 @@ const sbWidth = isOpen && bodyPad ? 0 : measureScrollbar(element); const fixedItems = getFixedItems(bd); + /* istanbul ignore else */ if (overflow) { setElementStyle(bd, { overflow: 'hidden', paddingRight: `${bodyPad + sbWidth}px`, }); + /* istanbul ignore else */ if (fixedItems.length) { fixedItems.forEach((fixed) => { const itemPadValue = getElementStyle(fixed, 'paddingRight'); - // @ts-ignore fixed.style.paddingRight = `${parseInt(itemPadValue, 10) + sbWidth}px`; + /* istanbul ignore else */ if ([stickyTopClass, positionStickyClass].some((c) => hasClass(fixed, c))) { const itemMValue = getElementStyle(fixed, 'marginRight'); - // @ts-ignore fixed.style.marginRight = `${parseInt(itemMValue, 10) - sbWidth}px`; } }); @@ -2822,9 +2902,11 @@ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement * * @param {Record | string} param `tagName` or object - * @return {HTMLElement | Element} a new `HTMLElement` or `Element` + * @return {HTMLElement} a new `HTMLElement` or `Element` */ function createElement(param) { + if (!param) return null; + if (typeof param === 'string') { return getDocument().createElement(param); } @@ -2851,8 +2933,8 @@ /** * Returns the current active modal / offcancas element. - * @param {(HTMLElement | Element)=} element the context element - * @returns {(HTMLElement | Element)?} the requested element + * @param {HTMLElement=} element the context element + * @returns {HTMLElement?} the requested element */ function getCurrentOpen(element) { return querySelector(`${modalActiveSelector},${offcanvasActiveSelector}`, getDocument(element)); @@ -2872,7 +2954,7 @@ /** * Append the overlay to DOM. - * @param {HTMLElement | Element} container + * @param {HTMLElement} container * @param {boolean} hasFade * @param {boolean=} isModal */ @@ -2901,7 +2983,7 @@ /** * Removes the overlay from DOM. - * @param {(HTMLElement | Element)=} element + * @param {HTMLElement=} element */ function removeOverlay(element) { if (!getCurrentOpen(element)) { @@ -2912,12 +2994,12 @@ } /** - * @param {HTMLElement | Element} element target + * @param {HTMLElement} element target * @returns {boolean} */ function isVisible(element) { - return element && getElementStyle(element, 'visibility') !== 'hidden' - // @ts-ignore + return isHTMLElement(element) + && getElementStyle(element, 'visibility') !== 'hidden' && element.offsetParent !== null; } @@ -2972,10 +3054,12 @@ const { clientHeight: modalHeight, scrollHeight: modalScrollHeight } = element; const modalOverflow = modalHeight !== modalScrollHeight; + /* istanbul ignore else */ if (!modalOverflow && scrollbarWidth) { - const pad = isRTL(element) ? 'paddingLeft' : 'paddingRight'; - // @ts-ignore -- cannot use `setElementStyle` - element.style[pad] = `${scrollbarWidth}px`; + const pad = !isRTL(element) ? 'paddingRight' : /* istanbul ignore next */'paddingLeft'; + const padStyle = {}; + padStyle[pad] = `${scrollbarWidth}px`; + setElementStyle(element, padStyle); } setScrollbar(element, (modalOverflow || clientHeight !== scrollHeight)); } @@ -2990,7 +3074,6 @@ const action = add ? addListener : removeListener; const { element } = self; action(element, mouseclickEvent, modalDismissHandler); - // @ts-ignore action(getWindow(element), resizeEvent, self.update, passiveHandler); action(getDocument(element), keydownEvent, modalKeyHandler); } @@ -3004,6 +3087,7 @@ const action = add ? addListener : removeListener; const { triggers } = self; + /* istanbul ignore else */ if (triggers.length) { triggers.forEach((btn) => action(btn, mouseclickEvent, modalClickHandler)); } @@ -3012,16 +3096,21 @@ /** * Executes after a modal is hidden to the user. * @param {Modal} self the `Modal` instance + * @param {Function} callback the `Modal` instance */ - function afterModalHide(self) { + function afterModalHide(self, callback) { const { triggers, element, relatedTarget } = self; removeOverlay(element); - setElementStyle(element, { paddingRight: '' }); + setElementStyle(element, { paddingRight: '', display: '' }); toggleModalDismiss(self); const focusElement = showModalEvent.relatedTarget || triggers.find(isVisible); + /* istanbul ignore else */ if (focusElement) focus(focusElement); + /* istanbul ignore else */ + if (callback) callback(); + hiddenModalEvent.relatedTarget = relatedTarget; dispatchEvent(element, hiddenModalEvent); } @@ -3048,6 +3137,7 @@ setElementStyle(element, { display: 'block' }); setModalScrollbar(self); + /* istanbul ignore else */ if (!getCurrentOpen(element)) { setElementStyle(getDocumentBody(element), { overflow: 'hidden' }); } @@ -3063,23 +3153,21 @@ /** * Executes before a modal is hidden to the user. * @param {Modal} self the `Modal` instance - * @param {boolean=} force when `true` skip animation + * @param {Function=} callback when `true` skip animation */ - function beforeModalHide(self, force) { + function beforeModalHide(self, callback) { const { element, options, hasFade, } = self; - setElementStyle(element, { display: '' }); - - // force can also be the transitionEvent object, we wanna make sure it's not + // callback can also be the transitionEvent object, we wanna make sure it's not // call is not forced and overlay is visible - if (options.backdrop && !force && hasFade && hasClass(overlay, showClass) + if (options.backdrop && !callback && hasFade && hasClass(overlay, showClass) && !getCurrentOpen(element)) { // AND no modal is visible hideOverlay(); emulateTransitionEnd(overlay, () => afterModalHide(self)); } else { - afterModalHide(self); + afterModalHide(self, callback); } } @@ -3088,17 +3176,15 @@ /** * Handles the `click` event listener for modal. * @param {MouseEvent} e the `Event` object - * @this {HTMLElement | Element} */ function modalClickHandler(e) { const { target } = e; - const trigger = target && closest(this, modalToggleSelector); + const trigger = target && closest(target, modalToggleSelector); const element = trigger && getTargetElement(trigger); const self = element && getModalInstance(element); - if (!self) return; - + /* istanbul ignore else */ if (trigger && trigger.tagName === 'A') e.preventDefault(); self.relatedTarget = trigger; self.toggle(); @@ -3110,11 +3196,12 @@ * * @param {KeyboardEvent} e the `Event` object */ - function modalKeyHandler({ code }) { - const element = querySelector(modalActiveSelector); + function modalKeyHandler({ code, target }) { + const element = querySelector(modalActiveSelector, getDocument(target)); const self = element && getModalInstance(element); - if (!self) return; + const { options } = self; + /* istanbul ignore else */ if (options.keyboard && code === keyEscape // the keyboard option is enabled and the key is 27 && hasClass(element, showClass)) { // the modal is not visible self.relatedTarget = null; @@ -3125,7 +3212,7 @@ /** * Handles the `click` event listeners that hide the modal. * - * @this {HTMLElement | Element} + * @this {HTMLElement} * @param {MouseEvent} e the `Event` object */ function modalDismissHandler(e) { @@ -3133,19 +3220,18 @@ const self = getModalInstance(element); // this timer is needed + /* istanbul ignore next: must have a filter */ if (!self || Timer.get(element)) return; const { options, isStatic, modalDialog } = self; const { backdrop } = options; const { target } = e; - // @ts-ignore const selectedText = getDocument(element).getSelection().toString().length; - // @ts-ignore const targetInsideDialog = modalDialog.contains(target); - // @ts-ignore const dismiss = target && closest(target, modalDismissSelector); + /* istanbul ignore else */ if (isStatic && !targetInsideDialog) { Timer.set(element, () => { addClass(element, modalStaticClass); @@ -3176,7 +3262,7 @@ /** Returns a new `Modal` instance. */ class Modal extends BaseComponent { /** - * @param {HTMLElement | Element | string} target usually the `.modal` element + * @param {HTMLElement | string} target usually the `.modal` element * @param {BSN.Options.Modal=} config instance options */ constructor(target, config) { @@ -3189,13 +3275,12 @@ const { element } = self; // the modal-dialog - /** @type {(HTMLElement | Element)} */ - // @ts-ignore + /** @type {(HTMLElement)} */ self.modalDialog = querySelector(`.${modalString}-dialog`, element); // modal can have multiple triggering elements - /** @type {(HTMLElement | Element)[]} */ - self.triggers = [...querySelectorAll(modalToggleSelector)] + /** @type {HTMLElement[]} */ + self.triggers = [...querySelectorAll(modalToggleSelector, getDocument(element))] .filter((btn) => getTargetElement(btn) === element); // additional internals @@ -3203,10 +3288,9 @@ self.isStatic = self.options.backdrop === 'static'; /** @type {boolean} */ self.hasFade = hasClass(element, fadeClass); - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.relatedTarget = null; - /** @type {HTMLBodyElement | HTMLElement | Element} */ - // @ts-ignore + /** @type {HTMLBodyElement | HTMLElement} */ self.container = getElementContainer(element); // attach event listeners @@ -3219,12 +3303,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return modalComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return modalDefaults; } /* eslint-enable */ @@ -3257,7 +3339,8 @@ const currentOpen = getCurrentOpen(element); if (currentOpen && currentOpen !== element) { const this1 = getModalInstance(currentOpen); - const that1 = this1 || getInstance(currentOpen, 'Offcanvas'); + const that1 = this1 + || /* istanbul ignore next */getInstance(currentOpen, 'Offcanvas'); that1.hide(); } @@ -3274,6 +3357,7 @@ setTimeout(() => beforeModalShow(self), overlayDelay); } else { beforeModalShow(self); + /* istanbul ignore else */ if (currentOpen && hasClass(overlay, showClass)) { hideOverlay(); } @@ -3282,9 +3366,9 @@ /** * Hide the modal from the user. - * @param {boolean=} force when `true` it will skip animation + * @param {Function=} callback when defined it will skip animation */ - hide(force) { + hide(callback) { const self = this; const { element, hasFade, relatedTarget, @@ -3299,28 +3383,31 @@ setAttribute(element, ariaHidden, 'true'); removeAttribute(element, ariaModal); - if (hasFade && force !== false) { - emulateTransitionEnd(element, () => beforeModalHide(self)); + // if (hasFade && callback) { + /* istanbul ignore else */ + if (hasFade) { + emulateTransitionEnd(element, () => beforeModalHide(self, callback)); } else { - beforeModalHide(self, force); + beforeModalHide(self, callback); } } - /** Updates the modal layout. */ + /** + * Updates the modal layout. + * @this {Modal} the modal instance + */ update() { const self = this; - + /* istanbul ignore else */ if (hasClass(self.element, showClass)) setModalScrollbar(self); } /** Removes the `Modal` component from target element. */ dispose() { const self = this; - self.hide(true); // forced call - toggleModalHandler(self); - - super.dispose(); + // use callback + self.hide(() => super.dispose()); } } @@ -3416,6 +3503,7 @@ function beforeOffcanvasShow(self) { const { element, options } = self; + /* istanbul ignore else */ if (!options.scroll) { setOffCanvasScrollbar(self); setElementStyle(getDocumentBody(element), { overflow: 'hidden' }); @@ -3432,18 +3520,18 @@ * Executes before hiding the offcanvas. * * @param {Offcanvas} self the `Offcanvas` instance + * @param {Function=} callback the hide callback */ - function beforeOffcanvasHide(self) { + function beforeOffcanvasHide(self, callback) { const { element, options } = self; const currentOpen = getCurrentOpen(element); - // @ts-ignore element.blur(); if (!currentOpen && options.backdrop && hasClass(overlay, showClass)) { hideOverlay(); - emulateTransitionEnd(overlay, () => hideOffcanvasComplete(self)); - } else hideOffcanvasComplete(self); + emulateTransitionEnd(overlay, () => hideOffcanvasComplete(self, callback)); + } else hideOffcanvasComplete(self, callback); } // OFFCANVAS EVENT HANDLERS @@ -3451,7 +3539,7 @@ /** * Handles the `click` event listeners. * - * @this {HTMLElement | Element} + * @this {HTMLElement} * @param {MouseEvent} e the `Event` object */ function offcanvasTriggerHandler(e) { @@ -3459,9 +3547,11 @@ const element = trigger && getTargetElement(trigger); const self = element && getOffcanvasInstance(element); + /* istanbul ignore else */ if (self) { self.relatedTarget = trigger; self.toggle(); + /* istanbul ignore else */ if (trigger && trigger.tagName === 'A') { e.preventDefault(); } @@ -3471,35 +3561,35 @@ /** * Handles the event listeners that close the offcanvas. * - * @this {Document} * @param {MouseEvent} e the `Event` object */ function offcanvasDismissHandler(e) { - const element = querySelector(offcanvasActiveSelector, this); - if (!element) return; - + const { target } = e; + const element = querySelector(offcanvasActiveSelector, getDocument(target)); const offCanvasDismiss = querySelector(offcanvasDismissSelector, element); const self = getOffcanvasInstance(element); + /* istanbul ignore next: must have a filter */ if (!self) return; const { options, triggers } = self; - const { target } = e; - // @ts-ignore -- `EventTarget` is `HTMLElement` + const { backdrop } = options; const trigger = closest(target, offcanvasToggleSelector); const selection = getDocument(element).getSelection(); + if (overlay.contains(target) && backdrop === 'static') return; + + /* istanbul ignore else */ if (!(selection && selection.toString().length) - // @ts-ignore - && ((!element.contains(target) && options.backdrop - && (!trigger || (trigger && !triggers.includes(trigger)))) - // @ts-ignore + && ((!element.contains(target) && backdrop + && /* istanbul ignore next */(!trigger || triggers.includes(target))) || (offCanvasDismiss && offCanvasDismiss.contains(target)))) { - // @ts-ignore self.relatedTarget = offCanvasDismiss && offCanvasDismiss.contains(target) ? offCanvasDismiss : null; self.hide(); } + + /* istanbul ignore next */ if (trigger && trigger.tagName === 'A') e.preventDefault(); } @@ -3508,15 +3598,17 @@ * to hide it when user type the `ESC` key. * * @param {KeyboardEvent} e the `Event` object - * @this {Document} */ - function offcanvasKeyDismissHandler({ code }) { - const element = querySelector(offcanvasActiveSelector, this); - if (!element) return; + function offcanvasKeyDismissHandler({ code, target }) { + const element = querySelector(offcanvasActiveSelector, getDocument(target)); const self = getOffcanvasInstance(element); - if (self && self.options.keyboard && code === keyEscape) { + /* istanbul ignore next: must filter */ + if (!self) return; + + /* istanbul ignore else */ + if (self.options.keyboard && code === keyEscape) { self.relatedTarget = null; self.hide(); } @@ -3545,8 +3637,9 @@ * Handles the `transitionend` when hiding the offcanvas. * * @param {Offcanvas} self the `Offcanvas` instance + * @param {Function} callback the hide callback */ - function hideOffcanvasComplete(self) { + function hideOffcanvasComplete(self, callback) { const { element, triggers } = self; setAttribute(element, ariaHidden, 'true'); @@ -3555,6 +3648,7 @@ setElementStyle(element, { visibility: '' }); const visibleTrigger = showOffcanvasEvent.relatedTarget || triggers.find((x) => isVisible(x)); + /* istanbul ignore else */ if (visibleTrigger) focus(visibleTrigger); removeOverlay(element); @@ -3566,6 +3660,8 @@ if (!getCurrentOpen(element)) { toggleOffCanvasDismiss(self); } + // callback + if (callback) callback(); } // OFFCANVAS DEFINITION @@ -3573,7 +3669,7 @@ /** Returns a new `Offcanvas` instance. */ class Offcanvas extends BaseComponent { /** - * @param {HTMLElement | Element | string} target usually an `.offcanvas` element + * @param {HTMLElement | string} target usually an `.offcanvas` element * @param {BSN.Options.Offcanvas=} config instance options */ constructor(target, config) { @@ -3584,15 +3680,14 @@ const { element } = self; // all the triggering buttons - /** @type {(HTMLElement | Element)[]} */ - self.triggers = [...querySelectorAll(offcanvasToggleSelector)] + /** @type {HTMLElement[]} */ + self.triggers = [...querySelectorAll(offcanvasToggleSelector, getDocument(element))] .filter((btn) => getTargetElement(btn) === element); // additional instance property - /** @type {HTMLBodyElement | HTMLElement | Element} */ - // @ts-ignore + /** @type {HTMLBodyElement | HTMLElement} */ self.container = getElementContainer(element); - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.relatedTarget = null; // attach event listeners @@ -3602,12 +3697,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return offcanvasComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return offcanvasDefaults; } /* eslint-enable */ @@ -3640,7 +3733,8 @@ const currentOpen = getCurrentOpen(element); if (currentOpen && currentOpen !== element) { const this1 = getOffcanvasInstance(currentOpen); - const that1 = this1 || getInstance(currentOpen, 'Modal'); + const that1 = this1 + || /* istanbul ignore next */getInstance(currentOpen, 'Modal'); that1.hide(); } @@ -3657,6 +3751,7 @@ setTimeout(() => beforeOffcanvasShow(self), overlayDelay); } else { beforeOffcanvasShow(self); + /* istanbul ignore else */ if (currentOpen && hasClass(overlay, showClass)) { hideOverlay(); } @@ -3665,9 +3760,9 @@ /** * Hides the offcanvas from the user. - * @param {boolean=} force when `true` it will skip animation + * @param {Function=} callback when `true` it will skip animation */ - hide(force) { + hide(callback) { const self = this; const { element, relatedTarget } = self; @@ -3681,17 +3776,16 @@ addClass(element, offcanvasTogglingClass); removeClass(element, showClass); - if (!force) { - emulateTransitionEnd(element, () => beforeOffcanvasHide(self)); - } else beforeOffcanvasHide(self); + if (!callback) { + emulateTransitionEnd(element, () => beforeOffcanvasHide(self, callback)); + } else beforeOffcanvasHide(self, callback); } /** Removes the `Offcanvas` from the target element. */ dispose() { const self = this; - self.hide(true); toggleOffcanvasEvents(self); - super.dispose(); + self.hide(() => super.dispose()); } } @@ -3733,9 +3827,11 @@ * @param {any} element the target element * @returns {boolean} the query result */ - const isMedia = (element) => element - && [SVGElement, HTMLImageElement, HTMLVideoElement] - .some((mediaType) => element instanceof mediaType); + + const isMedia = (element) => ( + element + && element.nodeType === 1 + && ['SVG', 'Image', 'Video'].some((s) => element.constructor.name.includes(s))) || false; /** * Returns an `{x,y}` object with the target @@ -3743,7 +3839,7 @@ * * @see https://github.com/floating-ui/floating-ui * - * @param {HTMLElement | Element | Window} element target node / element + * @param {HTMLElement | Window} element target node / element * @returns {{x: number, y: number}} the scroll tuple */ function getNodeScroll(element) { @@ -3762,6 +3858,7 @@ * @returns {boolean} the query result */ function isScaledElement(element) { + if (!element || !isHTMLElement(element)) return false; const { width, height } = getBoundingClientRect(element); const { offsetWidth, offsetHeight } = element; return Math.round(width) !== offsetWidth @@ -3772,16 +3869,17 @@ * Returns the rect relative to an offset parent. * @see https://github.com/floating-ui/floating-ui * - * @param {HTMLElement | Element} element target - * @param {HTMLElement | Element | Window} offsetParent the container / offset parent - * @param {{x: number, y: number}} scroll - * @returns {SHORTER.OffsetRect} + * @param {HTMLElement} element target + * @param {ParentNode | Window} offsetParent the container / offset parent + * @param {{x: number, y: number}} scroll the offsetParent scroll position + * @returns {SHORTY.OffsetRect} */ function getRectRelativeToOffsetParent(element, offsetParent, scroll) { - const isParentAnElement = offsetParent instanceof HTMLElement; + const isParentAnElement = isHTMLElement(offsetParent); const rect = getBoundingClientRect(element, isParentAnElement && isScaledElement(offsetParent)); const offsets = { x: 0, y: 0 }; + /* istanbul ignore next */ if (isParentAnElement) { const offsetRect = getBoundingClientRect(offsetParent, true); offsets.x = offsetRect.x + offsetParent.clientLeft; @@ -3797,7 +3895,7 @@ } /** @type {Record} */ - var tipClassPositions = { + const tipClassPositions = { top: 'top', bottom: 'bottom', left: 'start', @@ -3816,27 +3914,34 @@ } = self; const tipPositions = { ...tipClassPositions }; - // reset tooltip style (top: 0, left: 0 works best) - setElementStyle(tooltip, { top: '0px', left: '0px', right: '' }); - // @ts-ignore - const isPopover = self.name === popoverComponent; - const tipWidth = tooltip.offsetWidth; - const tipHeight = tooltip.offsetHeight; const RTL = isRTL(element); if (RTL) { tipPositions.left = 'end'; tipPositions.right = 'start'; } - const documentElement = getDocumentElement(element); - const windowWidth = documentElement.clientWidth; - const windowHeight = documentElement.clientHeight; + + // reset tooltip style (top: 0, left: 0 works best) + setElementStyle(tooltip, { + // top: '0px', left: '0px', right: '', bottom: '', + top: '', left: '', right: '', bottom: '', + }); + const isPopover = self.name === popoverComponent; + const { + offsetWidth: tipWidth, offsetHeight: tipHeight, + } = tooltip; + const { + clientWidth: htmlcw, clientHeight: htmlch, + } = getDocumentElement(element); const { container } = options; let { placement } = options; const { left: parentLeft, right: parentRight, top: parentTop, } = getBoundingClientRect(container, true); - const parentWidth = container.clientWidth; - const scrollbarWidth = Math.abs(parentWidth - container.offsetWidth); + const { + clientWidth: parentCWidth, offsetWidth: parentOWidth, + } = container; + const scrollbarWidth = Math.abs(parentCWidth - parentOWidth); + // const tipAbsolute = getElementStyle(tooltip, 'position') === 'absolute'; const parentPosition = getElementStyle(container, 'position'); // const absoluteParent = parentPosition === 'absolute'; const fixedParent = parentPosition === 'fixed'; @@ -3846,8 +3951,8 @@ // const absoluteTarget = getElementStyle(element, 'position') === 'absolute'; // const stickyFixedParent = ['sticky', 'fixed'].includes(parentPosition); const leftBoundry = RTL && fixedParent ? scrollbarWidth : 0; - const rightBoundry = fixedParent ? parentWidth + parentLeft + (RTL ? scrollbarWidth : 0) - : parentWidth + parentLeft + (windowWidth - parentRight) - 1; + const rightBoundry = fixedParent ? parentCWidth + parentLeft + (RTL ? scrollbarWidth : 0) + : parentCWidth + parentLeft + (htmlcw - parentRight) - 1; const { width: elemWidth, height: elemHeight, @@ -3859,7 +3964,9 @@ const scroll = getNodeScroll(offsetParent); const { x, y } = getRectRelativeToOffsetParent(element, offsetParent, scroll); // reset arrow style - setElementStyle(arrow, { top: '', left: '', right: '' }); + setElementStyle(arrow, { + top: '', left: '', right: '', bottom: '', + }); let topPosition; let leftPosition; let rightPosition; @@ -3874,18 +3981,19 @@ // check placement let topExceed = elemRectTop - tipHeight - arrowHeight < 0; let bottomExceed = elemRectTop + tipHeight + elemHeight - + arrowHeight >= windowHeight; + + arrowHeight >= htmlch; let leftExceed = elemRectLeft - tipWidth - arrowWidth < leftBoundry; let rightExceed = elemRectLeft + tipWidth + elemWidth + arrowWidth >= rightBoundry; const horizontal = ['left', 'right']; const vertical = ['top', 'bottom']; + topExceed = horizontal.includes(placement) ? elemRectTop + elemHeight / 2 - tipHeight / 2 - arrowHeight < 0 : topExceed; bottomExceed = horizontal.includes(placement) - ? elemRectTop + tipHeight / 2 + elemHeight / 2 + arrowHeight >= windowHeight + ? elemRectTop + tipHeight / 2 + elemHeight / 2 + arrowHeight >= htmlch : bottomExceed; leftExceed = vertical.includes(placement) ? elemRectLeft + elemWidth / 2 - tipWidth / 2 < leftBoundry @@ -3894,9 +4002,10 @@ ? elemRectLeft + tipWidth / 2 + elemWidth / 2 >= rightBoundry : rightExceed; - // recompute placement - // first, when both left and right limits are exceeded, we fall back to top|bottom + // first remove side positions if both left and right limits are exceeded + // we usually fall back to top|bottom placement = (horizontal.includes(placement)) && leftExceed && rightExceed ? 'top' : placement; + // second, recompute placement placement = placement === 'top' && topExceed ? 'bottom' : placement; placement = placement === 'bottom' && bottomExceed ? 'top' : placement; placement = placement === 'left' && leftExceed ? 'right' : placement; @@ -3908,6 +4017,7 @@ } // compute tooltip / popover coordinates + /* istanbul ignore else */ if (horizontal.includes(placement)) { // secondary|side positions if (placement === 'left') { // LEFT leftPosition = x - tipWidth - (isPopover ? arrowWidth : 0); @@ -3998,7 +4108,8 @@ }); // update arrow placement - if (arrow instanceof HTMLElement) { + /* istanbul ignore else */ + if (isHTMLElement(arrow)) { if (arrowTop !== undefined) { arrow.style.top = `${arrowTop}px`; } @@ -4027,7 +4138,7 @@ animation: true, // bool /** @type {number} */ delay: 200, // number - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ container: null, }; @@ -4067,6 +4178,12 @@ */ const mousehoverEvent = 'hover'; + /** + * A global namespace for `touchstart` event. + * @type {string} + */ + const touchstartEvent = 'touchstart'; + let elementUID = 0; let elementMapUID = 0; const elementIDMap = new Map(); @@ -4074,7 +4191,7 @@ /** * Returns a unique identifier for popover, tooltip, scrollspy. * - * @param {HTMLElement | Element} element target element + * @param {HTMLElement} element target element * @param {string=} key predefined key * @returns {number} an existing or new unique ID */ @@ -4102,7 +4219,14 @@ return result; } - // @ts-ignore + /** + * Checks if an object is a `Function`. + * + * @param {any} fn the target object + * @returns {boolean} the query result + */ + const isFunction = (fn) => (fn && fn.constructor.name === 'Function') || false; + const { userAgentData: uaDATA } = navigator; /** @@ -4123,8 +4247,8 @@ * A global `boolean` for Apple browsers. * @type {boolean} */ - const isApple = !userAgentData ? appleBrands.test(userAgent) - : userAgentData.brands.some((/** @type {Record} */x) => appleBrands.test(x.brand)); + const isApple = userAgentData ? userAgentData.brands.some((x) => appleBrands.test(x.brand)) + : /* istanbul ignore next */appleBrands.test(userAgent); /** * Global namespace for `data-bs-title` attribute. @@ -4134,47 +4258,76 @@ /** @type {string} */ const tooltipComponent = 'Tooltip'; + /** + * Checks if an object is a `NodeList`. + * => equivalent to `object instanceof NodeList` + * + * @param {any} object the target object + * @returns {boolean} the query result + */ + const isNodeList = (object) => (object && object.constructor.name === 'NodeList') || false; + + /** + * Shortcut for `typeof SOMETHING === "string"`. + * + * @param {any} str input value + * @returns {boolean} the query result + */ + const isString = (str) => typeof str === 'string'; + + /** + * Shortcut for `Array.isArray()` static method. + * + * @param {any} arr array-like iterable object + * @returns {boolean} the query result + */ + const isArray = (arr) => Array.isArray(arr); + /** * Append an existing `Element` to Popover / Tooltip component or HTML * markup string to be parsed & sanitized to be used as popover / tooltip content. * - * @param {HTMLElement | Element} element target - * @param {HTMLElement | Element | string} content the `Element` to append / string + * @param {HTMLElement} element target + * @param {Node | string} content the `Element` to append / string * @param {ReturnType} sanitizeFn a function to sanitize string content */ function setHtml(element, content, sanitizeFn) { - if (typeof content === 'string' && !content.length) return; + /* istanbul ignore next */ + if (!isHTMLElement(element) || (isString(content) && !content.length)) return; - if (typeof content === 'string') { + /* istanbul ignore else */ + if (isString(content)) { let dirty = content.trim(); // fixing #233 - if (typeof sanitizeFn === 'function') dirty = sanitizeFn(dirty); + if (isFunction(sanitizeFn)) dirty = sanitizeFn(dirty); - const domParser = new DOMParser(); + const win = getWindow(element); + const domParser = new win.DOMParser(); const tempDocument = domParser.parseFromString(dirty, 'text/html'); - const { body } = tempDocument; - const method = body.children.length ? 'innerHTML' : 'innerText'; - // @ts-ignore - element[method] = body[method]; - } else if (content instanceof HTMLElement) { + element.append(...[...tempDocument.body.childNodes]); + } else if (isHTMLElement(content)) { element.append(content); + } else if (isNodeList(content) + || (isArray(content) && content.every(isNode))) { + element.append(...[...content]); } } /** * Creates a new tooltip / popover. * - * @param {BSN.Popover | BSN.Tooltip} self the `Popover` instance + * @param {BSN.Popover | BSN.Tooltip} self the `Tooltip` / `Popover` instance */ function createTip(self) { const { id, element, options } = self; const { animation, customClass, sanitizeFn, placement, dismissible, + title, content, template, btnClose, } = options; - let { title, content } = options; const isTooltip = self.name === tooltipComponent; const tipString = isTooltip ? tooltipString : popoverString; - const { template, btnClose } = options; const tipPositions = { ...tipClassPositions }; + let titleParts = []; + let contentParts = []; if (isRTL(element)) { tipPositions.left = 'end'; @@ -4185,18 +4338,18 @@ const placementClass = `bs-${tipString}-${tipPositions[placement]}`; // load template - /** @type {(HTMLElement | Element)?} */ - let popoverTemplate; - if ([Element, HTMLElement].some((x) => template instanceof x)) { - popoverTemplate = template; + /** @type {HTMLElement?} */ + let tooltipTemplate; + if (isHTMLElement(template)) { + tooltipTemplate = template; } else { - const htmlMarkup = getDocument(element).createElement('div'); + const htmlMarkup = createElement('div'); setHtml(htmlMarkup, template, sanitizeFn); - popoverTemplate = htmlMarkup.firstElementChild; + tooltipTemplate = htmlMarkup.firstChild; } // set popover markup - self.tooltip = popoverTemplate && popoverTemplate.cloneNode(true); + self.tooltip = isHTMLElement(tooltipTemplate) && tooltipTemplate.cloneNode(true); const { tooltip } = self; @@ -4210,44 +4363,79 @@ // set arrow and enable access for styleTip self.arrow = querySelector(`.${tipString}-arrow`, tooltip); + const { arrow } = self; + + if (isHTMLElement(title)) titleParts = [title.cloneNode(true)]; + else { + const tempTitle = createElement('div'); + setHtml(tempTitle, title, sanitizeFn); + titleParts = [...[...tempTitle.childNodes]]; + } + + if (isHTMLElement(content)) contentParts = [content.cloneNode(true)]; + else { + const tempContent = createElement('div'); + setHtml(tempContent, content, sanitizeFn); + contentParts = [...[...tempContent.childNodes]]; + } // set dismissible button if (dismissible) { if (title) { - if (title instanceof HTMLElement) setHtml(title, btnClose, sanitizeFn); - else title += btnClose; + if (isHTMLElement(btnClose)) titleParts = [...titleParts, btnClose.cloneNode(true)]; + else { + const tempBtn = createElement('div'); + setHtml(tempBtn, btnClose, sanitizeFn); + titleParts = [...titleParts, tempBtn.firstChild]; + } } else { + /* istanbul ignore else */ if (tooltipHeader) tooltipHeader.remove(); - if (content instanceof HTMLElement) setHtml(content, btnClose, sanitizeFn); - else content += btnClose; + if (isHTMLElement(btnClose)) contentParts = [...contentParts, btnClose.cloneNode(true)]; + else { + const tempBtn = createElement('div'); + setHtml(tempBtn, btnClose, sanitizeFn); + contentParts = [...contentParts, tempBtn.firstChild]; + } } } // fill the template with content from options / data attributes // also sanitize title && content + /* istanbul ignore else */ if (!isTooltip) { - if (title && tooltipHeader) setHtml(tooltipHeader, title, sanitizeFn); - if (content && tooltipBody) setHtml(tooltipBody, content, sanitizeFn); - // @ts-ignore -- set btn + /* istanbul ignore else */ + if (title && tooltipHeader) setHtml(tooltipHeader, titleParts, sanitizeFn); + /* istanbul ignore else */ + if (content && tooltipBody) setHtml(tooltipBody, contentParts, sanitizeFn); + // set btn self.btn = querySelector('.btn-close', tooltip); } else if (title && tooltipBody) setHtml(tooltipBody, title, sanitizeFn); + // Bootstrap 5.2.x + addClass(tooltip, 'position-absolute'); + addClass(arrow, 'position-absolute'); + // set popover animation and placement + /* istanbul ignore else */ if (!hasClass(tooltip, tipString)) addClass(tooltip, tipString); + /* istanbul ignore else */ if (animation && !hasClass(tooltip, fadeClass)) addClass(tooltip, fadeClass); + /* istanbul ignore else */ if (customClass && !hasClass(tooltip, customClass)) { addClass(tooltip, customClass); } + /* istanbul ignore else */ if (!hasClass(tooltip, placementClass)) addClass(tooltip, placementClass); } /** - * @param {(HTMLElement | Element)?} tip target - * @param {HTMLElement | ParentNode} container parent container + * @param {HTMLElement} tip target + * @param {ParentNode} container parent container * @returns {boolean} */ function isVisibleTip(tip, container) { - return tip instanceof HTMLElement && container.contains(tip); + return isHTMLElement(tip) && container.contains(tip); } /* Native JavaScript for Bootstrap 5 | Tooltip @@ -4289,14 +4477,18 @@ * Executes after the instance has been disposed. * * @param {Tooltip} self the `Tooltip` instance + * @param {Function=} callback the parent dispose callback */ - function disposeTooltipComplete(self) { + function disposeTooltipComplete(self, callback) { const { element } = self; toggleTooltipHandlers(self); - if (element.hasAttribute(dataOriginalTitle) && self.name === tooltipString) { + /* istanbul ignore else */ + if (hasAttribute(element, dataOriginalTitle) && self.name === tooltipComponent) { toggleTooltipTitle(self); } + /* istanbul ignore else */ + if (callback) callback(); } /** @@ -4311,9 +4503,9 @@ action(getDocument(element), touchstartEvent, self.handleTouch, passiveHandler); + /* istanbul ignore else */ if (!isMedia(element)) { [scrollEvent, resizeEvent].forEach((ev) => { - // @ts-ignore action(getWindow(element), ev, self.update, passiveHandler); }); } @@ -4337,14 +4529,16 @@ * Executes after the tooltip was hidden to the user. * * @param {Tooltip} self the `Tooltip` instance + * @param {Function=} callback the dispose callback */ - function tooltipHiddenAction(self) { + function tooltipHiddenAction(self, callback) { const { element } = self; const hiddenTooltipEvent = OriginalEvent(`hidden.bs.${toLowerCase(self.name)}`); toggleTooltipAction(self); removeTooltip(self); dispatchEvent(element, hiddenTooltipEvent); + if (isFunction(callback)) callback(); Timer.clear(element, 'out'); } @@ -4356,7 +4550,7 @@ */ function toggleTooltipHandlers(self, add) { const action = add ? addListener : removeListener; - // @ts-ignore -- btn is only for dismissible popover + // btn is only for dismissible popover const { element, options, btn } = self; const { trigger, dismissible } = options; @@ -4373,10 +4567,12 @@ } triggerOptions.forEach((tr) => { + /* istanbul ignore else */ if (elemIsMedia || tr === mousehoverEvent) { action(element, mousedownEvent, self.show); action(element, mouseenterEvent, self.show); + /* istanbul ignore else */ if (dismissible && btn) { action(btn, mouseclickEvent, self.hide); } else { @@ -4387,8 +4583,12 @@ action(element, tr, (!dismissible ? self.toggle : self.show)); } else if (tr === focusEvent) { action(element, focusinEvent, self.show); + /* istanbul ignore else */ if (!dismissible) action(element, focusoutEvent, self.hide); - if (isApple) action(element, mouseclickEvent, () => focus(element)); + /* istanbul ignore else */ + if (isApple) { + action(element, mouseclickEvent, () => focus(element)); + } } }); } @@ -4407,11 +4607,11 @@ const parentModal = closest(element, `.${modalString}`); const parentOffcanvas = closest(element, `.${offcanvasString}`); + /* istanbul ignore else */ if (!isMedia(element)) { const win = getWindow(element); const overflow = offsetHeight !== scrollHeight; const scrollTarget = overflow || offsetParent !== win ? container : win; - // @ts-ignore action(win, resizeEvent, self.update, passiveHandler); action(scrollTarget, scrollEvent, self.update, passiveHandler); } @@ -4433,7 +4633,6 @@ const { element } = self; setAttribute(element, titleAtt[content ? 0 : 1], - // @ts-ignore (content || getAttribute(element, titleAtt[0]))); removeAttribute(element, titleAtt[content ? 1 : 0]); } @@ -4443,7 +4642,7 @@ /** Creates a new `Tooltip` instance. */ class Tooltip extends BaseComponent { /** - * @param {HTMLElement | Element | string} target the target element + * @param {HTMLElement | string} target the target element * @param {BSN.Options.Tooltip=} config the instance options */ constructor(target, config) { @@ -4456,6 +4655,7 @@ const tipString = isTooltip ? tooltipString : popoverString; const tipComponent = isTooltip ? tooltipComponent : popoverComponent; + /* istanbul ignore next: this is to set Popover too */ getTooltipInstance = (elem) => getInstance(elem, tipComponent); // additional properties @@ -4463,7 +4663,6 @@ self.tooltip = {}; if (!isTooltip) { /** @type {any?} */ - // @ts-ignore self.btn = null; } /** @type {any} */ @@ -4479,16 +4678,19 @@ const { options } = self; // invalidate - if ((!options.title && isTooltip) || (!isTooltip && !options.content)) return; + if ((!options.title && isTooltip) || (!isTooltip && !options.content)) { + // throw Error(`${this.name} Error: target has no content set.`); + return; + } - const container = querySelector(options.container); + const container = querySelector(options.container, getDocument(element)); const idealContainer = getElementContainer(element); // bypass container option when its position is static/relative self.options.container = !container || (container && ['static', 'relative'].includes(getElementStyle(container, 'position'))) ? idealContainer - : container || getDocumentBody(element); + : /* istanbul ignore next */container || getDocumentBody(element); // reset default options tooltipDefaults[titleAttr] = null; @@ -4501,7 +4703,8 @@ self.toggle = self.toggle.bind(self); // set title attributes and add event listeners - if (element.hasAttribute(titleAttr) && isTooltip) { + /* istanbul ignore else */ + if (hasAttribute(element, titleAttr) && isTooltip) { toggleTooltipTitle(self, options.title); } @@ -4515,12 +4718,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return tooltipComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return tooltipDefaults; } /* eslint-enable */ @@ -4558,7 +4759,9 @@ self.update(e); toggleTooltipOpenHandlers(self, true); + /* istanbul ignore else */ if (!hasClass(tooltip, showClass)) addClass(tooltip, showClass); + /* istanbul ignore else */ if (animation) emulateTransitionEnd(tooltip, () => tooltipShownAction(self)); else tooltipShownAction(self); }, 17, 'in'); @@ -4568,15 +4771,17 @@ /** * Hides the tooltip. * - * @this {Tooltip} + * @this {Tooltip} the Tooltip instance + * @param {Function=} callback the dispose callback */ - hide() { + hide(callback) { const self = this; const { options, tooltip, element } = self; const { container, animation, delay } = options; Timer.clear(element, 'in'); + /* istanbul ignore else */ if (tooltip && isVisibleTip(tooltip, container)) { Timer.set(element, () => { const hideTooltipEvent = OriginalEvent(`hide.bs.${toLowerCase(self.name)}`); @@ -4584,12 +4789,12 @@ if (hideTooltipEvent.defaultPrevented) return; - // @ts-ignore removeClass(tooltip, showClass); toggleTooltipOpenHandlers(self); - if (animation) emulateTransitionEnd(tooltip, () => tooltipHiddenAction(self)); - else tooltipHiddenAction(self); + /* istanbul ignore else */ + if (animation) emulateTransitionEnd(tooltip, () => tooltipHiddenAction(self, callback)); + else tooltipHiddenAction(self, callback); }, delay + 17, 'out'); } } @@ -4601,7 +4806,6 @@ * @this {Tooltip} the `Tooltip` instance */ update(e) { - // @ts-ignore styleTip(this, e); } @@ -4623,6 +4827,7 @@ enable() { const self = this; const { enabled } = self; + /* istanbul ignore else */ if (!enabled) { toggleTooltipHandlers(self, true); self.enabled = !enabled; @@ -4633,17 +4838,13 @@ disable() { const self = this; const { - element, tooltip, options, enabled, + tooltip, options, enabled, } = self; - const { animation, container, delay } = options; + const { animation, container } = options; + /* istanbul ignore else */ if (enabled) { if (isVisibleTip(tooltip, container) && animation) { - self.hide(); - - Timer.set(element, () => { - toggleTooltipHandlers(self); - Timer.clear(element, tooltipString); - }, getElementTransitionDuration(tooltip) + delay + 17, tooltipString); + self.hide(() => toggleTooltipHandlers(self)); } else { toggleTooltipHandlers(self); } @@ -4666,8 +4867,8 @@ handleTouch({ target }) { const { tooltip, element } = this; + /* istanbul ignore next */ if (tooltip.contains(target) || target === element - // @ts-ignore || (target && element.contains(target))) ; else { this.hide(); } @@ -4677,15 +4878,14 @@ dispose() { const self = this; const { tooltip, options } = self; + const callback = () => disposeTooltipComplete(self, () => super.dispose()); if (options.animation && isVisibleTip(tooltip, options.container)) { - options.delay = 0; // reset delay - self.hide(); - emulateTransitionEnd(tooltip, () => disposeTooltipComplete(self)); + self.options.delay = 0; // reset delay + self.hide(callback); } else { - disposeTooltipComplete(self); + callback(); } - super.dispose(); } } @@ -4721,7 +4921,7 @@ class Popover extends Tooltip { /* eslint-disable -- we want to specify Popover Options */ /** - * @param {HTMLElement | Element | string} target the target element + * @param {HTMLElement | string} target the target element * @param {BSN.Options.Popover=} config the instance options */ constructor(target, config) { @@ -4729,12 +4929,10 @@ } /** * Returns component name string. - * @readonly @static - */ + */ get name() { return popoverComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return popoverDefaults; } /* eslint-enable */ @@ -4742,8 +4940,9 @@ /* extend original `show()` */ show() { super.show(); - // @ts-ignore -- btn only exists within dismissible popover + // btn only exists within dismissible popover const { options, btn } = this; + /* istanbul ignore else */ if (options.dismissible && btn) setTimeout(() => focus(btn), 17); } } @@ -4774,12 +4973,11 @@ * like `ShadowRoot` do not support `getElementsByTagName`. * * @param {string} selector the tag name - * @param {(HTMLElement | Element | Document)=} parent optional Element to look into - * @return {HTMLCollectionOf} the 'HTMLCollection' + * @param {ParentNode=} parent optional Element to look into + * @return {HTMLCollectionOf} the 'HTMLCollection' */ function getElementsByTagName(selector, parent) { - const lookUp = parent && parentNodes - .some((x) => parent instanceof x) ? parent : getDocument(); + const lookUp = isNode(parent) ? parent : getDocument(); return lookUp.getElementsByTagName(selector); } @@ -4792,8 +4990,6 @@ /* Native JavaScript for Bootstrap 5 | ScrollSpy ------------------------------------------------ */ - // console.log(typeof addEventListener) - // SCROLLSPY PRIVATE GC // ==================== const scrollspySelector = '[data-bs-spy="scroll"]'; @@ -4832,15 +5028,15 @@ target, scrollTarget, options, itemsLength, scrollHeight, element, } = self; const { offset } = options; - const isWin = scrollTarget instanceof Window; + const isWin = isWindow(scrollTarget); const links = target && getElementsByTagName('A', target); const scrollHEIGHT = scrollTarget && getScrollHeight(scrollTarget); - // @ts-ignore self.scrollTop = isWin ? scrollTarget.scrollY : scrollTarget.scrollTop; // only update items/offsets once or with each mutation + /* istanbul ignore else */ if (links && (itemsLength !== links.length || scrollHEIGHT !== scrollHeight)) { let href; let targetItem; @@ -4860,7 +5056,6 @@ if (targetItem) { self.items.push(link); rect = getBoundingClientRect(targetItem); - // @ts-ignore self.offsets.push((isWin ? rect.top + self.scrollTop : targetItem.offsetTop) - offset); } }); @@ -4870,12 +5065,12 @@ /** * Returns the `scrollHeight` property of the scrolling element. - * @param {HTMLElement | Element | Window | globalThis} scrollTarget the `ScrollSpy` instance + * @param {Node | Window} scrollTarget the `ScrollSpy` instance * @return {number} `scrollTarget` height */ function getScrollHeight(scrollTarget) { - return scrollTarget instanceof HTMLElement - ? scrollTarget.scrollHeight // @ts-ignore + return isHTMLElement(scrollTarget) + ? scrollTarget.scrollHeight : getDocumentElement(scrollTarget).scrollHeight; } @@ -4885,14 +5080,14 @@ * @returns {number} */ function getOffsetHeight({ element, scrollTarget }) { - return (scrollTarget instanceof Window) + return (isWindow(scrollTarget)) ? scrollTarget.innerHeight : getBoundingClientRect(element).height; } /** * Clear all items of the target. - * @param {HTMLElement | Element} target a single item + * @param {HTMLElement} target a single item */ function clear(target) { [...getElementsByTagName('A', target)].forEach((item) => { @@ -4903,13 +5098,11 @@ /** * Activates a new item. * @param {ScrollSpy} self the `ScrollSpy` instance - * @param {HTMLElement | Element} item a single item + * @param {HTMLElement} item a single item */ function activate(self, item) { const { target, element } = self; - // @ts-ignore clear(target); - // @ts-ignore self.activeItem = item; addClass(item, activeClass); @@ -4917,15 +5110,15 @@ const parents = []; let parentItem = item; while (parentItem !== getDocumentBody(element)) { - // @ts-ignore parentItem = parentItem.parentElement; if (hasClass(parentItem, 'nav') || hasClass(parentItem, 'dropdown-menu')) parents.push(parentItem); } parents.forEach((menuItem) => { - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ const parentLink = menuItem.previousElementSibling; + /* istanbul ignore else */ if (parentLink && !hasClass(parentLink, activeClass)) { addClass(parentLink, activeClass); } @@ -4943,7 +5136,6 @@ */ function toggleSpyHandlers(self, add) { const action = add ? addListener : removeListener; - // @ts-ignore action(self.scrollTarget, scrollEvent, self.refresh, passiveHandler); } @@ -4952,7 +5144,7 @@ /** Returns a new `ScrollSpy` instance. */ class ScrollSpy extends BaseComponent { /** - * @param {HTMLElement | Element | string} target the target element + * @param {HTMLElement | string} target the target element * @param {BSN.Options.ScrollSpy=} config the instance options */ constructor(target, config) { @@ -4964,26 +5156,25 @@ const { element, options } = self; // additional properties - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.target = querySelector(options.target, getDocument(element)); // invalidate if (!self.target) return; - const win = getWindow(element); - // set initial state - /** @type {HTMLElement | Element | Window | globalThis} */ - self.scrollTarget = element.clientHeight < element.scrollHeight ? element : win; + /** @type {HTMLElement | Window} */ + self.scrollTarget = element.clientHeight < element.scrollHeight + ? element : getWindow(element); /** @type {number} */ self.scrollTop = 0; /** @type {number} */ self.maxScroll = 0; /** @type {number} */ self.scrollHeight = 0; - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.activeItem = null; - /** @type {(HTMLElement | Element)[]} */ + /** @type {HTMLElement[]} */ self.items = []; /** @type {number} */ self.itemsLength = 0; @@ -5002,12 +5193,10 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static */ get name() { return scrollspyComponent; } /** * Returns component default options. - * @readonly @static */ get defaults() { return scrollspyDefaults; } /* eslint-enable */ @@ -5020,7 +5209,7 @@ const { target } = self; // check if target is visible and invalidate - // @ts-ignore + /* istanbul ignore next */ if (target.offsetHeight === 0) return; updateSpyTargets(self); @@ -5032,6 +5221,7 @@ if (scrollTop >= maxScroll) { const newActiveItem = items[itemsLength - 1]; + /* istanbul ignore else */ if (activeItem !== newActiveItem) { activate(self, newActiveItem); } @@ -5042,7 +5232,6 @@ if (activeItem && scrollTop < offsets[0] && offsets[0] > 0) { self.activeItem = null; - // @ts-ignore clear(target); return; } @@ -5111,7 +5300,7 @@ /** * Stores the current active tab and its content * for a given `.nav` element. - * @type {Map<(HTMLElement | Element), any>} + * @type {Map} */ const tabPrivate = new Map(); @@ -5124,12 +5313,13 @@ function triggerTabEnd(self) { const { tabContent, nav } = self; + /* istanbul ignore else */ if (tabContent && hasClass(tabContent, collapsingClass)) { - // @ts-ignore tabContent.style.height = ''; removeClass(tabContent, collapsingClass); } + /* istanbul ignore else */ if (nav) Timer.clear(nav); } @@ -5143,14 +5333,15 @@ } = self; const { tab } = nav && tabPrivate.get(nav); - if (tabContent && hasClass(nextContent, fadeClass)) { // height animation + /* istanbul ignore else */ + if (tabContent && hasClass(nextContent, fadeClass)) { const { currentHeight, nextHeight } = tabPrivate.get(element); if (currentHeight === nextHeight) { triggerTabEnd(self); } else { - setTimeout(() => { // enables height animation - // @ts-ignore - tabContent.style.height = `${nextHeight}px`; // height animation + // enables height animation + setTimeout(() => { + tabContent.style.height = `${nextHeight}px`; reflow(tabContent); emulateTransitionEnd(tabContent, () => triggerTabEnd(self)); }, 50); @@ -5172,11 +5363,12 @@ const { tab, content } = nav && tabPrivate.get(nav); let currentHeight = 0; + /* istanbul ignore else */ if (tabContent && hasClass(nextContent, fadeClass)) { [content, nextContent].forEach((c) => { addClass(c, 'overflow-hidden'); }); - currentHeight = content.scrollHeight || 0; + currentHeight = content.scrollHeight || /* istanbul ignore next */0; } // update relatedTarget and dispatch event @@ -5188,12 +5380,12 @@ addClass(nextContent, activeClass); removeClass(content, activeClass); + /* istanbul ignore else */ if (tabContent && hasClass(nextContent, fadeClass)) { const nextHeight = nextContent.scrollHeight; tabPrivate.set(element, { currentHeight, nextHeight }); addClass(tabContent, collapsingClass); - // @ts-ignore -- height animation tabContent.style.height = `${currentHeight}px`; reflow(tabContent); [content, nextContent].forEach((c) => { @@ -5224,26 +5416,24 @@ function getActiveTab(self) { const { nav } = self; - // @ts-ignore const activeTabs = getElementsByClassName(activeClass, nav); - /** @type {(HTMLElement | Element)=} */ + /** @type {(HTMLElement)=} */ let tab; + /* istanbul ignore else */ if (activeTabs.length === 1 - // @ts-ignore && !dropdownMenuClasses.some((c) => hasClass(activeTabs[0].parentElement, c))) { [tab] = activeTabs; } else if (activeTabs.length > 1) { tab = activeTabs[activeTabs.length - 1]; } const content = tab ? getTargetElement(tab) : null; - // @ts-ignore return { tab, content }; } /** * Returns a parent dropdown. - * @param {HTMLElement | Element} element the `Tab` element - * @returns {(HTMLElement | Element)?} the parent dropdown + * @param {HTMLElement} element the `Tab` element + * @returns {HTMLElement?} the parent dropdown */ function getParentDropdown(element) { const dropdown = closest(element, `.${dropdownMenuClasses.join(',.')}`); @@ -5264,11 +5454,12 @@ // ================= /** * Handles the `click` event listener. - * @this {HTMLElement | Element} + * @this {HTMLElement} * @param {MouseEvent} e the `Event` object */ function tabClickHandler(e) { const self = getTabInstance(this); + /* istanbul ignore next: must filter */ if (!self) return; e.preventDefault(); @@ -5280,7 +5471,7 @@ /** Creates a new `Tab` instance. */ class Tab extends BaseComponent { /** - * @param {HTMLElement | Element | string} target the target element + * @param {HTMLElement | string} target the target element */ constructor(target) { super(target); @@ -5297,15 +5488,15 @@ const nav = closest(element, '.nav'); const container = closest(content, '.tab-content'); - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.nav = nav; - /** @type {HTMLElement | Element} */ + /** @type {HTMLElement} */ self.content = content; - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.tabContent = container; // event targets - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.dropdown = getParentDropdown(element); // show first Tab instance of none is shown @@ -5315,6 +5506,7 @@ const firstTab = querySelector(tabSelector, nav); const firstTabContent = firstTab && getTargetElement(firstTab); + /* istanbul ignore else */ if (firstTabContent) { addClass(firstTab, activeClass); addClass(firstTabContent, showClass); @@ -5330,8 +5522,7 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static - */ + */ get name() { return tabComponent; } /* eslint-enable */ @@ -5340,11 +5531,15 @@ /** Shows the tab to the user. */ show() { const self = this; - const { element, nav, dropdown } = self; + const { + element, content: nextContent, nav, dropdown, + } = self; + /* istanbul ignore else */ if (!(nav && Timer.get(nav)) && !hasClass(element, activeClass)) { const { tab, content } = getActiveTab(self); + /* istanbul ignore else */ if (nav) tabPrivate.set(nav, { tab, content }); // update relatedTarget and dispatch @@ -5361,16 +5556,21 @@ removeClass(activeDropdown, activeClass); } + /* istanbul ignore else */ if (nav) { - Timer.set(nav, () => { + const toggleTab = () => { removeClass(tab, activeClass); setAttribute(tab, ariaSelected, 'false'); if (dropdown && !hasClass(dropdown, activeClass)) addClass(dropdown, activeClass); - }, 1); + }; + + if (hasClass(content, fadeClass) || hasClass(nextContent, fadeClass)) { + Timer.set(nav, toggleTab, 1); + } else toggleTab(); } + removeClass(content, showClass); if (hasClass(content, fadeClass)) { - removeClass(content, showClass); emulateTransitionEnd(content, () => triggerTabHide(self)); } else { triggerTabHide(self); @@ -5404,6 +5604,7 @@ // ================ const toastSelector = `.${toastString}`; const toastDismissSelector = `[${dataBsDismiss}="${toastString}"]`; + const toastToggleSelector = `[${dataBsToggle}="${toastString}"]`; const showingClass = 'showing'; /** @deprecated */ const hideClass = 'hide'; @@ -5447,6 +5648,7 @@ Timer.clear(element, showingClass); dispatchEvent(element, shownToastEvent); + /* istanbul ignore else */ if (options.autohide) { Timer.set(element, () => self.hide(), options.delay, toastString); } @@ -5508,14 +5710,24 @@ */ function toggleToastHandlers(self, add) { const action = add ? addListener : removeListener; - const { element, dismiss, options } = self; + const { + element, triggers, dismiss, options, + } = self; + + /* istanbul ignore else */ if (dismiss) { action(dismiss, mouseclickEvent, self.hide); } + + /* istanbul ignore else */ if (options.autohide) { [focusinEvent, focusoutEvent, mouseenterEvent, mouseleaveEvent] .forEach((e) => action(element, e, interactiveToastHandler)); } + /* istanbul ignore else */ + if (triggers.length) { + triggers.forEach((btn) => action(btn, mouseclickEvent, toastClickHandler)); + } } // TOAST EVENT HANDLERS @@ -5529,18 +5741,36 @@ toggleToastHandlers(self); } + /** + * Handles the `click` event listener for toast. + * @param {MouseEvent} e the `Event` object + */ + function toastClickHandler(e) { + const { target } = e; + + const trigger = target && closest(target, toastToggleSelector); + const element = trigger && getTargetElement(trigger); + const self = element && getToastInstance(element); + + /* istanbul ignore else */ + if (trigger && trigger.tagName === 'A') e.preventDefault(); + self.relatedTarget = trigger; + self.show(); + } + /** * Executes when user interacts with the toast without closing it, * usually by hovering or focusing it. * - * @this {HTMLElement | Element} + * @this {HTMLElement} * @param {MouseEvent} e the `Toast` instance */ function interactiveToastHandler(e) { const element = this; const self = getToastInstance(element); const { type, relatedTarget } = e; - // @ts-ignore + + /* istanbul ignore next: a solid filter is required */ if (!self || (element === relatedTarget || element.contains(relatedTarget))) return; if ([mouseenterEvent, focusinEvent].includes(type)) { @@ -5555,7 +5785,7 @@ /** Creates a new `Toast` instance. */ class Toast extends BaseComponent { /** - * @param {HTMLElement | Element | string} target the target `.toast` element + * @param {HTMLElement | string} target the target `.toast` element * @param {BSN.Options.Toast=} config the instance options */ constructor(target, config) { @@ -5567,10 +5797,16 @@ // set fadeClass, the options.animation will override the markup if (options.animation && !hasClass(element, fadeClass)) addClass(element, fadeClass); else if (!options.animation && hasClass(element, fadeClass)) removeClass(element, fadeClass); + // dismiss button - /** @type {(HTMLElement | Element)?} */ + /** @type {HTMLElement?} */ self.dismiss = querySelector(toastDismissSelector, element); + // toast can have multiple triggering elements + /** @type {HTMLElement[]} */ + self.triggers = [...querySelectorAll(toastToggleSelector, getDocument(element))] + .filter((btn) => getTargetElement(btn) === element); + // bind self.show = self.show.bind(self); self.hide = self.hide.bind(self); @@ -5582,23 +5818,28 @@ /* eslint-disable */ /** * Returns component name string. - * @readonly @static - */ + */ get name() { return toastComponent; } /** * Returns component default options. - * @readonly @static - */ + */ get defaults() { return toastDefaults; } /* eslint-enable */ + /** + * Returns *true* when toast is visible. + */ + get isShown() { return hasClass(this.element, showClass); } + // TOAST PUBLIC METHODS // ==================== /** Shows the toast. */ show() { const self = this; - const { element } = self; - if (element && !hasClass(element, showClass)) { + const { element, isShown } = self; + + /* istanbul ignore else */ + if (element && !isShown) { dispatchEvent(element, showToastEvent); if (showToastEvent.defaultPrevented) return; @@ -5609,9 +5850,10 @@ /** Hides the toast. */ hide() { const self = this; - const { element } = self; + const { element, isShown } = self; - if (element && hasClass(element, showClass)) { + /* istanbul ignore else */ + if (element && isShown) { dispatchEvent(element, hideToastEvent); if (hideToastEvent.defaultPrevented) return; hideToast(self); @@ -5621,9 +5863,10 @@ /** Removes the `Toast` component from the target element. */ dispose() { const self = this; - const { element } = self; + const { element, isShown } = self; - if (hasClass(element, showClass)) { + /* istanbul ignore else */ + if (isShown) { removeClass(element, showClass); } @@ -5642,7 +5885,7 @@ /** * Check if element matches a CSS selector. * - * @param {HTMLElement | Element} target + * @param {HTMLElement} target * @param {string} selector * @returns {boolean} */ @@ -5669,7 +5912,7 @@ /** * Initialize all matched `Element`s for one component. * @param {BSN.InitCallback} callback - * @param {NodeListOf | (HTMLElement | Element)[]} collection + * @param {NodeList | Node[]} collection */ function initComponentDataAPI(callback, collection) { [...collection].forEach((x) => callback(x)); @@ -5678,7 +5921,7 @@ /** * Remove one component from a target container element or all in the page. * @param {string} component the component name - * @param {(Element | HTMLElement | Document)=} context parent `Element` + * @param {ParentNode} context parent `Node` */ function removeComponentDataAPI(component, context) { const compData = Data.getAllFor(component); @@ -5686,18 +5929,17 @@ if (compData) { [...compData].forEach((x) => { const [element, instance] = x; - if (context && context.contains(element)) instance.dispose(); + if (context.contains(element)) instance.dispose(); }); } } /** * Initialize all BSN components for a target container. - * @param {(Element | HTMLElement | Document)=} context parent `Element` + * @param {ParentNode=} context parent `Node` */ function initCallback(context) { - const lookUp = context && parentNodes.some((x) => context instanceof x) - ? context : undefined; + const lookUp = context && context.nodeName ? context : document; const elemCollection = [...getElementsByTagName('*', lookUp)]; ObjectKeys(componentsList).forEach((comp) => { @@ -5708,11 +5950,10 @@ /** * Remove all BSN components for a target container. - * @param {(Element | HTMLElement | Document)=} context parent `Element` + * @param {ParentNode=} context parent `Node` */ function removeDataAPI(context) { - const lookUp = context && parentNodes.some((x) => context instanceof x) - ? context : undefined; + const lookUp = context && context.nodeName ? context : document; ObjectKeys(componentsList).forEach((comp) => { removeComponentDataAPI(comp, lookUp); @@ -5742,7 +5983,7 @@ initCallback, removeDataAPI, Version, - EventListener, + EventListener: Listener, }; return BSN; diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css index f16c5be8..ddf519cc 100644 --- a/src/static/scripts/bootstrap.css +++ b/src/static/scripts/bootstrap.css @@ -1,8 +1,8 @@ @charset "UTF-8"; /*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. + * Bootstrap v5.2.0-beta1 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ :root { @@ -16,6 +16,7 @@ --bs-green: #198754; --bs-teal: #20c997; --bs-cyan: #0dcaf0; + --bs-black: #000; --bs-white: #fff; --bs-gray: #6c757d; --bs-gray-dark: #343a40; @@ -48,7 +49,7 @@ --bs-black-rgb: 0, 0, 0; --bs-body-color-rgb: 33, 37, 41; --bs-body-bg-rgb: 255, 255, 255; - --bs-font-sans-serif: 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"; + --bs-font-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"; --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); --bs-body-font-family: var(--bs-font-sans-serif); @@ -57,6 +58,21 @@ --bs-body-line-height: 1.5; --bs-body-color: #212529; --bs-body-bg: #fff; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0.375rem; + --bs-border-radius-sm: 0.25rem; + --bs-border-radius-lg: 0.5rem; + --bs-border-radius-xl: 1rem; + --bs-border-radius-2xl: 2rem; + --bs-border-radius-pill: 50rem; + --bs-heading-color: ; + --bs-link-color: #0d6efd; + --bs-link-hover-color: #0a58ca; + --bs-code-color: #d63384; + --bs-highlight-bg: #fff3cd; } *, @@ -87,20 +103,17 @@ body { hr { margin: 1rem 0; color: inherit; - background-color: currentColor; border: 0; + border-top: 1px solid; opacity: 0.25; } -hr:not([size]) { - height: 1px; -} - h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 { margin-top: 0; margin-bottom: 0.5rem; font-weight: 500; line-height: 1.2; + color: var(--bs-heading-color); } h1, .h1 { @@ -152,8 +165,7 @@ p { margin-bottom: 1rem; } -abbr[title], -abbr[data-bs-original-title] { +abbr[title] { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; @@ -209,8 +221,8 @@ small, .small { } mark, .mark { - padding: 0.2em; - background-color: #fcf8e3; + padding: 0.1875em; + background-color: var(--bs-highlight-bg); } sub, @@ -230,11 +242,11 @@ sup { } a { - color: #0d6efd; + color: var(--bs-link-color); text-decoration: underline; } a:hover { - color: #0a58ca; + color: var(--bs-link-hover-color); } a:not([href]):not([class]), a:not([href]):not([class]):hover { @@ -248,8 +260,6 @@ kbd, samp { font-family: var(--bs-font-monospace); font-size: 1em; - direction: ltr /* rtl:ignore */; - unicode-bidi: bidi-override; } pre { @@ -267,7 +277,7 @@ pre code { code { font-size: 0.875em; - color: #d63384; + color: var(--bs-code-color); word-wrap: break-word; } a > code { @@ -275,16 +285,15 @@ a > code { } kbd { - padding: 0.2rem 0.4rem; + padding: 0.1875rem 0.375rem; font-size: 0.875em; - color: #fff; - background-color: #212529; - border-radius: 0.2rem; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); + border-radius: 0.25rem; } kbd kbd { padding: 0; font-size: 1em; - font-weight: 700; } figure { @@ -304,7 +313,7 @@ table { caption { padding-top: 0.5rem; padding-bottom: 0.5rem; - color: #6c757d; + color: rgba(var(--bs-body-color-rgb), 0.75); text-align: left; } @@ -363,8 +372,8 @@ select:disabled { opacity: 1; } -[list]::-webkit-calendar-picker-indicator { - display: none; +[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { + display: none !important; } button, @@ -450,14 +459,11 @@ legend + * { ::-webkit-file-upload-button { font: inherit; + -webkit-appearance: button; } ::file-selector-button { font: inherit; -} - -::-webkit-file-upload-button { - font: inherit; -webkit-appearance: button; } @@ -601,8 +607,8 @@ progress { .img-thumbnail { padding: 0.25rem; background-color: #fff; - border: 1px solid #dee2e6; - border-radius: 0.25rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.375rem; max-width: 100%; height: auto; } @@ -628,9 +634,11 @@ progress { .container-lg, .container-md, .container-sm { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; width: 100%; - padding-right: var(--bs-gutter-x, 0.75rem); - padding-left: var(--bs-gutter-x, 0.75rem); + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); margin-right: auto; margin-left: auto; } @@ -1952,19 +1960,21 @@ progress { } } .table { + --bs-table-color: var(--bs-body-color); --bs-table-bg: transparent; + --bs-table-border-color: var(--bs-border-color); --bs-table-accent-bg: transparent; - --bs-table-striped-color: #212529; + --bs-table-striped-color: var(--bs-body-color); --bs-table-striped-bg: rgba(0, 0, 0, 0.05); - --bs-table-active-color: #212529; + --bs-table-active-color: var(--bs-body-color); --bs-table-active-bg: rgba(0, 0, 0, 0.1); - --bs-table-hover-color: #212529; + --bs-table-hover-color: var(--bs-body-color); --bs-table-hover-bg: rgba(0, 0, 0, 0.075); width: 100%; margin-bottom: 1rem; - color: #212529; + color: var(--bs-table-color); vertical-align: top; - border-color: #dee2e6; + border-color: var(--bs-table-border-color); } .table > :not(caption) > * > * { padding: 0.5rem 0.5rem; @@ -1978,8 +1988,9 @@ progress { .table > thead { vertical-align: bottom; } -.table > :not(:first-child) { - border-top: 2px solid currentColor; + +.table-group-divider { + border-top: 2px solid currentcolor; } .caption-top { @@ -2009,6 +2020,11 @@ progress { color: var(--bs-table-striped-color); } +.table-striped-columns > :not(caption) > tr > :nth-child(even) { + --bs-table-accent-bg: var(--bs-table-striped-bg); + color: var(--bs-table-striped-color); +} + .table-active { --bs-table-accent-bg: var(--bs-table-active-bg); color: var(--bs-table-active-color); @@ -2020,99 +2036,115 @@ progress { } .table-primary { + --bs-table-color: #000; --bs-table-bg: #cfe2ff; + --bs-table-border-color: #bacbe6; --bs-table-striped-bg: #c5d7f2; --bs-table-striped-color: #000; --bs-table-active-bg: #bacbe6; --bs-table-active-color: #000; --bs-table-hover-bg: #bfd1ec; --bs-table-hover-color: #000; - color: #000; - border-color: #bacbe6; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-secondary { + --bs-table-color: #000; --bs-table-bg: #e2e3e5; + --bs-table-border-color: #cbccce; --bs-table-striped-bg: #d7d8da; --bs-table-striped-color: #000; --bs-table-active-bg: #cbccce; --bs-table-active-color: #000; --bs-table-hover-bg: #d1d2d4; --bs-table-hover-color: #000; - color: #000; - border-color: #cbccce; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-success { + --bs-table-color: #000; --bs-table-bg: #d1e7dd; + --bs-table-border-color: #bcd0c7; --bs-table-striped-bg: #c7dbd2; --bs-table-striped-color: #000; --bs-table-active-bg: #bcd0c7; --bs-table-active-color: #000; --bs-table-hover-bg: #c1d6cc; --bs-table-hover-color: #000; - color: #000; - border-color: #bcd0c7; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-info { + --bs-table-color: #000; --bs-table-bg: #cff4fc; + --bs-table-border-color: #badce3; --bs-table-striped-bg: #c5e8ef; --bs-table-striped-color: #000; --bs-table-active-bg: #badce3; --bs-table-active-color: #000; --bs-table-hover-bg: #bfe2e9; --bs-table-hover-color: #000; - color: #000; - border-color: #badce3; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-warning { + --bs-table-color: #000; --bs-table-bg: #fff3cd; + --bs-table-border-color: #e6dbb9; --bs-table-striped-bg: #f2e7c3; --bs-table-striped-color: #000; --bs-table-active-bg: #e6dbb9; --bs-table-active-color: #000; --bs-table-hover-bg: #ece1be; --bs-table-hover-color: #000; - color: #000; - border-color: #e6dbb9; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-danger { + --bs-table-color: #000; --bs-table-bg: #f8d7da; + --bs-table-border-color: #dfc2c4; --bs-table-striped-bg: #eccccf; --bs-table-striped-color: #000; --bs-table-active-bg: #dfc2c4; --bs-table-active-color: #000; --bs-table-hover-bg: #e5c7ca; --bs-table-hover-color: #000; - color: #000; - border-color: #dfc2c4; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-light { + --bs-table-color: #000; --bs-table-bg: #f8f9fa; + --bs-table-border-color: #dfe0e1; --bs-table-striped-bg: #ecedee; --bs-table-striped-color: #000; --bs-table-active-bg: #dfe0e1; --bs-table-active-color: #000; --bs-table-hover-bg: #e5e6e7; --bs-table-hover-color: #000; - color: #000; - border-color: #dfe0e1; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-dark { + --bs-table-color: #fff; --bs-table-bg: #212529; + --bs-table-border-color: #373b3e; --bs-table-striped-bg: #2c3034; --bs-table-striped-color: #fff; --bs-table-active-bg: #373b3e; --bs-table-active-color: #fff; --bs-table-hover-bg: #323539; --bs-table-hover-color: #fff; - color: #fff; - border-color: #373b3e; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); } .table-responsive { @@ -2177,7 +2209,7 @@ progress { .form-text { margin-top: 0.25rem; font-size: 0.875em; - color: #6c757d; + color: rgba(var(--bs-body-color-rgb), 0.75); } .form-control { @@ -2194,7 +2226,7 @@ progress { -webkit-appearance: none; -moz-appearance: none; appearance: none; - border-radius: 0.25rem; + border-radius: 0.375rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -2276,31 +2308,6 @@ progress { .form-control:hover:not(:disabled):not([readonly])::file-selector-button { background-color: #dde0e3; } -.form-control::-webkit-file-upload-button { - padding: 0.375rem 0.75rem; - margin: -0.375rem -0.75rem; - -webkit-margin-end: 0.75rem; - margin-inline-end: 0.75rem; - color: #212529; - background-color: #e9ecef; - pointer-events: none; - border-color: inherit; - border-style: solid; - border-width: 0; - border-inline-end-width: 1px; - border-radius: 0; - -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} -@media (prefers-reduced-motion: reduce) { - .form-control::-webkit-file-upload-button { - -webkit-transition: none; - transition: none; - } -} -.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { - background-color: #dde0e3; -} .form-control-plaintext { display: block; @@ -2322,7 +2329,7 @@ progress { min-height: calc(1.5em + 0.5rem + 2px); padding: 0.25rem 0.5rem; font-size: 0.875rem; - border-radius: 0.2rem; + border-radius: 0.25rem; } .form-control-sm::-webkit-file-upload-button { padding: 0.25rem 0.5rem; @@ -2336,18 +2343,12 @@ progress { -webkit-margin-end: 0.5rem; margin-inline-end: 0.5rem; } -.form-control-sm::-webkit-file-upload-button { - padding: 0.25rem 0.5rem; - margin: -0.25rem -0.5rem; - -webkit-margin-end: 0.5rem; - margin-inline-end: 0.5rem; -} .form-control-lg { min-height: calc(1.5em + 1rem + 2px); padding: 0.5rem 1rem; font-size: 1.25rem; - border-radius: 0.3rem; + border-radius: 0.5rem; } .form-control-lg::-webkit-file-upload-button { padding: 0.5rem 1rem; @@ -2361,12 +2362,6 @@ progress { -webkit-margin-end: 1rem; margin-inline-end: 1rem; } -.form-control-lg::-webkit-file-upload-button { - padding: 0.5rem 1rem; - margin: -0.5rem -1rem; - -webkit-margin-end: 1rem; - margin-inline-end: 1rem; -} textarea.form-control { min-height: calc(1.5em + 0.75rem + 2px); @@ -2388,11 +2383,11 @@ textarea.form-control-lg { } .form-control-color::-moz-color-swatch { height: 1.5em; - border-radius: 0.25rem; + border-radius: 0.375rem; } .form-control-color::-webkit-color-swatch { height: 1.5em; - border-radius: 0.25rem; + border-radius: 0.375rem; } .form-select { @@ -2405,12 +2400,12 @@ textarea.form-control-lg { line-height: 1.5; color: #212529; background-color: #fff; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; border: 1px solid #ced4da; - border-radius: 0.25rem; + border-radius: 0.375rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -webkit-appearance: none; -moz-appearance: none; @@ -2443,7 +2438,7 @@ textarea.form-control-lg { padding-bottom: 0.25rem; padding-left: 0.5rem; font-size: 0.875rem; - border-radius: 0.2rem; + border-radius: 0.25rem; } .form-select-lg { @@ -2451,7 +2446,7 @@ textarea.form-control-lg { padding-bottom: 0.5rem; padding-left: 1rem; font-size: 1.25rem; - border-radius: 0.3rem; + border-radius: 0.5rem; } .form-check { @@ -2465,6 +2460,17 @@ textarea.form-control-lg { margin-left: -1.5em; } +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right; +} +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0; +} + .form-check-input { width: 1em; height: 1em; @@ -2480,6 +2486,7 @@ textarea.form-control-lg { appearance: none; -webkit-print-color-adjust: exact; color-adjust: exact; + print-color-adjust: exact; } .form-check-input[type=checkbox] { border-radius: 0.25em; @@ -2500,7 +2507,7 @@ textarea.form-control-lg { border-color: #0d6efd; } .form-check-input:checked[type=checkbox] { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); } .form-check-input:checked[type=radio] { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); @@ -2516,6 +2523,7 @@ textarea.form-control-lg { opacity: 0.5; } .form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { + cursor: default; opacity: 0.5; } @@ -2542,6 +2550,14 @@ textarea.form-control-lg { background-position: right center; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0; +} +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0; +} .form-check-inline { display: inline-block; @@ -2653,6 +2669,7 @@ textarea.form-control-lg { position: relative; } .form-floating > .form-control, +.form-floating > .form-control-plaintext, .form-floating > .form-select { height: calc(3.5rem + 2px); line-height: 1.25; @@ -2673,24 +2690,29 @@ textarea.form-control-lg { transition: none; } } -.form-floating > .form-control { +.form-floating > .form-control, +.form-floating > .form-control-plaintext { padding: 1rem 0.75rem; } -.form-floating > .form-control::-moz-placeholder { +.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder { color: transparent; } -.form-floating > .form-control::placeholder { +.form-floating > .form-control::placeholder, +.form-floating > .form-control-plaintext::placeholder { color: transparent; } -.form-floating > .form-control:not(:-moz-placeholder-shown) { +.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; } -.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown) { +.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown), +.form-floating > .form-control-plaintext:focus, +.form-floating > .form-control-plaintext:not(:placeholder-shown) { padding-top: 1.625rem; padding-bottom: 0.625rem; } -.form-floating > .form-control:-webkit-autofill { +.form-floating > .form-control:-webkit-autofill, +.form-floating > .form-control-plaintext:-webkit-autofill { padding-top: 1.625rem; padding-bottom: 0.625rem; } @@ -2704,6 +2726,7 @@ textarea.form-control-lg { } .form-floating > .form-control:focus ~ label, .form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-control-plaintext ~ label, .form-floating > .form-select ~ label { opacity: 0.65; transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); @@ -2712,6 +2735,9 @@ textarea.form-control-lg { opacity: 0.65; transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); } +.form-floating > .form-control-plaintext ~ label { + border-width: 1px 0; +} .input-group { position: relative; @@ -2751,7 +2777,7 @@ textarea.form-control-lg { white-space: nowrap; background-color: #e9ecef; border: 1px solid #ced4da; - border-radius: 0.25rem; + border-radius: 0.375rem; } .input-group-lg > .form-control, @@ -2760,7 +2786,7 @@ textarea.form-control-lg { .input-group-lg > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; - border-radius: 0.3rem; + border-radius: 0.5rem; } .input-group-sm > .form-control, @@ -2769,7 +2795,7 @@ textarea.form-control-lg { .input-group-sm > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; - border-radius: 0.2rem; + border-radius: 0.25rem; } .input-group-lg > .form-select, @@ -2812,7 +2838,7 @@ textarea.form-control-lg { font-size: 0.875rem; color: #fff; background-color: rgba(25, 135, 84, 0.9); - border-radius: 0.25rem; + border-radius: 0.375rem; } .was-validated :valid ~ .valid-feedback, @@ -2825,7 +2851,7 @@ textarea.form-control-lg { .was-validated .form-control:valid, .form-control.is-valid { border-color: #198754; padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.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'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' 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'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right calc(0.375em + 0.1875rem) center; background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); @@ -2845,7 +2871,7 @@ textarea.form-control-lg { } .was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { padding-right: 4.125rem; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.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'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' 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'/%3e%3c/svg%3e"); background-position: right 0.75rem center, center right 2.25rem; background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } @@ -2854,6 +2880,10 @@ textarea.form-control-lg { box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25); } +.was-validated .form-control-color:valid, .form-control-color.is-valid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} + .was-validated .form-check-input:valid, .form-check-input.is-valid { border-color: #198754; } @@ -2901,7 +2931,7 @@ textarea.form-control-lg { font-size: 0.875rem; color: #fff; background-color: rgba(220, 53, 69, 0.9); - border-radius: 0.25rem; + border-radius: 0.375rem; } .was-validated :invalid ~ .invalid-feedback, @@ -2934,7 +2964,7 @@ textarea.form-control-lg { } .was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] { padding-right: 4.125rem; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); background-position: right 0.75rem center, center right 2.25rem; background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } @@ -2943,6 +2973,10 @@ textarea.form-control-lg { box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); } +.was-validated .form-control-color:invalid, .form-control-color.is-invalid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} + .was-validated .form-check-input:invalid, .form-check-input.is-invalid { border-color: #dc3545; } @@ -2972,10 +3006,27 @@ textarea.form-control-lg { } .btn { + --bs-btn-padding-x: 0.75rem; + --bs-btn-padding-y: 0.375rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 1rem; + --bs-btn-font-weight: 400; + --bs-btn-line-height: 1.5; + --bs-btn-color: #212529; + --bs-btn-bg: transparent; + --bs-btn-border-width: 1px; + --bs-btn-border-color: transparent; + --bs-btn-border-radius: 0.375rem; + --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + --bs-btn-disabled-opacity: 0.65; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); display: inline-block; - font-weight: 400; - line-height: 1.5; - color: #212529; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); text-align: center; text-decoration: none; vertical-align: middle; @@ -2983,11 +3034,9 @@ textarea.form-control-lg { -webkit-user-select: none; -moz-user-select: none; user-select: none; - background-color: transparent; - border: 1px solid transparent; - padding: 0.375rem 0.75rem; - font-size: 1rem; - border-radius: 0.25rem; + border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); + border-radius: var(--bs-btn-border-radius); + background-color: var(--bs-btn-bg); transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -2996,523 +3045,368 @@ textarea.form-control-lg { } } .btn:hover { - color: #212529; + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); } .btn-check:focus + .btn, .btn:focus { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); outline: 0; - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, .btn-check:active + .btn, .btn:active, .btn.active, .btn.show { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color); +} +.btn-check:checked + .btn:focus, .btn-check:active + .btn:focus, .btn:active:focus, .btn.active:focus, .btn.show:focus { + box-shadow: var(--bs-btn-focus-box-shadow); } .btn:disabled, .btn.disabled, fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); pointer-events: none; - opacity: 0.65; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity); } .btn-primary { - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; -} -.btn-primary:hover { - color: #fff; - background-color: #0b5ed7; - border-color: #0a58ca; -} -.btn-check:focus + .btn-primary, .btn-primary:focus { - color: #fff; - background-color: #0b5ed7; - border-color: #0a58ca; - box-shadow: 0 0 0 0.25rem rgba(49, 132, 253, 0.5); -} -.btn-check:checked + .btn-primary, .btn-check:active + .btn-primary, .btn-primary:active, .btn-primary.active, .show > .btn-primary.dropdown-toggle { - color: #fff; - background-color: #0a58ca; - border-color: #0a53be; -} -.btn-check:checked + .btn-primary:focus, .btn-check:active + .btn-primary:focus, .btn-primary:active:focus, .btn-primary.active:focus, .show > .btn-primary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(49, 132, 253, 0.5); -} -.btn-primary:disabled, .btn-primary.disabled { - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; + --bs-btn-color: #fff; + --bs-btn-bg: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0b5ed7; + --bs-btn-hover-border-color: #0a58ca; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0a58ca; + --bs-btn-active-border-color: #0a53be; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #0d6efd; + --bs-btn-disabled-border-color: #0d6efd; } .btn-secondary { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; -} -.btn-secondary:hover { - color: #fff; - background-color: #5c636a; - border-color: #565e64; -} -.btn-check:focus + .btn-secondary, .btn-secondary:focus { - color: #fff; - background-color: #5c636a; - border-color: #565e64; - box-shadow: 0 0 0 0.25rem rgba(130, 138, 145, 0.5); -} -.btn-check:checked + .btn-secondary, .btn-check:active + .btn-secondary, .btn-secondary:active, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle { - color: #fff; - background-color: #565e64; - border-color: #51585e; -} -.btn-check:checked + .btn-secondary:focus, .btn-check:active + .btn-secondary:focus, .btn-secondary:active:focus, .btn-secondary.active:focus, .show > .btn-secondary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(130, 138, 145, 0.5); -} -.btn-secondary:disabled, .btn-secondary.disabled { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; + --bs-btn-color: #fff; + --bs-btn-bg: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #5c636a; + --bs-btn-hover-border-color: #565e64; + --bs-btn-focus-shadow-rgb: 130, 138, 145; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #565e64; + --bs-btn-active-border-color: #51585e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #6c757d; + --bs-btn-disabled-border-color: #6c757d; } .btn-success { - color: #fff; - background-color: #198754; - border-color: #198754; -} -.btn-success:hover { - color: #fff; - background-color: #157347; - border-color: #146c43; -} -.btn-check:focus + .btn-success, .btn-success:focus { - color: #fff; - background-color: #157347; - border-color: #146c43; - box-shadow: 0 0 0 0.25rem rgba(60, 153, 110, 0.5); -} -.btn-check:checked + .btn-success, .btn-check:active + .btn-success, .btn-success:active, .btn-success.active, .show > .btn-success.dropdown-toggle { - color: #fff; - background-color: #146c43; - border-color: #13653f; -} -.btn-check:checked + .btn-success:focus, .btn-check:active + .btn-success:focus, .btn-success:active:focus, .btn-success.active:focus, .show > .btn-success.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(60, 153, 110, 0.5); -} -.btn-success:disabled, .btn-success.disabled { - color: #fff; - background-color: #198754; - border-color: #198754; + --bs-btn-color: #fff; + --bs-btn-bg: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #157347; + --bs-btn-hover-border-color: #146c43; + --bs-btn-focus-shadow-rgb: 60, 153, 110; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #146c43; + --bs-btn-active-border-color: #13653f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #198754; + --bs-btn-disabled-border-color: #198754; } .btn-info { - color: #000; - background-color: #0dcaf0; - border-color: #0dcaf0; -} -.btn-info:hover { - color: #000; - background-color: #31d2f2; - border-color: #25cff2; -} -.btn-check:focus + .btn-info, .btn-info:focus { - color: #000; - background-color: #31d2f2; - border-color: #25cff2; - box-shadow: 0 0 0 0.25rem rgba(11, 172, 204, 0.5); -} -.btn-check:checked + .btn-info, .btn-check:active + .btn-info, .btn-info:active, .btn-info.active, .show > .btn-info.dropdown-toggle { - color: #000; - background-color: #3dd5f3; - border-color: #25cff2; -} -.btn-check:checked + .btn-info:focus, .btn-check:active + .btn-info:focus, .btn-info:active:focus, .btn-info.active:focus, .show > .btn-info.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(11, 172, 204, 0.5); -} -.btn-info:disabled, .btn-info.disabled { - color: #000; - background-color: #0dcaf0; - border-color: #0dcaf0; + --bs-btn-color: #000; + --bs-btn-bg: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #31d2f2; + --bs-btn-hover-border-color: #25cff2; + --bs-btn-focus-shadow-rgb: 11, 172, 204; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #3dd5f3; + --bs-btn-active-border-color: #25cff2; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #0dcaf0; + --bs-btn-disabled-border-color: #0dcaf0; } .btn-warning { - color: #000; - background-color: #ffc107; - border-color: #ffc107; -} -.btn-warning:hover { - color: #000; - background-color: #ffca2c; - border-color: #ffc720; -} -.btn-check:focus + .btn-warning, .btn-warning:focus { - color: #000; - background-color: #ffca2c; - border-color: #ffc720; - box-shadow: 0 0 0 0.25rem rgba(217, 164, 6, 0.5); -} -.btn-check:checked + .btn-warning, .btn-check:active + .btn-warning, .btn-warning:active, .btn-warning.active, .show > .btn-warning.dropdown-toggle { - color: #000; - background-color: #ffcd39; - border-color: #ffc720; -} -.btn-check:checked + .btn-warning:focus, .btn-check:active + .btn-warning:focus, .btn-warning:active:focus, .btn-warning.active:focus, .show > .btn-warning.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(217, 164, 6, 0.5); -} -.btn-warning:disabled, .btn-warning.disabled { - color: #000; - background-color: #ffc107; - border-color: #ffc107; + --bs-btn-color: #000; + --bs-btn-bg: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffca2c; + --bs-btn-hover-border-color: #ffc720; + --bs-btn-focus-shadow-rgb: 217, 164, 6; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffcd39; + --bs-btn-active-border-color: #ffc720; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ffc107; + --bs-btn-disabled-border-color: #ffc107; } .btn-danger { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; -} -.btn-danger:hover { - color: #fff; - background-color: #bb2d3b; - border-color: #b02a37; + --bs-btn-color: #fff; + --bs-btn-bg: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #bb2d3b; + --bs-btn-hover-border-color: #b02a37; + --bs-btn-focus-shadow-rgb: 225, 83, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #b02a37; + --bs-btn-active-border-color: #a52834; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #dc3545; + --bs-btn-disabled-border-color: #dc3545; } -.btn-check:focus + .btn-danger, .btn-danger:focus { - color: #fff; - background-color: #bb2d3b; - border-color: #b02a37; - box-shadow: 0 0 0 0.25rem rgba(225, 83, 97, 0.5); + +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f9fafb; + --bs-btn-hover-border-color: #f9fafb; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f9fafb; + --bs-btn-active-border-color: #f9fafb; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa; } -.btn-check:checked + .btn-danger, .btn-check:active + .btn-danger, .btn-danger:active, .btn-danger.active, .show > .btn-danger.dropdown-toggle { - color: #fff; - background-color: #b02a37; - border-color: #a52834; + +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #1c1f23; + --bs-btn-hover-border-color: #1a1e21; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #1a1e21; + --bs-btn-active-border-color: #191c1f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529; } -.btn-check:checked + .btn-danger:focus, .btn-check:active + .btn-danger:focus, .btn-danger:active:focus, .btn-danger.active:focus, .show > .btn-danger.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(225, 83, 97, 0.5); + +.btn-outline-primary { + --bs-btn-color: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0d6efd; + --bs-btn-hover-border-color: #0d6efd; + --bs-btn-focus-shadow-rgb: 13, 110, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0d6efd; + --bs-btn-active-border-color: #0d6efd; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0d6efd; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-danger:disabled, .btn-danger.disabled { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; + +.btn-outline-secondary { + --bs-btn-color: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #6c757d; + --bs-btn-hover-border-color: #6c757d; + --bs-btn-focus-shadow-rgb: 108, 117, 125; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #6c757d; + --bs-btn-active-border-color: #6c757d; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-light { - color: #000; - background-color: #f8f9fa; - border-color: #f8f9fa; +.btn-outline-success { + --bs-btn-color: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #198754; + --bs-btn-hover-border-color: #198754; + --bs-btn-focus-shadow-rgb: 25, 135, 84; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #198754; + --bs-btn-active-border-color: #198754; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #198754; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-light:hover { - color: #000; - background-color: #f9fafb; - border-color: #f9fafb; + +.btn-outline-info { + --bs-btn-color: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #0dcaf0; + --bs-btn-hover-border-color: #0dcaf0; + --bs-btn-focus-shadow-rgb: 13, 202, 240; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #0dcaf0; + --bs-btn-active-border-color: #0dcaf0; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0dcaf0; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-check:focus + .btn-light, .btn-light:focus { - color: #000; - background-color: #f9fafb; - border-color: #f9fafb; - box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); + +.btn-outline-warning { + --bs-btn-color: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffc107; + --bs-btn-hover-border-color: #ffc107; + --bs-btn-focus-shadow-rgb: 255, 193, 7; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffc107; + --bs-btn-active-border-color: #ffc107; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #ffc107; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-check:checked + .btn-light, .btn-check:active + .btn-light, .btn-light:active, .btn-light.active, .show > .btn-light.dropdown-toggle { - color: #000; - background-color: #f9fafb; - border-color: #f9fafb; + +.btn-outline-danger { + --bs-btn-color: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #dc3545; + --bs-btn-hover-border-color: #dc3545; + --bs-btn-focus-shadow-rgb: 220, 53, 69; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #dc3545; + --bs-btn-active-border-color: #dc3545; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #dc3545; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-check:checked + .btn-light:focus, .btn-check:active + .btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show > .btn-light.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5); + +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-light:disabled, .btn-light.disabled { - color: #000; - background-color: #f8f9fa; - border-color: #f8f9fa; + +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-gradient: none; } -.btn-dark { - color: #fff; - background-color: #212529; - border-color: #212529; +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: none; + text-decoration: underline; } -.btn-dark:hover { - color: #fff; - background-color: #1c1f23; - border-color: #1a1e21; +.btn-lg, .btn-group-lg > .btn { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 1.25rem; + --bs-btn-border-radius: 0.5rem; } -.btn-check:focus + .btn-dark, .btn-dark:focus { - color: #fff; - background-color: #1c1f23; - border-color: #1a1e21; - box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); + +.btn-sm, .btn-group-sm > .btn { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: 0.25rem; } -.btn-check:checked + .btn-dark, .btn-check:active + .btn-dark, .btn-dark:active, .btn-dark.active, .show > .btn-dark.dropdown-toggle { - color: #fff; - background-color: #1a1e21; - border-color: #191c1f; + +.fade { + transition: opacity 0.15s linear; } -.btn-check:checked + .btn-dark:focus, .btn-check:active + .btn-dark:focus, .btn-dark:active:focus, .btn-dark.active:focus, .show > .btn-dark.dropdown-toggle:focus { - box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5); +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } } -.btn-dark:disabled, .btn-dark.disabled { - color: #fff; - background-color: #212529; - border-color: #212529; +.fade:not(.show) { + opacity: 0; } -.btn-outline-primary { - color: #0d6efd; - border-color: #0d6efd; +.collapse:not(.show) { + display: none; } -.btn-outline-primary:hover { - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; + +.collapsing { + height: 0; + overflow: hidden; + transition: height 0.35s ease; } -.btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus { - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.5); +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } } -.btn-check:checked + .btn-outline-primary, .btn-check:active + .btn-outline-primary, .btn-outline-primary:active, .btn-outline-primary.active, .btn-outline-primary.dropdown-toggle.show { - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width 0.35s ease; } -.btn-check:checked + .btn-outline-primary:focus, .btn-check:active + .btn-outline-primary:focus, .btn-outline-primary:active:focus, .btn-outline-primary.active:focus, .btn-outline-primary.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.5); +@media (prefers-reduced-motion: reduce) { + .collapsing.collapse-horizontal { + transition: none; + } } -.btn-outline-primary:disabled, .btn-outline-primary.disabled { - color: #0d6efd; - background-color: transparent; -} - -.btn-outline-secondary { - color: #6c757d; - border-color: #6c757d; -} -.btn-outline-secondary:hover { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; -} -.btn-check:focus + .btn-outline-secondary, .btn-outline-secondary:focus { - box-shadow: 0 0 0 0.25rem rgba(108, 117, 125, 0.5); -} -.btn-check:checked + .btn-outline-secondary, .btn-check:active + .btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; -} -.btn-check:checked + .btn-outline-secondary:focus, .btn-check:active + .btn-outline-secondary:focus, .btn-outline-secondary:active:focus, .btn-outline-secondary.active:focus, .btn-outline-secondary.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(108, 117, 125, 0.5); -} -.btn-outline-secondary:disabled, .btn-outline-secondary.disabled { - color: #6c757d; - background-color: transparent; -} - -.btn-outline-success { - color: #198754; - border-color: #198754; -} -.btn-outline-success:hover { - color: #fff; - background-color: #198754; - border-color: #198754; -} -.btn-check:focus + .btn-outline-success, .btn-outline-success:focus { - box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.5); -} -.btn-check:checked + .btn-outline-success, .btn-check:active + .btn-outline-success, .btn-outline-success:active, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show { - color: #fff; - background-color: #198754; - border-color: #198754; -} -.btn-check:checked + .btn-outline-success:focus, .btn-check:active + .btn-outline-success:focus, .btn-outline-success:active:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.5); -} -.btn-outline-success:disabled, .btn-outline-success.disabled { - color: #198754; - background-color: transparent; -} - -.btn-outline-info { - color: #0dcaf0; - border-color: #0dcaf0; -} -.btn-outline-info:hover { - color: #000; - background-color: #0dcaf0; - border-color: #0dcaf0; -} -.btn-check:focus + .btn-outline-info, .btn-outline-info:focus { - box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.5); -} -.btn-check:checked + .btn-outline-info, .btn-check:active + .btn-outline-info, .btn-outline-info:active, .btn-outline-info.active, .btn-outline-info.dropdown-toggle.show { - color: #000; - background-color: #0dcaf0; - border-color: #0dcaf0; -} -.btn-check:checked + .btn-outline-info:focus, .btn-check:active + .btn-outline-info:focus, .btn-outline-info:active:focus, .btn-outline-info.active:focus, .btn-outline-info.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.5); -} -.btn-outline-info:disabled, .btn-outline-info.disabled { - color: #0dcaf0; - background-color: transparent; -} - -.btn-outline-warning { - color: #ffc107; - border-color: #ffc107; -} -.btn-outline-warning:hover { - color: #000; - background-color: #ffc107; - border-color: #ffc107; -} -.btn-check:focus + .btn-outline-warning, .btn-outline-warning:focus { - box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.5); -} -.btn-check:checked + .btn-outline-warning, .btn-check:active + .btn-outline-warning, .btn-outline-warning:active, .btn-outline-warning.active, .btn-outline-warning.dropdown-toggle.show { - color: #000; - background-color: #ffc107; - border-color: #ffc107; -} -.btn-check:checked + .btn-outline-warning:focus, .btn-check:active + .btn-outline-warning:focus, .btn-outline-warning:active:focus, .btn-outline-warning.active:focus, .btn-outline-warning.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.5); -} -.btn-outline-warning:disabled, .btn-outline-warning.disabled { - color: #ffc107; - background-color: transparent; -} - -.btn-outline-danger { - color: #dc3545; - border-color: #dc3545; -} -.btn-outline-danger:hover { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; -} -.btn-check:focus + .btn-outline-danger, .btn-outline-danger:focus { - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5); -} -.btn-check:checked + .btn-outline-danger, .btn-check:active + .btn-outline-danger, .btn-outline-danger:active, .btn-outline-danger.active, .btn-outline-danger.dropdown-toggle.show { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; -} -.btn-check:checked + .btn-outline-danger:focus, .btn-check:active + .btn-outline-danger:focus, .btn-outline-danger:active:focus, .btn-outline-danger.active:focus, .btn-outline-danger.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5); -} -.btn-outline-danger:disabled, .btn-outline-danger.disabled { - color: #dc3545; - background-color: transparent; -} - -.btn-outline-light { - color: #f8f9fa; - border-color: #f8f9fa; -} -.btn-outline-light:hover { - color: #000; - background-color: #f8f9fa; - border-color: #f8f9fa; -} -.btn-check:focus + .btn-outline-light, .btn-outline-light:focus { - box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); -} -.btn-check:checked + .btn-outline-light, .btn-check:active + .btn-outline-light, .btn-outline-light:active, .btn-outline-light.active, .btn-outline-light.dropdown-toggle.show { - color: #000; - background-color: #f8f9fa; - border-color: #f8f9fa; -} -.btn-check:checked + .btn-outline-light:focus, .btn-check:active + .btn-outline-light:focus, .btn-outline-light:active:focus, .btn-outline-light.active:focus, .btn-outline-light.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5); -} -.btn-outline-light:disabled, .btn-outline-light.disabled { - color: #f8f9fa; - background-color: transparent; -} - -.btn-outline-dark { - color: #212529; - border-color: #212529; -} -.btn-outline-dark:hover { - color: #fff; - background-color: #212529; - border-color: #212529; -} -.btn-check:focus + .btn-outline-dark, .btn-outline-dark:focus { - box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); -} -.btn-check:checked + .btn-outline-dark, .btn-check:active + .btn-outline-dark, .btn-outline-dark:active, .btn-outline-dark.active, .btn-outline-dark.dropdown-toggle.show { - color: #fff; - background-color: #212529; - border-color: #212529; -} -.btn-check:checked + .btn-outline-dark:focus, .btn-check:active + .btn-outline-dark:focus, .btn-outline-dark:active:focus, .btn-outline-dark.active:focus, .btn-outline-dark.dropdown-toggle.show:focus { - box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5); -} -.btn-outline-dark:disabled, .btn-outline-dark.disabled { - color: #212529; - background-color: transparent; -} - -.btn-link { - font-weight: 400; - color: #0d6efd; - text-decoration: underline; -} -.btn-link:hover { - color: #0a58ca; -} -.btn-link:disabled, .btn-link.disabled { - color: #6c757d; -} - -.btn-lg, .btn-group-lg > .btn { - padding: 0.5rem 1rem; - font-size: 1.25rem; - border-radius: 0.3rem; -} - -.btn-sm, .btn-group-sm > .btn { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - border-radius: 0.2rem; -} - -.fade { - transition: opacity 0.15s linear; -} -@media (prefers-reduced-motion: reduce) { - .fade { - transition: none; - } -} -.fade:not(.show) { - opacity: 0; -} - -.collapse:not(.show) { - display: none; -} - -.collapsing { - height: 0; - overflow: hidden; - transition: height 0.35s ease; -} -@media (prefers-reduced-motion: reduce) { - .collapsing { - transition: none; - } -} -.collapsing.collapse-horizontal { - width: 0; - height: auto; - transition: width 0.35s ease; -} -@media (prefers-reduced-motion: reduce) { - .collapsing.collapse-horizontal { - transition: none; - } -} - -.dropup, -.dropend, -.dropdown, -.dropstart { - position: relative; + +.dropup, +.dropend, +.dropdown, +.dropstart, +.dropup-center, +.dropdown-center { + position: relative; } .dropdown-toggle { @@ -3533,25 +3427,50 @@ textarea.form-control-lg { } .dropdown-menu { + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: #212529; + --bs-dropdown-bg: #fff; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: 0.375rem; + --bs-dropdown-border-width: 1px; + --bs-dropdown-inner-border-radius: calc(0.375rem - 1px); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-dropdown-link-color: #212529; + --bs-dropdown-link-hover-color: #1e2125; + --bs-dropdown-link-hover-bg: #e9ecef; + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; position: absolute; z-index: 1000; display: none; - min-width: 10rem; - padding: 0.5rem 0; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); margin: 0; - font-size: 1rem; - color: #212529; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); text-align: left; list-style: none; - background-color: #fff; + background-color: var(--bs-dropdown-bg); background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 0.25rem; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); } .dropdown-menu[data-bs-popper] { top: 100%; left: 0; - margin-top: 0.125rem; + margin-top: var(--bs-dropdown-spacer); } .dropdown-menu-start { @@ -3659,7 +3578,7 @@ textarea.form-control-lg { top: auto; bottom: 100%; margin-top: 0; - margin-bottom: 0.125rem; + margin-bottom: var(--bs-dropdown-spacer); } .dropup .dropdown-toggle::after { display: inline-block; @@ -3680,7 +3599,7 @@ textarea.form-control-lg { right: auto; left: 100%; margin-top: 0; - margin-left: 0.125rem; + margin-left: var(--bs-dropdown-spacer); } .dropend .dropdown-toggle::after { display: inline-block; @@ -3704,7 +3623,7 @@ textarea.form-control-lg { right: 100%; left: auto; margin-top: 0; - margin-right: 0.125rem; + margin-right: var(--bs-dropdown-spacer); } .dropstart .dropdown-toggle::after { display: inline-block; @@ -3733,18 +3652,19 @@ textarea.form-control-lg { .dropdown-divider { height: 0; - margin: 0.5rem 0; + margin: var(--bs-dropdown-divider-margin-y) 0; overflow: hidden; - border-top: 1px solid rgba(0, 0, 0, 0.15); + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1; } .dropdown-item { display: block; width: 100%; - padding: 0.25rem 1rem; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); clear: both; font-weight: 400; - color: #212529; + color: var(--bs-dropdown-link-color); text-align: inherit; text-decoration: none; white-space: nowrap; @@ -3752,16 +3672,16 @@ textarea.form-control-lg { border: 0; } .dropdown-item:hover, .dropdown-item:focus { - color: #1e2125; - background-color: #e9ecef; + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); } .dropdown-item.active, .dropdown-item:active { - color: #fff; + color: var(--bs-dropdown-link-active-color); text-decoration: none; - background-color: #0d6efd; + background-color: var(--bs-dropdown-link-active-bg); } .dropdown-item.disabled, .dropdown-item:disabled { - color: #adb5bd; + color: var(--bs-dropdown-link-disabled-color); pointer-events: none; background-color: transparent; } @@ -3772,46 +3692,32 @@ textarea.form-control-lg { .dropdown-header { display: block; - padding: 0.5rem 1rem; + padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x); margin-bottom: 0; font-size: 0.875rem; - color: #6c757d; + color: var(--bs-dropdown-header-color); white-space: nowrap; } .dropdown-item-text { display: block; - padding: 0.25rem 1rem; - color: #212529; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color); } .dropdown-menu-dark { - color: #dee2e6; - background-color: #343a40; - border-color: rgba(0, 0, 0, 0.15); -} -.dropdown-menu-dark .dropdown-item { - color: #dee2e6; -} -.dropdown-menu-dark .dropdown-item:hover, .dropdown-menu-dark .dropdown-item:focus { - color: #fff; - background-color: rgba(255, 255, 255, 0.15); -} -.dropdown-menu-dark .dropdown-item.active, .dropdown-menu-dark .dropdown-item:active { - color: #fff; - background-color: #0d6efd; -} -.dropdown-menu-dark .dropdown-item.disabled, .dropdown-menu-dark .dropdown-item:disabled { - color: #adb5bd; -} -.dropdown-menu-dark .dropdown-divider { - border-color: rgba(0, 0, 0, 0.15); -} -.dropdown-menu-dark .dropdown-item-text { - color: #dee2e6; -} -.dropdown-menu-dark .dropdown-header { - color: #adb5bd; + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd; } .btn-group, @@ -3849,11 +3755,15 @@ textarea.form-control-lg { width: auto; } +.btn-group { + border-radius: 0.375rem; +} .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) { margin-left: -1px; } .btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn.dropdown-toggle-split:first-child, .btn-group > .btn-group:not(:last-child) > .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; @@ -3911,6 +3821,12 @@ textarea.form-control-lg { } .nav { + --bs-nav-link-padding-x: 1rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-link-color); + --bs-nav-link-hover-color: var(--bs-link-hover-color); + --bs-nav-link-disabled-color: #6c757d; display: flex; flex-wrap: wrap; padding-left: 0; @@ -3920,8 +3836,10 @@ textarea.form-control-lg { .nav-link { display: block; - padding: 0.5rem 1rem; - color: #0d6efd; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); text-decoration: none; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; } @@ -3931,54 +3849,71 @@ textarea.form-control-lg { } } .nav-link:hover, .nav-link:focus { - color: #0a58ca; + color: var(--bs-nav-link-hover-color); } .nav-link.disabled { - color: #6c757d; + color: var(--bs-nav-link-disabled-color); pointer-events: none; cursor: default; } .nav-tabs { - border-bottom: 1px solid #dee2e6; + --bs-nav-tabs-border-width: 1px; + --bs-nav-tabs-border-color: #dee2e6; + --bs-nav-tabs-border-radius: 0.375rem; + --bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #dee2e6; + --bs-nav-tabs-link-active-color: #495057; + --bs-nav-tabs-link-active-bg: #fff; + --bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff; + border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color); } .nav-tabs .nav-link { - margin-bottom: -1px; + margin-bottom: calc(var(--bs-nav-tabs-border-width) * -1); background: none; - border: 1px solid transparent; - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; + border: var(--bs-nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--bs-nav-tabs-border-radius); + border-top-right-radius: var(--bs-nav-tabs-border-radius); } .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { - border-color: #e9ecef #e9ecef #dee2e6; isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color); } -.nav-tabs .nav-link.disabled { - color: #6c757d; +.nav-tabs .nav-link.disabled, .nav-tabs .nav-link:disabled { + color: var(--bs-nav-link-disabled-color); background-color: transparent; border-color: transparent; } .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { - color: #495057; - background-color: #fff; - border-color: #dee2e6 #dee2e6 #fff; + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color); } .nav-tabs .dropdown-menu { - margin-top: -1px; + margin-top: calc(var(--bs-nav-tabs-border-width) * -1); border-top-left-radius: 0; border-top-right-radius: 0; } +.nav-pills { + --bs-nav-pills-border-radius: 0.375rem; + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #0d6efd; +} .nav-pills .nav-link { background: none; border: 0; - border-radius: 0.25rem; + border-radius: var(--bs-nav-pills-border-radius); +} +.nav-pills .nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + background-color: transparent; + border-color: transparent; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { - color: #fff; - background-color: #0d6efd; + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg); } .nav-fill > .nav-link, @@ -4007,13 +3942,32 @@ textarea.form-control-lg { } .navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(0, 0, 0, 0.55); + --bs-navbar-hover-color: rgba(0, 0, 0, 0.7); + --bs-navbar-disabled-color: rgba(0, 0, 0, 0.3); + --bs-navbar-active-color: rgba(0, 0, 0, 0.9); + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: rgba(0, 0, 0, 0.9); + --bs-navbar-brand-hover-color: rgba(0, 0, 0, 0.9); + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(0, 0, 0, 0.1); + --bs-navbar-toggler-border-radius: 0.375rem; + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; position: relative; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; - padding-top: 0.5rem; - padding-bottom: 0.5rem; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); } .navbar > .container, .navbar > .container-fluid, @@ -4028,23 +3982,33 @@ textarea.form-control-lg { justify-content: space-between; } .navbar-brand { - padding-top: 0.3125rem; - padding-bottom: 0.3125rem; - margin-right: 1rem; - font-size: 1.25rem; + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); text-decoration: none; white-space: nowrap; } +.navbar-brand:hover, .navbar-brand:focus { + color: var(--bs-navbar-brand-hover-color); +} + .navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); display: flex; flex-direction: column; padding-left: 0; margin-bottom: 0; list-style: none; } -.navbar-nav .nav-link { - padding-right: 0; - padding-left: 0; +.navbar-nav .show > .nav-link, +.navbar-nav .nav-link.active { + color: var(--bs-navbar-active-color); } .navbar-nav .dropdown-menu { position: static; @@ -4053,6 +4017,12 @@ textarea.form-control-lg { .navbar-text { padding-top: 0.5rem; padding-bottom: 0.5rem; + color: var(--bs-navbar-color); +} +.navbar-text a, +.navbar-text a:hover, +.navbar-text a:focus { + color: var(--bs-navbar-active-color); } .navbar-collapse { @@ -4062,13 +4032,14 @@ textarea.form-control-lg { } .navbar-toggler { - padding: 0.25rem 0.75rem; - font-size: 1.25rem; + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); line-height: 1; + color: var(--bs-navbar-color); background-color: transparent; - border: 1px solid transparent; - border-radius: 0.25rem; - transition: box-shadow 0.15s ease-in-out; + border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition); } @media (prefers-reduced-motion: reduce) { .navbar-toggler { @@ -4081,7 +4052,7 @@ textarea.form-control-lg { .navbar-toggler:focus { text-decoration: none; outline: 0; - box-shadow: 0 0 0 0.25rem; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); } .navbar-toggler-icon { @@ -4089,6 +4060,7 @@ textarea.form-control-lg { width: 1.5em; height: 1.5em; vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); background-repeat: no-repeat; background-position: center; background-size: 100%; @@ -4111,8 +4083,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand-sm .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand-sm .navbar-nav-scroll { overflow: visible; @@ -4124,28 +4096,22 @@ textarea.form-control-lg { .navbar-expand-sm .navbar-toggler { display: none; } - .navbar-expand-sm .offcanvas-header { - display: none; - } .navbar-expand-sm .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } - .navbar-expand-sm .offcanvas-top, -.navbar-expand-sm .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none; } - .navbar-expand-sm .offcanvas-body { + .navbar-expand-sm .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; @@ -4164,8 +4130,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand-md .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand-md .navbar-nav-scroll { overflow: visible; @@ -4177,28 +4143,22 @@ textarea.form-control-lg { .navbar-expand-md .navbar-toggler { display: none; } - .navbar-expand-md .offcanvas-header { - display: none; - } .navbar-expand-md .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } - .navbar-expand-md .offcanvas-top, -.navbar-expand-md .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; + .navbar-expand-md .offcanvas .offcanvas-header { + display: none; } - .navbar-expand-md .offcanvas-body { + .navbar-expand-md .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; @@ -4217,8 +4177,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand-lg .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand-lg .navbar-nav-scroll { overflow: visible; @@ -4230,28 +4190,22 @@ textarea.form-control-lg { .navbar-expand-lg .navbar-toggler { display: none; } - .navbar-expand-lg .offcanvas-header { - display: none; - } .navbar-expand-lg .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } - .navbar-expand-lg .offcanvas-top, -.navbar-expand-lg .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none; } - .navbar-expand-lg .offcanvas-body { + .navbar-expand-lg .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; @@ -4270,8 +4224,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand-xl .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand-xl .navbar-nav-scroll { overflow: visible; @@ -4283,28 +4237,22 @@ textarea.form-control-lg { .navbar-expand-xl .navbar-toggler { display: none; } - .navbar-expand-xl .offcanvas-header { - display: none; - } .navbar-expand-xl .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } - .navbar-expand-xl .offcanvas-top, -.navbar-expand-xl .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none; } - .navbar-expand-xl .offcanvas-body { + .navbar-expand-xl .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; @@ -4323,8 +4271,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand-xxl .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand-xxl .navbar-nav-scroll { overflow: visible; @@ -4336,28 +4284,22 @@ textarea.form-control-lg { .navbar-expand-xxl .navbar-toggler { display: none; } - .navbar-expand-xxl .offcanvas-header { - display: none; - } .navbar-expand-xxl .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } - .navbar-expand-xxl .offcanvas-top, -.navbar-expand-xxl .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none; } - .navbar-expand-xxl .offcanvas-body { + .navbar-expand-xxl .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; @@ -4375,8 +4317,8 @@ textarea.form-control-lg { position: absolute; } .navbar-expand .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); } .navbar-expand .navbar-nav-scroll { overflow: visible; @@ -4388,114 +4330,67 @@ textarea.form-control-lg { .navbar-expand .navbar-toggler { display: none; } -.navbar-expand .offcanvas-header { - display: none; -} .navbar-expand .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; + position: static; + z-index: auto; flex-grow: 1; + width: auto !important; + height: auto !important; visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; + background-color: transparent !important; + border: 0 !important; + transform: none !important; transition: none; - transform: none; } -.navbar-expand .offcanvas-top, -.navbar-expand .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; +.navbar-expand .offcanvas .offcanvas-header { + display: none; } -.navbar-expand .offcanvas-body { +.navbar-expand .offcanvas .offcanvas-body { display: flex; flex-grow: 0; padding: 0; overflow-y: visible; } -.navbar-light .navbar-brand { - color: rgba(0, 0, 0, 0.9); -} -.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { - color: rgba(0, 0, 0, 0.9); -} -.navbar-light .navbar-nav .nav-link { - color: rgba(0, 0, 0, 0.55); -} -.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { - color: rgba(0, 0, 0, 0.7); -} -.navbar-light .navbar-nav .nav-link.disabled { - color: rgba(0, 0, 0, 0.3); -} -.navbar-light .navbar-nav .show > .nav-link, -.navbar-light .navbar-nav .nav-link.active { - color: rgba(0, 0, 0, 0.9); -} -.navbar-light .navbar-toggler { - color: rgba(0, 0, 0, 0.55); - border-color: rgba(0, 0, 0, 0.1); -} -.navbar-light .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); -} -.navbar-light .navbar-text { - color: rgba(0, 0, 0, 0.55); -} -.navbar-light .navbar-text a, -.navbar-light .navbar-text a:hover, -.navbar-light .navbar-text a:focus { - color: rgba(0, 0, 0, 0.9); -} - -.navbar-dark .navbar-brand { - color: #fff; -} -.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { - color: #fff; -} -.navbar-dark .navbar-nav .nav-link { - color: rgba(255, 255, 255, 0.55); -} -.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { - color: rgba(255, 255, 255, 0.75); -} -.navbar-dark .navbar-nav .nav-link.disabled { - color: rgba(255, 255, 255, 0.25); -} -.navbar-dark .navbar-nav .show > .nav-link, -.navbar-dark .navbar-nav .nav-link.active { - color: #fff; -} -.navbar-dark .navbar-toggler { - color: rgba(255, 255, 255, 0.55); - border-color: rgba(255, 255, 255, 0.1); -} -.navbar-dark .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); -} -.navbar-dark .navbar-text { - color: rgba(255, 255, 255, 0.55); -} -.navbar-dark .navbar-text a, -.navbar-dark .navbar-text a:hover, -.navbar-dark .navbar-text a:focus { - color: #fff; +.navbar-dark { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-border-width: 1px; + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: 0.375rem; + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc(0.375rem - 1px); + --bs-card-cap-padding-y: 0.5rem; + --bs-card-cap-padding-x: 1rem; + --bs-card-cap-bg: rgba(0, 0, 0, 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: #fff; + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; position: relative; display: flex; flex-direction: column; min-width: 0; + height: var(--bs-card-height); word-wrap: break-word; - background-color: #fff; + background-color: var(--bs-card-bg); background-clip: border-box; - border: 1px solid rgba(0, 0, 0, 0.125); - border-radius: 0.25rem; + border: var(--bs-card-border-width) solid var(--bs-card-border-color); + border-radius: var(--bs-card-border-radius); } .card > hr { margin-right: 0; @@ -4507,13 +4402,13 @@ textarea.form-control-lg { } .card > .list-group:first-child { border-top-width: 0; - border-top-left-radius: calc(0.25rem - 1px); - border-top-right-radius: calc(0.25rem - 1px); + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); } .card > .list-group:last-child { border-bottom-width: 0; - border-bottom-right-radius: calc(0.25rem - 1px); - border-bottom-left-radius: calc(0.25rem - 1px); + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); } .card > .card-header + .list-group, .card > .list-group + .card-footer { @@ -4522,15 +4417,16 @@ textarea.form-control-lg { .card-body { flex: 1 1 auto; - padding: 1rem 1rem; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color); } .card-title { - margin-bottom: 0.5rem; + margin-bottom: var(--bs-card-title-spacer-y); } .card-subtitle { - margin-top: -0.25rem; + margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); margin-bottom: 0; } @@ -4539,38 +4435,44 @@ textarea.form-control-lg { } .card-link + .card-link { - margin-left: 1rem; + margin-left: var(--bs-card-spacer-x); } .card-header { - padding: 0.5rem 1rem; + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); margin-bottom: 0; - background-color: rgba(0, 0, 0, 0.03); - border-bottom: 1px solid rgba(0, 0, 0, 0.125); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color); } .card-header:first-child { - border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; + border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0; } .card-footer { - padding: 0.5rem 1rem; - background-color: rgba(0, 0, 0, 0.03); - border-top: 1px solid rgba(0, 0, 0, 0.125); + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); } .card-footer:last-child { - border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); + border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius); } .card-header-tabs { - margin-right: -0.5rem; - margin-bottom: -0.5rem; - margin-left: -0.5rem; + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); border-bottom: 0; } +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg); +} .card-header-pills { - margin-right: -0.5rem; - margin-left: -0.5rem; + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); } .card-img-overlay { @@ -4579,8 +4481,8 @@ textarea.form-control-lg { right: 0; bottom: 0; left: 0; - padding: 1rem; - border-radius: calc(0.25rem - 1px); + padding: var(--bs-card-img-overlay-padding); + border-radius: var(--bs-card-inner-border-radius); } .card-img, @@ -4591,18 +4493,18 @@ textarea.form-control-lg { .card-img, .card-img-top { - border-top-left-radius: calc(0.25rem - 1px); - border-top-right-radius: calc(0.25rem - 1px); + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); } .card-img, .card-img-bottom { - border-bottom-right-radius: calc(0.25rem - 1px); - border-bottom-left-radius: calc(0.25rem - 1px); + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); } .card-group > .card { - margin-bottom: 0.75rem; + margin-bottom: var(--bs-card-group-margin); } @media (min-width: 576px) { .card-group { @@ -4643,20 +4545,45 @@ textarea.form-control-lg { } } +.accordion { + --bs-accordion-color: #000; + --bs-accordion-bg: #fff; + --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: 1px; + --bs-accordion-border-radius: 0.375rem; + --bs-accordion-inner-border-radius: calc(0.375rem - 1px); + --bs-accordion-btn-padding-x: 1.25rem; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='var%28--bs-body-color%29'%3e%3cpath 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'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 1.25rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath 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'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-border-color: #86b7fe; + --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-accordion-body-padding-x: 1.25rem; + --bs-accordion-body-padding-y: 1rem; + --bs-accordion-active-color: #0c63e4; + --bs-accordion-active-bg: #e7f1ff; +} + .accordion-button { position: relative; display: flex; align-items: center; width: 100%; - padding: 1rem 1.25rem; + padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); font-size: 1rem; - color: #212529; + color: var(--bs-accordion-btn-color); text-align: left; - background-color: #fff; + background-color: var(--bs-accordion-btn-bg); border: 0; border-radius: 0; overflow-anchor: none; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + transition: var(--bs-accordion-transition); } @media (prefers-reduced-motion: reduce) { .accordion-button { @@ -4664,24 +4591,24 @@ textarea.form-control-lg { } } .accordion-button:not(.collapsed) { - color: #0c63e4; - background-color: #e7f1ff; - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.125); + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(var(--bs-accordion-border-width) * -1) 0 var(--bs-accordion-border-color); } .accordion-button:not(.collapsed)::after { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath 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'/%3e%3c/svg%3e"); - transform: rotate(-180deg); + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform); } .accordion-button::after { flex-shrink: 0; - width: 1.25rem; - height: 1.25rem; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); margin-left: auto; content: ""; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath 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'/%3e%3c/svg%3e"); + background-image: var(--bs-accordion-btn-icon); background-repeat: no-repeat; - background-size: 1.25rem; - transition: transform 0.2s ease-in-out; + background-size: var(--bs-accordion-btn-icon-width); + transition: var(--bs-accordion-btn-icon-transition); } @media (prefers-reduced-motion: reduce) { .accordion-button::after { @@ -4693,9 +4620,9 @@ textarea.form-control-lg { } .accordion-button:focus { z-index: 3; - border-color: #86b7fe; + border-color: var(--bs-accordion-btn-focus-border-color); outline: 0; - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + box-shadow: var(--bs-accordion-btn-focus-box-shadow); } .accordion-header { @@ -4703,35 +4630,36 @@ textarea.form-control-lg { } .accordion-item { - background-color: #fff; - border: 1px solid rgba(0, 0, 0, 0.125); + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); } .accordion-item:first-of-type { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; + border-top-left-radius: var(--bs-accordion-border-radius); + border-top-right-radius: var(--bs-accordion-border-radius); } .accordion-item:first-of-type .accordion-button { - border-top-left-radius: calc(0.25rem - 1px); - border-top-right-radius: calc(0.25rem - 1px); + border-top-left-radius: var(--bs-accordion-inner-border-radius); + border-top-right-radius: var(--bs-accordion-inner-border-radius); } .accordion-item:not(:first-of-type) { border-top: 0; } .accordion-item:last-of-type { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); } .accordion-item:last-of-type .accordion-button.collapsed { - border-bottom-right-radius: calc(0.25rem - 1px); - border-bottom-left-radius: calc(0.25rem - 1px); + border-bottom-right-radius: var(--bs-accordion-inner-border-radius); + border-bottom-left-radius: var(--bs-accordion-inner-border-radius); } .accordion-item:last-of-type .accordion-collapse { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); } .accordion-body { - padding: 1rem 1.25rem; + padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x); } .accordion-flush .accordion-collapse { @@ -4753,27 +4681,58 @@ textarea.form-control-lg { } .breadcrumb { + --bs-breadcrumb-padding-x: 0; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: #6c757d; + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: #6c757d; display: flex; flex-wrap: wrap; - padding: 0 0; - margin-bottom: 1rem; + padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); list-style: none; + background-color: var(--bs-breadcrumb-bg); + border-radius: var(--bs-breadcrumb-border-radius); } .breadcrumb-item + .breadcrumb-item { - padding-left: 0.5rem; + padding-left: var(--bs-breadcrumb-item-padding-x); } .breadcrumb-item + .breadcrumb-item::before { float: left; - padding-right: 0.5rem; - color: #6c757d; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; } .breadcrumb-item.active { - color: #6c757d; + color: var(--bs-breadcrumb-item-active-color); } .pagination { + --bs-pagination-padding-x: 0.75rem; + --bs-pagination-padding-y: 0.375rem; + --bs-pagination-font-size: 1rem; + --bs-pagination-color: var(--bs-link-color); + --bs-pagination-bg: #fff; + --bs-pagination-border-width: 1px; + --bs-pagination-border-color: #dee2e6; + --bs-pagination-border-radius: 0.375rem; + --bs-pagination-hover-color: var(--bs-link-hover-color); + --bs-pagination-hover-bg: #e9ecef; + --bs-pagination-hover-border-color: #dee2e6; + --bs-pagination-focus-color: var(--bs-link-hover-color); + --bs-pagination-focus-bg: #e9ecef; + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #0d6efd; + --bs-pagination-active-border-color: #0d6efd; + --bs-pagination-disabled-color: #6c757d; + --bs-pagination-disabled-bg: #fff; + --bs-pagination-disabled-border-color: #dee2e6; display: flex; padding-left: 0; list-style: none; @@ -4782,10 +4741,12 @@ textarea.form-control-lg { .page-link { position: relative; display: block; - color: #0d6efd; + padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); text-decoration: none; - background-color: #fff; - border: 1px solid #dee2e6; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color); transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -4795,84 +4756,73 @@ textarea.form-control-lg { } .page-link:hover { z-index: 2; - color: #0a58ca; - background-color: #e9ecef; - border-color: #dee2e6; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color); } .page-link:focus { z-index: 3; - color: #0a58ca; - background-color: #e9ecef; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); outline: 0; - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + box-shadow: var(--bs-pagination-focus-box-shadow); } - -.page-item:not(:first-child) .page-link { - margin-left: -1px; -} -.page-item.active .page-link { +.page-link.active, .active > .page-link { z-index: 3; - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color); } -.page-item.disabled .page-link { - color: #6c757d; +.page-link.disabled, .disabled > .page-link { + color: var(--bs-pagination-disabled-color); pointer-events: none; - background-color: #fff; - border-color: #dee2e6; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color); } -.page-link { - padding: 0.375rem 0.75rem; +.page-item:not(:first-child) .page-link { + margin-left: -1px; } - .page-item:first-child .page-link { - border-top-left-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; + border-top-left-radius: var(--bs-pagination-border-radius); + border-bottom-left-radius: var(--bs-pagination-border-radius); } .page-item:last-child .page-link { - border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; + border-top-right-radius: var(--bs-pagination-border-radius); + border-bottom-right-radius: var(--bs-pagination-border-radius); } -.pagination-lg .page-link { - padding: 0.75rem 1.5rem; - font-size: 1.25rem; -} -.pagination-lg .page-item:first-child .page-link { - border-top-left-radius: 0.3rem; - border-bottom-left-radius: 0.3rem; -} -.pagination-lg .page-item:last-child .page-link { - border-top-right-radius: 0.3rem; - border-bottom-right-radius: 0.3rem; +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 1.25rem; + --bs-pagination-border-radius: 0.5rem; } -.pagination-sm .page-link { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; -} -.pagination-sm .page-item:first-child .page-link { - border-top-left-radius: 0.2rem; - border-bottom-left-radius: 0.2rem; -} -.pagination-sm .page-item:last-child .page-link { - border-top-right-radius: 0.2rem; - border-bottom-right-radius: 0.2rem; +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: 0.25rem; } .badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: 0.375rem; display: inline-block; - padding: 0.35em 0.65em; - font-size: 0.75em; - font-weight: 700; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); line-height: 1; - color: #fff; + color: var(--bs-badge-color); text-align: center; white-space: nowrap; vertical-align: baseline; - border-radius: 0.25rem; + border-radius: var(--bs-badge-border-radius, 0); } .badge:empty { display: none; @@ -4884,11 +4834,21 @@ textarea.form-control-lg { } .alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: 1px solid var(--bs-alert-border-color); + --bs-alert-border-radius: 0.375rem; position: relative; - padding: 1rem 1rem; - margin-bottom: 1rem; - border: 1px solid transparent; - border-radius: 0.25rem; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + border-radius: var(--bs-alert-border-radius, 0); } .alert-heading { @@ -4911,72 +4871,72 @@ textarea.form-control-lg { } .alert-primary { - color: #084298; - background-color: #cfe2ff; - border-color: #b6d4fe; + --bs-alert-color: #084298; + --bs-alert-bg: #cfe2ff; + --bs-alert-border-color: #b6d4fe; } .alert-primary .alert-link { color: #06357a; } .alert-secondary { - color: #41464b; - background-color: #e2e3e5; - border-color: #d3d6d8; + --bs-alert-color: #41464b; + --bs-alert-bg: #e2e3e5; + --bs-alert-border-color: #d3d6d8; } .alert-secondary .alert-link { color: #34383c; } .alert-success { - color: #0f5132; - background-color: #d1e7dd; - border-color: #badbcc; + --bs-alert-color: #0f5132; + --bs-alert-bg: #d1e7dd; + --bs-alert-border-color: #badbcc; } .alert-success .alert-link { color: #0c4128; } .alert-info { - color: #055160; - background-color: #cff4fc; - border-color: #b6effb; + --bs-alert-color: #055160; + --bs-alert-bg: #cff4fc; + --bs-alert-border-color: #b6effb; } .alert-info .alert-link { color: #04414d; } .alert-warning { - color: #664d03; - background-color: #fff3cd; - border-color: #ffecb5; + --bs-alert-color: #664d03; + --bs-alert-bg: #fff3cd; + --bs-alert-border-color: #ffecb5; } .alert-warning .alert-link { color: #523e02; } .alert-danger { - color: #842029; - background-color: #f8d7da; - border-color: #f5c2c7; + --bs-alert-color: #842029; + --bs-alert-bg: #f8d7da; + --bs-alert-border-color: #f5c2c7; } .alert-danger .alert-link { color: #6a1a21; } .alert-light { - color: #636464; - background-color: #fefefe; - border-color: #fdfdfe; + --bs-alert-color: #636464; + --bs-alert-bg: #fefefe; + --bs-alert-border-color: #fdfdfe; } .alert-light .alert-link { color: #4f5050; } .alert-dark { - color: #141619; - background-color: #d3d3d4; - border-color: #bcbebf; + --bs-alert-color: #141619; + --bs-alert-bg: #d3d3d4; + --bs-alert-border-color: #bcbebf; } .alert-dark .alert-link { color: #101214; @@ -4994,12 +4954,20 @@ textarea.form-control-lg { } } .progress { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.75rem; + --bs-progress-bg: #e9ecef; + --bs-progress-border-radius: 0.375rem; + --bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #0d6efd; + --bs-progress-bar-transition: width 0.6s ease; display: flex; - height: 1rem; + height: var(--bs-progress-height); overflow: hidden; - font-size: 0.75rem; - background-color: #e9ecef; - border-radius: 0.25rem; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); + border-radius: var(--bs-progress-border-radius); } .progress-bar { @@ -5007,11 +4975,11 @@ textarea.form-control-lg { flex-direction: column; justify-content: center; overflow: hidden; - color: #fff; + color: var(--bs-progress-bar-color); text-align: center; white-space: nowrap; - background-color: #0d6efd; - transition: width 0.6s ease; + background-color: var(--bs-progress-bar-bg); + transition: var(--bs-progress-bar-transition); } @media (prefers-reduced-motion: reduce) { .progress-bar { @@ -5021,7 +4989,7 @@ textarea.form-control-lg { .progress-bar-striped { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-size: 1rem 1rem; + background-size: var(--bs-progress-height) var(--bs-progress-height); } .progress-bar-animated { @@ -5036,46 +5004,63 @@ textarea.form-control-lg { } .list-group { + --bs-list-group-color: #212529; + --bs-list-group-bg: #fff; + --bs-list-group-border-color: rgba(0, 0, 0, 0.125); + --bs-list-group-border-width: 1px; + --bs-list-group-border-radius: 0.375rem; + --bs-list-group-item-padding-x: 1rem; + --bs-list-group-item-padding-y: 0.5rem; + --bs-list-group-action-color: #495057; + --bs-list-group-action-hover-color: #495057; + --bs-list-group-action-hover-bg: #f8f9fa; + --bs-list-group-action-active-color: #212529; + --bs-list-group-action-active-bg: #e9ecef; + --bs-list-group-disabled-color: #6c757d; + --bs-list-group-disabled-bg: #fff; + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #0d6efd; + --bs-list-group-active-border-color: #0d6efd; display: flex; flex-direction: column; padding-left: 0; margin-bottom: 0; - border-radius: 0.25rem; + border-radius: var(--bs-list-group-border-radius); } .list-group-numbered { list-style-type: none; counter-reset: section; } -.list-group-numbered > li::before { +.list-group-numbered > .list-group-item::before { content: counters(section, ".") ". "; counter-increment: section; } .list-group-item-action { width: 100%; - color: #495057; + color: var(--bs-list-group-action-color); text-align: inherit; } .list-group-item-action:hover, .list-group-item-action:focus { z-index: 1; - color: #495057; + color: var(--bs-list-group-action-hover-color); text-decoration: none; - background-color: #f8f9fa; + background-color: var(--bs-list-group-action-hover-bg); } .list-group-item-action:active { - color: #212529; - background-color: #e9ecef; + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg); } .list-group-item { position: relative; display: block; - padding: 0.5rem 1rem; - color: #212529; + padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); text-decoration: none; - background-color: #fff; - border: 1px solid rgba(0, 0, 0, 0.125); + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color); } .list-group-item:first-child { border-top-left-radius: inherit; @@ -5086,45 +5071,45 @@ textarea.form-control-lg { border-bottom-left-radius: inherit; } .list-group-item.disabled, .list-group-item:disabled { - color: #6c757d; + color: var(--bs-list-group-disabled-color); pointer-events: none; - background-color: #fff; + background-color: var(--bs-list-group-disabled-bg); } .list-group-item.active { z-index: 2; - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color); } .list-group-item + .list-group-item { border-top-width: 0; } .list-group-item + .list-group-item.active { - margin-top: -1px; - border-top-width: 1px; + margin-top: calc(var(--bs-list-group-border-width) * -1); + border-top-width: var(--bs-list-group-border-width); } .list-group-horizontal { flex-direction: row; } .list-group-horizontal > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal > .list-group-item.active { margin-top: 0; } .list-group-horizontal > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } @media (min-width: 576px) { @@ -5132,23 +5117,23 @@ textarea.form-control-lg { flex-direction: row; } .list-group-horizontal-sm > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal-sm > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal-sm > .list-group-item.active { margin-top: 0; } .list-group-horizontal-sm > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal-sm > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } } @media (min-width: 768px) { @@ -5156,23 +5141,23 @@ textarea.form-control-lg { flex-direction: row; } .list-group-horizontal-md > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal-md > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal-md > .list-group-item.active { margin-top: 0; } .list-group-horizontal-md > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal-md > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } } @media (min-width: 992px) { @@ -5180,23 +5165,23 @@ textarea.form-control-lg { flex-direction: row; } .list-group-horizontal-lg > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal-lg > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal-lg > .list-group-item.active { margin-top: 0; } .list-group-horizontal-lg > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal-lg > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } } @media (min-width: 1200px) { @@ -5204,23 +5189,23 @@ textarea.form-control-lg { flex-direction: row; } .list-group-horizontal-xl > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal-xl > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal-xl > .list-group-item.active { margin-top: 0; } .list-group-horizontal-xl > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal-xl > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } } @media (min-width: 1400px) { @@ -5228,30 +5213,30 @@ textarea.form-control-lg { flex-direction: row; } .list-group-horizontal-xxl > .list-group-item:first-child { - border-bottom-left-radius: 0.25rem; + border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } .list-group-horizontal-xxl > .list-group-item:last-child { - border-top-right-radius: 0.25rem; + border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } .list-group-horizontal-xxl > .list-group-item.active { margin-top: 0; } .list-group-horizontal-xxl > .list-group-item + .list-group-item { - border-top-width: 1px; + border-top-width: var(--bs-list-group-border-width); border-left-width: 0; } .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { - margin-left: -1px; - border-left-width: 1px; + margin-left: calc(var(--bs-list-group-border-width) * -1); + border-left-width: var(--bs-list-group-border-width); } } .list-group-flush { border-radius: 0; } .list-group-flush > .list-group-item { - border-width: 0 0 1px; + border-width: 0 0 var(--bs-list-group-border-width); } .list-group-flush > .list-group-item:last-child { border-bottom-width: 0; @@ -5375,9 +5360,9 @@ textarea.form-control-lg { height: 1em; padding: 0.25em 0.25em; color: #000; - background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath 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'/%3e%3c/svg%3e") center/1em auto no-repeat; border: 0; - border-radius: 0.25rem; + border-radius: 0.375rem; opacity: 0.5; } .btn-close:hover { @@ -5403,15 +5388,30 @@ textarea.form-control-lg { } .toast { - width: 350px; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(255, 255, 255, 0.85); + --bs-toast-border-width: 1px; + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: 0.375rem; + --bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-toast-header-color: #6c757d; + --bs-toast-header-bg: rgba(255, 255, 255, 0.85); + --bs-toast-header-border-color: rgba(0, 0, 0, 0.05); + width: var(--bs-toast-max-width); max-width: 100%; - font-size: 0.875rem; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); pointer-events: auto; - background-color: rgba(255, 255, 255, 0.85); + background-color: var(--bs-toast-bg); background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.1); - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); - border-radius: 0.25rem; + border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); + border-radius: var(--bs-toast-border-radius); } .toast.showing { opacity: 0; @@ -5421,6 +5421,8 @@ textarea.form-control-lg { } .toast-container { + position: absolute; + z-index: 1090; width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -5428,35 +5430,56 @@ textarea.form-control-lg { pointer-events: none; } .toast-container > :not(:last-child) { - margin-bottom: 0.75rem; + margin-bottom: var(--bs-toast-spacing); } .toast-header { display: flex; align-items: center; - padding: 0.5rem 0.75rem; - color: #6c757d; - background-color: rgba(255, 255, 255, 0.85); + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); background-clip: padding-box; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - border-top-left-radius: calc(0.25rem - 1px); - border-top-right-radius: calc(0.25rem - 1px); + border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color); + border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); + border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); } .toast-header .btn-close { - margin-right: -0.375rem; - margin-left: 0.75rem; + margin-right: calc(var(--bs-toast-padding-x) * -0.5); + margin-left: var(--bs-toast-padding-x); } .toast-body { - padding: 0.75rem; + padding: var(--bs-toast-padding-x); word-wrap: break-word; } .modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: ; + --bs-modal-bg: #fff; + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: 1px; + --bs-modal-border-radius: 0.5rem; + --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-modal-inner-border-radius: calc(0.5rem - 1px); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: 1px; + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: 1px; position: fixed; top: 0; left: 0; - z-index: 1055; + z-index: var(--bs-modal-zindex); display: none; width: 100%; height: 100%; @@ -5468,7 +5491,7 @@ textarea.form-control-lg { .modal-dialog { position: relative; width: auto; - margin: 0.5rem; + margin: var(--bs-modal-margin); pointer-events: none; } .modal.fade .modal-dialog { @@ -5488,7 +5511,7 @@ textarea.form-control-lg { } .modal-dialog-scrollable { - height: calc(100% - 1rem); + height: calc(100% - var(--bs-modal-margin) * 2); } .modal-dialog-scrollable .modal-content { max-height: 100%; @@ -5501,7 +5524,7 @@ textarea.form-control-lg { .modal-dialog-centered { display: flex; align-items: center; - min-height: calc(100% - 1rem); + min-height: calc(100% - var(--bs-modal-margin) * 2); } .modal-content { @@ -5509,28 +5532,32 @@ textarea.form-control-lg { display: flex; flex-direction: column; width: 100%; + color: var(--bs-modal-color); pointer-events: auto; - background-color: #fff; + background-color: var(--bs-modal-bg); background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 0.3rem; + border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); + border-radius: var(--bs-modal-border-radius); outline: 0; } .modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; position: fixed; top: 0; left: 0; - z-index: 1050; + z-index: var(--bs-backdrop-zindex); width: 100vw; height: 100vh; - background-color: #000; + background-color: var(--bs-backdrop-bg); } .modal-backdrop.fade { opacity: 0; } .modal-backdrop.show { - opacity: 0.5; + opacity: var(--bs-backdrop-opacity); } .modal-header { @@ -5538,69 +5565,68 @@ textarea.form-control-lg { flex-shrink: 0; align-items: center; justify-content: space-between; - padding: 1rem 1rem; - border-bottom: 1px solid #dee2e6; - border-top-left-radius: calc(0.3rem - 1px); - border-top-right-radius: calc(0.3rem - 1px); + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color); + border-top-left-radius: var(--bs-modal-inner-border-radius); + border-top-right-radius: var(--bs-modal-inner-border-radius); } .modal-header .btn-close { - padding: 0.5rem 0.5rem; - margin: -0.5rem -0.5rem -0.5rem auto; + padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); + margin: calc(var(--bs-modal-header-padding-y) * -0.5) calc(var(--bs-modal-header-padding-x) * -0.5) calc(var(--bs-modal-header-padding-y) * -0.5) auto; } .modal-title { margin-bottom: 0; - line-height: 1.5; + line-height: var(--bs-modal-title-line-height); } .modal-body { position: relative; flex: 1 1 auto; - padding: 1rem; + padding: var(--bs-modal-padding); } .modal-footer { display: flex; - flex-wrap: wrap; flex-shrink: 0; + flex-wrap: wrap; align-items: center; justify-content: flex-end; - padding: 0.75rem; - border-top: 1px solid #dee2e6; - border-bottom-right-radius: calc(0.3rem - 1px); - border-bottom-left-radius: calc(0.3rem - 1px); + padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color); + border-bottom-right-radius: var(--bs-modal-inner-border-radius); + border-bottom-left-radius: var(--bs-modal-inner-border-radius); } .modal-footer > * { - margin: 0.25rem; + margin: calc(var(--bs-modal-footer-gap) * 0.5); } @media (min-width: 576px) { - .modal-dialog { - max-width: 500px; - margin: 1.75rem auto; - } - - .modal-dialog-scrollable { - height: calc(100% - 3.5rem); + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); } - .modal-dialog-centered { - min-height: calc(100% - 3.5rem); + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto; } .modal-sm { - max-width: 300px; + --bs-modal-width: 300px; } } @media (min-width: 992px) { .modal-lg, .modal-xl { - max-width: 800px; + --bs-modal-width: 800px; } } @media (min-width: 1200px) { .modal-xl { - max-width: 1140px; + --bs-modal-width: 1140px; } } .modal-fullscreen { @@ -5614,15 +5640,13 @@ textarea.form-control-lg { border: 0; border-radius: 0; } -.modal-fullscreen .modal-header { +.modal-fullscreen .modal-header, +.modal-fullscreen .modal-footer { border-radius: 0; } .modal-fullscreen .modal-body { overflow-y: auto; } -.modal-fullscreen .modal-footer { - border-radius: 0; -} @media (max-width: 575.98px) { .modal-fullscreen-sm-down { @@ -5636,15 +5660,13 @@ textarea.form-control-lg { border: 0; border-radius: 0; } - .modal-fullscreen-sm-down .modal-header { + .modal-fullscreen-sm-down .modal-header, +.modal-fullscreen-sm-down .modal-footer { border-radius: 0; } .modal-fullscreen-sm-down .modal-body { overflow-y: auto; } - .modal-fullscreen-sm-down .modal-footer { - border-radius: 0; - } } @media (max-width: 767.98px) { .modal-fullscreen-md-down { @@ -5658,15 +5680,13 @@ textarea.form-control-lg { border: 0; border-radius: 0; } - .modal-fullscreen-md-down .modal-header { + .modal-fullscreen-md-down .modal-header, +.modal-fullscreen-md-down .modal-footer { border-radius: 0; } .modal-fullscreen-md-down .modal-body { overflow-y: auto; } - .modal-fullscreen-md-down .modal-footer { - border-radius: 0; - } } @media (max-width: 991.98px) { .modal-fullscreen-lg-down { @@ -5680,15 +5700,13 @@ textarea.form-control-lg { border: 0; border-radius: 0; } - .modal-fullscreen-lg-down .modal-header { + .modal-fullscreen-lg-down .modal-header, +.modal-fullscreen-lg-down .modal-footer { border-radius: 0; } .modal-fullscreen-lg-down .modal-body { overflow-y: auto; } - .modal-fullscreen-lg-down .modal-footer { - border-radius: 0; - } } @media (max-width: 1199.98px) { .modal-fullscreen-xl-down { @@ -5702,15 +5720,13 @@ textarea.form-control-lg { border: 0; border-radius: 0; } - .modal-fullscreen-xl-down .modal-header { + .modal-fullscreen-xl-down .modal-header, +.modal-fullscreen-xl-down .modal-footer { border-radius: 0; } .modal-fullscreen-xl-down .modal-body { overflow-y: auto; } - .modal-fullscreen-xl-down .modal-footer { - border-radius: 0; - } } @media (max-width: 1399.98px) { .modal-fullscreen-xxl-down { @@ -5724,21 +5740,31 @@ textarea.form-control-lg { border: 0; border-radius: 0; } - .modal-fullscreen-xxl-down .modal-header { + .modal-fullscreen-xxl-down .modal-header, +.modal-fullscreen-xxl-down .modal-footer { border-radius: 0; } .modal-fullscreen-xxl-down .modal-body { overflow-y: auto; } - .modal-fullscreen-xxl-down .modal-footer { - border-radius: 0; - } } .tooltip { - position: absolute; - z-index: 1080; + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.875rem; + --bs-tooltip-color: #fff; + --bs-tooltip-bg: #000; + --bs-tooltip-border-radius: 0.375rem; + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); display: block; - margin: 0; + padding: var(--bs-tooltip-arrow-height); + margin: var(--bs-tooltip-margin); font-family: var(--bs-font-sans-serif); font-style: normal; font-weight: 400; @@ -5750,21 +5776,20 @@ textarea.form-control-lg { text-transform: none; letter-spacing: normal; word-break: normal; - word-spacing: normal; white-space: normal; + word-spacing: normal; line-break: auto; - font-size: 0.875rem; + font-size: var(--bs-tooltip-font-size); word-wrap: break-word; opacity: 0; } .tooltip.show { - opacity: 0.9; + opacity: var(--bs-tooltip-opacity); } .tooltip .tooltip-arrow { - position: absolute; display: block; - width: 0.8rem; - height: 0.4rem; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height); } .tooltip .tooltip-arrow::before { position: absolute; @@ -5773,74 +5798,83 @@ textarea.form-control-lg { border-style: solid; } -.bs-tooltip-top, .bs-tooltip-auto[data-popper-placement^=top] { - padding: 0.4rem 0; -} .bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { bottom: 0; } .bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { top: -1px; - border-width: 0.4rem 0.4rem 0; - border-top-color: #000; + border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-top-color: var(--bs-tooltip-bg); } -.bs-tooltip-end, .bs-tooltip-auto[data-popper-placement^=right] { - padding: 0 0.4rem; -} +/* rtl:begin:ignore */ .bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { left: 0; - width: 0.4rem; - height: 0.8rem; + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); } .bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { right: -1px; - border-width: 0.4rem 0.4rem 0.4rem 0; - border-right-color: #000; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-right-color: var(--bs-tooltip-bg); } -.bs-tooltip-bottom, .bs-tooltip-auto[data-popper-placement^=bottom] { - padding: 0.4rem 0; -} +/* rtl:end:ignore */ .bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { top: 0; } .bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { bottom: -1px; - border-width: 0 0.4rem 0.4rem; - border-bottom-color: #000; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg); } -.bs-tooltip-start, .bs-tooltip-auto[data-popper-placement^=left] { - padding: 0 0.4rem; -} +/* rtl:begin:ignore */ .bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { right: 0; - width: 0.4rem; - height: 0.8rem; + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); } .bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { left: -1px; - border-width: 0.4rem 0 0.4rem 0.4rem; - border-left-color: #000; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg); } +/* rtl:end:ignore */ .tooltip-inner { - max-width: 200px; - padding: 0.25rem 0.5rem; - color: #fff; + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); text-align: center; - background-color: #000; - border-radius: 0.25rem; + background-color: var(--bs-tooltip-bg); + border-radius: var(--bs-tooltip-border-radius, 0); } .popover { - position: absolute; - top: 0; - left: 0 /* rtl:ignore */; - z-index: 1070; + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.875rem; + --bs-popover-bg: #fff; + --bs-popover-border-width: 1px; + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: 0.5rem; + --bs-popover-inner-border-radius: calc(0.5rem - 1px); + --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 1rem; + --bs-popover-header-color: var(--bs-heading-color); + --bs-popover-header-bg: #f0f0f0; + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: #212529; + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); display: block; - max-width: 276px; + max-width: var(--bs-popover-max-width); font-family: var(--bs-font-sans-serif); font-style: normal; font-weight: 400; @@ -5852,21 +5886,20 @@ textarea.form-control-lg { text-transform: none; letter-spacing: normal; word-break: normal; - word-spacing: normal; white-space: normal; + word-spacing: normal; line-break: auto; - font-size: 0.875rem; + font-size: var(--bs-popover-font-size); word-wrap: break-word; - background-color: #fff; + background-color: var(--bs-popover-bg); background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 0.3rem; + border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-radius: var(--bs-popover-border-radius); } .popover .popover-arrow { - position: absolute; display: block; - width: 1rem; - height: 0.5rem; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height); } .popover .popover-arrow::before, .popover .popover-arrow::after { position: absolute; @@ -5874,94 +5907,104 @@ textarea.form-control-lg { content: ""; border-color: transparent; border-style: solid; + border-width: 0; } .bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow { - bottom: calc(-0.5rem - 1px); + bottom: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); +} +.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { + border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; } .bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before { bottom: 0; - border-width: 0.5rem 0.5rem 0; - border-top-color: rgba(0, 0, 0, 0.25); + border-top-color: var(--bs-popover-arrow-border); } .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { - bottom: 1px; - border-width: 0.5rem 0.5rem 0; - border-top-color: #fff; + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg); } +/* rtl:begin:ignore */ .bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow { - left: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; + left: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; } .bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before { left: 0; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: rgba(0, 0, 0, 0.25); + border-right-color: var(--bs-popover-arrow-border); } .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { - left: 1px; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: #fff; + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg); } +/* rtl:end:ignore */ .bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { - top: calc(-0.5rem - 1px); + top: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); +} +.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { + border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); } .bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before { top: 0; - border-width: 0 0.5rem 0.5rem 0.5rem; - border-bottom-color: rgba(0, 0, 0, 0.25); + border-bottom-color: var(--bs-popover-arrow-border); } .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { - top: 1px; - border-width: 0 0.5rem 0.5rem 0.5rem; - border-bottom-color: #fff; + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg); } .bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before { position: absolute; top: 0; left: 50%; display: block; - width: 1rem; - margin-left: -0.5rem; + width: var(--bs-popover-arrow-width); + margin-left: calc(var(--bs-popover-arrow-width) * -0.5); content: ""; - border-bottom: 1px solid #f0f0f0; + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg); } +/* rtl:begin:ignore */ .bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow { - right: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; + right: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); } .bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before { right: 0; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: rgba(0, 0, 0, 0.25); + border-left-color: var(--bs-popover-arrow-border); } .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { - right: 1px; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: #fff; + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg); } +/* rtl:end:ignore */ .popover-header { - padding: 0.5rem 1rem; + padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); margin-bottom: 0; - font-size: 1rem; - background-color: #f0f0f0; - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - border-top-left-radius: calc(0.3rem - 1px); - border-top-right-radius: calc(0.3rem - 1px); + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-top-left-radius: var(--bs-popover-inner-border-radius); + border-top-right-radius: var(--bs-popover-inner-border-radius); } .popover-header:empty { display: none; } .popover-body { - padding: 1rem 1rem; - color: #212529; + padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color); } .carousel { @@ -6171,78 +6214,537 @@ textarea.form-control-lg { color: #000; } +.spinner-grow, +.spinner-border { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + -webkit-animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); + animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); +} + @-webkit-keyframes spinner-border { to { transform: rotate(360deg) /* rtl:ignore */; } } - -@keyframes spinner-border { - to { - transform: rotate(360deg) /* rtl:ignore */; + +@keyframes spinner-border { + to { + transform: rotate(360deg) /* rtl:ignore */; + } +} +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent; +} + +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em; +} + +@-webkit-keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} + +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0; +} + +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; +} + +@media (prefers-reduced-motion: reduce) { + .spinner-border, +.spinner-grow { + --bs-spinner-animation-speed: 1.5s; + } +} +.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm { + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: ; + --bs-offcanvas-bg: #fff; + --bs-offcanvas-border-width: 1px; + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); +} + +@media (max-width: 575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: 1045; + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: transform 0.3s ease-in-out; + } +} +@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-sm { + transition: none; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) { + transform: none; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show { + visibility: visible; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } +} +@media (min-width: 576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-sm .offcanvas-header { + display: none; + } + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: 1045; + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: transform 0.3s ease-in-out; + } +} +@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-md { + transition: none; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) { + transform: none; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show { + visibility: visible; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } +} +@media (min-width: 768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-md .offcanvas-header { + display: none; + } + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: 1045; + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: transform 0.3s ease-in-out; + } +} +@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-lg { + transition: none; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) { + transform: none; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show { + visibility: visible; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } +} +@media (min-width: 992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-lg .offcanvas-header { + display: none; + } + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: 1045; + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: transform 0.3s ease-in-out; + } +} +@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xl { + transition: none; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) { + transform: none; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show { + visibility: visible; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); } } -.spinner-border { - display: inline-block; - width: 2rem; - height: 2rem; - vertical-align: -0.125em; - border: 0.25em solid currentColor; - border-right-color: transparent; - border-radius: 50%; - -webkit-animation: 0.75s linear infinite spinner-border; - animation: 0.75s linear infinite spinner-border; +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } } - -.spinner-border-sm { - width: 1rem; - height: 1rem; - border-width: 0.2em; +@media (min-width: 1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xl .offcanvas-header { + display: none; + } + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } } -@-webkit-keyframes spinner-grow { - 0% { - transform: scale(0); +@media (max-width: 1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: 1045; + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: transform 0.3s ease-in-out; + } +} +@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xxl { + transition: none; } - 50% { - opacity: 1; +} +@media (max-width: 1399.98px) { + .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) { transform: none; } } - -@keyframes spinner-grow { - 0% { - transform: scale(0); +@media (max-width: 1399.98px) { + .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show { + visibility: visible; } - 50% { - opacity: 1; - transform: none; +} +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); } } -.spinner-grow { - display: inline-block; - width: 2rem; - height: 2rem; - vertical-align: -0.125em; - background-color: currentColor; - border-radius: 50%; - opacity: 0; - -webkit-animation: 0.75s linear infinite spinner-grow; - animation: 0.75s linear infinite spinner-grow; +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } } - -.spinner-grow-sm { - width: 1rem; - height: 1rem; +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } } - -@media (prefers-reduced-motion: reduce) { - .spinner-border, -.spinner-grow { - -webkit-animation-duration: 1.5s; - animation-duration: 1.5s; +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } +} +@media (min-width: 1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xxl .offcanvas-header { + display: none; + } + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; } } + .offcanvas { position: fixed; bottom: 0; @@ -6250,8 +6752,9 @@ textarea.form-control-lg { display: flex; flex-direction: column; max-width: 100%; + color: var(--bs-offcanvas-color); visibility: hidden; - background-color: #fff; + background-color: var(--bs-offcanvas-bg); background-clip: padding-box; outline: 0; transition: transform 0.3s ease-in-out; @@ -6261,6 +6764,43 @@ textarea.form-control-lg { transition: none; } } +.offcanvas.showing, .offcanvas.show:not(.hiding) { + transform: none; +} +.offcanvas.showing, .offcanvas.hiding, .offcanvas.show { + visibility: visible; +} +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); +} +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); +} +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); +} +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); +} .offcanvas-backdrop { position: fixed; @@ -6282,13 +6822,13 @@ textarea.form-control-lg { display: flex; align-items: center; justify-content: space-between; - padding: 1rem 1rem; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); } .offcanvas-header .btn-close { - padding: 0.5rem 0.5rem; - margin-top: -0.5rem; - margin-right: -0.5rem; - margin-bottom: -0.5rem; + padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); + margin-top: calc(var(--bs-offcanvas-padding-y) * -0.5); + margin-right: calc(var(--bs-offcanvas-padding-x) * -0.5); + margin-bottom: calc(var(--bs-offcanvas-padding-y) * -0.5); } .offcanvas-title { @@ -6298,55 +6838,16 @@ textarea.form-control-lg { .offcanvas-body { flex-grow: 1; - padding: 1rem 1rem; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); overflow-y: auto; } -.offcanvas-start { - top: 0; - left: 0; - width: 400px; - border-right: 1px solid rgba(0, 0, 0, 0.2); - transform: translateX(-100%); -} - -.offcanvas-end { - top: 0; - right: 0; - width: 400px; - border-left: 1px solid rgba(0, 0, 0, 0.2); - transform: translateX(100%); -} - -.offcanvas-top { - top: 0; - right: 0; - left: 0; - height: 30vh; - max-height: 100%; - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - transform: translateY(-100%); -} - -.offcanvas-bottom { - right: 0; - left: 0; - height: 30vh; - max-height: 100%; - border-top: 1px solid rgba(0, 0, 0, 0.2); - transform: translateY(100%); -} - -.offcanvas.show { - transform: none; -} - .placeholder { display: inline-block; min-height: 1em; vertical-align: middle; cursor: wait; - background-color: currentColor; + background-color: currentcolor; opacity: 0.5; } .placeholder.btn::before { @@ -6410,60 +6911,100 @@ textarea.form-control-lg { content: ""; } +.text-bg-primary { + color: #fff !important; + background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-secondary { + color: #fff !important; + background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-success { + color: #fff !important; + background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-info { + color: #000 !important; + background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-warning { + color: #000 !important; + background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-danger { + color: #fff !important; + background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-light { + color: #000 !important; + background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-dark { + color: #fff !important; + background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important; +} + .link-primary { - color: #0d6efd; + color: #0d6efd !important; } .link-primary:hover, .link-primary:focus { - color: #0a58ca; + color: #0a58ca !important; } .link-secondary { - color: #6c757d; + color: #6c757d !important; } .link-secondary:hover, .link-secondary:focus { - color: #565e64; + color: #565e64 !important; } .link-success { - color: #198754; + color: #198754 !important; } .link-success:hover, .link-success:focus { - color: #146c43; + color: #146c43 !important; } .link-info { - color: #0dcaf0; + color: #0dcaf0 !important; } .link-info:hover, .link-info:focus { - color: #3dd5f3; + color: #3dd5f3 !important; } .link-warning { - color: #ffc107; + color: #ffc107 !important; } .link-warning:hover, .link-warning:focus { - color: #ffcd39; + color: #ffcd39 !important; } .link-danger { - color: #dc3545; + color: #dc3545 !important; } .link-danger:hover, .link-danger:focus { - color: #b02a37; + color: #b02a37 !important; } .link-light { - color: #f8f9fa; + color: #f8f9fa !important; } .link-light:hover, .link-light:focus { - color: #f9fafb; + color: #f9fafb !important; } .link-dark { - color: #212529; + color: #212529 !important; } .link-dark:hover, .link-dark:focus { - color: #1a1e21; + color: #1a1e21 !important; } .ratio { @@ -6522,6 +7063,13 @@ textarea.form-control-lg { z-index: 1020; } +.sticky-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; +} + @media (min-width: 576px) { .sticky-sm-top { position: -webkit-sticky; @@ -6529,6 +7077,13 @@ textarea.form-control-lg { top: 0; z-index: 1020; } + + .sticky-sm-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } } @media (min-width: 768px) { .sticky-md-top { @@ -6537,6 +7092,13 @@ textarea.form-control-lg { top: 0; z-index: 1020; } + + .sticky-md-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } } @media (min-width: 992px) { .sticky-lg-top { @@ -6545,6 +7107,13 @@ textarea.form-control-lg { top: 0; z-index: 1020; } + + .sticky-lg-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } } @media (min-width: 1200px) { .sticky-xl-top { @@ -6553,6 +7122,13 @@ textarea.form-control-lg { top: 0; z-index: 1020; } + + .sticky-xl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } } @media (min-width: 1400px) { .sticky-xxl-top { @@ -6561,6 +7137,13 @@ textarea.form-control-lg { top: 0; z-index: 1020; } + + .sticky-xxl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } } .hstack { display: flex; @@ -6610,7 +7193,7 @@ textarea.form-control-lg { align-self: stretch; width: 1px; min-height: 1em; - background-color: currentColor; + background-color: currentcolor; opacity: 0.25; } @@ -6824,7 +7407,7 @@ textarea.form-control-lg { } .border { - border: 1px solid #dee2e6 !important; + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; } .border-0 { @@ -6832,7 +7415,7 @@ textarea.form-control-lg { } .border-top { - border-top: 1px solid #dee2e6 !important; + border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; } .border-top-0 { @@ -6840,7 +7423,7 @@ textarea.form-control-lg { } .border-end { - border-right: 1px solid #dee2e6 !important; + border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; } .border-end-0 { @@ -6848,7 +7431,7 @@ textarea.form-control-lg { } .border-bottom { - border-bottom: 1px solid #dee2e6 !important; + border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; } .border-bottom-0 { @@ -6856,7 +7439,7 @@ textarea.form-control-lg { } .border-start { - border-left: 1px solid #dee2e6 !important; + border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; } .border-start-0 { @@ -6864,59 +7447,88 @@ textarea.form-control-lg { } .border-primary { - border-color: #0d6efd !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important; } .border-secondary { - border-color: #6c757d !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important; } .border-success { - border-color: #198754 !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important; } .border-info { - border-color: #0dcaf0 !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important; } .border-warning { - border-color: #ffc107 !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important; } .border-danger { - border-color: #dc3545 !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; } .border-light { - border-color: #f8f9fa !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important; } .border-dark { - border-color: #212529 !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important; } .border-white { - border-color: #fff !important; + --bs-border-opacity: 1; + border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important; } .border-1 { - border-width: 1px !important; + --bs-border-width: 1px; } .border-2 { - border-width: 2px !important; + --bs-border-width: 2px; } .border-3 { - border-width: 3px !important; + --bs-border-width: 3px; } .border-4 { - border-width: 4px !important; + --bs-border-width: 4px; } .border-5 { - border-width: 5px !important; + --bs-border-width: 5px; +} + +.border-opacity-10 { + --bs-border-opacity: 0.1; +} + +.border-opacity-25 { + --bs-border-opacity: 0.25; +} + +.border-opacity-50 { + --bs-border-opacity: 0.5; +} + +.border-opacity-75 { + --bs-border-opacity: 0.75; +} + +.border-opacity-100 { + --bs-border-opacity: 1; } .w-25 { @@ -7031,30 +7643,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } -.gap-0 { - gap: 0 !important; -} - -.gap-1 { - gap: 0.25rem !important; -} - -.gap-2 { - gap: 0.5rem !important; -} - -.gap-3 { - gap: 1rem !important; -} - -.gap-4 { - gap: 1.5rem !important; -} - -.gap-5 { - gap: 3rem !important; -} - .justify-content-start { justify-content: flex-start !important; } @@ -7561,12 +8149,36 @@ textarea.form-control-lg { padding-left: 1rem !important; } -.ps-4 { - padding-left: 1.5rem !important; +.ps-4 { + padding-left: 1.5rem !important; +} + +.ps-5 { + padding-left: 3rem !important; +} + +.gap-0 { + gap: 0 !important; +} + +.gap-1 { + gap: 0.25rem !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +.gap-3 { + gap: 1rem !important; +} + +.gap-4 { + gap: 1.5rem !important; } -.ps-5 { - padding-left: 3rem !important; +.gap-5 { + gap: 3rem !important; } .font-monospace { @@ -7621,6 +8233,10 @@ textarea.form-control-lg { font-weight: 700 !important; } +.fw-semibold { + font-weight: 600 !important; +} + .fw-bolder { font-weight: bolder !important; } @@ -7749,7 +8365,7 @@ textarea.form-control-lg { .text-muted { --bs-text-opacity: 1; - color: #6c757d !important; + color: rgba(var(--bs-body-color-rgb), 0.75) !important; } .text-black-50 { @@ -7894,7 +8510,7 @@ textarea.form-control-lg { } .rounded { - border-radius: 0.25rem !important; + border-radius: var(--bs-border-radius) !important; } .rounded-0 { @@ -7902,15 +8518,23 @@ textarea.form-control-lg { } .rounded-1 { - border-radius: 0.2rem !important; + border-radius: var(--bs-border-radius-sm) !important; } .rounded-2 { - border-radius: 0.25rem !important; + border-radius: var(--bs-border-radius) !important; } .rounded-3 { - border-radius: 0.3rem !important; + border-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-5 { + border-radius: var(--bs-border-radius-2xl) !important; } .rounded-circle { @@ -7918,27 +8542,27 @@ textarea.form-control-lg { } .rounded-pill { - border-radius: 50rem !important; + border-radius: var(--bs-border-radius-pill) !important; } .rounded-top { - border-top-left-radius: 0.25rem !important; - border-top-right-radius: 0.25rem !important; + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; } .rounded-end { - border-top-right-radius: 0.25rem !important; - border-bottom-right-radius: 0.25rem !important; + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; } .rounded-bottom { - border-bottom-right-radius: 0.25rem !important; - border-bottom-left-radius: 0.25rem !important; + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; } .rounded-start { - border-bottom-left-radius: 0.25rem !important; - border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; } .visible { @@ -8050,30 +8674,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } - .gap-sm-0 { - gap: 0 !important; - } - - .gap-sm-1 { - gap: 0.25rem !important; - } - - .gap-sm-2 { - gap: 0.5rem !important; - } - - .gap-sm-3 { - gap: 1rem !important; - } - - .gap-sm-4 { - gap: 1.5rem !important; - } - - .gap-sm-5 { - gap: 3rem !important; - } - .justify-content-sm-start { justify-content: flex-start !important; } @@ -8588,6 +9188,30 @@ textarea.form-control-lg { padding-left: 3rem !important; } + .gap-sm-0 { + gap: 0 !important; + } + + .gap-sm-1 { + gap: 0.25rem !important; + } + + .gap-sm-2 { + gap: 0.5rem !important; + } + + .gap-sm-3 { + gap: 1rem !important; + } + + .gap-sm-4 { + gap: 1.5rem !important; + } + + .gap-sm-5 { + gap: 3rem !important; + } + .text-sm-start { text-align: left !important; } @@ -8701,30 +9325,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } - .gap-md-0 { - gap: 0 !important; - } - - .gap-md-1 { - gap: 0.25rem !important; - } - - .gap-md-2 { - gap: 0.5rem !important; - } - - .gap-md-3 { - gap: 1rem !important; - } - - .gap-md-4 { - gap: 1.5rem !important; - } - - .gap-md-5 { - gap: 3rem !important; - } - .justify-content-md-start { justify-content: flex-start !important; } @@ -9239,6 +9839,30 @@ textarea.form-control-lg { padding-left: 3rem !important; } + .gap-md-0 { + gap: 0 !important; + } + + .gap-md-1 { + gap: 0.25rem !important; + } + + .gap-md-2 { + gap: 0.5rem !important; + } + + .gap-md-3 { + gap: 1rem !important; + } + + .gap-md-4 { + gap: 1.5rem !important; + } + + .gap-md-5 { + gap: 3rem !important; + } + .text-md-start { text-align: left !important; } @@ -9352,30 +9976,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } - .gap-lg-0 { - gap: 0 !important; - } - - .gap-lg-1 { - gap: 0.25rem !important; - } - - .gap-lg-2 { - gap: 0.5rem !important; - } - - .gap-lg-3 { - gap: 1rem !important; - } - - .gap-lg-4 { - gap: 1.5rem !important; - } - - .gap-lg-5 { - gap: 3rem !important; - } - .justify-content-lg-start { justify-content: flex-start !important; } @@ -9890,6 +10490,30 @@ textarea.form-control-lg { padding-left: 3rem !important; } + .gap-lg-0 { + gap: 0 !important; + } + + .gap-lg-1 { + gap: 0.25rem !important; + } + + .gap-lg-2 { + gap: 0.5rem !important; + } + + .gap-lg-3 { + gap: 1rem !important; + } + + .gap-lg-4 { + gap: 1.5rem !important; + } + + .gap-lg-5 { + gap: 3rem !important; + } + .text-lg-start { text-align: left !important; } @@ -10003,30 +10627,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } - .gap-xl-0 { - gap: 0 !important; - } - - .gap-xl-1 { - gap: 0.25rem !important; - } - - .gap-xl-2 { - gap: 0.5rem !important; - } - - .gap-xl-3 { - gap: 1rem !important; - } - - .gap-xl-4 { - gap: 1.5rem !important; - } - - .gap-xl-5 { - gap: 3rem !important; - } - .justify-content-xl-start { justify-content: flex-start !important; } @@ -10541,6 +11141,30 @@ textarea.form-control-lg { padding-left: 3rem !important; } + .gap-xl-0 { + gap: 0 !important; + } + + .gap-xl-1 { + gap: 0.25rem !important; + } + + .gap-xl-2 { + gap: 0.5rem !important; + } + + .gap-xl-3 { + gap: 1rem !important; + } + + .gap-xl-4 { + gap: 1.5rem !important; + } + + .gap-xl-5 { + gap: 3rem !important; + } + .text-xl-start { text-align: left !important; } @@ -10654,30 +11278,6 @@ textarea.form-control-lg { flex-wrap: wrap-reverse !important; } - .gap-xxl-0 { - gap: 0 !important; - } - - .gap-xxl-1 { - gap: 0.25rem !important; - } - - .gap-xxl-2 { - gap: 0.5rem !important; - } - - .gap-xxl-3 { - gap: 1rem !important; - } - - .gap-xxl-4 { - gap: 1.5rem !important; - } - - .gap-xxl-5 { - gap: 3rem !important; - } - .justify-content-xxl-start { justify-content: flex-start !important; } @@ -11192,6 +11792,30 @@ textarea.form-control-lg { padding-left: 3rem !important; } + .gap-xxl-0 { + gap: 0 !important; + } + + .gap-xxl-1 { + gap: 0.25rem !important; + } + + .gap-xxl-2 { + gap: 0.5rem !important; + } + + .gap-xxl-3 { + gap: 1rem !important; + } + + .gap-xxl-4 { + gap: 1.5rem !important; + } + + .gap-xxl-5 { + gap: 3rem !important; + } + .text-xxl-start { text-align: left !important; } diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index c67a3b0e..bc9eb81c 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -4,13 +4,175 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-1.11.5 + * https://datatables.net/download/#bs5/dt-1.12.1 * * Included libraries: - * DataTables 1.11.5 + * DataTables 1.12.1 */ @charset "UTF-8"; +table.dataTable td.dt-control { + text-align: center; + cursor: pointer; +} +table.dataTable td.dt-control:before { + height: 1em; + width: 1em; + margin-top: -9px; + display: inline-block; + color: white; + border: 0.15em solid white; + border-radius: 1em; + box-shadow: 0 0 0.2em #444; + box-sizing: content-box; + text-align: center; + text-indent: 0 !important; + font-family: "Courier New", Courier, monospace; + line-height: 1em; + content: "+"; + background-color: #31b131; +} +table.dataTable tr.dt-hasChild td.dt-control:before { + content: "-"; + background-color: #d33333; +} + +table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled, +table.dataTable thead > tr > td.sorting, +table.dataTable thead > tr > td.sorting_asc, +table.dataTable thead > tr > td.sorting_desc, +table.dataTable thead > tr > td.sorting_asc_disabled, +table.dataTable thead > tr > td.sorting_desc_disabled { + cursor: pointer; + position: relative; + padding-right: 26px; +} +table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting:before, +table.dataTable thead > tr > td.sorting:after, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_asc:after, +table.dataTable thead > tr > td.sorting_desc:before, +table.dataTable thead > tr > td.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_asc_disabled:after, +table.dataTable thead > tr > td.sorting_desc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:after { + position: absolute; + display: block; + opacity: 0.125; + right: 10px; + line-height: 9px; + font-size: 0.9em; +} +table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, +table.dataTable thead > tr > td.sorting:before, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_desc:before, +table.dataTable thead > tr > td.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:before { + bottom: 50%; + content: "▴"; +} +table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting:after, +table.dataTable thead > tr > td.sorting_asc:after, +table.dataTable thead > tr > td.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc_disabled:after, +table.dataTable thead > tr > td.sorting_desc_disabled:after { + top: 50%; + content: "▾"; +} +table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, +table.dataTable thead > tr > td.sorting_asc:before, +table.dataTable thead > tr > td.sorting_desc:after { + opacity: 0.6; +} +table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, +table.dataTable thead > tr > td.sorting_desc_disabled:after, +table.dataTable thead > tr > td.sorting_asc_disabled:before { + display: none; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} + +div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after, +div.dataTables_scrollBody table.dataTable thead > tr > td:before, +div.dataTables_scrollBody table.dataTable thead > tr > td:after { + display: none; +} + +div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -26px; + text-align: center; + padding: 2px; +} +div.dataTables_processing > div:last-child { + position: relative; + width: 80px; + height: 15px; + margin: 1em auto; +} +div.dataTables_processing > div:last-child > div { + position: absolute; + top: 0; + width: 13px; + height: 13px; + border-radius: 50%; + background: rgba(13, 110, 253, 0.9); + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +div.dataTables_processing > div:last-child > div:nth-child(1) { + left: 8px; + animation: datatables-loader-1 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(2) { + left: 8px; + animation: datatables-loader-2 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(3) { + left: 32px; + animation: datatables-loader-2 0.6s infinite; +} +div.dataTables_processing > div:last-child > div:nth-child(4) { + left: 56px; + animation: datatables-loader-3 0.6s infinite; +} + +@keyframes datatables-loader-1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes datatables-loader-3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes datatables-loader-2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(24px, 0); + } +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} table.dataTable th.dt-left, table.dataTable td.dt-left { text-align: left; @@ -32,6 +194,12 @@ table.dataTable th.dt-nowrap, table.dataTable td.dt-nowrap { white-space: nowrap; } +table.dataTable thead th, +table.dataTable thead td, +table.dataTable tfoot th, +table.dataTable tfoot td { + text-align: left; +} table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, table.dataTable tfoot th.dt-head-left, @@ -82,31 +250,6 @@ table.dataTable tbody th.dt-body-nowrap, table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } -table.dataTable td.dt-control { - text-align: center; - cursor: pointer; -} -table.dataTable td.dt-control:before { - height: 1em; - width: 1em; - margin-top: -9px; - display: inline-block; - color: white; - border: 0.15em solid white; - border-radius: 1em; - box-shadow: 0 0 0.2em #444; - box-sizing: content-box; - text-align: center; - text-indent: 0 !important; - font-family: "Courier New", Courier, monospace; - line-height: 1em; - content: "+"; - background-color: #31b131; -} -table.dataTable tr.dt-hasChild td.dt-control:before { - content: "-"; - background-color: #d33333; -} /*! Bootstrap 5 integration for DataTables * @@ -134,6 +277,28 @@ table.dataTable.nowrap th, table.dataTable.nowrap td { white-space: nowrap; } +table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * { + box-shadow: none; +} +table.dataTable > tbody > tr { + background-color: transparent; +} +table.dataTable > tbody > tr.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); + color: white; +} +table.dataTable.table-striped > tbody > tr.odd > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05); +} +table.dataTable.table-striped > tbody > tr.odd.selected > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); +} +table.dataTable.table-hover > tbody > tr:hover > * { + box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.075); +} +table.dataTable.table-hover > tbody > tr.selected:hover > * { + box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); +} div.dataTables_wrapper div.dataTables_length label { font-weight: normal; @@ -170,71 +335,6 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination { white-space: nowrap; justify-content: flex-end; } -div.dataTables_wrapper div.dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 200px; - margin-left: -100px; - margin-top: -26px; - text-align: center; - padding: 1em 0; -} - -table.dataTable > thead > tr > th:active, -table.dataTable > thead > tr > td:active { - outline: none; -} -table.dataTable > thead > tr > th:not(.sorting_disabled), -table.dataTable > thead > tr > td:not(.sorting_disabled) { - padding-right: 30px; -} -table.dataTable > thead .sorting, -table.dataTable > thead .sorting_asc, -table.dataTable > thead .sorting_desc, -table.dataTable > thead .sorting_asc_disabled, -table.dataTable > thead .sorting_desc_disabled { - cursor: pointer; - position: relative; -} -table.dataTable > thead .sorting:before, table.dataTable > thead .sorting:after, -table.dataTable > thead .sorting_asc:before, -table.dataTable > thead .sorting_asc:after, -table.dataTable > thead .sorting_desc:before, -table.dataTable > thead .sorting_desc:after, -table.dataTable > thead .sorting_asc_disabled:before, -table.dataTable > thead .sorting_asc_disabled:after, -table.dataTable > thead .sorting_desc_disabled:before, -table.dataTable > thead .sorting_desc_disabled:after { - position: absolute; - bottom: 0.5em; - display: block; - opacity: 0.3; -} -table.dataTable > thead .sorting:before, -table.dataTable > thead .sorting_asc:before, -table.dataTable > thead .sorting_desc:before, -table.dataTable > thead .sorting_asc_disabled:before, -table.dataTable > thead .sorting_desc_disabled:before { - right: 1em; - content: "↑"; -} -table.dataTable > thead .sorting:after, -table.dataTable > thead .sorting_asc:after, -table.dataTable > thead .sorting_desc:after, -table.dataTable > thead .sorting_asc_disabled:after, -table.dataTable > thead .sorting_desc_disabled:after { - right: 0.5em; - content: "↓"; -} -table.dataTable > thead .sorting_asc:before, -table.dataTable > thead .sorting_desc:after { - opacity: 1; -} -table.dataTable > thead .sorting_asc_disabled:before, -table.dataTable > thead .sorting_desc_disabled:after { - opacity: 0; -} div.dataTables_scrollHead table.dataTable { margin-bottom: 0 !important; @@ -280,17 +380,6 @@ div.dataTables_wrapper div.dataTables_paginate { table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) { padding-right: 20px; } -table.dataTable.table-sm .sorting:before, -table.dataTable.table-sm .sorting_asc:before, -table.dataTable.table-sm .sorting_desc:before { - top: 5px; - right: 0.85em; -} -table.dataTable.table-sm .sorting:after, -table.dataTable.table-sm .sorting_asc:after, -table.dataTable.table-sm .sorting_desc:after { - top: 5px; -} table.table-bordered.dataTable { border-right-width: 0; @@ -332,11 +421,4 @@ div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last- padding-right: 0; } -table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) { - --bs-table-accent-bg: transparent; -} -table.dataTable.table-striped > tbody > tr.odd { - --bs-table-accent-bg: var(--bs-table-striped-bg); -} - diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index f4b6fd95..9b1448ad 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,24 +4,23 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#bs5/dt-1.11.5 + * https://datatables.net/download/#bs5/dt-1.12.1 * * Included libraries: - * DataTables 1.11.5 + * DataTables 1.12.1 */ -/*! DataTables 1.11.5 - * ©2008-2021 SpryMedia Ltd - datatables.net/license +/*! DataTables 1.12.1 + * ©2008-2022 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables - * @version 1.11.5 - * @file jquery.dataTables.js + * @version 1.12.1 * @author SpryMedia Ltd * @contact www.datatables.net - * @copyright Copyright 2008-2021 SpryMedia Ltd. + * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license @@ -1077,7 +1076,7 @@ success: function ( json ) { _fnCamelToHungarian( defaults.oLanguage, json ); _fnLanguageCompat( json ); - $.extend( true, oLanguage, json ); + $.extend( true, oLanguage, json, oSettings.oInit.oLanguage ); _fnCallbackFire( oSettings, null, 'i18n', [oSettings]); _fnInitialise( oSettings ); @@ -2310,9 +2309,17 @@ th.addClass( oOptions.sClass ); } + var origClass = oCol.sClass; + $.extend( oCol, oOptions ); _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); + // Merge class from previously defined classes with this one, rather than just + // overwriting it in the extend above + if (origClass !== oCol.sClass) { + oCol.sClass = origClass + ' ' + oCol.sClass; + } + /* iDataSort to be applied (backwards compatibility), but aDataSort will take * priority if defined */ @@ -2585,9 +2592,11 @@ def = aoColDefs[i]; /* Each definition can target multiple columns, as it is an array */ - var aTargets = def.targets !== undefined ? - def.targets : - def.aTargets; + var aTargets = def.target !== undefined + ? def.target + : def.targets !== undefined + ? def.targets + : def.aTargets; if ( ! Array.isArray( aTargets ) ) { @@ -5089,6 +5098,7 @@ 'class': settings.oClasses.sProcessing } ) .html( settings.oLanguage.sProcessing ) + .append('
') .insertBefore( settings.nTable )[0]; } @@ -5338,6 +5348,7 @@ footerCopy = footer.clone().prependTo( table ); footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized footerSrcEls = footerCopy.find('tr'); + footerCopy.find('[id]').removeAttr('id'); } // Clone the current header and footer elements and then place it into the inner table @@ -5345,6 +5356,7 @@ headerTrgEls = header.find('tr'); // original header is in its own table headerSrcEls = headerCopy.find('tr'); headerCopy.find('th, td').removeAttr('tabindex'); + headerCopy.find('[id]').removeAttr('id'); /* @@ -6471,6 +6483,17 @@ // Store the saved state so it might be accessed at any time settings.oLoadedState = $.extend( true, {}, s ); + // Page Length + if ( s.length !== undefined ) { + // If already initialised just set the value directly so that the select element is also updated + if (api) { + api.page.len(s.length) + } + else { + settings._iDisplayLength = s.length; + } + } + // Restore key features - todo - for 1.11 this needs to be done by // subscribed events if ( s.start !== undefined ) { @@ -6479,13 +6502,9 @@ settings.iInitDisplayStart = s.start; } else { - _fnPageChange(settings, s.start/s.length); - + _fnPageChange(settings, s.start/settings._iDisplayLength); } } - if ( s.length !== undefined ) { - settings._iDisplayLength = s.length; - } // Order if ( s.order !== undefined ) { @@ -7218,8 +7237,10 @@ pluck: function ( prop ) { + let fn = DataTable.util.get(prop); + return this.map( function ( el ) { - return el[ prop ]; + return fn(el); } ); }, @@ -8448,7 +8469,7 @@ var api = new _Api( settings ); var namespace = '.dt.DT_details'; var drawEvent = 'draw'+namespace; - var colvisEvent = 'column-visibility'+namespace; + var colvisEvent = 'column-sizing'+namespace; var destroyEvent = 'destroy'+namespace; var data = settings.aoData; @@ -9500,7 +9521,6 @@ remove = remove || false; return this.iterator( 'table', function ( settings ) { - var orig = settings.nTableWrapper.parentNode; var classes = settings.oClasses; var table = settings.nTable; var tbody = settings.nTBody; @@ -9555,6 +9575,8 @@ jqTbody.children().detach(); jqTbody.append( rows ); + var orig = settings.nTableWrapper.parentNode; + // Remove the DataTables generated nodes, events and classes var removedMethod = remove ? 'remove' : 'detach'; jqTable[ removedMethod ](); @@ -9648,7 +9670,7 @@ * @type string * @default Version number */ - DataTable.version = "1.11.5"; + DataTable.version = "1.12.1"; /** * Private data store, containing all of the settings objects that are @@ -9719,5632 +9741,5854 @@ "return": false }; - - - + + + + /** + * Template object for the way in which DataTables holds information about + * each individual row. This is the object format used for the settings + * aoData array. + * @namespace + */ + DataTable.models.oRow = { /** - * Template object for the way in which DataTables holds information about - * each individual row. This is the object format used for the settings - * aoData array. - * @namespace + * TR element for the row + * @type node + * @default null */ - DataTable.models.oRow = { - /** - * TR element for the row - * @type node - * @default null - */ - "nTr": null, - - /** - * Array of TD elements for each row. This is null until the row has been - * created. - * @type array nodes - * @default [] - */ - "anCells": null, - - /** - * Data object from the original data source for the row. This is either - * an array if using the traditional form of DataTables, or an object if - * using mData options. The exact type will depend on the passed in - * data from the data source, or will be an array if using DOM a data - * source. - * @type array|object - * @default [] - */ - "_aData": [], - - /** - * Sorting data cache - this array is ostensibly the same length as the - * number of columns (although each index is generated only as it is - * needed), and holds the data that is used for sorting each column in the - * row. We do this cache generation at the start of the sort in order that - * the formatting of the sort data need be done only once for each cell - * per sort. This array should not be read from or written to by anything - * other than the master sorting methods. - * @type array - * @default null - * @private - */ - "_aSortData": null, - - /** - * Per cell filtering data cache. As per the sort data cache, used to - * increase the performance of the filtering in DataTables - * @type array - * @default null - * @private - */ - "_aFilterData": null, - - /** - * Filtering data cache. This is the same as the cell filtering cache, but - * in this case a string rather than an array. This is easily computed with - * a join on `_aFilterData`, but is provided as a cache so the join isn't - * needed on every search (memory traded for performance) - * @type array - * @default null - * @private - */ - "_sFilterRow": null, - - /** - * Cache of the class name that DataTables has applied to the row, so we - * can quickly look at this variable rather than needing to do a DOM check - * on className for the nTr property. - * @type string - * @default Empty string - * @private - */ - "_sRowStripe": "", - - /** - * Denote if the original data source was from the DOM, or the data source - * object. This is used for invalidating data, so DataTables can - * automatically read data from the original source, unless uninstructed - * otherwise. - * @type string - * @default null - * @private - */ - "src": null, - - /** - * Index in the aoData array. This saves an indexOf lookup when we have the - * object, but want to know the index - * @type integer - * @default -1 - * @private - */ - "idx": -1 - }; - - + "nTr": null, + /** - * Template object for the column information object in DataTables. This object - * is held in the settings aoColumns array and contains all the information that - * DataTables needs about each individual column. - * - * Note that this object is related to {@link DataTable.defaults.column} - * but this one is the internal data store for DataTables's cache of columns. - * It should NOT be manipulated outside of DataTables. Any configuration should - * be done through the initialisation options. - * @namespace + * Array of TD elements for each row. This is null until the row has been + * created. + * @type array nodes + * @default [] */ - DataTable.models.oColumn = { - /** - * Column index. This could be worked out on-the-fly with $.inArray, but it - * is faster to just hold it as a variable - * @type integer - * @default null - */ - "idx": null, - - /** - * A list of the columns that sorting should occur on when this column - * is sorted. That this property is an array allows multi-column sorting - * to be defined for a column (for example first name / last name columns - * would benefit from this). The values are integers pointing to the - * columns to be sorted on (typically it will be a single integer pointing - * at itself, but that doesn't need to be the case). - * @type array - */ - "aDataSort": null, - - /** - * Define the sorting directions that are applied to the column, in sequence - * as the column is repeatedly sorted upon - i.e. the first value is used - * as the sorting direction when the column if first sorted (clicked on). - * Sort it again (click again) and it will move on to the next index. - * Repeat until loop. - * @type array - */ - "asSorting": null, - - /** - * Flag to indicate if the column is searchable, and thus should be included - * in the filtering or not. - * @type boolean - */ - "bSearchable": null, - - /** - * Flag to indicate if the column is sortable or not. - * @type boolean - */ - "bSortable": null, - - /** - * Flag to indicate if the column is currently visible in the table or not - * @type boolean - */ - "bVisible": null, - - /** - * Store for manual type assignment using the `column.type` option. This - * is held in store so we can manipulate the column's `sType` property. - * @type string - * @default null - * @private - */ - "_sManualType": null, - - /** - * Flag to indicate if HTML5 data attributes should be used as the data - * source for filtering or sorting. True is either are. - * @type boolean - * @default false - * @private - */ - "_bAttrSrc": false, - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} nTd The TD node that has been created - * @param {*} sData The Data for the cell - * @param {array|object} oData The data for the whole row - * @param {int} iRow The row index for the aoData data store - * @default null - */ - "fnCreatedCell": null, - - /** - * Function to get data from a cell in a column. You should never - * access data directly through _aData internally in DataTables - always use - * the method attached to this property. It allows mData to function as - * required. This function is automatically assigned by the column - * initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {string} sSpecific The specific data type you want to get - - * 'display', 'type' 'filter' 'sort' - * @returns {*} The data for the cell from the given row's data - * @default null - */ - "fnGetData": null, - - /** - * Function to set data for a cell in the column. You should never - * set the data directly to _aData internally in DataTables - always use - * this method. It allows mData to function as required. This function - * is automatically assigned by the column initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {*} sValue Value to set - * @default null - */ - "fnSetData": null, - - /** - * Property to read the value for the cells in the column from the data - * source array / object. If null, then the default content is used, if a - * function is given then the return from the function is used. - * @type function|int|string|null - * @default null - */ - "mData": null, - - /** - * Partner property to mData which is used (only when defined) to get - * the data - i.e. it is basically the same as mData, but without the - * 'set' option, and also the data fed to it is the result from mData. - * This is the rendering method to match the data method of mData. - * @type function|int|string|null - * @default null - */ - "mRender": null, - - /** - * Unique header TH/TD element for this column - this is what the sorting - * listener is attached to (if sorting is enabled.) - * @type node - * @default null - */ - "nTh": null, - - /** - * Unique footer TH/TD element for this column (if there is one). Not used - * in DataTables as such, but can be used for plug-ins to reference the - * footer for each column. - * @type node - * @default null - */ - "nTf": null, - - /** - * The class to apply to all TD elements in the table's TBODY for the column - * @type string - * @default null - */ - "sClass": null, - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * @type string - */ - "sContentPadding": null, - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because mData - * is set to null, or because the data source itself is null). - * @type string - * @default null - */ - "sDefaultContent": null, - - /** - * Name for the column, allowing reference to the column by name as well as - * by index (needs a lookup to work by name). - * @type string - */ - "sName": null, - - /** - * Custom sorting data type - defines which of the available plug-ins in - * afnSortData the custom sorting will use - if any is defined. - * @type string - * @default std - */ - "sSortDataType": 'std', - - /** - * Class to be applied to the header element when sorting on this column - * @type string - * @default null - */ - "sSortingClass": null, - - /** - * Class to be applied to the header element when sorting on this column - - * when jQuery UI theming is used. - * @type string - * @default null - */ - "sSortingClassJUI": null, - - /** - * Title of the column - what is seen in the TH element (nTh). - * @type string - */ - "sTitle": null, - - /** - * Column sorting and filtering type - * @type string - * @default null - */ - "sType": null, - - /** - * Width of the column - * @type string - * @default null - */ - "sWidth": null, - - /** - * Width of the column when it was first "encountered" - * @type string - * @default null - */ - "sWidthOrig": null - }; - - - /* - * Developer note: The properties of the object below are given in Hungarian - * notation, that was used as the interface for DataTables prior to v1.10, however - * from v1.10 onwards the primary interface is camel case. In order to avoid - * breaking backwards compatibility utterly with this change, the Hungarian - * version is still, internally the primary interface, but is is not documented - * - hence the @name tags in each doc comment. This allows a Javascript function - * to create a map from Hungarian notation to camel case (going the other direction - * would require each property to be listed, which would add around 3K to the size - * of DataTables, while this method is about a 0.5K hit). - * - * Ultimately this does pave the way for Hungarian notation to be dropped - * completely, but that is a massive amount of work and will break current - * installs (therefore is on-hold until v2). + "anCells": null, + + /** + * Data object from the original data source for the row. This is either + * an array if using the traditional form of DataTables, or an object if + * using mData options. The exact type will depend on the passed in + * data from the data source, or will be an array if using DOM a data + * source. + * @type array|object + * @default [] */ - + "_aData": [], + /** - * Initialisation options that can be given to DataTables at initialisation - * time. - * @namespace + * Sorting data cache - this array is ostensibly the same length as the + * number of columns (although each index is generated only as it is + * needed), and holds the data that is used for sorting each column in the + * row. We do this cache generation at the start of the sort in order that + * the formatting of the sort data need be done only once for each cell + * per sort. This array should not be read from or written to by anything + * other than the master sorting methods. + * @type array + * @default null + * @private */ - DataTable.defaults = { - /** - * An array of data to use for the table, passed in at initialisation which - * will be used in preference to any data which is already in the DOM. This is - * particularly useful for constructing tables purely in Javascript, for - * example with a custom Ajax call. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.data - * - * @example - * // Using a 2D array data source - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], - * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], - * ], - * "columns": [ - * { "title": "Engine" }, - * { "title": "Browser" }, - * { "title": "Platform" }, - * { "title": "Version" }, - * { "title": "Grade" } - * ] - * } ); - * } ); - * - * @example - * // Using an array of objects as a data source (`data`) - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * { - * "engine": "Trident", - * "browser": "Internet Explorer 4.0", - * "platform": "Win 95+", - * "version": 4, - * "grade": "X" - * }, - * { - * "engine": "Trident", - * "browser": "Internet Explorer 5.0", - * "platform": "Win 95+", - * "version": 5, - * "grade": "C" - * } - * ], - * "columns": [ - * { "title": "Engine", "data": "engine" }, - * { "title": "Browser", "data": "browser" }, - * { "title": "Platform", "data": "platform" }, - * { "title": "Version", "data": "version" }, - * { "title": "Grade", "data": "grade" } - * ] - * } ); - * } ); - */ - "aaData": null, - - - /** - * If ordering is enabled, then DataTables will perform a first pass sort on - * initialisation. You can define which column(s) the sort is performed - * upon, and the sorting direction, with this variable. The `sorting` array - * should contain an array for each column to be sorted initially containing - * the column's index and a direction string ('asc' or 'desc'). - * @type array - * @default [[0,'asc']] - * - * @dtopt Option - * @name DataTable.defaults.order - * - * @example - * // Sort by 3rd column first, and then 4th column - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [[2,'asc'], [3,'desc']] - * } ); - * } ); - * - * // No initial sorting - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [] - * } ); - * } ); - */ - "aaSorting": [[0,'asc']], - - - /** - * This parameter is basically identical to the `sorting` parameter, but - * cannot be overridden by user interaction with the table. What this means - * is that you could have a column (visible or hidden) which the sorting - * will always be forced on first - any sorting after that (from the user) - * will then be performed as required. This can be useful for grouping rows - * together. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.orderFixed - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderFixed": [[0,'asc']] - * } ); - * } ) - */ - "aaSortingFixed": [], - - - /** - * DataTables can be instructed to load data to display in the table from a - * Ajax source. This option defines how that Ajax call is made and where to. - * - * The `ajax` property has three different modes of operation, depending on - * how it is defined. These are: - * - * * `string` - Set the URL from where the data should be loaded from. - * * `object` - Define properties for `jQuery.ajax`. - * * `function` - Custom data get function - * - * `string` - * -------- - * - * As a string, the `ajax` property simply defines the URL from which - * DataTables will load data. - * - * `object` - * -------- - * - * As an object, the parameters in the object are passed to - * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control - * of the Ajax request. DataTables has a number of default parameters which - * you can override using this option. Please refer to the jQuery - * documentation for a full description of the options available, although - * the following parameters provide additional options in DataTables or - * require special consideration: - * - * * `data` - As with jQuery, `data` can be provided as an object, but it - * can also be used as a function to manipulate the data DataTables sends - * to the server. The function takes a single parameter, an object of - * parameters with the values that DataTables has readied for sending. An - * object may be returned which will be merged into the DataTables - * defaults, or you can add the items to the object that was passed in and - * not return anything from the function. This supersedes `fnServerParams` - * from DataTables 1.9-. - * - * * `dataSrc` - By default DataTables will look for the property `data` (or - * `aaData` for compatibility with DataTables 1.9-) when obtaining data - * from an Ajax source or for server-side processing - this parameter - * allows that property to be changed. You can use Javascript dotted - * object notation to get a data source for multiple levels of nesting, or - * it my be used as a function. As a function it takes a single parameter, - * the JSON returned from the server, which can be manipulated as - * required, with the returned value being that used by DataTables as the - * data source for the table. This supersedes `sAjaxDataProp` from - * DataTables 1.9-. - * - * * `success` - Should not be overridden it is used internally in - * DataTables. To manipulate / transform the data returned by the server - * use `ajax.dataSrc`, or use `ajax` as a function (see below). - * - * `function` - * ---------- - * - * As a function, making the Ajax call is left up to yourself allowing - * complete control of the Ajax request. Indeed, if desired, a method other - * than Ajax could be used to obtain the required data, such as Web storage - * or an AIR database. - * - * The function is given four parameters and no return is required. The - * parameters are: - * - * 1. _object_ - Data to send to the server - * 2. _function_ - Callback function that must be executed when the required - * data has been obtained. That data should be passed into the callback - * as the only parameter - * 3. _object_ - DataTables settings object for the table - * - * Note that this supersedes `fnServerData` from DataTables 1.9-. - * - * @type string|object|function - * @default null - * - * @dtopt Option - * @name DataTable.defaults.ajax - * @since 1.10.0 - * - * @example - * // Get JSON data from a file via Ajax. - * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). - * $('#example').dataTable( { - * "ajax": "data.json" - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to change - * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "tableData" - * } - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to read data - * // from a plain array rather than an array in an object - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "" - * } - * } ); - * - * @example - * // Manipulate the data returned from the server - add a link to data - * // (note this can, should, be done using `render` for the column - this - * // is just a simple example of how the data can be manipulated). - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": function ( json ) { - * for ( var i=0, ien=json.length ; iView message'; - * } - * return json; - * } - * } - * } ); - * - * @example - * // Add data to the request - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "data": function ( d ) { - * return { - * "extra_search": $('#extra').val() - * }; - * } - * } - * } ); - * - * @example - * // Send request as POST - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "type": "POST" - * } - * } ); - * - * @example - * // Get the data from localStorage (could interface with a form for - * // adding, editing and removing rows). - * $('#example').dataTable( { - * "ajax": function (data, callback, settings) { - * callback( - * JSON.parse( localStorage.getItem('dataTablesData') ) - * ); - * } - * } ); - */ - "ajax": null, - - - /** - * This parameter allows you to readily specify the entries in the length drop - * down menu that DataTables shows when pagination is enabled. It can be - * either a 1D array of options which will be used for both the displayed - * option and the value, or a 2D array which will use the array in the first - * position as the value, and the array in the second position as the - * displayed options (useful for language strings such as 'All'). - * - * Note that the `pageLength` property will be automatically set to the - * first value given in this array, unless `pageLength` is also provided. - * @type array - * @default [ 10, 25, 50, 100 ] - * - * @dtopt Option - * @name DataTable.defaults.lengthMenu - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] - * } ); - * } ); - */ - "aLengthMenu": [ 10, 25, 50, 100 ], - - - /** - * The `columns` option in the initialisation parameter allows you to define - * details about the way individual columns behave. For a full list of - * column options that can be set, please see - * {@link DataTable.defaults.column}. Note that if you use `columns` to - * define your columns, you must have an entry in the array for every single - * column that you have in your table (these can be null if you don't which - * to specify any options). - * @member - * - * @name DataTable.defaults.column - */ - "aoColumns": null, - - /** - * Very similar to `columns`, `columnDefs` allows you to target a specific - * column, multiple columns, or all columns, using the `targets` property of - * each object in the array. This allows great flexibility when creating - * tables, as the `columnDefs` arrays can be of any length, targeting the - * columns you specifically want. `columnDefs` may use any of the column - * options available: {@link DataTable.defaults.column}, but it _must_ - * have `targets` defined in each object in the array. Values in the `targets` - * array may be: - *
    - *
  • a string - class name will be matched on the TH for the column
  • - *
  • 0 or a positive integer - column index counting from the left
  • - *
  • a negative integer - column index counting from the right
  • - *
  • the string "_all" - all columns (i.e. assign a default)
  • - *
- * @member - * - * @name DataTable.defaults.columnDefs - */ - "aoColumnDefs": null, - - - /** - * Basically the same as `search`, this parameter defines the individual column - * filtering state at initialisation time. The array must be of the same size - * as the number of columns, and each element be an object with the parameters - * `search` and `escapeRegex` (the latter is optional). 'null' is also - * accepted and the default will be used. - * @type array - * @default [] - * - * @dtopt Option - * @name DataTable.defaults.searchCols - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchCols": [ - * null, - * { "search": "My filter" }, - * null, - * { "search": "^[0-9]", "escapeRegex": false } - * ] - * } ); - * } ) - */ - "aoSearchCols": [], - - - /** - * An array of CSS classes that should be applied to displayed rows. This - * array may be of any length, and DataTables will apply each class - * sequentially, looping when required. - * @type array - * @default null Will take the values determined by the `oClasses.stripe*` - * options - * - * @dtopt Option - * @name DataTable.defaults.stripeClasses - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] - * } ); - * } ) - */ - "asStripeClasses": null, - - - /** - * Enable or disable automatic column width calculation. This can be disabled - * as an optimisation (it takes some time to calculate the widths) if the - * tables widths are passed in using `columns`. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.autoWidth - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "autoWidth": false - * } ); - * } ); - */ - "bAutoWidth": true, - - - /** - * Deferred rendering can provide DataTables with a huge speed boost when you - * are using an Ajax or JS data source for the table. This option, when set to - * true, will cause DataTables to defer the creation of the table elements for - * each row until they are needed for a draw - saving a significant amount of - * time. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.deferRender - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajax": "sources/arrays.txt", - * "deferRender": true - * } ); - * } ); - */ - "bDeferRender": false, - - - /** - * Replace a DataTable which matches the given selector and replace it with - * one which has the properties of the new initialisation object passed. If no - * table matches the selector, then the new DataTable will be constructed as - * per normal. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.destroy - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "srollY": "200px", - * "paginate": false - * } ); - * - * // Some time later.... - * $('#example').dataTable( { - * "filter": false, - * "destroy": true - * } ); - * } ); - */ - "bDestroy": false, - - - /** - * Enable or disable filtering of data. Filtering in DataTables is "smart" in - * that it allows the end user to input multiple words (space separated) and - * will match a row containing those words, even if not in the order that was - * specified (this allow matching across multiple columns). Note that if you - * wish to use filtering in DataTables this must remain 'true' - to remove the - * default filtering input box and retain filtering abilities, please use - * {@link DataTable.defaults.dom}. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.searching - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "searching": false - * } ); - * } ); - */ - "bFilter": true, - - - /** - * Enable or disable the table information display. This shows information - * about the data that is currently visible on the page, including information - * about filtered data if that action is being performed. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.info - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "info": false - * } ); - * } ); - */ - "bInfo": true, - - - /** - * Allows the end user to select the size of a formatted page from a select - * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.lengthChange - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "lengthChange": false - * } ); - * } ); - */ - "bLengthChange": true, - - - /** - * Enable or disable pagination. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.paging - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "paging": false - * } ); - * } ); - */ - "bPaginate": true, - - - /** - * Enable or disable the display of a 'processing' indicator when the table is - * being processed (e.g. a sort). This is particularly useful for tables with - * large amounts of data where it can take a noticeable amount of time to sort - * the entries. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.processing - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "processing": true - * } ); - * } ); - */ - "bProcessing": false, - - - /** - * Retrieve the DataTables object for the given selector. Note that if the - * table has already been initialised, this parameter will cause DataTables - * to simply return the object that has already been set up - it will not take - * account of any changes you might have made to the initialisation object - * passed to DataTables (setting this parameter to true is an acknowledgement - * that you understand this). `destroy` can be used to reinitialise a table if - * you need. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.retrieve - * - * @example - * $(document).ready( function() { - * initTable(); - * tableActions(); - * } ); - * - * function initTable () - * { - * return $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false, - * "retrieve": true - * } ); - * } - * - * function tableActions () - * { - * var table = initTable(); - * // perform API operations with oTable - * } - */ - "bRetrieve": false, - - - /** - * When vertical (y) scrolling is enabled, DataTables will force the height of - * the table's viewport to the given height at all times (useful for layout). - * However, this can look odd when filtering data down to a small data set, - * and the footer is left "floating" further down. This parameter (when - * enabled) will cause DataTables to collapse the table's viewport down when - * the result set will fit within the given Y height. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.scrollCollapse - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200", - * "scrollCollapse": true - * } ); - * } ); - */ - "bScrollCollapse": false, - - - /** - * Configure DataTables to use server-side processing. Note that the - * `ajax` parameter must also be given in order to give DataTables a - * source to obtain the required data for each draw. - * @type boolean - * @default false - * - * @dtopt Features - * @dtopt Server-side - * @name DataTable.defaults.serverSide - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "xhr.php" - * } ); - * } ); - */ - "bServerSide": false, - - - /** - * Enable or disable sorting of columns. Sorting of individual columns can be - * disabled by the `sortable` option for each column. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.ordering - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "ordering": false - * } ); - * } ); - */ - "bSort": true, - - - /** - * Enable or display DataTables' ability to sort multiple columns at the - * same time (activated by shift-click by the user). - * @type boolean - * @default true - * - * @dtopt Options - * @name DataTable.defaults.orderMulti - * - * @example - * // Disable multiple column sorting ability - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderMulti": false - * } ); - * } ); - */ - "bSortMulti": true, - - - /** - * Allows control over whether DataTables should use the top (true) unique - * cell that is found for a single column, or the bottom (false - default). - * This is useful when using complex headers. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.orderCellsTop - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderCellsTop": true - * } ); - * } ); - */ - "bSortCellsTop": false, - - - /** - * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and - * `sorting\_3` to the columns which are currently being sorted on. This is - * presented as a feature switch as it can increase processing time (while - * classes are removed and added) so for large data sets you might want to - * turn this off. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.orderClasses - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderClasses": false - * } ); - * } ); - */ - "bSortClasses": true, - - - /** - * Enable or disable state saving. When enabled HTML5 `localStorage` will be - * used to save table display information such as pagination information, - * display length, filtering and sorting. As such when the end user reloads - * the page the display display will match what thy had previously set up. - * - * Due to the use of `localStorage` the default state saving is not supported - * in IE6 or 7. If state saving is required in those browsers, use - * `stateSaveCallback` to provide a storage solution such as cookies. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.stateSave - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "stateSave": true - * } ); - * } ); - */ - "bStateSave": false, - - - /** - * This function is called when a TR element is created (and all TD child - * elements have been inserted), or registered if using a DOM source, allowing - * manipulation of the TR element (adding classes etc). - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} dataIndex The index of this row in the internal aoData array - * - * @dtopt Callbacks - * @name DataTable.defaults.createdRow - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "createdRow": function( row, data, dataIndex ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) - * { - * $('td:eq(4)', row).html( 'A' ); - * } - * } - * } ); - * } ); - */ - "fnCreatedRow": null, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify any aspect you want about the created DOM. - * @type function - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @name DataTable.defaults.drawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "drawCallback": function( settings ) { - * alert( 'DataTables has redrawn the table' ); - * } - * } ); - * } ); - */ - "fnDrawCallback": null, - - - /** - * Identical to fnHeaderCallback() but for the table footer this function - * allows you to modify the table footer on every 'draw' event. - * @type function - * @param {node} foot "TR" element for the footer - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.footerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "footerCallback": function( tfoot, data, start, end, display ) { - * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; - * } - * } ); - * } ) - */ - "fnFooterCallback": null, - - - /** - * When rendering large numbers in the information element for the table - * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers - * to have a comma separator for the 'thousands' units (e.g. 1 million is - * rendered as "1,000,000") to help readability for the end user. This - * function will override the default method DataTables uses. - * @type function - * @member - * @param {int} toFormat number to be formatted - * @returns {string} formatted string for DataTables to show the number - * - * @dtopt Callbacks - * @name DataTable.defaults.formatNumber - * - * @example - * // Format a number using a single quote for the separator (note that - * // this can also be done with the language.thousands option) - * $(document).ready( function() { - * $('#example').dataTable( { - * "formatNumber": function ( toFormat ) { - * return toFormat.toString().replace( - * /\B(?=(\d{3})+(?!\d))/g, "'" - * ); - * }; - * } ); - * } ); - */ - "fnFormatNumber": function ( toFormat ) { - return toFormat.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, - this.oLanguage.sThousands - ); - }, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify the header row. This can be used to calculate and - * display useful information about the table. - * @type function - * @param {node} head "TR" element for the header - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.headerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "fheaderCallback": function( head, data, start, end, display ) { - * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; - * } - * } ); - * } ) - */ - "fnHeaderCallback": null, - - - /** - * The information element can be used to convey information about the current - * state of the table. Although the internationalisation options presented by - * DataTables are quite capable of dealing with most customisations, there may - * be times where you wish to customise the string further. This callback - * allows you to do exactly that. - * @type function - * @param {object} oSettings DataTables settings object - * @param {int} start Starting position in data for the draw - * @param {int} end End position in data for the draw - * @param {int} max Total number of rows in the table (regardless of - * filtering) - * @param {int} total Total number of rows in the data set, after filtering - * @param {string} pre The string that DataTables has formatted using it's - * own rules - * @returns {string} The string to be displayed in the information element. - * - * @dtopt Callbacks - * @name DataTable.defaults.infoCallback - * - * @example - * $('#example').dataTable( { - * "infoCallback": function( settings, start, end, max, total, pre ) { - * return start +" to "+ end; - * } - * } ); - */ - "fnInfoCallback": null, - - - /** - * Called when the table has been initialised. Normally DataTables will - * initialise sequentially and there will be no need for this function, - * however, this does not hold true when using external language information - * since that is obtained using an async XHR call. - * @type function - * @param {object} settings DataTables settings object - * @param {object} json The JSON object request from the server - only - * present if client-side Ajax sourced data is used - * - * @dtopt Callbacks - * @name DataTable.defaults.initComplete - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "initComplete": function(settings, json) { - * alert( 'DataTables has finished its initialisation.' ); - * } - * } ); - * } ) - */ - "fnInitComplete": null, - - - /** - * Called at the very start of each table draw and can be used to cancel the - * draw by returning false, any other return (including undefined) results in - * the full draw occurring). - * @type function - * @param {object} settings DataTables settings object - * @returns {boolean} False will cancel the draw, anything else (including no - * return) will allow it to complete. - * - * @dtopt Callbacks - * @name DataTable.defaults.preDrawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "preDrawCallback": function( settings ) { - * if ( $('#test').val() == 1 ) { - * return false; - * } - * } - * } ); - * } ); - */ - "fnPreDrawCallback": null, - - - /** - * This function allows you to 'post process' each row after it have been - * generated for each table draw, but before it is rendered on screen. This - * function might be used for setting the row class name etc. - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} displayIndex The display index for the current table draw - * @param {int} displayIndexFull The index of the data in the full list of - * rows (after filtering) - * - * @dtopt Callbacks - * @name DataTable.defaults.rowCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) { - * $('td:eq(4)', row).html( 'A' ); - * } - * } - * } ); - * } ); - */ - "fnRowCallback": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * This parameter allows you to override the default function which obtains - * the data from the server so something more suitable for your application. - * For example you could use POST data, or pull information from a Gears or - * AIR database. - * @type function - * @member - * @param {string} source HTTP source to obtain the data from (`ajax`) - * @param {array} data A key/value pair object containing the data to send - * to the server - * @param {function} callback to be called on completion of the data get - * process that will draw the data on the page. - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverData - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerData": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * It is often useful to send extra data to the server when making an Ajax - * request - for example custom filtering information, and this callback - * function makes it trivial to send extra information to the server. The - * passed in parameter is the data set that has been constructed by - * DataTables, and you can add to this or modify it as you require. - * @type function - * @param {array} data Data array (array of objects which are name/value - * pairs) that has been constructed by DataTables and will be sent to the - * server. In the case of Ajax sourced data with server-side processing - * this will be an empty array, for server-side processing there will be a - * significant number of parameters! - * @returns {undefined} Ensure that you modify the data array passed in, - * as this is passed by reference. - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverParams - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerParams": null, - - - /** - * Load the table state. With this function you can define from where, and how, the - * state of a table is loaded. By default DataTables will load from `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @param {object} callback Callback that can be executed when done. It - * should be passed the loaded state object. - * @return {object} The DataTables state object to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadCallback": function (settings, callback) { - * $.ajax( { - * "url": "/state_load", - * "dataType": "json", - * "success": function (json) { - * callback( json ); - * } - * } ); - * } - * } ); - * } ); - */ - "fnStateLoadCallback": function ( settings ) { - try { - return JSON.parse( - (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname - ) - ); - } catch (e) { - return {}; - } - }, - - - /** - * Callback which allows modification of the saved state prior to loading that state. - * This callback is called when the table is loading state from the stored data, but - * prior to the settings object being modified by the saved state. Note that for - * plug-in authors, you should use the `stateLoadParams` event to load parameters for - * a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that is to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadParams - * - * @example - * // Remove a saved filter, so filtering is never loaded - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - * - * @example - * // Disallow state loading by returning false - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * return false; - * } - * } ); - * } ); - */ - "fnStateLoadParams": null, - - - /** - * Callback that is called when the state has been loaded from the state saving method - * and the DataTables settings object has been modified as a result of the loaded state. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that was loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoaded - * - * @example - * // Show an alert with the filtering value that was saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoaded": function (settings, data) { - * alert( 'Saved filter was: '+data.oSearch.sSearch ); - * } - * } ); - * } ); - */ - "fnStateLoaded": null, - - - /** - * Save the table state. This function allows you to define where and how the state - * information for the table is stored By default DataTables will use `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveCallback": function (settings, data) { - * // Send an Ajax request to the server with the state object - * $.ajax( { - * "url": "/state_save", - * "data": data, - * "dataType": "json", - * "method": "POST" - * "success": function () {} - * } ); - * } - * } ); - * } ); - */ - "fnStateSaveCallback": function ( settings, data ) { - try { - (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname, - JSON.stringify( data ) - ); - } catch (e) {} - }, - - - /** - * Callback which allows modification of the state to be saved. Called when the table - * has changed state a new state save is required. This method allows modification of - * the state saving object prior to actually doing the save, including addition or - * other state properties or modification. Note that for plug-in authors, you should - * use the `stateSaveParams` event to save parameters for a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveParams - * - * @example - * // Remove a saved filter, so filtering is never saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - */ - "fnStateSaveParams": null, - - - /** - * Duration for which the saved state information is considered valid. After this period - * has elapsed the state will be returned to the default. - * Value is given in seconds. - * @type int - * @default 7200 (2 hours) - * - * @dtopt Options - * @name DataTable.defaults.stateDuration - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateDuration": 60*60*24; // 1 day - * } ); - * } ) - */ - "iStateDuration": 7200, - - - /** - * When enabled DataTables will not make a request to the server for the first - * page draw - rather it will use the data already on the page (no sorting etc - * will be applied to it), thus saving on an XHR at load time. `deferLoading` - * is used to indicate that deferred loading is required, but it is also used - * to tell DataTables how many records there are in the full table (allowing - * the information element and pagination to be displayed correctly). In the case - * where a filtering is applied to the table on initial load, this can be - * indicated by giving the parameter as an array, where the first element is - * the number of records available after filtering and the second element is the - * number of records without filtering (allowing the table information element - * to be shown correctly). - * @type int | array - * @default null - * - * @dtopt Options - * @name DataTable.defaults.deferLoading - * - * @example - * // 57 records available in the table, no filtering applied - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": 57 - * } ); - * } ); - * - * @example - * // 57 records after filtering, 100 without filtering (an initial filter applied) - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": [ 57, 100 ], - * "search": { - * "search": "my_filter" - * } - * } ); - * } ); - */ - "iDeferLoading": null, - - - /** - * Number of rows to display on a single page when using pagination. If - * feature enabled (`lengthChange`) then the end user will be able to override - * this to a custom setting using a pop-up menu. - * @type int - * @default 10 - * - * @dtopt Options - * @name DataTable.defaults.pageLength - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pageLength": 50 - * } ); - * } ) - */ - "iDisplayLength": 10, - - - /** - * Define the starting point for data display when using DataTables with - * pagination. Note that this parameter is the number of records, rather than - * the page number, so if you have 10 records per page and want to start on - * the third page, it should be "20". - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.displayStart - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "displayStart": 20 - * } ); - * } ) - */ - "iDisplayStart": 0, - - - /** - * By default DataTables allows keyboard navigation of the table (sorting, paging, - * and filtering) by adding a `tabindex` attribute to the required elements. This - * allows you to tab through the controls and press the enter key to activate them. - * The tabindex is default 0, meaning that the tab follows the flow of the document. - * You can overrule this using this parameter if you wish. Use a value of -1 to - * disable built-in keyboard navigation. - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.tabIndex - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "tabIndex": 1 - * } ); - * } ); - */ - "iTabIndex": 0, - - - /** - * Classes that DataTables assigns to the various components and features - * that it adds to the HTML table. This allows classes to be configured - * during initialisation in addition to through the static - * {@link DataTable.ext.oStdClasses} object). - * @namespace - * @name DataTable.defaults.classes - */ - "oClasses": {}, - - - /** - * All strings that DataTables uses in the user interface that it creates - * are defined in this object, allowing you to modified them individually or - * completely replace them all as required. - * @namespace - * @name DataTable.defaults.language - */ - "oLanguage": { - /** - * Strings that are used for WAI-ARIA labels and controls only (these are not - * actually visible on the page, but will be read by screenreaders, and thus - * must be internationalised as well). - * @namespace - * @name DataTable.defaults.language.aria - */ - "oAria": { - /** - * ARIA label that is added to the table headers when the column may be - * sorted ascending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortAscending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortAscending": " - click/return to sort ascending" - * } - * } - * } ); - * } ); - */ - "sSortAscending": ": activate to sort column ascending", - - /** - * ARIA label that is added to the table headers when the column may be - * sorted descending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortDescending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortDescending": " - click/return to sort descending" - * } - * } - * } ); - * } ); - */ - "sSortDescending": ": activate to sort column descending" - }, - - /** - * Pagination string used by DataTables for the built-in pagination - * control types. - * @namespace - * @name DataTable.defaults.language.paginate - */ - "oPaginate": { - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the first page. - * @type string - * @default First - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.first - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "first": "First page" - * } - * } - * } ); - * } ); - */ - "sFirst": "First", - - - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the last page. - * @type string - * @default Last - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.last - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "last": "Last page" - * } - * } - * } ); - * } ); - */ - "sLast": "Last", - - - /** - * Text to use for the 'next' pagination button (to take the user to the - * next page). - * @type string - * @default Next - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.next - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "next": "Next page" - * } - * } - * } ); - * } ); - */ - "sNext": "Next", - - - /** - * Text to use for the 'previous' pagination button (to take the user to - * the previous page). - * @type string - * @default Previous - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.previous - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "previous": "Previous page" - * } - * } - * } ); - * } ); - */ - "sPrevious": "Previous" - }, - - /** - * This string is shown in preference to `zeroRecords` when the table is - * empty of data (regardless of filtering). Note that this is an optional - * parameter - if it is not given, the value of `zeroRecords` will be used - * instead (either the default or given value). - * @type string - * @default No data available in table - * - * @dtopt Language - * @name DataTable.defaults.language.emptyTable - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "emptyTable": "No data available in table" - * } - * } ); - * } ); - */ - "sEmptyTable": "No data available in table", - - - /** - * This string gives information to the end user about the information - * that is current on display on the page. The following tokens can be - * used in the string and will be dynamically replaced as the table - * display updates. This tokens can be placed anywhere in the string, or - * removed as needed by the language requires: - * - * * `\_START\_` - Display index of the first record on the current page - * * `\_END\_` - Display index of the last record on the current page - * * `\_TOTAL\_` - Number of records in the table after filtering - * * `\_MAX\_` - Number of records in the table without filtering - * * `\_PAGE\_` - Current page number - * * `\_PAGES\_` - Total number of pages of data in the table - * - * @type string - * @default Showing _START_ to _END_ of _TOTAL_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.info - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "info": "Showing page _PAGE_ of _PAGES_" - * } - * } ); - * } ); - */ - "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", - - - /** - * Display information string for when the table is empty. Typically the - * format of this string should match `info`. - * @type string - * @default Showing 0 to 0 of 0 entries - * - * @dtopt Language - * @name DataTable.defaults.language.infoEmpty - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoEmpty": "No entries to show" - * } - * } ); - * } ); - */ - "sInfoEmpty": "Showing 0 to 0 of 0 entries", - - - /** - * When a user filters the information in a table, this string is appended - * to the information (`info`) to give an idea of how strong the filtering - * is. The variable _MAX_ is dynamically updated. - * @type string - * @default (filtered from _MAX_ total entries) - * - * @dtopt Language - * @name DataTable.defaults.language.infoFiltered - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoFiltered": " - filtering from _MAX_ records" - * } - * } ); - * } ); - */ - "sInfoFiltered": "(filtered from _MAX_ total entries)", - - - /** - * If can be useful to append extra information to the info string at times, - * and this variable does exactly that. This information will be appended to - * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are - * being used) at all times. - * @type string - * @default Empty string - * - * @dtopt Language - * @name DataTable.defaults.language.infoPostFix - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoPostFix": "All records shown are derived from real information." - * } - * } ); - * } ); - */ - "sInfoPostFix": "", - - - /** - * This decimal place operator is a little different from the other - * language options since DataTables doesn't output floating point - * numbers, so it won't ever use this for display of a number. Rather, - * what this parameter does is modify the sort methods of the table so - * that numbers which are in a format which has a character other than - * a period (`.`) as a decimal place will be sorted numerically. - * - * Note that numbers with different decimal places cannot be shown in - * the same table and still be sortable, the table must be consistent. - * However, multiple different tables on the page can use different - * decimal place characters. - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.decimal - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "decimal": "," - * "thousands": "." - * } - * } ); - * } ); - */ - "sDecimal": "", - - - /** - * DataTables has a build in number formatter (`formatNumber`) which is - * used to format large numbers that are used in the table information. - * By default a comma is used, but this can be trivially changed to any - * character you wish with this parameter. - * @type string - * @default , - * - * @dtopt Language - * @name DataTable.defaults.language.thousands - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "thousands": "'" - * } - * } ); - * } ); - */ - "sThousands": ",", - - - /** - * Detail the action that will be taken when the drop down menu for the - * pagination length option is changed. The '_MENU_' variable is replaced - * with a default select list of 10, 25, 50 and 100, and can be replaced - * with a custom select box if required. - * @type string - * @default Show _MENU_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.lengthMenu - * - * @example - * // Language change only - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": "Display _MENU_ records" - * } - * } ); - * } ); - * - * @example - * // Language and options change - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": 'Display records' - * } - * } ); - * } ); - */ - "sLengthMenu": "Show _MENU_ entries", - - - /** - * When using Ajax sourced data and during the first draw when DataTables is - * gathering the data, this message is shown in an empty row in the table to - * indicate to the end user the the data is being loaded. Note that this - * parameter is not used when loading data by server-side processing, just - * Ajax sourced data with client-side processing. - * @type string - * @default Loading... - * - * @dtopt Language - * @name DataTable.defaults.language.loadingRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "loadingRecords": "Please wait - loading..." - * } - * } ); - * } ); - */ - "sLoadingRecords": "Loading...", - - - /** - * Text which is displayed when the table is processing a user action - * (usually a sort command or similar). - * @type string - * @default Processing... - * - * @dtopt Language - * @name DataTable.defaults.language.processing - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "processing": "DataTables is currently busy" - * } - * } ); - * } ); - */ - "sProcessing": "Processing...", - - - /** - * Details the actions that will be taken when the user types into the - * filtering input text box. The variable "_INPUT_", if used in the string, - * is replaced with the HTML text box for the filtering input allowing - * control over where it appears in the string. If "_INPUT_" is not given - * then the input box is appended to the string automatically. - * @type string - * @default Search: - * - * @dtopt Language - * @name DataTable.defaults.language.search - * - * @example - * // Input text box will be appended at the end automatically - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Filter records:" - * } - * } ); - * } ); - * - * @example - * // Specify where the filter should appear - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Apply filter _INPUT_ to table" - * } - * } ); - * } ); - */ - "sSearch": "Search:", - - - /** - * Assign a `placeholder` attribute to the search `input` element - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.searchPlaceholder - */ - "sSearchPlaceholder": "", - - - /** - * All of the language information can be stored in a file on the - * server-side, which DataTables will look up if this parameter is passed. - * It must store the URL of the language file, which is in a JSON format, - * and the object has the same properties as the oLanguage object in the - * initialiser object (i.e. the above parameters). Please refer to one of - * the example language files to see how this works in action. - * @type string - * @default Empty string - i.e. disabled - * - * @dtopt Language - * @name DataTable.defaults.language.url - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" - * } - * } ); - * } ); - */ - "sUrl": "", - - - /** - * Text shown inside the table records when the is no information to be - * displayed after filtering. `emptyTable` is shown when there is simply no - * information in the table at all (regardless of filtering). - * @type string - * @default No matching records found - * - * @dtopt Language - * @name DataTable.defaults.language.zeroRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "zeroRecords": "No records to display" - * } - * } ); - * } ); - */ - "sZeroRecords": "No matching records found" - }, - - - /** - * This parameter allows you to have define the global filtering state at - * initialisation time. As an object the `search` parameter must be - * defined, but all other parameters are optional. When `regex` is true, - * the search string will be treated as a regular expression, when false - * (default) it will be treated as a straight string. When `smart` - * DataTables will use it's smart filtering methods (to word match at - * any point in the data), when false this will not be done. - * @namespace - * @extends DataTable.models.oSearch - * - * @dtopt Options - * @name DataTable.defaults.search - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "search": {"search": "Initial search"} - * } ); - * } ) - */ - "oSearch": $.extend( {}, DataTable.models.oSearch ), - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * By default DataTables will look for the property `data` (or `aaData` for - * compatibility with DataTables 1.9-) when obtaining data from an Ajax - * source or for server-side processing - this parameter allows that - * property to be changed. You can use Javascript dotted object notation to - * get a data source for multiple levels of nesting. - * @type string - * @default data - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxDataProp - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxDataProp": "data", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * You can instruct DataTables to load data from an external - * source using this parameter (use aData if you want to pass data in you - * already have). Simply provide a url a JSON object can be obtained from. - * @type string - * @default null - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxSource - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxSource": null, - - - /** - * This initialisation variable allows you to specify exactly where in the - * DOM you want DataTables to inject the various controls it adds to the page - * (for example you might want the pagination controls at the top of the - * table). DIV elements (with or without a custom class) can also be added to - * aid styling. The follow syntax is used: - *
    - *
  • The following options are allowed: - *
      - *
    • 'l' - Length changing
    • - *
    • 'f' - Filtering input
    • - *
    • 't' - The table!
    • - *
    • 'i' - Information
    • - *
    • 'p' - Pagination
    • - *
    • 'r' - pRocessing
    • - *
    - *
  • - *
  • The following constants are allowed: - *
      - *
    • 'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')
    • - *
    • 'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')
    • - *
    - *
  • - *
  • The following syntax is expected: - *
      - *
    • '<' and '>' - div elements
    • - *
    • '<"class" and '>' - div with a class
    • - *
    • '<"#id" and '>' - div with an ID
    • - *
    - *
  • - *
  • Examples: - *
      - *
    • '<"wrapper"flipt>'
    • - *
    • '<lf<t>ip>'
    • - *
    - *
  • - *
- * @type string - * @default lfrtip (when `jQueryUI` is false) or - * <"H"lfr>t<"F"ip> (when `jQueryUI` is true) - * - * @dtopt Options - * @name DataTable.defaults.dom - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "dom": '<"top"i>rt<"bottom"flp><"clear">' - * } ); - * } ); - */ - "sDom": "lfrtip", - - - /** - * Search delay option. This will throttle full table searches that use the - * DataTables provided search input element (it does not effect calls to - * `dt-api search()`, providing a delay before the search is made. - * @type integer - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.searchDelay - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchDelay": 200 - * } ); - * } ) - */ - "searchDelay": null, - - - /** - * DataTables features six different built-in options for the buttons to - * display for pagination control: - * - * * `numbers` - Page number buttons only - * * `simple` - 'Previous' and 'Next' buttons only - * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers - * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons - * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers - * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers - * - * Further methods can be added using {@link DataTable.ext.oPagination}. - * @type string - * @default simple_numbers - * - * @dtopt Options - * @name DataTable.defaults.pagingType - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pagingType": "full_numbers" - * } ); - * } ) - */ - "sPaginationType": "simple_numbers", - - - /** - * Enable horizontal scrolling. When a table is too wide to fit into a - * certain layout, or you have a large number of columns in the table, you - * can enable x-scrolling to show the table in a viewport, which can be - * scrolled. This property can be `true` which will allow the table to - * scroll horizontally when needed, or any CSS unit, or a number (in which - * case it will be treated as a pixel measurement). Setting as simply `true` - * is recommended. - * @type boolean|string - * @default blank string - i.e. disabled - * - * @dtopt Features - * @name DataTable.defaults.scrollX - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": true, - * "scrollCollapse": true - * } ); - * } ); - */ - "sScrollX": "", - - - /** - * This property can be used to force a DataTable to use more width than it - * might otherwise do when x-scrolling is enabled. For example if you have a - * table which requires to be well spaced, this parameter is useful for - * "over-sizing" the table, and thus forcing scrolling. This property can by - * any CSS unit, or a number (in which case it will be treated as a pixel - * measurement). - * @type string - * @default blank string - i.e. disabled - * - * @dtopt Options - * @name DataTable.defaults.scrollXInner - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": "100%", - * "scrollXInner": "110%" - * } ); - * } ); - */ - "sScrollXInner": "", - - - /** - * Enable vertical scrolling. Vertical scrolling will constrain the DataTable - * to the given height, and enable scrolling for any data which overflows the - * current viewport. This can be used as an alternative to paging to display - * a lot of data in a small area (although paging and scrolling can both be - * enabled at the same time). This property can be any CSS unit, or a number - * (in which case it will be treated as a pixel measurement). - * @type string - * @default blank string - i.e. disabled - * - * @dtopt Features - * @name DataTable.defaults.scrollY - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false - * } ); - * } ); - */ - "sScrollY": "", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * Set the HTTP method that is used to make the Ajax call for server-side - * processing or Ajax sourced data. - * @type string - * @default GET - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.serverMethod - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sServerMethod": "GET", - - - /** - * DataTables makes use of renderers when displaying HTML elements for - * a table. These renderers can be added or modified by plug-ins to - * generate suitable mark-up for a site. For example the Bootstrap - * integration plug-in for DataTables uses a paging button renderer to - * display pagination buttons in the mark-up required by Bootstrap. - * - * For further information about the renderers available see - * DataTable.ext.renderer - * @type string|object - * @default null - * - * @name DataTable.defaults.renderer - * - */ - "renderer": null, - - - /** - * Set the data property name that DataTables should use to get a row's id - * to set as the `id` property in the node. - * @type string - * @default DT_RowId - * - * @name DataTable.defaults.rowId - */ - "rowId": "DT_RowId" - }; - - _fnHungarianMap( DataTable.defaults ); - - - - /* - * Developer note - See note in model.defaults.js about the use of Hungarian - * notation and camel case. - */ - - /** - * Column options that can be given to DataTables at initialisation time. - * @namespace - */ - DataTable.defaults.column = { - /** - * Define which column(s) an order will occur on for this column. This - * allows a column's ordering to take multiple columns into account when - * doing a sort or use the data from a different column. For example first - * name / last name columns make sense to do a multi-column sort over the - * two columns. - * @type array|int - * @default null Takes the value of the column index automatically - * - * @name DataTable.defaults.column.orderData - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, - * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, - * { "orderData": 2, "targets": [ 2 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderData": [ 0, 1 ] }, - * { "orderData": [ 1, 0 ] }, - * { "orderData": 2 }, - * null, - * null - * ] - * } ); - * } ); - */ - "aDataSort": null, - "iDataSort": -1, - - - /** - * You can control the default ordering direction, and even alter the - * behaviour of the sort handler (i.e. only allow ascending ordering etc) - * using this parameter. - * @type array - * @default [ 'asc', 'desc' ] - * - * @name DataTable.defaults.column.orderSequence - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, - * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, - * { "orderSequence": [ "desc" ], "targets": [ 3 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * { "orderSequence": [ "asc" ] }, - * { "orderSequence": [ "desc", "asc", "asc" ] }, - * { "orderSequence": [ "desc" ] }, - * null - * ] - * } ); - * } ); - */ - "asSorting": [ 'asc', 'desc' ], - - - /** - * Enable or disable filtering on the data in this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.searchable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "searchable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "searchable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSearchable": true, - - - /** - * Enable or disable ordering on this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.orderable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSortable": true, - - - /** - * Enable or disable the display of this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.visible - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "visible": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "visible": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bVisible": true, - - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} td The TD node that has been created - * @param {*} cellData The Data for the cell - * @param {array|object} rowData The data for the whole row - * @param {int} row The row index for the aoData data store - * @param {int} col The column index for aoColumns - * - * @name DataTable.defaults.column.createdCell - * @dtopt Columns - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [3], - * "createdCell": function (td, cellData, rowData, row, col) { - * if ( cellData == "1.7" ) { - * $(td).css('color', 'blue') - * } - * } - * } ] - * }); - * } ); - */ - "fnCreatedCell": null, - - - /** - * This parameter has been replaced by `data` in DataTables to ensure naming - * consistency. `dataProp` can still be used, as there is backwards - * compatibility in DataTables for this option, but it is strongly - * recommended that you use `data` in preference to `dataProp`. - * @name DataTable.defaults.column.dataProp - */ - - - /** - * This property can be used to read data from any data source property, - * including deeply nested objects / properties. `data` can be given in a - * number of different ways which effect its behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. Note that - * function notation is recommended for use in `render` rather than - * `data` as it is much simpler to use as a renderer. - * * `null` - use the original data source for the row rather than plucking - * data directly from it. This action has effects on two other - * initialisation options: - * * `defaultContent` - When null is given as the `data` option and - * `defaultContent` is specified for the column, the value defined by - * `defaultContent` will be used for the cell. - * * `render` - When null is used for the `data` option and the `render` - * option is specified for the column, the whole data source for the - * row is used for the renderer. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * `{array|object}` The data source for the row - * * `{string}` The type call data requested - this will be 'set' when - * setting data or 'filter', 'display', 'type', 'sort' or undefined - * when gathering data. Note that when `undefined` is given for the - * type DataTables expects to get the raw data for the object back< - * * `{*}` Data to set when the second parameter is 'set'. - * * Return: - * * The return value from the function is not required when 'set' is - * the type of call, but otherwise the return is what will be used - * for the data requested. - * - * Note that `data` is a getter and setter option. If you just require - * formatting of data for output, you will likely want to use `render` which - * is simply a getter and thus simpler to use. - * - * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The - * name change reflects the flexibility of this property and is consistent - * with the naming of mRender. If 'mDataProp' is given, then it will still - * be used by DataTables, as it automatically maps the old name to the new - * if required. - * - * @type string|int|function|null - * @default null Use automatically calculated column index - * - * @name DataTable.defaults.column.data - * @dtopt Columns - * - * @example - * // Read table data from objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": {value}, - * // "version": {value}, - * // "grade": {value} - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/objects.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform" }, - * { "data": "version" }, - * { "data": "grade" } - * ] - * } ); - * } ); - * - * @example - * // Read information from deeply nested objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": { - * // "inner": {value} - * // }, - * // "details": [ - * // {value}, {value} - * // ] - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform.inner" }, - * { "data": "details.0" }, - * { "data": "details.1" } - * ] - * } ); - * } ); - * - * @example - * // Using `data` as a function to provide different information for - * // sorting, filtering and display. In this case, currency (price) - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": function ( source, type, val ) { - * if (type === 'set') { - * source.price = val; - * // Store the computed display and filter values for efficiency - * source.price_display = val=="" ? "" : "$"+numberFormat(val); - * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; - * return; - * } - * else if (type === 'display') { - * return source.price_display; - * } - * else if (type === 'filter') { - * return source.price_filter; - * } - * // 'sort', 'type' and undefined all just use the integer - * return source.price; - * } - * } ] - * } ); - * } ); - * - * @example - * // Using default content - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, - * "defaultContent": "Click to edit" - * } ] - * } ); - * } ); - * - * @example - * // Using array notation - outputting a list from an array - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "name[, ]" - * } ] - * } ); - * } ); - * - */ - "mData": null, - - - /** - * This property is the rendering partner to `data` and it is suggested that - * when you want to manipulate data for display (including filtering, - * sorting etc) without altering the underlying data for the table, use this - * property. `render` can be considered to be the the read only companion to - * `data` which is read / write (then as such more complex). Like `data` - * this option can be given in a number of different ways to effect its - * behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. - * * `object` - use different data for the different data types requested by - * DataTables ('filter', 'display', 'type' or 'sort'). The property names - * of the object is the data type the property refers to and the value can - * defined using an integer, string or function using the same rules as - * `render` normally does. Note that an `_` option _must_ be specified. - * This is the default value to use if you haven't specified a value for - * the data type requested by DataTables. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * {array|object} The data source for the row (based on `data`) - * * {string} The type call data requested - this will be 'filter', - * 'display', 'type' or 'sort'. - * * {array|object} The full data source for the row (not based on - * `data`) - * * Return: - * * The return value from the function is what will be used for the - * data requested. - * - * @type string|int|function|object|null - * @default null Use the data source value. - * - * @name DataTable.defaults.column.render - * @dtopt Columns - * - * @example - * // Create a comma separated list from an array of objects - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { - * "data": "platform", - * "render": "[, ].name" - * } - * ] - * } ); - * } ); - * - * @example - * // Execute a function to obtain data - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": "browserName()" - * } ] - * } ); - * } ); - * - * @example - * // As an object, extracting different data for the different types - * // This would be used with a data source such as: - * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } - * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` - * // (which has both forms) is used for filtering for if a user inputs either format, while - * // the formatted phone number is the one that is shown in the table. - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": { - * "_": "phone", - * "filter": "phone_filter", - * "display": "phone_display" - * } - * } ] - * } ); - * } ); - * - * @example - * // Use as a function to create a link from the data source - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "download_link", - * "render": function ( data, type, full ) { - * return 'Download'; - * } - * } ] - * } ); - * } ); - */ - "mRender": null, - - - /** - * Change the cell type created for the column - either TD cells or TH cells. This - * can be useful as TH cells have semantic meaning in the table body, allowing them - * to act as a header for a row (you may wish to add scope='row' to the TH elements). - * @type string - * @default td - * - * @name DataTable.defaults.column.cellType - * @dtopt Columns - * - * @example - * // Make the first column use TH cells - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "cellType": "th" - * } ] - * } ); - * } ); - */ - "sCellType": "td", - - - /** - * Class to give to each cell in this column. - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.class - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "class": "my_class", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "class": "my_class" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sClass": "", - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * Generally you shouldn't need this! - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.contentPadding - * @dtopt Columns - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "contentPadding": "mmm" - * } - * ] - * } ); - * } ); - */ - "sContentPadding": "", - - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because `data` - * is set to null, or because the data source itself is null). - * @type string - * @default null - * - * @name DataTable.defaults.column.defaultContent - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { - * "data": null, - * "defaultContent": "Edit", - * "targets": [ -1 ] - * } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "data": null, - * "defaultContent": "Edit" - * } - * ] - * } ); - * } ); - */ - "sDefaultContent": null, - - - /** - * This parameter is only used in DataTables' server-side processing. It can - * be exceptionally useful to know what columns are being displayed on the - * client side, and to map these to database fields. When defined, the names - * also allow DataTables to reorder information from the server if it comes - * back in an unexpected order (i.e. if you switch your columns around on the - * client-side, your server-side code does not also need updating). - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.name - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "name": "engine", "targets": [ 0 ] }, - * { "name": "browser", "targets": [ 1 ] }, - * { "name": "platform", "targets": [ 2 ] }, - * { "name": "version", "targets": [ 3 ] }, - * { "name": "grade", "targets": [ 4 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "name": "engine" }, - * { "name": "browser" }, - * { "name": "platform" }, - * { "name": "version" }, - * { "name": "grade" } - * ] - * } ); - * } ); - */ - "sName": "", - - - /** - * Defines a data source type for the ordering which can be used to read - * real-time information from the table (updating the internally cached - * version) prior to ordering. This allows ordering to occur on user - * editable elements such as form inputs. - * @type string - * @default std - * - * @name DataTable.defaults.column.orderDataType - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, - * { "type": "numeric", "targets": [ 3 ] }, - * { "orderDataType": "dom-select", "targets": [ 4 ] }, - * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * { "orderDataType": "dom-text" }, - * { "orderDataType": "dom-text", "type": "numeric" }, - * { "orderDataType": "dom-select" }, - * { "orderDataType": "dom-checkbox" } - * ] - * } ); - * } ); - */ - "sSortDataType": "std", - - - /** - * The title of this column. - * @type string - * @default null Derived from the 'TH' value for this column in the - * original HTML table. - * - * @name DataTable.defaults.column.title - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "title": "My column title", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "title": "My column title" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sTitle": null, - - - /** - * The type allows you to specify how the data for this column will be - * ordered. Four types (string, numeric, date and html (which will strip - * HTML tags before ordering)) are currently available. Note that only date - * formats understood by Javascript's Date() object will be accepted as type - * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', - * 'numeric', 'date' or 'html' (by default). Further types can be adding - * through plug-ins. - * @type string - * @default null Auto-detected from raw data - * - * @name DataTable.defaults.column.type - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "type": "html", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "type": "html" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sType": null, - - - /** - * Defining the width of the column, this parameter may take any CSS value - * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not - * been given a specific width through this interface ensuring that the table - * remains readable. - * @type string - * @default null Automatic - * - * @name DataTable.defaults.column.width - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "width": "20%", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "width": "20%" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sWidth": null - }; - - _fnHungarianMap( DataTable.defaults.column ); - - - + "_aSortData": null, + + /** + * Per cell filtering data cache. As per the sort data cache, used to + * increase the performance of the filtering in DataTables + * @type array + * @default null + * @private + */ + "_aFilterData": null, + + /** + * Filtering data cache. This is the same as the cell filtering cache, but + * in this case a string rather than an array. This is easily computed with + * a join on `_aFilterData`, but is provided as a cache so the join isn't + * needed on every search (memory traded for performance) + * @type array + * @default null + * @private + */ + "_sFilterRow": null, + + /** + * Cache of the class name that DataTables has applied to the row, so we + * can quickly look at this variable rather than needing to do a DOM check + * on className for the nTr property. + * @type string + * @default Empty string + * @private + */ + "_sRowStripe": "", + + /** + * Denote if the original data source was from the DOM, or the data source + * object. This is used for invalidating data, so DataTables can + * automatically read data from the original source, unless uninstructed + * otherwise. + * @type string + * @default null + * @private + */ + "src": null, + + /** + * Index in the aoData array. This saves an indexOf lookup when we have the + * object, but want to know the index + * @type integer + * @default -1 + * @private + */ + "idx": -1 + }; + + + /** + * Template object for the column information object in DataTables. This object + * is held in the settings aoColumns array and contains all the information that + * DataTables needs about each individual column. + * + * Note that this object is related to {@link DataTable.defaults.column} + * but this one is the internal data store for DataTables's cache of columns. + * It should NOT be manipulated outside of DataTables. Any configuration should + * be done through the initialisation options. + * @namespace + */ + DataTable.models.oColumn = { + /** + * Column index. This could be worked out on-the-fly with $.inArray, but it + * is faster to just hold it as a variable + * @type integer + * @default null + */ + "idx": null, + + /** + * A list of the columns that sorting should occur on when this column + * is sorted. That this property is an array allows multi-column sorting + * to be defined for a column (for example first name / last name columns + * would benefit from this). The values are integers pointing to the + * columns to be sorted on (typically it will be a single integer pointing + * at itself, but that doesn't need to be the case). + * @type array + */ + "aDataSort": null, + + /** + * Define the sorting directions that are applied to the column, in sequence + * as the column is repeatedly sorted upon - i.e. the first value is used + * as the sorting direction when the column if first sorted (clicked on). + * Sort it again (click again) and it will move on to the next index. + * Repeat until loop. + * @type array + */ + "asSorting": null, + + /** + * Flag to indicate if the column is searchable, and thus should be included + * in the filtering or not. + * @type boolean + */ + "bSearchable": null, + + /** + * Flag to indicate if the column is sortable or not. + * @type boolean + */ + "bSortable": null, + + /** + * Flag to indicate if the column is currently visible in the table or not + * @type boolean + */ + "bVisible": null, + + /** + * Store for manual type assignment using the `column.type` option. This + * is held in store so we can manipulate the column's `sType` property. + * @type string + * @default null + * @private + */ + "_sManualType": null, + + /** + * Flag to indicate if HTML5 data attributes should be used as the data + * source for filtering or sorting. True is either are. + * @type boolean + * @default false + * @private + */ + "_bAttrSrc": false, + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + * @type function + * @param {element} nTd The TD node that has been created + * @param {*} sData The Data for the cell + * @param {array|object} oData The data for the whole row + * @param {int} iRow The row index for the aoData data store + * @default null + */ + "fnCreatedCell": null, + + /** + * Function to get data from a cell in a column. You should never + * access data directly through _aData internally in DataTables - always use + * the method attached to this property. It allows mData to function as + * required. This function is automatically assigned by the column + * initialisation method + * @type function + * @param {array|object} oData The data array/object for the array + * (i.e. aoData[]._aData) + * @param {string} sSpecific The specific data type you want to get - + * 'display', 'type' 'filter' 'sort' + * @returns {*} The data for the cell from the given row's data + * @default null + */ + "fnGetData": null, + + /** + * Function to set data for a cell in the column. You should never + * set the data directly to _aData internally in DataTables - always use + * this method. It allows mData to function as required. This function + * is automatically assigned by the column initialisation method + * @type function + * @param {array|object} oData The data array/object for the array + * (i.e. aoData[]._aData) + * @param {*} sValue Value to set + * @default null + */ + "fnSetData": null, + + /** + * Property to read the value for the cells in the column from the data + * source array / object. If null, then the default content is used, if a + * function is given then the return from the function is used. + * @type function|int|string|null + * @default null + */ + "mData": null, + + /** + * Partner property to mData which is used (only when defined) to get + * the data - i.e. it is basically the same as mData, but without the + * 'set' option, and also the data fed to it is the result from mData. + * This is the rendering method to match the data method of mData. + * @type function|int|string|null + * @default null + */ + "mRender": null, + + /** + * Unique header TH/TD element for this column - this is what the sorting + * listener is attached to (if sorting is enabled.) + * @type node + * @default null + */ + "nTh": null, + + /** + * Unique footer TH/TD element for this column (if there is one). Not used + * in DataTables as such, but can be used for plug-ins to reference the + * footer for each column. + * @type node + * @default null + */ + "nTf": null, + + /** + * The class to apply to all TD elements in the table's TBODY for the column + * @type string + * @default null + */ + "sClass": null, + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + * @type string + */ + "sContentPadding": null, + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because mData + * is set to null, or because the data source itself is null). + * @type string + * @default null + */ + "sDefaultContent": null, + + /** + * Name for the column, allowing reference to the column by name as well as + * by index (needs a lookup to work by name). + * @type string + */ + "sName": null, + + /** + * Custom sorting data type - defines which of the available plug-ins in + * afnSortData the custom sorting will use - if any is defined. + * @type string + * @default std + */ + "sSortDataType": 'std', + + /** + * Class to be applied to the header element when sorting on this column + * @type string + * @default null + */ + "sSortingClass": null, + + /** + * Class to be applied to the header element when sorting on this column - + * when jQuery UI theming is used. + * @type string + * @default null + */ + "sSortingClassJUI": null, + + /** + * Title of the column - what is seen in the TH element (nTh). + * @type string + */ + "sTitle": null, + + /** + * Column sorting and filtering type + * @type string + * @default null + */ + "sType": null, + + /** + * Width of the column + * @type string + * @default null + */ + "sWidth": null, + + /** + * Width of the column when it was first "encountered" + * @type string + * @default null + */ + "sWidthOrig": null + }; + + + /* + * Developer note: The properties of the object below are given in Hungarian + * notation, that was used as the interface for DataTables prior to v1.10, however + * from v1.10 onwards the primary interface is camel case. In order to avoid + * breaking backwards compatibility utterly with this change, the Hungarian + * version is still, internally the primary interface, but is is not documented + * - hence the @name tags in each doc comment. This allows a Javascript function + * to create a map from Hungarian notation to camel case (going the other direction + * would require each property to be listed, which would add around 3K to the size + * of DataTables, while this method is about a 0.5K hit). + * + * Ultimately this does pave the way for Hungarian notation to be dropped + * completely, but that is a massive amount of work and will break current + * installs (therefore is on-hold until v2). + */ + + /** + * Initialisation options that can be given to DataTables at initialisation + * time. + * @namespace + */ + DataTable.defaults = { + /** + * An array of data to use for the table, passed in at initialisation which + * will be used in preference to any data which is already in the DOM. This is + * particularly useful for constructing tables purely in Javascript, for + * example with a custom Ajax call. + * @type array + * @default null + * + * @dtopt Option + * @name DataTable.defaults.data + * + * @example + * // Using a 2D array data source + * $(document).ready( function () { + * $('#example').dataTable( { + * "data": [ + * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], + * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], + * ], + * "columns": [ + * { "title": "Engine" }, + * { "title": "Browser" }, + * { "title": "Platform" }, + * { "title": "Version" }, + * { "title": "Grade" } + * ] + * } ); + * } ); + * + * @example + * // Using an array of objects as a data source (`data`) + * $(document).ready( function () { + * $('#example').dataTable( { + * "data": [ + * { + * "engine": "Trident", + * "browser": "Internet Explorer 4.0", + * "platform": "Win 95+", + * "version": 4, + * "grade": "X" + * }, + * { + * "engine": "Trident", + * "browser": "Internet Explorer 5.0", + * "platform": "Win 95+", + * "version": 5, + * "grade": "C" + * } + * ], + * "columns": [ + * { "title": "Engine", "data": "engine" }, + * { "title": "Browser", "data": "browser" }, + * { "title": "Platform", "data": "platform" }, + * { "title": "Version", "data": "version" }, + * { "title": "Grade", "data": "grade" } + * ] + * } ); + * } ); + */ + "aaData": null, + + + /** + * If ordering is enabled, then DataTables will perform a first pass sort on + * initialisation. You can define which column(s) the sort is performed + * upon, and the sorting direction, with this variable. The `sorting` array + * should contain an array for each column to be sorted initially containing + * the column's index and a direction string ('asc' or 'desc'). + * @type array + * @default [[0,'asc']] + * + * @dtopt Option + * @name DataTable.defaults.order + * + * @example + * // Sort by 3rd column first, and then 4th column + * $(document).ready( function() { + * $('#example').dataTable( { + * "order": [[2,'asc'], [3,'desc']] + * } ); + * } ); + * + * // No initial sorting + * $(document).ready( function() { + * $('#example').dataTable( { + * "order": [] + * } ); + * } ); + */ + "aaSorting": [[0,'asc']], + + + /** + * This parameter is basically identical to the `sorting` parameter, but + * cannot be overridden by user interaction with the table. What this means + * is that you could have a column (visible or hidden) which the sorting + * will always be forced on first - any sorting after that (from the user) + * will then be performed as required. This can be useful for grouping rows + * together. + * @type array + * @default null + * + * @dtopt Option + * @name DataTable.defaults.orderFixed + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "orderFixed": [[0,'asc']] + * } ); + * } ) + */ + "aaSortingFixed": [], + + + /** + * DataTables can be instructed to load data to display in the table from a + * Ajax source. This option defines how that Ajax call is made and where to. + * + * The `ajax` property has three different modes of operation, depending on + * how it is defined. These are: + * + * * `string` - Set the URL from where the data should be loaded from. + * * `object` - Define properties for `jQuery.ajax`. + * * `function` - Custom data get function + * + * `string` + * -------- + * + * As a string, the `ajax` property simply defines the URL from which + * DataTables will load data. + * + * `object` + * -------- + * + * As an object, the parameters in the object are passed to + * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control + * of the Ajax request. DataTables has a number of default parameters which + * you can override using this option. Please refer to the jQuery + * documentation for a full description of the options available, although + * the following parameters provide additional options in DataTables or + * require special consideration: + * + * * `data` - As with jQuery, `data` can be provided as an object, but it + * can also be used as a function to manipulate the data DataTables sends + * to the server. The function takes a single parameter, an object of + * parameters with the values that DataTables has readied for sending. An + * object may be returned which will be merged into the DataTables + * defaults, or you can add the items to the object that was passed in and + * not return anything from the function. This supersedes `fnServerParams` + * from DataTables 1.9-. + * + * * `dataSrc` - By default DataTables will look for the property `data` (or + * `aaData` for compatibility with DataTables 1.9-) when obtaining data + * from an Ajax source or for server-side processing - this parameter + * allows that property to be changed. You can use Javascript dotted + * object notation to get a data source for multiple levels of nesting, or + * it my be used as a function. As a function it takes a single parameter, + * the JSON returned from the server, which can be manipulated as + * required, with the returned value being that used by DataTables as the + * data source for the table. This supersedes `sAjaxDataProp` from + * DataTables 1.9-. + * + * * `success` - Should not be overridden it is used internally in + * DataTables. To manipulate / transform the data returned by the server + * use `ajax.dataSrc`, or use `ajax` as a function (see below). + * + * `function` + * ---------- + * + * As a function, making the Ajax call is left up to yourself allowing + * complete control of the Ajax request. Indeed, if desired, a method other + * than Ajax could be used to obtain the required data, such as Web storage + * or an AIR database. + * + * The function is given four parameters and no return is required. The + * parameters are: + * + * 1. _object_ - Data to send to the server + * 2. _function_ - Callback function that must be executed when the required + * data has been obtained. That data should be passed into the callback + * as the only parameter + * 3. _object_ - DataTables settings object for the table + * + * Note that this supersedes `fnServerData` from DataTables 1.9-. + * + * @type string|object|function + * @default null + * + * @dtopt Option + * @name DataTable.defaults.ajax + * @since 1.10.0 + * + * @example + * // Get JSON data from a file via Ajax. + * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). + * $('#example').dataTable( { + * "ajax": "data.json" + * } ); + * + * @example + * // Get JSON data from a file via Ajax, using `dataSrc` to change + * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": "tableData" + * } + * } ); + * + * @example + * // Get JSON data from a file via Ajax, using `dataSrc` to read data + * // from a plain array rather than an array in an object + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": "" + * } + * } ); + * + * @example + * // Manipulate the data returned from the server - add a link to data + * // (note this can, should, be done using `render` for the column - this + * // is just a simple example of how the data can be manipulated). + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "dataSrc": function ( json ) { + * for ( var i=0, ien=json.length ; iView message'; + * } + * return json; + * } + * } + * } ); + * + * @example + * // Add data to the request + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "data": function ( d ) { + * return { + * "extra_search": $('#extra').val() + * }; + * } + * } + * } ); + * + * @example + * // Send request as POST + * $('#example').dataTable( { + * "ajax": { + * "url": "data.json", + * "type": "POST" + * } + * } ); + * + * @example + * // Get the data from localStorage (could interface with a form for + * // adding, editing and removing rows). + * $('#example').dataTable( { + * "ajax": function (data, callback, settings) { + * callback( + * JSON.parse( localStorage.getItem('dataTablesData') ) + * ); + * } + * } ); + */ + "ajax": null, + + + /** + * This parameter allows you to readily specify the entries in the length drop + * down menu that DataTables shows when pagination is enabled. It can be + * either a 1D array of options which will be used for both the displayed + * option and the value, or a 2D array which will use the array in the first + * position as the value, and the array in the second position as the + * displayed options (useful for language strings such as 'All'). + * + * Note that the `pageLength` property will be automatically set to the + * first value given in this array, unless `pageLength` is also provided. + * @type array + * @default [ 10, 25, 50, 100 ] + * + * @dtopt Option + * @name DataTable.defaults.lengthMenu + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] + * } ); + * } ); + */ + "aLengthMenu": [ 10, 25, 50, 100 ], + + + /** + * The `columns` option in the initialisation parameter allows you to define + * details about the way individual columns behave. For a full list of + * column options that can be set, please see + * {@link DataTable.defaults.column}. Note that if you use `columns` to + * define your columns, you must have an entry in the array for every single + * column that you have in your table (these can be null if you don't which + * to specify any options). + * @member + * + * @name DataTable.defaults.column + */ + "aoColumns": null, + + /** + * Very similar to `columns`, `columnDefs` allows you to target a specific + * column, multiple columns, or all columns, using the `targets` property of + * each object in the array. This allows great flexibility when creating + * tables, as the `columnDefs` arrays can be of any length, targeting the + * columns you specifically want. `columnDefs` may use any of the column + * options available: {@link DataTable.defaults.column}, but it _must_ + * have `targets` defined in each object in the array. Values in the `targets` + * array may be: + *
    + *
  • a string - class name will be matched on the TH for the column
  • + *
  • 0 or a positive integer - column index counting from the left
  • + *
  • a negative integer - column index counting from the right
  • + *
  • the string "_all" - all columns (i.e. assign a default)
  • + *
+ * @member + * + * @name DataTable.defaults.columnDefs + */ + "aoColumnDefs": null, + + + /** + * Basically the same as `search`, this parameter defines the individual column + * filtering state at initialisation time. The array must be of the same size + * as the number of columns, and each element be an object with the parameters + * `search` and `escapeRegex` (the latter is optional). 'null' is also + * accepted and the default will be used. + * @type array + * @default [] + * + * @dtopt Option + * @name DataTable.defaults.searchCols + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "searchCols": [ + * null, + * { "search": "My filter" }, + * null, + * { "search": "^[0-9]", "escapeRegex": false } + * ] + * } ); + * } ) + */ + "aoSearchCols": [], + + + /** + * An array of CSS classes that should be applied to displayed rows. This + * array may be of any length, and DataTables will apply each class + * sequentially, looping when required. + * @type array + * @default null Will take the values determined by the `oClasses.stripe*` + * options + * + * @dtopt Option + * @name DataTable.defaults.stripeClasses + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] + * } ); + * } ) + */ + "asStripeClasses": null, + + + /** + * Enable or disable automatic column width calculation. This can be disabled + * as an optimisation (it takes some time to calculate the widths) if the + * tables widths are passed in using `columns`. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.autoWidth + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "autoWidth": false + * } ); + * } ); + */ + "bAutoWidth": true, + + + /** + * Deferred rendering can provide DataTables with a huge speed boost when you + * are using an Ajax or JS data source for the table. This option, when set to + * true, will cause DataTables to defer the creation of the table elements for + * each row until they are needed for a draw - saving a significant amount of + * time. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.deferRender + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajax": "sources/arrays.txt", + * "deferRender": true + * } ); + * } ); + */ + "bDeferRender": false, + + + /** + * Replace a DataTable which matches the given selector and replace it with + * one which has the properties of the new initialisation object passed. If no + * table matches the selector, then the new DataTable will be constructed as + * per normal. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.destroy + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "srollY": "200px", + * "paginate": false + * } ); + * + * // Some time later.... + * $('#example').dataTable( { + * "filter": false, + * "destroy": true + * } ); + * } ); + */ + "bDestroy": false, + + + /** + * Enable or disable filtering of data. Filtering in DataTables is "smart" in + * that it allows the end user to input multiple words (space separated) and + * will match a row containing those words, even if not in the order that was + * specified (this allow matching across multiple columns). Note that if you + * wish to use filtering in DataTables this must remain 'true' - to remove the + * default filtering input box and retain filtering abilities, please use + * {@link DataTable.defaults.dom}. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.searching + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "searching": false + * } ); + * } ); + */ + "bFilter": true, + + + /** + * Enable or disable the table information display. This shows information + * about the data that is currently visible on the page, including information + * about filtered data if that action is being performed. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.info + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "info": false + * } ); + * } ); + */ + "bInfo": true, + + + /** + * Allows the end user to select the size of a formatted page from a select + * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.lengthChange + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "lengthChange": false + * } ); + * } ); + */ + "bLengthChange": true, + + + /** + * Enable or disable pagination. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.paging + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "paging": false + * } ); + * } ); + */ + "bPaginate": true, + + + /** + * Enable or disable the display of a 'processing' indicator when the table is + * being processed (e.g. a sort). This is particularly useful for tables with + * large amounts of data where it can take a noticeable amount of time to sort + * the entries. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.processing + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "processing": true + * } ); + * } ); + */ + "bProcessing": false, + + + /** + * Retrieve the DataTables object for the given selector. Note that if the + * table has already been initialised, this parameter will cause DataTables + * to simply return the object that has already been set up - it will not take + * account of any changes you might have made to the initialisation object + * passed to DataTables (setting this parameter to true is an acknowledgement + * that you understand this). `destroy` can be used to reinitialise a table if + * you need. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.retrieve + * + * @example + * $(document).ready( function() { + * initTable(); + * tableActions(); + * } ); + * + * function initTable () + * { + * return $('#example').dataTable( { + * "scrollY": "200px", + * "paginate": false, + * "retrieve": true + * } ); + * } + * + * function tableActions () + * { + * var table = initTable(); + * // perform API operations with oTable + * } + */ + "bRetrieve": false, + + + /** + * When vertical (y) scrolling is enabled, DataTables will force the height of + * the table's viewport to the given height at all times (useful for layout). + * However, this can look odd when filtering data down to a small data set, + * and the footer is left "floating" further down. This parameter (when + * enabled) will cause DataTables to collapse the table's viewport down when + * the result set will fit within the given Y height. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.scrollCollapse + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollY": "200", + * "scrollCollapse": true + * } ); + * } ); + */ + "bScrollCollapse": false, + + + /** + * Configure DataTables to use server-side processing. Note that the + * `ajax` parameter must also be given in order to give DataTables a + * source to obtain the required data for each draw. + * @type boolean + * @default false + * + * @dtopt Features + * @dtopt Server-side + * @name DataTable.defaults.serverSide + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "xhr.php" + * } ); + * } ); + */ + "bServerSide": false, + + + /** + * Enable or disable sorting of columns. Sorting of individual columns can be + * disabled by the `sortable` option for each column. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.ordering + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "ordering": false + * } ); + * } ); + */ + "bSort": true, + + + /** + * Enable or display DataTables' ability to sort multiple columns at the + * same time (activated by shift-click by the user). + * @type boolean + * @default true + * + * @dtopt Options + * @name DataTable.defaults.orderMulti + * + * @example + * // Disable multiple column sorting ability + * $(document).ready( function () { + * $('#example').dataTable( { + * "orderMulti": false + * } ); + * } ); + */ + "bSortMulti": true, + + + /** + * Allows control over whether DataTables should use the top (true) unique + * cell that is found for a single column, or the bottom (false - default). + * This is useful when using complex headers. + * @type boolean + * @default false + * + * @dtopt Options + * @name DataTable.defaults.orderCellsTop + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "orderCellsTop": true + * } ); + * } ); + */ + "bSortCellsTop": false, + + + /** + * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and + * `sorting\_3` to the columns which are currently being sorted on. This is + * presented as a feature switch as it can increase processing time (while + * classes are removed and added) so for large data sets you might want to + * turn this off. + * @type boolean + * @default true + * + * @dtopt Features + * @name DataTable.defaults.orderClasses + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "orderClasses": false + * } ); + * } ); + */ + "bSortClasses": true, + + + /** + * Enable or disable state saving. When enabled HTML5 `localStorage` will be + * used to save table display information such as pagination information, + * display length, filtering and sorting. As such when the end user reloads + * the page the display display will match what thy had previously set up. + * + * Due to the use of `localStorage` the default state saving is not supported + * in IE6 or 7. If state saving is required in those browsers, use + * `stateSaveCallback` to provide a storage solution such as cookies. + * @type boolean + * @default false + * + * @dtopt Features + * @name DataTable.defaults.stateSave + * + * @example + * $(document).ready( function () { + * $('#example').dataTable( { + * "stateSave": true + * } ); + * } ); + */ + "bStateSave": false, + + + /** + * This function is called when a TR element is created (and all TD child + * elements have been inserted), or registered if using a DOM source, allowing + * manipulation of the TR element (adding classes etc). + * @type function + * @param {node} row "TR" element for the current row + * @param {array} data Raw data array for this row + * @param {int} dataIndex The index of this row in the internal aoData array + * + * @dtopt Callbacks + * @name DataTable.defaults.createdRow + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "createdRow": function( row, data, dataIndex ) { + * // Bold the grade for all 'A' grade browsers + * if ( data[4] == "A" ) + * { + * $('td:eq(4)', row).html( 'A' ); + * } + * } + * } ); + * } ); + */ + "fnCreatedRow": null, + + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify any aspect you want about the created DOM. + * @type function + * @param {object} settings DataTables settings object + * + * @dtopt Callbacks + * @name DataTable.defaults.drawCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "drawCallback": function( settings ) { + * alert( 'DataTables has redrawn the table' ); + * } + * } ); + * } ); + */ + "fnDrawCallback": null, + + + /** + * Identical to fnHeaderCallback() but for the table footer this function + * allows you to modify the table footer on every 'draw' event. + * @type function + * @param {node} foot "TR" element for the footer + * @param {array} data Full table data (as derived from the original HTML) + * @param {int} start Index for the current display starting point in the + * display array + * @param {int} end Index for the current display ending point in the + * display array + * @param {array int} display Index array to translate the visual position + * to the full data array + * + * @dtopt Callbacks + * @name DataTable.defaults.footerCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "footerCallback": function( tfoot, data, start, end, display ) { + * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; + * } + * } ); + * } ) + */ + "fnFooterCallback": null, + + + /** + * When rendering large numbers in the information element for the table + * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers + * to have a comma separator for the 'thousands' units (e.g. 1 million is + * rendered as "1,000,000") to help readability for the end user. This + * function will override the default method DataTables uses. + * @type function + * @member + * @param {int} toFormat number to be formatted + * @returns {string} formatted string for DataTables to show the number + * + * @dtopt Callbacks + * @name DataTable.defaults.formatNumber + * + * @example + * // Format a number using a single quote for the separator (note that + * // this can also be done with the language.thousands option) + * $(document).ready( function() { + * $('#example').dataTable( { + * "formatNumber": function ( toFormat ) { + * return toFormat.toString().replace( + * /\B(?=(\d{3})+(?!\d))/g, "'" + * ); + * }; + * } ); + * } ); + */ + "fnFormatNumber": function ( toFormat ) { + return toFormat.toString().replace( + /\B(?=(\d{3})+(?!\d))/g, + this.oLanguage.sThousands + ); + }, + + + /** + * This function is called on every 'draw' event, and allows you to + * dynamically modify the header row. This can be used to calculate and + * display useful information about the table. + * @type function + * @param {node} head "TR" element for the header + * @param {array} data Full table data (as derived from the original HTML) + * @param {int} start Index for the current display starting point in the + * display array + * @param {int} end Index for the current display ending point in the + * display array + * @param {array int} display Index array to translate the visual position + * to the full data array + * + * @dtopt Callbacks + * @name DataTable.defaults.headerCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "fheaderCallback": function( head, data, start, end, display ) { + * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; + * } + * } ); + * } ) + */ + "fnHeaderCallback": null, + + + /** + * The information element can be used to convey information about the current + * state of the table. Although the internationalisation options presented by + * DataTables are quite capable of dealing with most customisations, there may + * be times where you wish to customise the string further. This callback + * allows you to do exactly that. + * @type function + * @param {object} oSettings DataTables settings object + * @param {int} start Starting position in data for the draw + * @param {int} end End position in data for the draw + * @param {int} max Total number of rows in the table (regardless of + * filtering) + * @param {int} total Total number of rows in the data set, after filtering + * @param {string} pre The string that DataTables has formatted using it's + * own rules + * @returns {string} The string to be displayed in the information element. + * + * @dtopt Callbacks + * @name DataTable.defaults.infoCallback + * + * @example + * $('#example').dataTable( { + * "infoCallback": function( settings, start, end, max, total, pre ) { + * return start +" to "+ end; + * } + * } ); + */ + "fnInfoCallback": null, + + + /** + * Called when the table has been initialised. Normally DataTables will + * initialise sequentially and there will be no need for this function, + * however, this does not hold true when using external language information + * since that is obtained using an async XHR call. + * @type function + * @param {object} settings DataTables settings object + * @param {object} json The JSON object request from the server - only + * present if client-side Ajax sourced data is used + * + * @dtopt Callbacks + * @name DataTable.defaults.initComplete + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "initComplete": function(settings, json) { + * alert( 'DataTables has finished its initialisation.' ); + * } + * } ); + * } ) + */ + "fnInitComplete": null, + + + /** + * Called at the very start of each table draw and can be used to cancel the + * draw by returning false, any other return (including undefined) results in + * the full draw occurring). + * @type function + * @param {object} settings DataTables settings object + * @returns {boolean} False will cancel the draw, anything else (including no + * return) will allow it to complete. + * + * @dtopt Callbacks + * @name DataTable.defaults.preDrawCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "preDrawCallback": function( settings ) { + * if ( $('#test').val() == 1 ) { + * return false; + * } + * } + * } ); + * } ); + */ + "fnPreDrawCallback": null, + + + /** + * This function allows you to 'post process' each row after it have been + * generated for each table draw, but before it is rendered on screen. This + * function might be used for setting the row class name etc. + * @type function + * @param {node} row "TR" element for the current row + * @param {array} data Raw data array for this row + * @param {int} displayIndex The display index for the current table draw + * @param {int} displayIndexFull The index of the data in the full list of + * rows (after filtering) + * + * @dtopt Callbacks + * @name DataTable.defaults.rowCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { + * // Bold the grade for all 'A' grade browsers + * if ( data[4] == "A" ) { + * $('td:eq(4)', row).html( 'A' ); + * } + * } + * } ); + * } ); + */ + "fnRowCallback": null, + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * This parameter allows you to override the default function which obtains + * the data from the server so something more suitable for your application. + * For example you could use POST data, or pull information from a Gears or + * AIR database. + * @type function + * @member + * @param {string} source HTTP source to obtain the data from (`ajax`) + * @param {array} data A key/value pair object containing the data to send + * to the server + * @param {function} callback to be called on completion of the data get + * process that will draw the data on the page. + * @param {object} settings DataTables settings object + * + * @dtopt Callbacks + * @dtopt Server-side + * @name DataTable.defaults.serverData + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "fnServerData": null, + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * It is often useful to send extra data to the server when making an Ajax + * request - for example custom filtering information, and this callback + * function makes it trivial to send extra information to the server. The + * passed in parameter is the data set that has been constructed by + * DataTables, and you can add to this or modify it as you require. + * @type function + * @param {array} data Data array (array of objects which are name/value + * pairs) that has been constructed by DataTables and will be sent to the + * server. In the case of Ajax sourced data with server-side processing + * this will be an empty array, for server-side processing there will be a + * significant number of parameters! + * @returns {undefined} Ensure that you modify the data array passed in, + * as this is passed by reference. + * + * @dtopt Callbacks + * @dtopt Server-side + * @name DataTable.defaults.serverParams + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "fnServerParams": null, + + + /** + * Load the table state. With this function you can define from where, and how, the + * state of a table is loaded. By default DataTables will load from `localStorage` + * but you might wish to use a server-side database or cookies. + * @type function + * @member + * @param {object} settings DataTables settings object + * @param {object} callback Callback that can be executed when done. It + * should be passed the loaded state object. + * @return {object} The DataTables state object to be loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoadCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadCallback": function (settings, callback) { + * $.ajax( { + * "url": "/state_load", + * "dataType": "json", + * "success": function (json) { + * callback( json ); + * } + * } ); + * } + * } ); + * } ); + */ + "fnStateLoadCallback": function ( settings ) { + try { + return JSON.parse( + (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( + 'DataTables_'+settings.sInstance+'_'+location.pathname + ) + ); + } catch (e) { + return {}; + } + }, + + + /** + * Callback which allows modification of the saved state prior to loading that state. + * This callback is called when the table is loading state from the stored data, but + * prior to the settings object being modified by the saved state. Note that for + * plug-in authors, you should use the `stateLoadParams` event to load parameters for + * a plug-in. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object that is to be loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoadParams + * + * @example + * // Remove a saved filter, so filtering is never loaded + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadParams": function (settings, data) { + * data.oSearch.sSearch = ""; + * } + * } ); + * } ); + * + * @example + * // Disallow state loading by returning false + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoadParams": function (settings, data) { + * return false; + * } + * } ); + * } ); + */ + "fnStateLoadParams": null, + + + /** + * Callback that is called when the state has been loaded from the state saving method + * and the DataTables settings object has been modified as a result of the loaded state. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object that was loaded + * + * @dtopt Callbacks + * @name DataTable.defaults.stateLoaded + * + * @example + * // Show an alert with the filtering value that was saved + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateLoaded": function (settings, data) { + * alert( 'Saved filter was: '+data.oSearch.sSearch ); + * } + * } ); + * } ); + */ + "fnStateLoaded": null, + + + /** + * Save the table state. This function allows you to define where and how the state + * information for the table is stored By default DataTables will use `localStorage` + * but you might wish to use a server-side database or cookies. + * @type function + * @member + * @param {object} settings DataTables settings object + * @param {object} data The state object to be saved + * + * @dtopt Callbacks + * @name DataTable.defaults.stateSaveCallback + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateSaveCallback": function (settings, data) { + * // Send an Ajax request to the server with the state object + * $.ajax( { + * "url": "/state_save", + * "data": data, + * "dataType": "json", + * "method": "POST" + * "success": function () {} + * } ); + * } + * } ); + * } ); + */ + "fnStateSaveCallback": function ( settings, data ) { + try { + (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( + 'DataTables_'+settings.sInstance+'_'+location.pathname, + JSON.stringify( data ) + ); + } catch (e) {} + }, + + + /** + * Callback which allows modification of the state to be saved. Called when the table + * has changed state a new state save is required. This method allows modification of + * the state saving object prior to actually doing the save, including addition or + * other state properties or modification. Note that for plug-in authors, you should + * use the `stateSaveParams` event to save parameters for a plug-in. + * @type function + * @param {object} settings DataTables settings object + * @param {object} data The state object to be saved + * + * @dtopt Callbacks + * @name DataTable.defaults.stateSaveParams + * + * @example + * // Remove a saved filter, so filtering is never saved + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateSave": true, + * "stateSaveParams": function (settings, data) { + * data.oSearch.sSearch = ""; + * } + * } ); + * } ); + */ + "fnStateSaveParams": null, + + + /** + * Duration for which the saved state information is considered valid. After this period + * has elapsed the state will be returned to the default. + * Value is given in seconds. + * @type int + * @default 7200 (2 hours) + * + * @dtopt Options + * @name DataTable.defaults.stateDuration + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "stateDuration": 60*60*24; // 1 day + * } ); + * } ) + */ + "iStateDuration": 7200, + + + /** + * When enabled DataTables will not make a request to the server for the first + * page draw - rather it will use the data already on the page (no sorting etc + * will be applied to it), thus saving on an XHR at load time. `deferLoading` + * is used to indicate that deferred loading is required, but it is also used + * to tell DataTables how many records there are in the full table (allowing + * the information element and pagination to be displayed correctly). In the case + * where a filtering is applied to the table on initial load, this can be + * indicated by giving the parameter as an array, where the first element is + * the number of records available after filtering and the second element is the + * number of records without filtering (allowing the table information element + * to be shown correctly). + * @type int | array + * @default null + * + * @dtopt Options + * @name DataTable.defaults.deferLoading + * + * @example + * // 57 records available in the table, no filtering applied + * $(document).ready( function() { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "scripts/server_processing.php", + * "deferLoading": 57 + * } ); + * } ); + * + * @example + * // 57 records after filtering, 100 without filtering (an initial filter applied) + * $(document).ready( function() { + * $('#example').dataTable( { + * "serverSide": true, + * "ajax": "scripts/server_processing.php", + * "deferLoading": [ 57, 100 ], + * "search": { + * "search": "my_filter" + * } + * } ); + * } ); + */ + "iDeferLoading": null, + + + /** + * Number of rows to display on a single page when using pagination. If + * feature enabled (`lengthChange`) then the end user will be able to override + * this to a custom setting using a pop-up menu. + * @type int + * @default 10 + * + * @dtopt Options + * @name DataTable.defaults.pageLength + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "pageLength": 50 + * } ); + * } ) + */ + "iDisplayLength": 10, + + + /** + * Define the starting point for data display when using DataTables with + * pagination. Note that this parameter is the number of records, rather than + * the page number, so if you have 10 records per page and want to start on + * the third page, it should be "20". + * @type int + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.displayStart + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "displayStart": 20 + * } ); + * } ) + */ + "iDisplayStart": 0, + + + /** + * By default DataTables allows keyboard navigation of the table (sorting, paging, + * and filtering) by adding a `tabindex` attribute to the required elements. This + * allows you to tab through the controls and press the enter key to activate them. + * The tabindex is default 0, meaning that the tab follows the flow of the document. + * You can overrule this using this parameter if you wish. Use a value of -1 to + * disable built-in keyboard navigation. + * @type int + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.tabIndex + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "tabIndex": 1 + * } ); + * } ); + */ + "iTabIndex": 0, + + /** - * DataTables settings object - this holds all the information needed for a - * given table, including configuration, data and current application of the - * table options. DataTables does not have a single instance for each DataTable - * with the settings attached to that instance, but rather instances of the - * DataTable "class" are created on-the-fly as needed (typically by a - * $().dataTable() call) and the settings object is then applied to that - * instance. - * - * Note that this object is related to {@link DataTable.defaults} but this - * one is the internal data store for DataTables's cache of columns. It should - * NOT be manipulated outside of DataTables. Any configuration should be done - * through the initialisation options. + * Classes that DataTables assigns to the various components and features + * that it adds to the HTML table. This allows classes to be configured + * during initialisation in addition to through the static + * {@link DataTable.ext.oStdClasses} object). * @namespace - * @todo Really should attach the settings object to individual instances so we - * don't need to create new instances on each $().dataTable() call (if the - * table already exists). It would also save passing oSettings around and - * into every single function. However, this is a very significant - * architecture change for DataTables and will almost certainly break - * backwards compatibility with older installations. This is something that - * will be done in 2.0. - */ - DataTable.models.oSettings = { - /** - * Primary features of DataTables and their enablement state. - * @namespace - */ - "oFeatures": { - - /** - * Flag to say if DataTables should automatically try to calculate the - * optimum table and columns widths (true) or not (false). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bAutoWidth": null, - - /** - * Delay the creation of TR and TD elements until they are actually - * needed by a driven page draw. This can give a significant speed - * increase for Ajax source and Javascript source data, but makes no - * difference at all for DOM and server-side processing tables. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bDeferRender": null, - - /** - * Enable filtering on the table or not. Note that if this is disabled - * then there is no filtering at all on the table, including fnFilter. - * To just remove the filtering input use sDom and remove the 'f' option. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bFilter": null, - - /** - * Table information element (the 'Showing x of y records' div) enable - * flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bInfo": null, - - /** - * Present a user control allowing the end user to change the page size - * when pagination is enabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bLengthChange": null, - - /** - * Pagination enabled or not. Note that if this is disabled then length - * changing must also be disabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bPaginate": null, - - /** - * Processing indicator enable flag whenever DataTables is enacting a - * user request - typically an Ajax request for server-side processing. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bProcessing": null, - - /** - * Server-side processing enabled flag - when enabled DataTables will - * get all data from the server for every draw - there is no filtering, - * sorting or paging done on the client-side. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bServerSide": null, - - /** - * Sorting enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSort": null, - - /** - * Multi-column sorting - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortMulti": null, - - /** - * Apply a class to the columns which are being sorted to provide a - * visual highlight or not. This can slow things down when enabled since - * there is a lot of DOM interaction. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortClasses": null, - - /** - * State saving enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bStateSave": null - }, - - + * @name DataTable.defaults.classes + */ + "oClasses": {}, + + + /** + * All strings that DataTables uses in the user interface that it creates + * are defined in this object, allowing you to modified them individually or + * completely replace them all as required. + * @namespace + * @name DataTable.defaults.language + */ + "oLanguage": { /** - * Scrolling settings for a table. + * Strings that are used for WAI-ARIA labels and controls only (these are not + * actually visible on the page, but will be read by screenreaders, and thus + * must be internationalised as well). * @namespace + * @name DataTable.defaults.language.aria */ - "oScroll": { - /** - * When the table is shorter in height than sScrollY, collapse the - * table container down to the height of the table (when true). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bCollapse": null, - - /** - * Width of the scrollbar for the web-browser's platform. Calculated - * during table initialisation. - * @type int - * @default 0 - */ - "iBarWidth": 0, - + "oAria": { /** - * Viewport width for horizontal scrolling. Horizontal scrolling is - * disabled if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * ARIA label that is added to the table headers when the column may be + * sorted ascending by activing the column (click or return when focused). + * Note that the column header is prefixed to this string. * @type string + * @default : activate to sort column ascending + * + * @dtopt Language + * @name DataTable.defaults.language.aria.sortAscending + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "aria": { + * "sortAscending": " - click/return to sort ascending" + * } + * } + * } ); + * } ); */ - "sX": null, - - /** - * Width to expand the table to when using x-scrolling. Typically you - * should not need to use this. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @deprecated - */ - "sXInner": null, - + "sSortAscending": ": activate to sort column ascending", + /** - * Viewport height for vertical scrolling. Vertical scrolling is disabled - * if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * ARIA label that is added to the table headers when the column may be + * sorted descending by activing the column (click or return when focused). + * Note that the column header is prefixed to this string. * @type string + * @default : activate to sort column ascending + * + * @dtopt Language + * @name DataTable.defaults.language.aria.sortDescending + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "aria": { + * "sortDescending": " - click/return to sort descending" + * } + * } + * } ); + * } ); */ - "sY": null - }, - - /** - * Language information for the table. - * @namespace - * @extends DataTable.defaults.oLanguage - */ - "oLanguage": { - /** - * Information callback function. See - * {@link DataTable.defaults.fnInfoCallback} - * @type function - * @default null - */ - "fnInfoCallback": null + "sSortDescending": ": activate to sort column descending" }, - + /** - * Browser support parameters + * Pagination string used by DataTables for the built-in pagination + * control types. * @namespace + * @name DataTable.defaults.language.paginate */ - "oBrowser": { + "oPaginate": { /** - * Indicate if the browser incorrectly calculates width:100% inside a - * scrolling element (IE6/7) - * @type boolean - * @default false + * Text to use when using the 'full_numbers' type of pagination for the + * button to take the user to the first page. + * @type string + * @default First + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.first + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "first": "First page" + * } + * } + * } ); + * } ); */ - "bScrollOversize": false, - + "sFirst": "First", + + /** - * Determine if the vertical scrollbar is on the right or left of the - * scrolling container - needed for rtl language layout, although not - * all browsers move the scrollbar (Safari). - * @type boolean - * @default false + * Text to use when using the 'full_numbers' type of pagination for the + * button to take the user to the last page. + * @type string + * @default Last + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.last + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "last": "Last page" + * } + * } + * } ); + * } ); */ - "bScrollbarLeft": false, - + "sLast": "Last", + + /** - * Flag for if `getBoundingClientRect` is fully supported or not - * @type boolean - * @default false + * Text to use for the 'next' pagination button (to take the user to the + * next page). + * @type string + * @default Next + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.next + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "next": "Next page" + * } + * } + * } ); + * } ); */ - "bBounding": false, - + "sNext": "Next", + + /** - * Browser scrollbar width - * @type integer - * @default 0 + * Text to use for the 'previous' pagination button (to take the user to + * the previous page). + * @type string + * @default Previous + * + * @dtopt Language + * @name DataTable.defaults.language.paginate.previous + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "paginate": { + * "previous": "Previous page" + * } + * } + * } ); + * } ); */ - "barWidth": 0 + "sPrevious": "Previous" }, - - - "ajax": null, - - - /** - * Array referencing the nodes which are used for the features. The - * parameters of this object match what is allowed by sDom - i.e. - *
    - *
  • 'l' - Length changing
  • - *
  • 'f' - Filtering input
  • - *
  • 't' - The table!
  • - *
  • 'i' - Information
  • - *
  • 'p' - Pagination
  • - *
  • 'r' - pRocessing
  • - *
- * @type array - * @default [] - */ - "aanFeatures": [], - - /** - * Store data information - see {@link DataTable.models.oRow} for detailed - * information. - * @type array - * @default [] - */ - "aoData": [], - - /** - * Array of indexes which are in the current display (after filtering etc) - * @type array - * @default [] - */ - "aiDisplay": [], - - /** - * Array of indexes for display - no filtering - * @type array - * @default [] - */ - "aiDisplayMaster": [], - - /** - * Map of row ids to data indexes - * @type object - * @default {} - */ - "aIds": {}, - - /** - * Store information about each column that is in use - * @type array - * @default [] - */ - "aoColumns": [], - - /** - * Store information about the table's header - * @type array - * @default [] - */ - "aoHeader": [], - - /** - * Store information about the table's footer - * @type array - * @default [] - */ - "aoFooter": [], - - /** - * Store the applied global search information in case we want to force a - * research or compare the old search to a new one. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @namespace - * @extends DataTable.models.oSearch - */ - "oPreviousSearch": {}, - - /** - * Store the applied search for each column - see - * {@link DataTable.models.oSearch} for the format that is used for the - * filtering information for each column. - * @type array - * @default [] - */ - "aoPreSearchCols": [], - - /** - * Sorting that is applied to the table. Note that the inner arrays are - * used in the following manner: - *
    - *
  • Index 0 - column number
  • - *
  • Index 1 - current sorting direction
  • - *
- * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @todo These inner arrays should really be objects - */ - "aaSorting": null, - - /** - * Sorting that is always applied to the table (i.e. prefixed in front of - * aaSorting). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aaSortingFixed": [], - - /** - * Classes to use for the striping of a table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "asStripeClasses": null, - - /** - * If restoring a table - we should restore its striping classes as well - * @type array - * @default [] - */ - "asDestroyStripes": [], - - /** - * If restoring a table - we should restore its width - * @type int - * @default 0 - */ - "sDestroyWidth": 0, - - /** - * Callback functions array for every time a row is inserted (i.e. on a draw). - * @type array - * @default [] - */ - "aoRowCallback": [], - - /** - * Callback functions for the header on each draw. - * @type array - * @default [] - */ - "aoHeaderCallback": [], - - /** - * Callback function for the footer on each draw. - * @type array - * @default [] - */ - "aoFooterCallback": [], - - /** - * Array of callback functions for draw callback functions - * @type array - * @default [] - */ - "aoDrawCallback": [], - - /** - * Array of callback functions for row created function - * @type array - * @default [] - */ - "aoRowCreatedCallback": [], - - /** - * Callback functions for just before the table is redrawn. A return of - * false will be used to cancel the draw. - * @type array - * @default [] - */ - "aoPreDrawCallback": [], - - /** - * Callback functions for when the table has been initialised. - * @type array - * @default [] - */ - "aoInitComplete": [], - - - /** - * Callbacks for modifying the settings to be stored for state saving, prior to - * saving state. - * @type array - * @default [] - */ - "aoStateSaveParams": [], - - /** - * Callbacks for modifying the settings that have been stored for state saving - * prior to using the stored values to restore the state. - * @type array - * @default [] - */ - "aoStateLoadParams": [], - - /** - * Callbacks for operating on the settings object once the saved state has been - * loaded - * @type array - * @default [] - */ - "aoStateLoaded": [], - + /** - * Cache the table ID for quick access + * This string is shown in preference to `zeroRecords` when the table is + * empty of data (regardless of filtering). Note that this is an optional + * parameter - if it is not given, the value of `zeroRecords` will be used + * instead (either the default or given value). * @type string - * @default Empty string - */ - "sTableId": "", - - /** - * The TABLE node for the main table - * @type node - * @default null - */ - "nTable": null, - - /** - * Permanent ref to the thead element - * @type node - * @default null - */ - "nTHead": null, - - /** - * Permanent ref to the tfoot element - if it exists - * @type node - * @default null - */ - "nTFoot": null, - - /** - * Permanent ref to the tbody element - * @type node - * @default null - */ - "nTBody": null, - - /** - * Cache the wrapper node (contains all DataTables controlled elements) - * @type node - * @default null - */ - "nTableWrapper": null, - - /** - * Indicate if when using server-side processing the loading of data - * should be deferred until the second draw. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - * @default false - */ - "bDeferLoading": false, - - /** - * Indicate if all required information has been read in - * @type boolean - * @default false + * @default No data available in table + * + * @dtopt Language + * @name DataTable.defaults.language.emptyTable + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "emptyTable": "No data available in table" + * } + * } ); + * } ); */ - "bInitialised": false, - + "sEmptyTable": "No data available in table", + + /** - * Information about open rows. Each object in the array has the parameters - * 'nTr' and 'nParent' - * @type array - * @default [] + * This string gives information to the end user about the information + * that is current on display on the page. The following tokens can be + * used in the string and will be dynamically replaced as the table + * display updates. This tokens can be placed anywhere in the string, or + * removed as needed by the language requires: + * + * * `\_START\_` - Display index of the first record on the current page + * * `\_END\_` - Display index of the last record on the current page + * * `\_TOTAL\_` - Number of records in the table after filtering + * * `\_MAX\_` - Number of records in the table without filtering + * * `\_PAGE\_` - Current page number + * * `\_PAGES\_` - Total number of pages of data in the table + * + * @type string + * @default Showing _START_ to _END_ of _TOTAL_ entries + * + * @dtopt Language + * @name DataTable.defaults.language.info + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "info": "Showing page _PAGE_ of _PAGES_" + * } + * } ); + * } ); */ - "aoOpenRows": [], - + "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", + + /** - * Dictate the positioning of DataTables' control elements - see - * {@link DataTable.model.oInit.sDom}. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * Display information string for when the table is empty. Typically the + * format of this string should match `info`. * @type string - * @default null + * @default Showing 0 to 0 of 0 entries + * + * @dtopt Language + * @name DataTable.defaults.language.infoEmpty + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoEmpty": "No entries to show" + * } + * } ); + * } ); */ - "sDom": null, - + "sInfoEmpty": "Showing 0 to 0 of 0 entries", + + /** - * Search delay (in mS) - * @type integer - * @default null + * When a user filters the information in a table, this string is appended + * to the information (`info`) to give an idea of how strong the filtering + * is. The variable _MAX_ is dynamically updated. + * @type string + * @default (filtered from _MAX_ total entries) + * + * @dtopt Language + * @name DataTable.defaults.language.infoFiltered + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoFiltered": " - filtering from _MAX_ records" + * } + * } ); + * } ); */ - "searchDelay": null, - + "sInfoFiltered": "(filtered from _MAX_ total entries)", + + /** - * Which type of pagination should be used. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * If can be useful to append extra information to the info string at times, + * and this variable does exactly that. This information will be appended to + * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are + * being used) at all times. * @type string - * @default two_button - */ - "sPaginationType": "two_button", - - /** - * The state duration (for `stateSave`) in seconds. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type int - * @default 0 + * @default Empty string + * + * @dtopt Language + * @name DataTable.defaults.language.infoPostFix + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "infoPostFix": "All records shown are derived from real information." + * } + * } ); + * } ); */ - "iStateDuration": 0, - + "sInfoPostFix": "", + + /** - * Array of callback functions for state saving. Each array element is an - * object with the following parameters: - *
    - *
  • function:fn - function to call. Takes two parameters, oSettings - * and the JSON string to save that has been thus far created. Returns - * a JSON string to be inserted into a json object - * (i.e. '"param": [ 0, 1, 2]')
  • - *
  • string:sName - name of callback
  • - *
- * @type array - * @default [] + * This decimal place operator is a little different from the other + * language options since DataTables doesn't output floating point + * numbers, so it won't ever use this for display of a number. Rather, + * what this parameter does is modify the sort methods of the table so + * that numbers which are in a format which has a character other than + * a period (`.`) as a decimal place will be sorted numerically. + * + * Note that numbers with different decimal places cannot be shown in + * the same table and still be sortable, the table must be consistent. + * However, multiple different tables on the page can use different + * decimal place characters. + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.decimal + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "decimal": "," + * "thousands": "." + * } + * } ); + * } ); */ - "aoStateSave": [], - + "sDecimal": "", + + /** - * Array of callback functions for state loading. Each array element is an - * object with the following parameters: - *
    - *
  • function:fn - function to call. Takes two parameters, oSettings - * and the object stored. May return false to cancel state loading
  • - *
  • string:sName - name of callback
  • - *
- * @type array - * @default [] + * DataTables has a build in number formatter (`formatNumber`) which is + * used to format large numbers that are used in the table information. + * By default a comma is used, but this can be trivially changed to any + * character you wish with this parameter. + * @type string + * @default , + * + * @dtopt Language + * @name DataTable.defaults.language.thousands + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "thousands": "'" + * } + * } ); + * } ); */ - "aoStateLoad": [], - + "sThousands": ",", + + /** - * State that was saved. Useful for back reference - * @type object - * @default null + * Detail the action that will be taken when the drop down menu for the + * pagination length option is changed. The '_MENU_' variable is replaced + * with a default select list of 10, 25, 50 and 100, and can be replaced + * with a custom select box if required. + * @type string + * @default Show _MENU_ entries + * + * @dtopt Language + * @name DataTable.defaults.language.lengthMenu + * + * @example + * // Language change only + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "lengthMenu": "Display _MENU_ records" + * } + * } ); + * } ); + * + * @example + * // Language and options change + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "lengthMenu": 'Display records' + * } + * } ); + * } ); */ - "oSavedState": null, - + "sLengthMenu": "Show _MENU_ entries", + + /** - * State that was loaded. Useful for back reference - * @type object - * @default null + * When using Ajax sourced data and during the first draw when DataTables is + * gathering the data, this message is shown in an empty row in the table to + * indicate to the end user the the data is being loaded. Note that this + * parameter is not used when loading data by server-side processing, just + * Ajax sourced data with client-side processing. + * @type string + * @default Loading... + * + * @dtopt Language + * @name DataTable.defaults.language.loadingRecords + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "loadingRecords": "Please wait - loading..." + * } + * } ); + * } ); */ - "oLoadedState": null, - + "sLoadingRecords": "Loading...", + + /** - * Source url for AJAX data for the table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * Text which is displayed when the table is processing a user action + * (usually a sort command or similar). * @type string - * @default null + * + * @dtopt Language + * @name DataTable.defaults.language.processing + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "processing": "DataTables is currently busy" + * } + * } ); + * } ); */ - "sAjaxSource": null, - + "sProcessing": "", + + /** - * Property from a given object from which to read the table data from. This - * can be an empty string (when not server-side processing), in which case - * it is assumed an an array is given directly. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. + * Details the actions that will be taken when the user types into the + * filtering input text box. The variable "_INPUT_", if used in the string, + * is replaced with the HTML text box for the filtering input allowing + * control over where it appears in the string. If "_INPUT_" is not given + * then the input box is appended to the string automatically. * @type string + * @default Search: + * + * @dtopt Language + * @name DataTable.defaults.language.search + * + * @example + * // Input text box will be appended at the end automatically + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "search": "Filter records:" + * } + * } ); + * } ); + * + * @example + * // Specify where the filter should appear + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "search": "Apply filter _INPUT_ to table" + * } + * } ); + * } ); */ - "sAjaxDataProp": null, - + "sSearch": "Search:", + + /** - * The last jQuery XHR object that was used for server-side data gathering. - * This can be used for working with the XHR information in one of the - * callbacks - * @type object - * @default null + * Assign a `placeholder` attribute to the search `input` element + * @type string + * @default + * + * @dtopt Language + * @name DataTable.defaults.language.searchPlaceholder */ - "jqXHR": null, - + "sSearchPlaceholder": "", + + /** - * JSON returned from the server in the last Ajax request - * @type object - * @default undefined + * All of the language information can be stored in a file on the + * server-side, which DataTables will look up if this parameter is passed. + * It must store the URL of the language file, which is in a JSON format, + * and the object has the same properties as the oLanguage object in the + * initialiser object (i.e. the above parameters). Please refer to one of + * the example language files to see how this works in action. + * @type string + * @default Empty string - i.e. disabled + * + * @dtopt Language + * @name DataTable.defaults.language.url + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" + * } + * } ); + * } ); */ - "json": undefined, - + "sUrl": "", + + /** - * Data submitted as part of the last Ajax request - * @type object - * @default undefined + * Text shown inside the table records when the is no information to be + * displayed after filtering. `emptyTable` is shown when there is simply no + * information in the table at all (regardless of filtering). + * @type string + * @default No matching records found + * + * @dtopt Language + * @name DataTable.defaults.language.zeroRecords + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "language": { + * "zeroRecords": "No records to display" + * } + * } ); + * } ); */ - "oAjaxData": undefined, - + "sZeroRecords": "No matching records found" + }, + + + /** + * This parameter allows you to have define the global filtering state at + * initialisation time. As an object the `search` parameter must be + * defined, but all other parameters are optional. When `regex` is true, + * the search string will be treated as a regular expression, when false + * (default) it will be treated as a straight string. When `smart` + * DataTables will use it's smart filtering methods (to word match at + * any point in the data), when false this will not be done. + * @namespace + * @extends DataTable.models.oSearch + * + * @dtopt Options + * @name DataTable.defaults.search + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "search": {"search": "Initial search"} + * } ); + * } ) + */ + "oSearch": $.extend( {}, DataTable.models.oSearch ), + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * By default DataTables will look for the property `data` (or `aaData` for + * compatibility with DataTables 1.9-) when obtaining data from an Ajax + * source or for server-side processing - this parameter allows that + * property to be changed. You can use Javascript dotted object notation to + * get a data source for multiple levels of nesting. + * @type string + * @default data + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.ajaxDataProp + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sAjaxDataProp": "data", + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * You can instruct DataTables to load data from an external + * source using this parameter (use aData if you want to pass data in you + * already have). Simply provide a url a JSON object can be obtained from. + * @type string + * @default null + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.ajaxSource + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sAjaxSource": null, + + + /** + * This initialisation variable allows you to specify exactly where in the + * DOM you want DataTables to inject the various controls it adds to the page + * (for example you might want the pagination controls at the top of the + * table). DIV elements (with or without a custom class) can also be added to + * aid styling. The follow syntax is used: + *
    + *
  • The following options are allowed: + *
      + *
    • 'l' - Length changing
    • + *
    • 'f' - Filtering input
    • + *
    • 't' - The table!
    • + *
    • 'i' - Information
    • + *
    • 'p' - Pagination
    • + *
    • 'r' - pRocessing
    • + *
    + *
  • + *
  • The following constants are allowed: + *
      + *
    • 'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')
    • + *
    • 'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')
    • + *
    + *
  • + *
  • The following syntax is expected: + *
      + *
    • '<' and '>' - div elements
    • + *
    • '<"class" and '>' - div with a class
    • + *
    • '<"#id" and '>' - div with an ID
    • + *
    + *
  • + *
  • Examples: + *
      + *
    • '<"wrapper"flipt>'
    • + *
    • '<lf<t>ip>'
    • + *
    + *
  • + *
+ * @type string + * @default lfrtip (when `jQueryUI` is false) or + * <"H"lfr>t<"F"ip> (when `jQueryUI` is true) + * + * @dtopt Options + * @name DataTable.defaults.dom + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "dom": '<"top"i>rt<"bottom"flp><"clear">' + * } ); + * } ); + */ + "sDom": "lfrtip", + + + /** + * Search delay option. This will throttle full table searches that use the + * DataTables provided search input element (it does not effect calls to + * `dt-api search()`, providing a delay before the search is made. + * @type integer + * @default 0 + * + * @dtopt Options + * @name DataTable.defaults.searchDelay + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "searchDelay": 200 + * } ); + * } ) + */ + "searchDelay": null, + + + /** + * DataTables features six different built-in options for the buttons to + * display for pagination control: + * + * * `numbers` - Page number buttons only + * * `simple` - 'Previous' and 'Next' buttons only + * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers + * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons + * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers + * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers + * + * Further methods can be added using {@link DataTable.ext.oPagination}. + * @type string + * @default simple_numbers + * + * @dtopt Options + * @name DataTable.defaults.pagingType + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "pagingType": "full_numbers" + * } ); + * } ) + */ + "sPaginationType": "simple_numbers", + + + /** + * Enable horizontal scrolling. When a table is too wide to fit into a + * certain layout, or you have a large number of columns in the table, you + * can enable x-scrolling to show the table in a viewport, which can be + * scrolled. This property can be `true` which will allow the table to + * scroll horizontally when needed, or any CSS unit, or a number (in which + * case it will be treated as a pixel measurement). Setting as simply `true` + * is recommended. + * @type boolean|string + * @default blank string - i.e. disabled + * + * @dtopt Features + * @name DataTable.defaults.scrollX + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollX": true, + * "scrollCollapse": true + * } ); + * } ); + */ + "sScrollX": "", + + + /** + * This property can be used to force a DataTable to use more width than it + * might otherwise do when x-scrolling is enabled. For example if you have a + * table which requires to be well spaced, this parameter is useful for + * "over-sizing" the table, and thus forcing scrolling. This property can by + * any CSS unit, or a number (in which case it will be treated as a pixel + * measurement). + * @type string + * @default blank string - i.e. disabled + * + * @dtopt Options + * @name DataTable.defaults.scrollXInner + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollX": "100%", + * "scrollXInner": "110%" + * } ); + * } ); + */ + "sScrollXInner": "", + + + /** + * Enable vertical scrolling. Vertical scrolling will constrain the DataTable + * to the given height, and enable scrolling for any data which overflows the + * current viewport. This can be used as an alternative to paging to display + * a lot of data in a small area (although paging and scrolling can both be + * enabled at the same time). This property can be any CSS unit, or a number + * (in which case it will be treated as a pixel measurement). + * @type string + * @default blank string - i.e. disabled + * + * @dtopt Features + * @name DataTable.defaults.scrollY + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "scrollY": "200px", + * "paginate": false + * } ); + * } ); + */ + "sScrollY": "", + + + /** + * __Deprecated__ The functionality provided by this parameter has now been + * superseded by that provided through `ajax`, which should be used instead. + * + * Set the HTTP method that is used to make the Ajax call for server-side + * processing or Ajax sourced data. + * @type string + * @default GET + * + * @dtopt Options + * @dtopt Server-side + * @name DataTable.defaults.serverMethod + * + * @deprecated 1.10. Please use `ajax` for this functionality now. + */ + "sServerMethod": "GET", + + + /** + * DataTables makes use of renderers when displaying HTML elements for + * a table. These renderers can be added or modified by plug-ins to + * generate suitable mark-up for a site. For example the Bootstrap + * integration plug-in for DataTables uses a paging button renderer to + * display pagination buttons in the mark-up required by Bootstrap. + * + * For further information about the renderers available see + * DataTable.ext.renderer + * @type string|object + * @default null + * + * @name DataTable.defaults.renderer + * + */ + "renderer": null, + + + /** + * Set the data property name that DataTables should use to get a row's id + * to set as the `id` property in the node. + * @type string + * @default DT_RowId + * + * @name DataTable.defaults.rowId + */ + "rowId": "DT_RowId" + }; + + _fnHungarianMap( DataTable.defaults ); + + + + /* + * Developer note - See note in model.defaults.js about the use of Hungarian + * notation and camel case. + */ + + /** + * Column options that can be given to DataTables at initialisation time. + * @namespace + */ + DataTable.defaults.column = { + /** + * Define which column(s) an order will occur on for this column. This + * allows a column's ordering to take multiple columns into account when + * doing a sort or use the data from a different column. For example first + * name / last name columns make sense to do a multi-column sort over the + * two columns. + * @type array|int + * @default null Takes the value of the column index automatically + * + * @name DataTable.defaults.column.orderData + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, + * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, + * { "orderData": 2, "targets": [ 2 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "orderData": [ 0, 1 ] }, + * { "orderData": [ 1, 0 ] }, + * { "orderData": 2 }, + * null, + * null + * ] + * } ); + * } ); + */ + "aDataSort": null, + "iDataSort": -1, + + + /** + * You can control the default ordering direction, and even alter the + * behaviour of the sort handler (i.e. only allow ascending ordering etc) + * using this parameter. + * @type array + * @default [ 'asc', 'desc' ] + * + * @name DataTable.defaults.column.orderSequence + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, + * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, + * { "orderSequence": [ "desc" ], "targets": [ 3 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * { "orderSequence": [ "asc" ] }, + * { "orderSequence": [ "desc", "asc", "asc" ] }, + * { "orderSequence": [ "desc" ] }, + * null + * ] + * } ); + * } ); + */ + "asSorting": [ 'asc', 'desc' ], + + + /** + * Enable or disable filtering on the data in this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.searchable + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "searchable": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "searchable": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bSearchable": true, + + + /** + * Enable or disable ordering on this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.orderable + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderable": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "orderable": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bSortable": true, + + + /** + * Enable or disable the display of this column. + * @type boolean + * @default true + * + * @name DataTable.defaults.column.visible + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "visible": false, "targets": [ 0 ] } + * ] } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "visible": false }, + * null, + * null, + * null, + * null + * ] } ); + * } ); + */ + "bVisible": true, + + + /** + * Developer definable function that is called whenever a cell is created (Ajax source, + * etc) or processed for input (DOM source). This can be used as a compliment to mRender + * allowing you to modify the DOM element (add background colour for example) when the + * element is available. + * @type function + * @param {element} td The TD node that has been created + * @param {*} cellData The Data for the cell + * @param {array|object} rowData The data for the whole row + * @param {int} row The row index for the aoData data store + * @param {int} col The column index for aoColumns + * + * @name DataTable.defaults.column.createdCell + * @dtopt Columns + * + * @example + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [3], + * "createdCell": function (td, cellData, rowData, row, col) { + * if ( cellData == "1.7" ) { + * $(td).css('color', 'blue') + * } + * } + * } ] + * }); + * } ); + */ + "fnCreatedCell": null, + + + /** + * This parameter has been replaced by `data` in DataTables to ensure naming + * consistency. `dataProp` can still be used, as there is backwards + * compatibility in DataTables for this option, but it is strongly + * recommended that you use `data` in preference to `dataProp`. + * @name DataTable.defaults.column.dataProp + */ + + + /** + * This property can be used to read data from any data source property, + * including deeply nested objects / properties. `data` can be given in a + * number of different ways which effect its behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. Note that + * function notation is recommended for use in `render` rather than + * `data` as it is much simpler to use as a renderer. + * * `null` - use the original data source for the row rather than plucking + * data directly from it. This action has effects on two other + * initialisation options: + * * `defaultContent` - When null is given as the `data` option and + * `defaultContent` is specified for the column, the value defined by + * `defaultContent` will be used for the cell. + * * `render` - When null is used for the `data` option and the `render` + * option is specified for the column, the whole data source for the + * row is used for the renderer. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * `{array|object}` The data source for the row + * * `{string}` The type call data requested - this will be 'set' when + * setting data or 'filter', 'display', 'type', 'sort' or undefined + * when gathering data. Note that when `undefined` is given for the + * type DataTables expects to get the raw data for the object back< + * * `{*}` Data to set when the second parameter is 'set'. + * * Return: + * * The return value from the function is not required when 'set' is + * the type of call, but otherwise the return is what will be used + * for the data requested. + * + * Note that `data` is a getter and setter option. If you just require + * formatting of data for output, you will likely want to use `render` which + * is simply a getter and thus simpler to use. + * + * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The + * name change reflects the flexibility of this property and is consistent + * with the naming of mRender. If 'mDataProp' is given, then it will still + * be used by DataTables, as it automatically maps the old name to the new + * if required. + * + * @type string|int|function|null + * @default null Use automatically calculated column index + * + * @name DataTable.defaults.column.data + * @dtopt Columns + * + * @example + * // Read table data from objects + * // JSON structure for each row: + * // { + * // "engine": {value}, + * // "browser": {value}, + * // "platform": {value}, + * // "version": {value}, + * // "grade": {value} + * // } + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/objects.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { "data": "platform" }, + * { "data": "version" }, + * { "data": "grade" } + * ] + * } ); + * } ); + * + * @example + * // Read information from deeply nested objects + * // JSON structure for each row: + * // { + * // "engine": {value}, + * // "browser": {value}, + * // "platform": { + * // "inner": {value} + * // }, + * // "details": [ + * // {value}, {value} + * // ] + * // } + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/deep.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { "data": "platform.inner" }, + * { "data": "details.0" }, + * { "data": "details.1" } + * ] + * } ); + * } ); + * + * @example + * // Using `data` as a function to provide different information for + * // sorting, filtering and display. In this case, currency (price) + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": function ( source, type, val ) { + * if (type === 'set') { + * source.price = val; + * // Store the computed display and filter values for efficiency + * source.price_display = val=="" ? "" : "$"+numberFormat(val); + * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; + * return; + * } + * else if (type === 'display') { + * return source.price_display; + * } + * else if (type === 'filter') { + * return source.price_filter; + * } + * // 'sort', 'type' and undefined all just use the integer + * return source.price; + * } + * } ] + * } ); + * } ); + * + * @example + * // Using default content + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, + * "defaultContent": "Click to edit" + * } ] + * } ); + * } ); + * + * @example + * // Using array notation - outputting a list from an array + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": "name[, ]" + * } ] + * } ); + * } ); + * + */ + "mData": null, + + + /** + * This property is the rendering partner to `data` and it is suggested that + * when you want to manipulate data for display (including filtering, + * sorting etc) without altering the underlying data for the table, use this + * property. `render` can be considered to be the the read only companion to + * `data` which is read / write (then as such more complex). Like `data` + * this option can be given in a number of different ways to effect its + * behaviour: + * + * * `integer` - treated as an array index for the data source. This is the + * default that DataTables uses (incrementally increased for each column). + * * `string` - read an object property from the data source. There are + * three 'special' options that can be used in the string to alter how + * DataTables reads the data from the source object: + * * `.` - Dotted Javascript notation. Just as you use a `.` in + * Javascript to read from nested objects, so to can the options + * specified in `data`. For example: `browser.version` or + * `browser.name`. If your object parameter name contains a period, use + * `\\` to escape it - i.e. `first\\.name`. + * * `[]` - Array notation. DataTables can automatically combine data + * from and array source, joining the data with the characters provided + * between the two brackets. For example: `name[, ]` would provide a + * comma-space separated list from the source array. If no characters + * are provided between the brackets, the original array source is + * returned. + * * `()` - Function notation. Adding `()` to the end of a parameter will + * execute a function of the name given. For example: `browser()` for a + * simple function on the data source, `browser.version()` for a + * function in a nested property or even `browser().version` to get an + * object property if the function called returns an object. + * * `object` - use different data for the different data types requested by + * DataTables ('filter', 'display', 'type' or 'sort'). The property names + * of the object is the data type the property refers to and the value can + * defined using an integer, string or function using the same rules as + * `render` normally does. Note that an `_` option _must_ be specified. + * This is the default value to use if you haven't specified a value for + * the data type requested by DataTables. + * * `function` - the function given will be executed whenever DataTables + * needs to set or get the data for a cell in the column. The function + * takes three parameters: + * * Parameters: + * * {array|object} The data source for the row (based on `data`) + * * {string} The type call data requested - this will be 'filter', + * 'display', 'type' or 'sort'. + * * {array|object} The full data source for the row (not based on + * `data`) + * * Return: + * * The return value from the function is what will be used for the + * data requested. + * + * @type string|int|function|object|null + * @default null Use the data source value. + * + * @name DataTable.defaults.column.render + * @dtopt Columns + * + * @example + * // Create a comma separated list from an array of objects + * $(document).ready( function() { + * $('#example').dataTable( { + * "ajaxSource": "sources/deep.txt", + * "columns": [ + * { "data": "engine" }, + * { "data": "browser" }, + * { + * "data": "platform", + * "render": "[, ].name" + * } + * ] + * } ); + * } ); + * + * @example + * // Execute a function to obtain data + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, // Use the full data source object for the renderer's source + * "render": "browserName()" + * } ] + * } ); + * } ); + * + * @example + * // As an object, extracting different data for the different types + * // This would be used with a data source such as: + * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } + * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` + * // (which has both forms) is used for filtering for if a user inputs either format, while + * // the formatted phone number is the one that is shown in the table. + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": null, // Use the full data source object for the renderer's source + * "render": { + * "_": "phone", + * "filter": "phone_filter", + * "display": "phone_display" + * } + * } ] + * } ); + * } ); + * + * @example + * // Use as a function to create a link from the data source + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "data": "download_link", + * "render": function ( data, type, full ) { + * return 'Download'; + * } + * } ] + * } ); + * } ); + */ + "mRender": null, + + + /** + * Change the cell type created for the column - either TD cells or TH cells. This + * can be useful as TH cells have semantic meaning in the table body, allowing them + * to act as a header for a row (you may wish to add scope='row' to the TH elements). + * @type string + * @default td + * + * @name DataTable.defaults.column.cellType + * @dtopt Columns + * + * @example + * // Make the first column use TH cells + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ { + * "targets": [ 0 ], + * "cellType": "th" + * } ] + * } ); + * } ); + */ + "sCellType": "td", + + + /** + * Class to give to each cell in this column. + * @type string + * @default Empty string + * + * @name DataTable.defaults.column.class + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "class": "my_class", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "class": "my_class" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sClass": "", + + /** + * When DataTables calculates the column widths to assign to each column, + * it finds the longest string in each column and then constructs a + * temporary table and reads the widths from that. The problem with this + * is that "mmm" is much wider then "iiii", but the latter is a longer + * string - thus the calculation can go wrong (doing it properly and putting + * it into an DOM object and measuring that is horribly(!) slow). Thus as + * a "work around" we provide this option. It will append its value to the + * text that is found to be the longest string for the column - i.e. padding. + * Generally you shouldn't need this! + * @type string + * @default Empty string + * + * @name DataTable.defaults.column.contentPadding + * @dtopt Columns + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * null, + * { + * "contentPadding": "mmm" + * } + * ] + * } ); + * } ); + */ + "sContentPadding": "", + + + /** + * Allows a default value to be given for a column's data, and will be used + * whenever a null data source is encountered (this can be because `data` + * is set to null, or because the data source itself is null). + * @type string + * @default null + * + * @name DataTable.defaults.column.defaultContent + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { + * "data": null, + * "defaultContent": "Edit", + * "targets": [ -1 ] + * } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * null, + * { + * "data": null, + * "defaultContent": "Edit" + * } + * ] + * } ); + * } ); + */ + "sDefaultContent": null, + + + /** + * This parameter is only used in DataTables' server-side processing. It can + * be exceptionally useful to know what columns are being displayed on the + * client side, and to map these to database fields. When defined, the names + * also allow DataTables to reorder information from the server if it comes + * back in an unexpected order (i.e. if you switch your columns around on the + * client-side, your server-side code does not also need updating). + * @type string + * @default Empty string + * + * @name DataTable.defaults.column.name + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "name": "engine", "targets": [ 0 ] }, + * { "name": "browser", "targets": [ 1 ] }, + * { "name": "platform", "targets": [ 2 ] }, + * { "name": "version", "targets": [ 3 ] }, + * { "name": "grade", "targets": [ 4 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "name": "engine" }, + * { "name": "browser" }, + * { "name": "platform" }, + * { "name": "version" }, + * { "name": "grade" } + * ] + * } ); + * } ); + */ + "sName": "", + + + /** + * Defines a data source type for the ordering which can be used to read + * real-time information from the table (updating the internally cached + * version) prior to ordering. This allows ordering to occur on user + * editable elements such as form inputs. + * @type string + * @default std + * + * @name DataTable.defaults.column.orderDataType + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, + * { "type": "numeric", "targets": [ 3 ] }, + * { "orderDataType": "dom-select", "targets": [ 4 ] }, + * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * null, + * null, + * { "orderDataType": "dom-text" }, + * { "orderDataType": "dom-text", "type": "numeric" }, + * { "orderDataType": "dom-select" }, + * { "orderDataType": "dom-checkbox" } + * ] + * } ); + * } ); + */ + "sSortDataType": "std", + + + /** + * The title of this column. + * @type string + * @default null Derived from the 'TH' value for this column in the + * original HTML table. + * + * @name DataTable.defaults.column.title + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "title": "My column title", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "title": "My column title" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sTitle": null, + + + /** + * The type allows you to specify how the data for this column will be + * ordered. Four types (string, numeric, date and html (which will strip + * HTML tags before ordering)) are currently available. Note that only date + * formats understood by Javascript's Date() object will be accepted as type + * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', + * 'numeric', 'date' or 'html' (by default). Further types can be adding + * through plug-ins. + * @type string + * @default null Auto-detected from raw data + * + * @name DataTable.defaults.column.type + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "type": "html", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "type": "html" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sType": null, + + + /** + * Defining the width of the column, this parameter may take any CSS value + * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not + * been given a specific width through this interface ensuring that the table + * remains readable. + * @type string + * @default null Automatic + * + * @name DataTable.defaults.column.width + * @dtopt Columns + * + * @example + * // Using `columnDefs` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columnDefs": [ + * { "width": "20%", "targets": [ 0 ] } + * ] + * } ); + * } ); + * + * @example + * // Using `columns` + * $(document).ready( function() { + * $('#example').dataTable( { + * "columns": [ + * { "width": "20%" }, + * null, + * null, + * null, + * null + * ] + * } ); + * } ); + */ + "sWidth": null + }; + + _fnHungarianMap( DataTable.defaults.column ); + + + + /** + * DataTables settings object - this holds all the information needed for a + * given table, including configuration, data and current application of the + * table options. DataTables does not have a single instance for each DataTable + * with the settings attached to that instance, but rather instances of the + * DataTable "class" are created on-the-fly as needed (typically by a + * $().dataTable() call) and the settings object is then applied to that + * instance. + * + * Note that this object is related to {@link DataTable.defaults} but this + * one is the internal data store for DataTables's cache of columns. It should + * NOT be manipulated outside of DataTables. Any configuration should be done + * through the initialisation options. + * @namespace + * @todo Really should attach the settings object to individual instances so we + * don't need to create new instances on each $().dataTable() call (if the + * table already exists). It would also save passing oSettings around and + * into every single function. However, this is a very significant + * architecture change for DataTables and will almost certainly break + * backwards compatibility with older installations. This is something that + * will be done in 2.0. + */ + DataTable.models.oSettings = { + /** + * Primary features of DataTables and their enablement state. + * @namespace + */ + "oFeatures": { + /** - * Function to get the server-side data. + * Flag to say if DataTables should automatically try to calculate the + * optimum table and columns widths (true) or not (false). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. - * @type function - */ - "fnServerData": null, - - /** - * Functions which are called prior to sending an Ajax request so extra - * parameters can easily be sent to the server - * @type array - * @default [] + * @type boolean */ - "aoServerParams": [], - + "bAutoWidth": null, + /** - * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if - * required). + * Delay the creation of TR and TD elements until they are actually + * needed by a driven page draw. This can give a significant speed + * increase for Ajax source and Javascript source data, but makes no + * difference at all for DOM and server-side processing tables. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. - * @type string + * @type boolean */ - "sServerMethod": null, - + "bDeferRender": null, + /** - * Format numbers for display. + * Enable filtering on the table or not. Note that if this is disabled + * then there is no filtering at all on the table, including fnFilter. + * To just remove the filtering input use sDom and remove the 'f' option. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. - * @type function + * @type boolean */ - "fnFormatNumber": null, - + "bFilter": null, + /** - * List of options that can be used for the user selectable length menu. + * Table information element (the 'Showing x of y records' div) enable + * flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aLengthMenu": null, - - /** - * Counter for the draws that the table does. Also used as a tracker for - * server-side processing - * @type int - * @default 0 - */ - "iDraw": 0, - - /** - * Indicate if a redraw is being done - useful for Ajax * @type boolean - * @default false */ - "bDrawing": false, - + "bInfo": null, + /** - * Draw index (iDraw) of the last error when parsing the returned data - * @type int - * @default -1 + * Present a user control allowing the end user to change the page size + * when pagination is enabled. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean */ - "iDrawError": -1, - + "bLengthChange": null, + /** - * Paging display length - * @type int - * @default 10 + * Pagination enabled or not. Note that if this is disabled then length + * changing must also be disabled. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean */ - "_iDisplayLength": 10, - + "bPaginate": null, + /** - * Paging start point - aiDisplay index - * @type int - * @default 0 + * Processing indicator enable flag whenever DataTables is enacting a + * user request - typically an Ajax request for server-side processing. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean */ - "_iDisplayStart": 0, - + "bProcessing": null, + /** - * Server-side processing - number of records in the result set - * (i.e. before filtering), Use fnRecordsTotal rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. - * @type int - * @default 0 - * @private + * Server-side processing enabled flag - when enabled DataTables will + * get all data from the server for every draw - there is no filtering, + * sorting or paging done on the client-side. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean */ - "_iRecordsTotal": 0, - + "bServerSide": null, + /** - * Server-side processing - number of records in the current display set - * (i.e. after filtering). Use fnRecordsDisplay rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. + * Sorting enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. * @type boolean - * @default 0 - * @private */ - "_iRecordsDisplay": 0, - + "bSort": null, + /** - * The classes to use for the table - * @type object - * @default {} + * Multi-column sorting + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean */ - "oClasses": {}, - + "bSortMulti": null, + /** - * Flag attached to the settings object so you can check in the draw - * callback if filtering has been done in the draw. Deprecated in favour of - * events. + * Apply a class to the columns which are being sorted to provide a + * visual highlight or not. This can slow things down when enabled since + * there is a lot of DOM interaction. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. * @type boolean - * @default false - * @deprecated */ - "bFiltered": false, - + "bSortClasses": null, + /** - * Flag attached to the settings object so you can check in the draw - * callback if sorting has been done in the draw. Deprecated in favour of - * events. + * State saving enablement flag. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. * @type boolean - * @default false - * @deprecated */ - "bSorted": false, - + "bStateSave": null + }, + + + /** + * Scrolling settings for a table. + * @namespace + */ + "oScroll": { /** - * Indicate that if multiple rows are in the header and there is more than - * one unique cell per column, if the top one (true) or bottom one (false) - * should be used for sorting / title by DataTables. + * When the table is shorter in height than sScrollY, collapse the + * table container down to the height of the table (when true). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ - "bSortCellsTop": null, - + "bCollapse": null, + /** - * Initialisation object that is used for the table - * @type object - * @default null + * Width of the scrollbar for the web-browser's platform. Calculated + * during table initialisation. + * @type int + * @default 0 */ - "oInit": null, - + "iBarWidth": 0, + /** - * Destroy callback functions - for plug-ins to attach themselves to the - * destroy so they can clean up markup and events. - * @type array - * @default [] + * Viewport width for horizontal scrolling. Horizontal scrolling is + * disabled if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string */ - "aoDestroyCallback": [], - - + "sX": null, + /** - * Get the number of records in the current record set, before filtering - * @type function + * Width to expand the table to when using x-scrolling. Typically you + * should not need to use this. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @deprecated */ - "fnRecordsTotal": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsTotal * 1 : - this.aiDisplayMaster.length; - }, - + "sXInner": null, + /** - * Get the number of records in the current record set, after filtering - * @type function + * Viewport height for vertical scrolling. Vertical scrolling is disabled + * if an empty string. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string */ - "fnRecordsDisplay": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsDisplay * 1 : - this.aiDisplay.length; - }, - + "sY": null + }, + + /** + * Language information for the table. + * @namespace + * @extends DataTable.defaults.oLanguage + */ + "oLanguage": { /** - * Get the display end point - aiDisplay index + * Information callback function. See + * {@link DataTable.defaults.fnInfoCallback} * @type function - */ - "fnDisplayEnd": function () - { - var - len = this._iDisplayLength, - start = this._iDisplayStart, - calc = start + len, - records = this.aiDisplay.length, - features = this.oFeatures, - paginate = features.bPaginate; - - if ( features.bServerSide ) { - return paginate === false || len === -1 ? - start + records : - Math.min( start+len, this._iRecordsDisplay ); - } - else { - return ! paginate || calc>records || len===-1 ? - records : - calc; - } - }, - - /** - * The DataTables object for this table - * @type object * @default null */ - "oInstance": null, - - /** - * Unique identifier for each instance of the DataTables object. If there - * is an ID on the table node, then it takes that value, otherwise an - * incrementing internal counter is used. - * @type string - * @default null - */ - "sInstance": null, - - /** - * tabindex attribute value that is added to DataTables control elements, allowing - * keyboard navigation of the table and its controls. - */ - "iTabIndex": 0, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollHead": null, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollFoot": null, - + "fnInfoCallback": null + }, + + /** + * Browser support parameters + * @namespace + */ + "oBrowser": { /** - * Last applied sort - * @type array - * @default [] + * Indicate if the browser incorrectly calculates width:100% inside a + * scrolling element (IE6/7) + * @type boolean + * @default false */ - "aLastSort": [], - + "bScrollOversize": false, + /** - * Stored plug-in instances - * @type object - * @default {} + * Determine if the vertical scrollbar is on the right or left of the + * scrolling container - needed for rtl language layout, although not + * all browsers move the scrollbar (Safari). + * @type boolean + * @default false */ - "oPlugins": {}, - + "bScrollbarLeft": false, + /** - * Function used to get a row's id from the row's data - * @type function - * @default null + * Flag for if `getBoundingClientRect` is fully supported or not + * @type boolean + * @default false */ - "rowIdFn": null, - + "bBounding": false, + /** - * Data location where to store a row's id - * @type string - * @default null + * Browser scrollbar width + * @type integer + * @default 0 */ - "rowId": null - }; - + "barWidth": 0 + }, + + + "ajax": null, + + + /** + * Array referencing the nodes which are used for the features. The + * parameters of this object match what is allowed by sDom - i.e. + *
    + *
  • 'l' - Length changing
  • + *
  • 'f' - Filtering input
  • + *
  • 't' - The table!
  • + *
  • 'i' - Information
  • + *
  • 'p' - Pagination
  • + *
  • 'r' - pRocessing
  • + *
+ * @type array + * @default [] + */ + "aanFeatures": [], + + /** + * Store data information - see {@link DataTable.models.oRow} for detailed + * information. + * @type array + * @default [] + */ + "aoData": [], + + /** + * Array of indexes which are in the current display (after filtering etc) + * @type array + * @default [] + */ + "aiDisplay": [], + + /** + * Array of indexes for display - no filtering + * @type array + * @default [] + */ + "aiDisplayMaster": [], + + /** + * Map of row ids to data indexes + * @type object + * @default {} + */ + "aIds": {}, + + /** + * Store information about each column that is in use + * @type array + * @default [] + */ + "aoColumns": [], + + /** + * Store information about the table's header + * @type array + * @default [] + */ + "aoHeader": [], + + /** + * Store information about the table's footer + * @type array + * @default [] + */ + "aoFooter": [], + + /** + * Store the applied global search information in case we want to force a + * research or compare the old search to a new one. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @namespace + * @extends DataTable.models.oSearch + */ + "oPreviousSearch": {}, + + /** + * Store the applied search for each column - see + * {@link DataTable.models.oSearch} for the format that is used for the + * filtering information for each column. + * @type array + * @default [] + */ + "aoPreSearchCols": [], + + /** + * Sorting that is applied to the table. Note that the inner arrays are + * used in the following manner: + *
    + *
  • Index 0 - column number
  • + *
  • Index 1 - current sorting direction
  • + *
+ * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @todo These inner arrays should really be objects + */ + "aaSorting": null, + + /** + * Sorting that is always applied to the table (i.e. prefixed in front of + * aaSorting). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "aaSortingFixed": [], + + /** + * Classes to use for the striping of a table. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "asStripeClasses": null, + + /** + * If restoring a table - we should restore its striping classes as well + * @type array + * @default [] + */ + "asDestroyStripes": [], + + /** + * If restoring a table - we should restore its width + * @type int + * @default 0 + */ + "sDestroyWidth": 0, + + /** + * Callback functions array for every time a row is inserted (i.e. on a draw). + * @type array + * @default [] + */ + "aoRowCallback": [], + + /** + * Callback functions for the header on each draw. + * @type array + * @default [] + */ + "aoHeaderCallback": [], + + /** + * Callback function for the footer on each draw. + * @type array + * @default [] + */ + "aoFooterCallback": [], + + /** + * Array of callback functions for draw callback functions + * @type array + * @default [] + */ + "aoDrawCallback": [], + + /** + * Array of callback functions for row created function + * @type array + * @default [] + */ + "aoRowCreatedCallback": [], + + /** + * Callback functions for just before the table is redrawn. A return of + * false will be used to cancel the draw. + * @type array + * @default [] + */ + "aoPreDrawCallback": [], + + /** + * Callback functions for when the table has been initialised. + * @type array + * @default [] + */ + "aoInitComplete": [], + + + /** + * Callbacks for modifying the settings to be stored for state saving, prior to + * saving state. + * @type array + * @default [] + */ + "aoStateSaveParams": [], + + /** + * Callbacks for modifying the settings that have been stored for state saving + * prior to using the stored values to restore the state. + * @type array + * @default [] + */ + "aoStateLoadParams": [], + + /** + * Callbacks for operating on the settings object once the saved state has been + * loaded + * @type array + * @default [] + */ + "aoStateLoaded": [], + + /** + * Cache the table ID for quick access + * @type string + * @default Empty string + */ + "sTableId": "", + + /** + * The TABLE node for the main table + * @type node + * @default null + */ + "nTable": null, + + /** + * Permanent ref to the thead element + * @type node + * @default null + */ + "nTHead": null, + + /** + * Permanent ref to the tfoot element - if it exists + * @type node + * @default null + */ + "nTFoot": null, + + /** + * Permanent ref to the tbody element + * @type node + * @default null + */ + "nTBody": null, + + /** + * Cache the wrapper node (contains all DataTables controlled elements) + * @type node + * @default null + */ + "nTableWrapper": null, + + /** + * Indicate if when using server-side processing the loading of data + * should be deferred until the second draw. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + * @default false + */ + "bDeferLoading": false, + + /** + * Indicate if all required information has been read in + * @type boolean + * @default false + */ + "bInitialised": false, + + /** + * Information about open rows. Each object in the array has the parameters + * 'nTr' and 'nParent' + * @type array + * @default [] + */ + "aoOpenRows": [], + + /** + * Dictate the positioning of DataTables' control elements - see + * {@link DataTable.model.oInit.sDom}. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default null + */ + "sDom": null, + + /** + * Search delay (in mS) + * @type integer + * @default null + */ + "searchDelay": null, + + /** + * Which type of pagination should be used. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default two_button + */ + "sPaginationType": "two_button", + + /** + * The state duration (for `stateSave`) in seconds. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type int + * @default 0 + */ + "iStateDuration": 0, + + /** + * Array of callback functions for state saving. Each array element is an + * object with the following parameters: + *
    + *
  • function:fn - function to call. Takes two parameters, oSettings + * and the JSON string to save that has been thus far created. Returns + * a JSON string to be inserted into a json object + * (i.e. '"param": [ 0, 1, 2]')
  • + *
  • string:sName - name of callback
  • + *
+ * @type array + * @default [] + */ + "aoStateSave": [], + + /** + * Array of callback functions for state loading. Each array element is an + * object with the following parameters: + *
    + *
  • function:fn - function to call. Takes two parameters, oSettings + * and the object stored. May return false to cancel state loading
  • + *
  • string:sName - name of callback
  • + *
+ * @type array + * @default [] + */ + "aoStateLoad": [], + + /** + * State that was saved. Useful for back reference + * @type object + * @default null + */ + "oSavedState": null, + + /** + * State that was loaded. Useful for back reference + * @type object + * @default null + */ + "oLoadedState": null, + + /** + * Source url for AJAX data for the table. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + * @default null + */ + "sAjaxSource": null, + + /** + * Property from a given object from which to read the table data from. This + * can be an empty string (when not server-side processing), in which case + * it is assumed an an array is given directly. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sAjaxDataProp": null, + + /** + * The last jQuery XHR object that was used for server-side data gathering. + * This can be used for working with the XHR information in one of the + * callbacks + * @type object + * @default null + */ + "jqXHR": null, + + /** + * JSON returned from the server in the last Ajax request + * @type object + * @default undefined + */ + "json": undefined, + + /** + * Data submitted as part of the last Ajax request + * @type object + * @default undefined + */ + "oAjaxData": undefined, + + /** + * Function to get the server-side data. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type function + */ + "fnServerData": null, + + /** + * Functions which are called prior to sending an Ajax request so extra + * parameters can easily be sent to the server + * @type array + * @default [] + */ + "aoServerParams": [], + + /** + * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if + * required). + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type string + */ + "sServerMethod": null, + + /** + * Format numbers for display. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type function + */ + "fnFormatNumber": null, + + /** + * List of options that can be used for the user selectable length menu. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type array + * @default [] + */ + "aLengthMenu": null, + + /** + * Counter for the draws that the table does. Also used as a tracker for + * server-side processing + * @type int + * @default 0 + */ + "iDraw": 0, + + /** + * Indicate if a redraw is being done - useful for Ajax + * @type boolean + * @default false + */ + "bDrawing": false, + + /** + * Draw index (iDraw) of the last error when parsing the returned data + * @type int + * @default -1 + */ + "iDrawError": -1, + + /** + * Paging display length + * @type int + * @default 10 + */ + "_iDisplayLength": 10, + + /** + * Paging start point - aiDisplay index + * @type int + * @default 0 + */ + "_iDisplayStart": 0, + + /** + * Server-side processing - number of records in the result set + * (i.e. before filtering), Use fnRecordsTotal rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + * @type int + * @default 0 + * @private + */ + "_iRecordsTotal": 0, + + /** + * Server-side processing - number of records in the current display set + * (i.e. after filtering). Use fnRecordsDisplay rather than + * this property to get the value of the number of records, regardless of + * the server-side processing setting. + * @type boolean + * @default 0 + * @private + */ + "_iRecordsDisplay": 0, + + /** + * The classes to use for the table + * @type object + * @default {} + */ + "oClasses": {}, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if filtering has been done in the draw. Deprecated in favour of + * events. + * @type boolean + * @default false + * @deprecated + */ + "bFiltered": false, + + /** + * Flag attached to the settings object so you can check in the draw + * callback if sorting has been done in the draw. Deprecated in favour of + * events. + * @type boolean + * @default false + * @deprecated + */ + "bSorted": false, + + /** + * Indicate that if multiple rows are in the header and there is more than + * one unique cell per column, if the top one (true) or bottom one (false) + * should be used for sorting / title by DataTables. + * Note that this parameter will be set by the initialisation routine. To + * set a default use {@link DataTable.defaults}. + * @type boolean + */ + "bSortCellsTop": null, + + /** + * Initialisation object that is used for the table + * @type object + * @default null + */ + "oInit": null, + + /** + * Destroy callback functions - for plug-ins to attach themselves to the + * destroy so they can clean up markup and events. + * @type array + * @default [] + */ + "aoDestroyCallback": [], + + + /** + * Get the number of records in the current record set, before filtering + * @type function + */ + "fnRecordsTotal": function () + { + return _fnDataSource( this ) == 'ssp' ? + this._iRecordsTotal * 1 : + this.aiDisplayMaster.length; + }, + + /** + * Get the number of records in the current record set, after filtering + * @type function + */ + "fnRecordsDisplay": function () + { + return _fnDataSource( this ) == 'ssp' ? + this._iRecordsDisplay * 1 : + this.aiDisplay.length; + }, + + /** + * Get the display end point - aiDisplay index + * @type function + */ + "fnDisplayEnd": function () + { + var + len = this._iDisplayLength, + start = this._iDisplayStart, + calc = start + len, + records = this.aiDisplay.length, + features = this.oFeatures, + paginate = features.bPaginate; + + if ( features.bServerSide ) { + return paginate === false || len === -1 ? + start + records : + Math.min( start+len, this._iRecordsDisplay ); + } + else { + return ! paginate || calc>records || len===-1 ? + records : + calc; + } + }, + + /** + * The DataTables object for this table + * @type object + * @default null + */ + "oInstance": null, + + /** + * Unique identifier for each instance of the DataTables object. If there + * is an ID on the table node, then it takes that value, otherwise an + * incrementing internal counter is used. + * @type string + * @default null + */ + "sInstance": null, + + /** + * tabindex attribute value that is added to DataTables control elements, allowing + * keyboard navigation of the table and its controls. + */ + "iTabIndex": 0, + + /** + * DIV container for the footer scrolling table if scrolling + */ + "nScrollHead": null, + + /** + * DIV container for the footer scrolling table if scrolling + */ + "nScrollFoot": null, + + /** + * Last applied sort + * @type array + * @default [] + */ + "aLastSort": [], + + /** + * Stored plug-in instances + * @type object + * @default {} + */ + "oPlugins": {}, + + /** + * Function used to get a row's id from the row's data + * @type function + * @default null + */ + "rowIdFn": null, + + /** + * Data location where to store a row's id + * @type string + * @default null + */ + "rowId": null + }; + + /** + * Extension object for DataTables that is used to provide all extension + * options. + * + * Note that the `DataTable.ext` object is available through + * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is + * also aliased to `jQuery.fn.dataTableExt` for historic reasons. + * @namespace + * @extends DataTable.models.ext + */ + + + /** + * DataTables extensions + * + * This namespace acts as a collection area for plug-ins that can be used to + * extend DataTables capabilities. Indeed many of the build in methods + * use this method to provide their own capabilities (sorting methods for + * example). + * + * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy + * reasons + * + * @namespace + */ + DataTable.ext = _ext = { + /** + * Buttons. For use with the Buttons extension for DataTables. This is + * defined here so other extensions can define buttons regardless of load + * order. It is _not_ used by DataTables core. + * + * @type object + * @default {} + */ + buttons: {}, + + + /** + * Element class names + * + * @type object + * @default {} + */ + classes: {}, + + + /** + * DataTables build type (expanded by the download builder) + * + * @type string + */ + build:"bs5/dt-1.12.1", + + + /** + * Error reporting. + * + * How should DataTables report an error. Can take the value 'alert', + * 'throw', 'none' or a function. + * + * @type string|function + * @default alert + */ + errMode: "alert", + + + /** + * Feature plug-ins. + * + * This is an array of objects which describe the feature plug-ins that are + * available to DataTables. These feature plug-ins are then available for + * use through the `dom` initialisation option. + * + * Each feature plug-in is described by an object which must have the + * following properties: + * + * * `fnInit` - function that is used to initialise the plug-in, + * * `cFeature` - a character so the feature can be enabled by the `dom` + * instillation option. This is case sensitive. + * + * The `fnInit` function has the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * + * And the following return is expected: + * + * * {node|null} The element which contains your feature. Note that the + * return may also be void if your plug-in does not require to inject any + * DOM elements into DataTables control (`dom`) - for example this might + * be useful when developing a plug-in which allows table control via + * keyboard entry + * + * @type array + * + * @example + * $.fn.dataTable.ext.features.push( { + * "fnInit": function( oSettings ) { + * return new TableTools( { "oDTSettings": oSettings } ); + * }, + * "cFeature": "T" + * } ); + */ + feature: [], + + + /** + * Row searching. + * + * This method of searching is complimentary to the default type based + * searching, and a lot more comprehensive as it allows you complete control + * over the searching logic. Each element in this array is a function + * (parameters described below) that is called for every row in the table, + * and your logic decides if it should be included in the searching data set + * or not. + * + * Searching functions have the following input parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{array|object}` Data for the row to be processed (same as the + * original format that was passed in as the data source, or an array + * from a DOM data source + * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which + * can be useful to retrieve the `TR` element if you need DOM interaction. + * + * And the following return is expected: + * + * * {boolean} Include the row in the searched result set (true) or not + * (false) + * + * Note that as with the main search ability in DataTables, technically this + * is "filtering", since it is subtractive. However, for consistency in + * naming we call it searching here. + * + * @type array + * @default [] + * + * @example + * // The following example shows custom search being applied to the + * // fourth column (i.e. the data[3] index) based on two input values + * // from the end-user, matching the data in a certain range. + * $.fn.dataTable.ext.search.push( + * function( settings, data, dataIndex ) { + * var min = document.getElementById('min').value * 1; + * var max = document.getElementById('max').value * 1; + * var version = data[3] == "-" ? 0 : data[3]*1; + * + * if ( min == "" && max == "" ) { + * return true; + * } + * else if ( min == "" && version < max ) { + * return true; + * } + * else if ( min < version && "" == max ) { + * return true; + * } + * else if ( min < version && version < max ) { + * return true; + * } + * return false; + * } + * ); + */ + search: [], + + /** - * Extension object for DataTables that is used to provide all extension + * Selector extensions + * + * The `selector` option can be used to extend the options available for the + * selector modifier options (`selector-modifier` object data type) that + * each of the three built in selector types offer (row, column and cell + + * their plural counterparts). For example the Select extension uses this + * mechanism to provide an option to select only rows, columns and cells + * that have been marked as selected by the end user (`{selected: true}`), + * which can be used in conjunction with the existing built in selector * options. * - * Note that the `DataTable.ext` object is available through - * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is - * also aliased to `jQuery.fn.dataTableExt` for historic reasons. - * @namespace - * @extends DataTable.models.ext + * Each property is an array to which functions can be pushed. The functions + * take three attributes: + * + * * Settings object for the host table + * * Options object (`selector-modifier` object type) + * * Array of selected item indexes + * + * The return is an array of the resulting item indexes after the custom + * selector has been applied. + * + * @type object */ - - + selector: { + cell: [], + column: [], + row: [] + }, + + /** - * DataTables extensions + * Internal functions, exposed for used in plug-ins. * - * This namespace acts as a collection area for plug-ins that can be used to - * extend DataTables capabilities. Indeed many of the build in methods - * use this method to provide their own capabilities (sorting methods for - * example). + * Please note that you should not need to use the internal methods for + * anything other than a plug-in (and even then, try to avoid if possible). + * The internal function may change between releases. * - * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy - * reasons + * @type object + * @default {} + */ + internal: {}, + + + /** + * Legacy configuration options. Enable and disable legacy options that + * are available in DataTables. * - * @namespace + * @type object */ - DataTable.ext = _ext = { - /** - * Buttons. For use with the Buttons extension for DataTables. This is - * defined here so other extensions can define buttons regardless of load - * order. It is _not_ used by DataTables core. - * - * @type object - * @default {} - */ - buttons: {}, - - - /** - * Element class names - * - * @type object - * @default {} - */ - classes: {}, - - - /** - * DataTables build type (expanded by the download builder) - * - * @type string - */ - build:"bs5/dt-1.11.5", - - - /** - * Error reporting. - * - * How should DataTables report an error. Can take the value 'alert', - * 'throw', 'none' or a function. - * - * @type string|function - * @default alert - */ - errMode: "alert", - - + legacy: { /** - * Feature plug-ins. - * - * This is an array of objects which describe the feature plug-ins that are - * available to DataTables. These feature plug-ins are then available for - * use through the `dom` initialisation option. - * - * Each feature plug-in is described by an object which must have the - * following properties: - * - * * `fnInit` - function that is used to initialise the plug-in, - * * `cFeature` - a character so the feature can be enabled by the `dom` - * instillation option. This is case sensitive. - * - * The `fnInit` function has the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * - * And the following return is expected: - * - * * {node|null} The element which contains your feature. Note that the - * return may also be void if your plug-in does not require to inject any - * DOM elements into DataTables control (`dom`) - for example this might - * be useful when developing a plug-in which allows table control via - * keyboard entry - * - * @type array + * Enable / disable DataTables 1.9 compatible server-side processing + * requests * - * @example - * $.fn.dataTable.ext.features.push( { - * "fnInit": function( oSettings ) { - * return new TableTools( { "oDTSettings": oSettings } ); - * }, - * "cFeature": "T" - * } ); + * @type boolean + * @default null */ - feature: [], - - + ajax: null + }, + + + /** + * Pagination plug-in methods. + * + * Each entry in this object is a function and defines which buttons should + * be shown by the pagination rendering method that is used for the table: + * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the + * buttons are displayed in the document, while the functions here tell it + * what buttons to display. This is done by returning an array of button + * descriptions (what each button will do). + * + * Pagination types (the four built in options and any additional plug-in + * options defined here) can be used through the `paginationType` + * initialisation parameter. + * + * The functions defined take two parameters: + * + * 1. `{int} page` The current page index + * 2. `{int} pages` The number of pages in the table + * + * Each function is expected to return an array where each element of the + * array can be one of: + * + * * `first` - Jump to first page when activated + * * `last` - Jump to last page when activated + * * `previous` - Show previous page when activated + * * `next` - Show next page when activated + * * `{int}` - Show page of the index given + * * `{array}` - A nested array containing the above elements to add a + * containing 'DIV' element (might be useful for styling). + * + * Note that DataTables v1.9- used this object slightly differently whereby + * an object with two functions would be defined for each plug-in. That + * ability is still supported by DataTables 1.10+ to provide backwards + * compatibility, but this option of use is now decremented and no longer + * documented in DataTables 1.10+. + * + * @type object + * @default {} + * + * @example + * // Show previous, next and current page buttons only + * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { + * return [ 'previous', page, 'next' ]; + * }; + */ + pager: {}, + + + renderer: { + pageButton: {}, + header: {} + }, + + + /** + * Ordering plug-ins - custom data source + * + * The extension options for ordering of data available here is complimentary + * to the default type based ordering that DataTables typically uses. It + * allows much greater control over the the data that is being used to + * order a column, but is necessarily therefore more complex. + * + * This type of ordering is useful if you want to do ordering based on data + * live from the DOM (for example the contents of an 'input' element) rather + * than just the static string that DataTables knows of. + * + * The way these plug-ins work is that you create an array of the values you + * wish to be ordering for the column in question and then return that + * array. The data in the array much be in the index order of the rows in + * the table (not the currently ordering order!). Which order data gathering + * function is run here depends on the `dt-init columns.orderDataType` + * parameter that is used for the column (if any). + * + * The functions defined take two parameters: + * + * 1. `{object}` DataTables settings object: see + * {@link DataTable.models.oSettings} + * 2. `{int}` Target column index + * + * Each function is expected to return an array: + * + * * `{array}` Data for the column to be ordering upon + * + * @type array + * + * @example + * // Ordering using `input` node values + * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) + * { + * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { + * return $('input', td).val(); + * } ); + * } + */ + order: {}, + + + /** + * Type based plug-ins. + * + * Each column in DataTables has a type assigned to it, either by automatic + * detection or by direct assignment using the `type` option for the column. + * The type of a column will effect how it is ordering and search (plug-ins + * can also make use of the column type if required). + * + * @namespace + */ + type: { /** - * Row searching. - * - * This method of searching is complimentary to the default type based - * searching, and a lot more comprehensive as it allows you complete control - * over the searching logic. Each element in this array is a function - * (parameters described below) that is called for every row in the table, - * and your logic decides if it should be included in the searching data set - * or not. + * Type detection functions. * - * Searching functions have the following input parameters: + * The functions defined in this object are used to automatically detect + * a column's type, making initialisation of DataTables super easy, even + * when complex data is in the table. * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{array|object}` Data for the row to be processed (same as the - * original format that was passed in as the data source, or an array - * from a DOM data source - * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which - * can be useful to retrieve the `TR` element if you need DOM interaction. + * The functions defined take two parameters: * - * And the following return is expected: + * 1. `{*}` Data from the column cell to be analysed + * 2. `{settings}` DataTables settings object. This can be used to + * perform context specific type detection - for example detection + * based on language settings such as using a comma for a decimal + * place. Generally speaking the options from the settings will not + * be required * - * * {boolean} Include the row in the searched result set (true) or not - * (false) + * Each function is expected to return: * - * Note that as with the main search ability in DataTables, technically this - * is "filtering", since it is subtractive. However, for consistency in - * naming we call it searching here. + * * `{string|null}` Data type detected, or null if unknown (and thus + * pass it on to the other type detection functions. * * @type array - * @default [] * * @example - * // The following example shows custom search being applied to the - * // fourth column (i.e. the data[3] index) based on two input values - * // from the end-user, matching the data in a certain range. - * $.fn.dataTable.ext.search.push( - * function( settings, data, dataIndex ) { - * var min = document.getElementById('min').value * 1; - * var max = document.getElementById('max').value * 1; - * var version = data[3] == "-" ? 0 : data[3]*1; - * - * if ( min == "" && max == "" ) { - * return true; - * } - * else if ( min == "" && version < max ) { - * return true; + * // Currency type detection plug-in: + * $.fn.dataTable.ext.type.detect.push( + * function ( data, settings ) { + * // Check the numeric part + * if ( ! data.substring(1).match(/[0-9]/) ) { + * return null; * } - * else if ( min < version && "" == max ) { - * return true; - * } - * else if ( min < version && version < max ) { - * return true; + * + * // Check prefixed by currency + * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { + * return 'currency'; * } - * return false; + * return null; * } * ); */ - search: [], - - + detect: [], + + /** - * Selector extensions + * Type based search formatting. * - * The `selector` option can be used to extend the options available for the - * selector modifier options (`selector-modifier` object data type) that - * each of the three built in selector types offer (row, column and cell + - * their plural counterparts). For example the Select extension uses this - * mechanism to provide an option to select only rows, columns and cells - * that have been marked as selected by the end user (`{selected: true}`), - * which can be used in conjunction with the existing built in selector - * options. + * The type based searching functions can be used to pre-format the + * data to be search on. For example, it can be used to strip HTML + * tags or to de-format telephone numbers for numeric only searching. * - * Each property is an array to which functions can be pushed. The functions - * take three attributes: + * Note that is a search is not defined for a column of a given type, + * no search formatting will be performed. + * + * Pre-processing of searching data plug-ins - When you assign the sType + * for a column (or have it automatically detected for you by DataTables + * or a type detection plug-in), you will typically be using this for + * custom sorting, but it can also be used to provide custom searching + * by allowing you to pre-processing the data and returning the data in + * the format that should be searched upon. This is done by adding + * functions this object with a parameter name which matches the sType + * for that target column. This is the corollary of afnSortData + * for searching data. * - * * Settings object for the host table - * * Options object (`selector-modifier` object type) - * * Array of selected item indexes + * The functions defined take a single parameter: * - * The return is an array of the resulting item indexes after the custom - * selector has been applied. + * 1. `{*}` Data from the column cell to be prepared for searching * - * @type object - */ - selector: { - cell: [], - column: [], - row: [] - }, - - - /** - * Internal functions, exposed for used in plug-ins. - * - * Please note that you should not need to use the internal methods for - * anything other than a plug-in (and even then, try to avoid if possible). - * The internal function may change between releases. + * Each function is expected to return: + * + * * `{string|null}` Formatted string that will be used for the searching. * * @type object * @default {} - */ - internal: {}, - - - /** - * Legacy configuration options. Enable and disable legacy options that - * are available in DataTables. * - * @type object + * @example + * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { + * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); + * } */ - legacy: { - /** - * Enable / disable DataTables 1.9 compatible server-side processing - * requests - * - * @type boolean - * @default null - */ - ajax: null - }, - - + search: {}, + + /** - * Pagination plug-in methods. - * - * Each entry in this object is a function and defines which buttons should - * be shown by the pagination rendering method that is used for the table: - * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the - * buttons are displayed in the document, while the functions here tell it - * what buttons to display. This is done by returning an array of button - * descriptions (what each button will do). - * - * Pagination types (the four built in options and any additional plug-in - * options defined here) can be used through the `paginationType` - * initialisation parameter. + * Type based ordering. * - * The functions defined take two parameters: + * The column type tells DataTables what ordering to apply to the table + * when a column is sorted upon. The order for each type that is defined, + * is defined by the functions available in this object. * - * 1. `{int} page` The current page index - * 2. `{int} pages` The number of pages in the table + * Each ordering option can be described by three properties added to + * this object: * - * Each function is expected to return an array where each element of the - * array can be one of: + * * `{type}-pre` - Pre-formatting function + * * `{type}-asc` - Ascending order function + * * `{type}-desc` - Descending order function * - * * `first` - Jump to first page when activated - * * `last` - Jump to last page when activated - * * `previous` - Show previous page when activated - * * `next` - Show next page when activated - * * `{int}` - Show page of the index given - * * `{array}` - A nested array containing the above elements to add a - * containing 'DIV' element (might be useful for styling). + * All three can be used together, only `{type}-pre` or only + * `{type}-asc` and `{type}-desc` together. It is generally recommended + * that only `{type}-pre` is used, as this provides the optimal + * implementation in terms of speed, although the others are provided + * for compatibility with existing Javascript sort functions. * - * Note that DataTables v1.9- used this object slightly differently whereby - * an object with two functions would be defined for each plug-in. That - * ability is still supported by DataTables 1.10+ to provide backwards - * compatibility, but this option of use is now decremented and no longer - * documented in DataTables 1.10+. + * `{type}-pre`: Functions defined take a single parameter: * - * @type object - * @default {} + * 1. `{*}` Data from the column cell to be prepared for ordering * - * @example - * // Show previous, next and current page buttons only - * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { - * return [ 'previous', page, 'next' ]; - * }; - */ - pager: {}, - - - renderer: { - pageButton: {}, - header: {} - }, - - - /** - * Ordering plug-ins - custom data source - * - * The extension options for ordering of data available here is complimentary - * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the the data that is being used to - * order a column, but is necessarily therefore more complex. - * - * This type of ordering is useful if you want to do ordering based on data - * live from the DOM (for example the contents of an 'input' element) rather - * than just the static string that DataTables knows of. - * - * The way these plug-ins work is that you create an array of the values you - * wish to be ordering for the column in question and then return that - * array. The data in the array much be in the index order of the rows in - * the table (not the currently ordering order!). Which order data gathering - * function is run here depends on the `dt-init columns.orderDataType` - * parameter that is used for the column (if any). + * And return: * - * The functions defined take two parameters: + * * `{*}` Data to be sorted upon * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{int}` Target column index + * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort + * functions, taking two parameters: * - * Each function is expected to return an array: + * 1. `{*}` Data to compare to the second parameter + * 2. `{*}` Data to compare to the first parameter * - * * `{array}` Data for the column to be ordering upon + * And returning: * - * @type array + * * `{*}` Ordering match: <0 if first parameter should be sorted lower + * than the second parameter, ===0 if the two parameters are equal and + * >0 if the first parameter should be sorted height than the second + * parameter. + * + * @type object + * @default {} * * @example - * // Ordering using `input` node values - * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) - * { - * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { - * return $('input', td).val(); - * } ); - * } - */ - order: {}, - - - /** - * Type based plug-ins. - * - * Each column in DataTables has a type assigned to it, either by automatic - * detection or by direct assignment using the `type` option for the column. - * The type of a column will effect how it is ordering and search (plug-ins - * can also make use of the column type if required). - * - * @namespace - */ - type: { - /** - * Type detection functions. - * - * The functions defined in this object are used to automatically detect - * a column's type, making initialisation of DataTables super easy, even - * when complex data is in the table. - * - * The functions defined take two parameters: - * - * 1. `{*}` Data from the column cell to be analysed - * 2. `{settings}` DataTables settings object. This can be used to - * perform context specific type detection - for example detection - * based on language settings such as using a comma for a decimal - * place. Generally speaking the options from the settings will not - * be required - * - * Each function is expected to return: - * - * * `{string|null}` Data type detected, or null if unknown (and thus - * pass it on to the other type detection functions. - * - * @type array - * - * @example - * // Currency type detection plug-in: - * $.fn.dataTable.ext.type.detect.push( - * function ( data, settings ) { - * // Check the numeric part - * if ( ! data.substring(1).match(/[0-9]/) ) { - * return null; - * } - * - * // Check prefixed by currency - * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { - * return 'currency'; - * } - * return null; - * } - * ); - */ - detect: [], - - - /** - * Type based search formatting. - * - * The type based searching functions can be used to pre-format the - * data to be search on. For example, it can be used to strip HTML - * tags or to de-format telephone numbers for numeric only searching. - * - * Note that is a search is not defined for a column of a given type, - * no search formatting will be performed. - * - * Pre-processing of searching data plug-ins - When you assign the sType - * for a column (or have it automatically detected for you by DataTables - * or a type detection plug-in), you will typically be using this for - * custom sorting, but it can also be used to provide custom searching - * by allowing you to pre-processing the data and returning the data in - * the format that should be searched upon. This is done by adding - * functions this object with a parameter name which matches the sType - * for that target column. This is the corollary of afnSortData - * for searching data. - * - * The functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for searching - * - * Each function is expected to return: - * - * * `{string|null}` Formatted string that will be used for the searching. - * - * @type object - * @default {} - * - * @example - * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { - * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); - * } - */ - search: {}, - - - /** - * Type based ordering. - * - * The column type tells DataTables what ordering to apply to the table - * when a column is sorted upon. The order for each type that is defined, - * is defined by the functions available in this object. - * - * Each ordering option can be described by three properties added to - * this object: - * - * * `{type}-pre` - Pre-formatting function - * * `{type}-asc` - Ascending order function - * * `{type}-desc` - Descending order function - * - * All three can be used together, only `{type}-pre` or only - * `{type}-asc` and `{type}-desc` together. It is generally recommended - * that only `{type}-pre` is used, as this provides the optimal - * implementation in terms of speed, although the others are provided - * for compatibility with existing Javascript sort functions. - * - * `{type}-pre`: Functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for ordering - * - * And return: - * - * * `{*}` Data to be sorted upon - * - * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort - * functions, taking two parameters: - * - * 1. `{*}` Data to compare to the second parameter - * 2. `{*}` Data to compare to the first parameter - * - * And returning: - * - * * `{*}` Ordering match: <0 if first parameter should be sorted lower - * than the second parameter, ===0 if the two parameters are equal and - * >0 if the first parameter should be sorted height than the second - * parameter. - * - * @type object - * @default {} - * - * @example - * // Numeric ordering of formatted numbers with a pre-formatter - * $.extend( $.fn.dataTable.ext.type.order, { - * "string-pre": function(x) { - * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - * return parseFloat( a ); - * } - * } ); - * - * @example - * // Case-sensitive string ordering, with no pre-formatting method - * $.extend( $.fn.dataTable.ext.order, { - * "string-case-asc": function(x,y) { - * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - * }, - * "string-case-desc": function(x,y) { - * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - * } - * } ); - */ - order: {} - }, - - /** - * Unique DataTables instance counter + * // Numeric ordering of formatted numbers with a pre-formatter + * $.extend( $.fn.dataTable.ext.type.order, { + * "string-pre": function(x) { + * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); + * return parseFloat( a ); + * } + * } ); * - * @type int - * @private - */ - _unique: 0, - - - // - // Depreciated - // The following properties are retained for backwards compatibility only. - // The should not be used in new projects and will be removed in a future - // version - // - - /** - * Version check function. - * @type function - * @depreciated Since 1.10 - */ - fnVersionCheck: DataTable.fnVersionCheck, - - - /** - * Index for what 'this' index API functions should use - * @type int - * @deprecated Since v1.10 - */ - iApiIndex: 0, - - - /** - * jQuery UI class container - * @type object - * @deprecated Since v1.10 - */ - oJUIClasses: {}, - - - /** - * Software version - * @type string - * @deprecated Since v1.10 + * @example + * // Case-sensitive string ordering, with no pre-formatting method + * $.extend( $.fn.dataTable.ext.order, { + * "string-case-asc": function(x,y) { + * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + * }, + * "string-case-desc": function(x,y) { + * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + * } + * } ); */ - sVersion: DataTable.version - }; - - + order: {} + }, + + /** + * Unique DataTables instance counter + * + * @type int + * @private + */ + _unique: 0, + + // - // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // Depreciated + // The following properties are retained for backwards compatibility only. + // The should not be used in new projects and will be removed in a future + // version // - $.extend( _ext, { - afnFiltering: _ext.search, - aTypes: _ext.type.detect, - ofnSearch: _ext.type.search, - oSort: _ext.type.order, - afnSortData: _ext.order, - aoFeatures: _ext.feature, - oApi: _ext.internal, - oStdClasses: _ext.classes, - oPagination: _ext.pager - } ); - - - $.extend( DataTable.ext.classes, { - "sTable": "dataTable", - "sNoFooter": "no-footer", - - /* Paging buttons */ - "sPageButton": "paginate_button", - "sPageButtonActive": "current", - "sPageButtonDisabled": "disabled", - - /* Striping classes */ - "sStripeOdd": "odd", - "sStripeEven": "even", - - /* Empty row */ - "sRowEmpty": "dataTables_empty", - - /* Features */ - "sWrapper": "dataTables_wrapper", - "sFilter": "dataTables_filter", - "sInfo": "dataTables_info", - "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ - "sLength": "dataTables_length", - "sProcessing": "dataTables_processing", - - /* Sorting */ - "sSortAsc": "sorting_asc", - "sSortDesc": "sorting_desc", - "sSortable": "sorting", /* Sortable in both directions */ - "sSortableAsc": "sorting_desc_disabled", - "sSortableDesc": "sorting_asc_disabled", - "sSortableNone": "sorting_disabled", - "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ - - /* Filtering */ - "sFilterInput": "", - - /* Page length */ - "sLengthSelect": "", - - /* Scrolling */ - "sScrollWrapper": "dataTables_scroll", - "sScrollHead": "dataTables_scrollHead", - "sScrollHeadInner": "dataTables_scrollHeadInner", - "sScrollBody": "dataTables_scrollBody", - "sScrollFoot": "dataTables_scrollFoot", - "sScrollFootInner": "dataTables_scrollFootInner", - - /* Misc */ - "sHeaderTH": "", - "sFooterTH": "", - - // Deprecated - "sSortJUIAsc": "", - "sSortJUIDesc": "", - "sSortJUI": "", - "sSortJUIAscAllowed": "", - "sSortJUIDescAllowed": "", - "sSortJUIWrapper": "", - "sSortIcon": "", - "sJUIHeader": "", - "sJUIFooter": "" - } ); - - - var extPagination = DataTable.ext.pager; - - function _numbers ( page, pages ) { - var - numbers = [], - buttons = extPagination.numbers_length, - half = Math.floor( buttons / 2 ), - i = 1; - - if ( pages <= buttons ) { - numbers = _range( 0, pages ); - } - else if ( page <= half ) { - numbers = _range( 0, buttons-2 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - } - else if ( page >= pages - 1 - half ) { - numbers = _range( pages-(buttons-2), pages ); - numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 - numbers.splice( 0, 0, 0 ); - } - else { - numbers = _range( page-half+2, page+half-1 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - numbers.splice( 0, 0, 'ellipsis' ); - numbers.splice( 0, 0, 0 ); - } - - numbers.DT_el = 'span'; - return numbers; + + /** + * Version check function. + * @type function + * @depreciated Since 1.10 + */ + fnVersionCheck: DataTable.fnVersionCheck, + + + /** + * Index for what 'this' index API functions should use + * @type int + * @deprecated Since v1.10 + */ + iApiIndex: 0, + + + /** + * jQuery UI class container + * @type object + * @deprecated Since v1.10 + */ + oJUIClasses: {}, + + + /** + * Software version + * @type string + * @deprecated Since v1.10 + */ + sVersion: DataTable.version + }; + + + // + // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // + $.extend( _ext, { + afnFiltering: _ext.search, + aTypes: _ext.type.detect, + ofnSearch: _ext.type.search, + oSort: _ext.type.order, + afnSortData: _ext.order, + aoFeatures: _ext.feature, + oApi: _ext.internal, + oStdClasses: _ext.classes, + oPagination: _ext.pager + } ); + + + $.extend( DataTable.ext.classes, { + "sTable": "dataTable", + "sNoFooter": "no-footer", + + /* Paging buttons */ + "sPageButton": "paginate_button", + "sPageButtonActive": "current", + "sPageButtonDisabled": "disabled", + + /* Striping classes */ + "sStripeOdd": "odd", + "sStripeEven": "even", + + /* Empty row */ + "sRowEmpty": "dataTables_empty", + + /* Features */ + "sWrapper": "dataTables_wrapper", + "sFilter": "dataTables_filter", + "sInfo": "dataTables_info", + "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ + "sLength": "dataTables_length", + "sProcessing": "dataTables_processing", + + /* Sorting */ + "sSortAsc": "sorting_asc", + "sSortDesc": "sorting_desc", + "sSortable": "sorting", /* Sortable in both directions */ + "sSortableAsc": "sorting_desc_disabled", + "sSortableDesc": "sorting_asc_disabled", + "sSortableNone": "sorting_disabled", + "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ + + /* Filtering */ + "sFilterInput": "", + + /* Page length */ + "sLengthSelect": "", + + /* Scrolling */ + "sScrollWrapper": "dataTables_scroll", + "sScrollHead": "dataTables_scrollHead", + "sScrollHeadInner": "dataTables_scrollHeadInner", + "sScrollBody": "dataTables_scrollBody", + "sScrollFoot": "dataTables_scrollFoot", + "sScrollFootInner": "dataTables_scrollFootInner", + + /* Misc */ + "sHeaderTH": "", + "sFooterTH": "", + + // Deprecated + "sSortJUIAsc": "", + "sSortJUIDesc": "", + "sSortJUI": "", + "sSortJUIAscAllowed": "", + "sSortJUIDescAllowed": "", + "sSortJUIWrapper": "", + "sSortIcon": "", + "sJUIHeader": "", + "sJUIFooter": "" + } ); + + + var extPagination = DataTable.ext.pager; + + function _numbers ( page, pages ) { + var + numbers = [], + buttons = extPagination.numbers_length, + half = Math.floor( buttons / 2 ), + i = 1; + + if ( pages <= buttons ) { + numbers = _range( 0, pages ); } + else if ( page <= half ) { + numbers = _range( 0, buttons-2 ); + numbers.push( 'ellipsis' ); + numbers.push( pages-1 ); + } + else if ( page >= pages - 1 - half ) { + numbers = _range( pages-(buttons-2), pages ); + numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 + numbers.splice( 0, 0, 0 ); + } + else { + numbers = _range( page-half+2, page+half-1 ); + numbers.push( 'ellipsis' ); + numbers.push( pages-1 ); + numbers.splice( 0, 0, 'ellipsis' ); + numbers.splice( 0, 0, 0 ); + } + + numbers.DT_el = 'span'; + return numbers; + } + + + $.extend( extPagination, { + simple: function ( page, pages ) { + return [ 'previous', 'next' ]; + }, + + full: function ( page, pages ) { + return [ 'first', 'previous', 'next', 'last' ]; + }, + + numbers: function ( page, pages ) { + return [ _numbers(page, pages) ]; + }, + + simple_numbers: function ( page, pages ) { + return [ 'previous', _numbers(page, pages), 'next' ]; + }, + + full_numbers: function ( page, pages ) { + return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; + }, - - $.extend( extPagination, { - simple: function ( page, pages ) { - return [ 'previous', 'next' ]; - }, - - full: function ( page, pages ) { - return [ 'first', 'previous', 'next', 'last' ]; - }, - - numbers: function ( page, pages ) { - return [ _numbers(page, pages) ]; - }, - - simple_numbers: function ( page, pages ) { - return [ 'previous', _numbers(page, pages), 'next' ]; - }, - - full_numbers: function ( page, pages ) { - return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; - }, - - first_last_numbers: function (page, pages) { - return ['first', _numbers(page, pages), 'last']; - }, - - // For testing and plug-ins to use - _numbers: _numbers, - - // Number of number buttons (including ellipsis) to show. _Must be odd!_ - numbers_length: 7 - } ); - - - $.extend( true, DataTable.ext.renderer, { - pageButton: { - _: function ( settings, host, idx, buttons, page, pages ) { - var classes = settings.oClasses; - var lang = settings.oLanguage.oPaginate; - var aria = settings.oLanguage.oAria.paginate || {}; - var btnDisplay, btnClass, counter=0; - - var attach = function( container, buttons ) { - var i, ien, node, button, tabIndex; - var disabledClass = classes.sPageButtonDisabled; - var clickHandler = function ( e ) { - _fnPageChange( settings, e.data.action, true ); - }; - - for ( i=0, ien=buttons.length ; i' ) - .appendTo( container ); - attach( inner, button ); + first_last_numbers: function (page, pages) { + return ['first', _numbers(page, pages), 'last']; + }, + + // For testing and plug-ins to use + _numbers: _numbers, + + // Number of number buttons (including ellipsis) to show. _Must be odd!_ + numbers_length: 7 + } ); + + + $.extend( true, DataTable.ext.renderer, { + pageButton: { + _: function ( settings, host, idx, buttons, page, pages ) { + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var aria = settings.oLanguage.oAria.paginate || {}; + var btnDisplay, btnClass, counter=0; + + var attach = function( container, buttons ) { + var i, ien, node, button, tabIndex; + var disabledClass = classes.sPageButtonDisabled; + var clickHandler = function ( e ) { + _fnPageChange( settings, e.data.action, true ); + }; + + for ( i=0, ien=buttons.length ; i' ) + .appendTo( container ); + attach( inner, button ); + } + else { + btnDisplay = null; + btnClass = button; + tabIndex = settings.iTabIndex; + + switch ( button ) { + case 'ellipsis': + container.append(''); + break; + + case 'first': + btnDisplay = lang.sFirst; + + if ( page === 0 ) { + tabIndex = -1; + btnClass += ' ' + disabledClass; + } + break; + + case 'previous': + btnDisplay = lang.sPrevious; + + if ( page === 0 ) { + tabIndex = -1; + btnClass += ' ' + disabledClass; + } + break; + + case 'next': + btnDisplay = lang.sNext; + + if ( pages === 0 || page === pages-1 ) { + tabIndex = -1; + btnClass += ' ' + disabledClass; + } + break; + + case 'last': + btnDisplay = lang.sLast; + + if ( pages === 0 || page === pages-1 ) { + tabIndex = -1; + btnClass += ' ' + disabledClass; + } + break; + + default: + btnDisplay = settings.fnFormatNumber( button + 1 ); + btnClass = page === button ? + classes.sPageButtonActive : ''; + break; } - else { - btnDisplay = null; - btnClass = button; - tabIndex = settings.iTabIndex; - - switch ( button ) { - case 'ellipsis': - container.append(''); - break; - - case 'first': - btnDisplay = lang.sFirst; - - if ( page === 0 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'previous': - btnDisplay = lang.sPrevious; - - if ( page === 0 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'next': - btnDisplay = lang.sNext; - - if ( pages === 0 || page === pages-1 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'last': - btnDisplay = lang.sLast; - - if ( pages === 0 || page === pages-1 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - default: - btnDisplay = settings.fnFormatNumber( button + 1 ); - btnClass = page === button ? - classes.sPageButtonActive : ''; - break; - } - - if ( btnDisplay !== null ) { - node = $('', { - 'class': classes.sPageButton+' '+btnClass, - 'aria-controls': settings.sTableId, - 'aria-label': aria[ button ], - 'data-dt-idx': counter, - 'tabindex': tabIndex, - 'id': idx === 0 && typeof button === 'string' ? - settings.sTableId +'_'+ button : - null - } ) - .html( btnDisplay ) - .appendTo( container ); - - _fnBindAction( - node, {action: button}, clickHandler - ); - - counter++; - } + + if ( btnDisplay !== null ) { + node = $('', { + 'class': classes.sPageButton+' '+btnClass, + 'aria-controls': settings.sTableId, + 'aria-label': aria[ button ], + 'data-dt-idx': counter, + 'tabindex': tabIndex, + 'id': idx === 0 && typeof button === 'string' ? + settings.sTableId +'_'+ button : + null + } ) + .html( btnDisplay ) + .appendTo( container ); + + _fnBindAction( + node, {action: button}, clickHandler + ); + + counter++; } } - }; - - // IE9 throws an 'unknown error' if document.activeElement is used - // inside an iframe or frame. Try / catch the error. Not good for - // accessibility, but neither are frames. - var activeEl; - - try { - // Because this approach is destroying and recreating the paging - // elements, focus is lost on the select button which is bad for - // accessibility. So we want to restore focus once the draw has - // completed - activeEl = $(host).find(document.activeElement).data('dt-idx'); - } - catch (e) {} - - attach( $(host).empty(), buttons ); - - if ( activeEl !== undefined ) { - $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); } + }; + + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame. Try / catch the error. Not good for + // accessibility, but neither are frames. + var activeEl; + + try { + // Because this approach is destroying and recreating the paging + // elements, focus is lost on the select button which is bad for + // accessibility. So we want to restore focus once the draw has + // completed + activeEl = $(host).find(document.activeElement).data('dt-idx'); + } + catch (e) {} + + attach( $(host).empty(), buttons ); + + if ( activeEl !== undefined ) { + $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); } } - } ); - - - - // Built in type detection. See model.ext.aTypes for information about - // what is required from this methods. - $.extend( DataTable.ext.type.detect, [ - // Plain numbers - first since V8 detects some plain numbers as dates - // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal ) ? 'num'+decimal : null; - }, - - // Dates (only those recognised by the browser's Date.parse) - function ( d, settings ) - { - // V8 tries _very_ hard to make a string passed into `Date.parse()` - // valid, so we need to use a regex to restrict date formats. Use a - // plug-in for anything other than ISO8601 style strings - if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { - return null; - } - var parsed = Date.parse(d); - return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; - }, - - // Formatted numbers - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; - }, - - // HTML numeric - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; - }, - - // HTML numeric, formatted - function ( d, settings ) + } + } ); + + + + // Built in type detection. See model.ext.aTypes for information about + // what is required from this methods. + $.extend( DataTable.ext.type.detect, [ + // Plain numbers - first since V8 detects some plain numbers as dates + // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _isNumber( d, decimal ) ? 'num'+decimal : null; + }, + + // Dates (only those recognised by the browser's Date.parse) + function ( d, settings ) + { + // V8 tries _very_ hard to make a string passed into `Date.parse()` + // valid, so we need to use a regex to restrict date formats. Use a + // plug-in for anything other than ISO8601 style strings + if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { + return null; + } + var parsed = Date.parse(d); + return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; + }, + + // Formatted numbers + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; + }, + + // HTML numeric + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; + }, + + // HTML numeric, formatted + function ( d, settings ) + { + var decimal = settings.oLanguage.sDecimal; + return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; + }, + + // HTML (this is strict checking - there must be html) + function ( d, settings ) + { + return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? + 'html' : null; + } + ] ); + + + + // Filter formatting functions. See model.ext.ofnSearch for information about + // what is required from these methods. + // + // Note that additional search methods are added for the html numbers and + // html formatted numbers by `_addNumericSort()` when we know what the decimal + // place is + + + $.extend( DataTable.ext.type.search, { + html: function ( data ) { + return _empty(data) ? + data : + typeof data === 'string' ? + data + .replace( _re_new_lines, " " ) + .replace( _re_html, "" ) : + ''; + }, + + string: function ( data ) { + return _empty(data) ? + data : + typeof data === 'string' ? + data.replace( _re_new_lines, " " ) : + data; + } + } ); + + + + var __numericReplace = function ( d, decimalPlace, re1, re2 ) { + if ( d !== 0 && (!d || d === '-') ) { + return -Infinity; + } + + // If a decimal place other than `.` is used, it needs to be given to the + // function so we can detect it and replace with a `.` which is the only + // decimal place Javascript recognises - it is not locale aware. + if ( decimalPlace ) { + d = _numToDecimal( d, decimalPlace ); + } + + if ( d.replace ) { + if ( re1 ) { + d = d.replace( re1, '' ); + } + + if ( re2 ) { + d = d.replace( re2, '' ); + } + } + + return d * 1; + }; + + + // Add the numeric 'deformatting' functions for sorting and search. This is done + // in a function to provide an easy ability for the language options to add + // additional methods if a non-period decimal place is used. + function _addNumericSort ( decimalPlace ) { + $.each( { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; + // Plain numbers + "num": function ( d ) { + return __numericReplace( d, decimalPlace ); + }, + + // Formatted numbers + "num-fmt": function ( d ) { + return __numericReplace( d, decimalPlace, _re_formatted_numeric ); + }, + + // HTML numeric + "html-num": function ( d ) { + return __numericReplace( d, decimalPlace, _re_html ); + }, + + // HTML numeric, formatted + "html-num-fmt": function ( d ) { + return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); + } }, - - // HTML (this is strict checking - there must be html) - function ( d, settings ) - { - return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? - 'html' : null; + function ( key, fn ) { + // Add the ordering method + _ext.type.order[ key+decimalPlace+'-pre' ] = fn; + + // For HTML types add a search formatter that will strip the HTML + if ( key.match(/^html\-/) ) { + _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; + } } - ] ); - - - - // Filter formatting functions. See model.ext.ofnSearch for information about - // what is required from these methods. - // - // Note that additional search methods are added for the html numbers and - // html formatted numbers by `_addNumericSort()` when we know what the decimal - // place is - - - $.extend( DataTable.ext.type.search, { - html: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data - .replace( _re_new_lines, " " ) - .replace( _re_html, "" ) : - ''; + ); + } + + + // Default sort methods + $.extend( _ext.type.order, { + // Dates + "date-pre": function ( d ) { + var ts = Date.parse( d ); + return isNaN(ts) ? -Infinity : ts; + }, + + // html + "html-pre": function ( a ) { + return _empty(a) ? + '' : + a.replace ? + a.replace( /<.*?>/g, "" ).toLowerCase() : + a+''; + }, + + // string + "string-pre": function ( a ) { + // This is a little complex, but faster than always calling toString, + // http://jsperf.com/tostring-v-check + return _empty(a) ? + '' : + typeof a === 'string' ? + a.toLowerCase() : + ! a.toString ? + '' : + a.toString(); + }, + + // string-asc and -desc are retained only for compatibility with the old + // sort methods + "string-asc": function ( x, y ) { + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }, + + "string-desc": function ( x, y ) { + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + } + } ); + + + // Numeric sorting types - order doesn't matter here + _addNumericSort( '' ); + + + $.extend( true, DataTable.ext.renderer, { + header: { + _: function ( settings, cell, column, classes ) { + // No additional mark-up required + // Attach a sort listener to update on sort - note that using the + // `DT` namespace will allow the event to be removed automatically + // on destroy, while the `dt` namespaced event is the one we are + // listening for + $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { + if ( settings !== ctx ) { // need to check this this is the host + return; // table, not a nested one + } + + var colIdx = column.idx; + + cell + .removeClass( + classes.sSortAsc +' '+ + classes.sSortDesc + ) + .addClass( columns[ colIdx ] == 'asc' ? + classes.sSortAsc : columns[ colIdx ] == 'desc' ? + classes.sSortDesc : + column.sSortingClass + ); + } ); }, - - string: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data.replace( _re_new_lines, " " ) : - data; + + jqueryui: function ( settings, cell, column, classes ) { + $('
') + .addClass( classes.sSortJUIWrapper ) + .append( cell.contents() ) + .append( $('') + .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) + ) + .appendTo( cell ); + + // Attach a sort listener to update on sort + $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { + if ( settings !== ctx ) { + return; + } + + var colIdx = column.idx; + + cell + .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) + .addClass( columns[ colIdx ] == 'asc' ? + classes.sSortAsc : columns[ colIdx ] == 'desc' ? + classes.sSortDesc : + column.sSortingClass + ); + + cell + .find( 'span.'+classes.sSortIcon ) + .removeClass( + classes.sSortJUIAsc +" "+ + classes.sSortJUIDesc +" "+ + classes.sSortJUI +" "+ + classes.sSortJUIAscAllowed +" "+ + classes.sSortJUIDescAllowed + ) + .addClass( columns[ colIdx ] == 'asc' ? + classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? + classes.sSortJUIDesc : + column.sSortingClassJUI + ); + } ); } - } ); - - + } + } ); + + /* + * Public helper functions. These aren't used internally by DataTables, or + * called by any of the options passed into DataTables, but they can be used + * externally by developers working with DataTables. They are helper functions + * to make working with DataTables a little bit easier. + */ + + var __htmlEscapeEntities = function ( d ) { + if (Array.isArray(d)) { + d = d.join(','); + } + + return typeof d === 'string' ? + d + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') : + d; + }; + + // Common logic for moment, luxon or a date action + function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) { + if (window.moment) { + return dt[momentFn]( arg1 ); + } + else if (window.luxon) { + return dt[luxonFn]( arg1 ); + } - var __numericReplace = function ( d, decimalPlace, re1, re2 ) { - if ( d !== 0 && (!d || d === '-') ) { - return -Infinity; + return dateFn ? dt[dateFn]( arg1 ) : dt; + } + + + var __mlWarning = false; + function __mldObj (d, format, locale) { + var dt; + + if (window.moment) { + dt = window.moment.utc( d, format, locale, true ); + + if (! dt.isValid()) { + return null; } - - // If a decimal place other than `.` is used, it needs to be given to the - // function so we can detect it and replace with a `.` which is the only - // decimal place Javascript recognises - it is not locale aware. - if ( decimalPlace ) { - d = _numToDecimal( d, decimalPlace ); + } + else if (window.luxon) { + dt = format + ? window.luxon.DateTime.fromFormat( d, format ) + : window.luxon.DateTime.fromISO( d ); + + if (! dt.isValid) { + return null; } - - if ( d.replace ) { - if ( re1 ) { - d = d.replace( re1, '' ); - } - - if ( re2 ) { - d = d.replace( re2, '' ); - } + + dt.setLocale(locale); + } + else if (! format) { + // No format given, must be ISO + dt = new Date(d); + } + else { + if (! __mlWarning) { + alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17'); } - - return d * 1; - }; - - - // Add the numeric 'deformatting' functions for sorting and search. This is done - // in a function to provide an easy ability for the language options to add - // additional methods if a non-period decimal place is used. - function _addNumericSort ( decimalPlace ) { - $.each( - { - // Plain numbers - "num": function ( d ) { - return __numericReplace( d, decimalPlace ); - }, - - // Formatted numbers - "num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_formatted_numeric ); - }, - - // HTML numeric - "html-num": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html ); - }, - - // HTML numeric, formatted - "html-num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); + + __mlWarning = true; + } + + return dt; + } + + // Wrapper for date, datetime and time which all operate the same way with the exception of + // the output string for auto locale support + function __mlHelper (localeString) { + return function ( from, to, locale, def ) { + // Luxon and Moment support + // Argument shifting + if ( arguments.length === 0 ) { + locale = 'en'; + to = null; // means toLocaleString + from = null; // means iso8601 + } + else if ( arguments.length === 1 ) { + locale = 'en'; + to = from; + from = null; + } + else if ( arguments.length === 2 ) { + locale = to; + to = from; + from = null; + } + + var typeName = 'datetime-' + to; + + // Add type detection and sorting specific to this date format - we need to be able to identify + // date type columns as such, rather than as numbers in extensions. Hence the need for this. + if (! DataTable.ext.type.order[typeName]) { + // The renderer will give the value to type detect as the type! + DataTable.ext.type.detect.unshift(function (d) { + return d === typeName ? typeName : false; + }); + + // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a + // `valueOf` which gives milliseconds epoch + DataTable.ext.type.order[typeName + '-asc'] = function (a, b) { + var x = a.valueOf(); + var y = b.valueOf(); + + return x === y + ? 0 + : x < y + ? -1 + : 1; + } + + DataTable.ext.type.order[typeName + '-desc'] = function (a, b) { + var x = a.valueOf(); + var y = b.valueOf(); + + return x === y + ? 0 + : x > y + ? -1 + : 1; + } + } + + return function ( d, type ) { + // Allow for a default value + if (d === null || d === undefined) { + if (def === '--now') { + // We treat everything as UTC further down, so no changes are + // made, as such need to get the local date / time as if it were + // UTC + var local = new Date(); + d = new Date( Date.UTC( + local.getFullYear(), local.getMonth(), local.getDate(), + local.getHours(), local.getMinutes(), local.getSeconds() + ) ); } - }, - function ( key, fn ) { - // Add the ordering method - _ext.type.order[ key+decimalPlace+'-pre' ] = fn; - - // For HTML types add a search formatter that will strip the HTML - if ( key.match(/^html\-/) ) { - _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; + else { + d = ''; } } - ); + + if (type === 'type') { + // Typing uses the type name for fast matching + return typeName; + } + + if (d === '') { + return type !== 'sort' + ? '' + : __mldObj('0000-01-01 00:00:00', null, locale); + } + + // Shortcut. If `from` and `to` are the same, we are using the renderer to + // format for ordering, not display - its already in the display format. + if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) { + return d; + } + + var dt = __mldObj(d, from, locale); + + if (dt === null) { + return d; + } + + if (type === 'sort') { + return dt; + } + + var formatted = to === null + ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]() + : __mld(dt, 'format', 'toFormat', 'toISOString', to); + + // XSS protection + return type === 'display' ? + __htmlEscapeEntities( formatted ) : + formatted; + }; } + } + + // Based on locale, determine standard number formatting + // Fallback for legacy browsers is US English + var __thousands = ','; + var __decimal = '.'; + + if (Intl) { + try { + var num = new Intl.NumberFormat().formatToParts(100000.1); - - // Default sort methods - $.extend( _ext.type.order, { - // Dates - "date-pre": function ( d ) { - var ts = Date.parse( d ); - return isNaN(ts) ? -Infinity : ts; - }, - - // html - "html-pre": function ( a ) { - return _empty(a) ? - '' : - a.replace ? - a.replace( /<.*?>/g, "" ).toLowerCase() : - a+''; - }, - - // string - "string-pre": function ( a ) { - // This is a little complex, but faster than always calling toString, - // http://jsperf.com/tostring-v-check - return _empty(a) ? - '' : - typeof a === 'string' ? - a.toLowerCase() : - ! a.toString ? - '' : - a.toString(); - }, - - // string-asc and -desc are retained only for compatibility with the old - // sort methods - "string-asc": function ( x, y ) { - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }, - - "string-desc": function ( x, y ) { - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - } - } ); - - - // Numeric sorting types - order doesn't matter here - _addNumericSort( '' ); - - - $.extend( true, DataTable.ext.renderer, { - header: { - _: function ( settings, cell, column, classes ) { - // No additional mark-up required - // Attach a sort listener to update on sort - note that using the - // `DT` namespace will allow the event to be removed automatically - // on destroy, while the `dt` namespaced event is the one we are - // listening for - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { // need to check this this is the host - return; // table, not a nested one - } - - var colIdx = column.idx; - - cell - .removeClass( - classes.sSortAsc +' '+ - classes.sSortDesc - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - } ); - }, - - jqueryui: function ( settings, cell, column, classes ) { - $('
') - .addClass( classes.sSortJUIWrapper ) - .append( cell.contents() ) - .append( $('') - .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) - ) - .appendTo( cell ); - - // Attach a sort listener to update on sort - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { - return; - } - - var colIdx = column.idx; - - cell - .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - - cell - .find( 'span.'+classes.sSortIcon ) - .removeClass( - classes.sSortJUIAsc +" "+ - classes.sSortJUIDesc +" "+ - classes.sSortJUI +" "+ - classes.sSortJUIAscAllowed +" "+ - classes.sSortJUIDescAllowed - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? - classes.sSortJUIDesc : - column.sSortingClassJUI - ); - } ); + for (var i=0 ; i/g, '>') - .replace(/"/g, '"') : - d; - }; - - /** - * Helpers for `columns.render`. - * - * The options defined here can be used with the `columns.render` initialisation - * option to provide a display renderer. The following functions are defined: - * - * * `number` - Will format numeric data (defined by `columns.data`) for - * display, retaining the original unformatted data for sorting and filtering. - * It takes 5 parameters: - * * `string` - Thousands grouping separator - * * `string` - Decimal point indicator - * * `integer` - Number of decimal points to show - * * `string` (optional) - Prefix. - * * `string` (optional) - Postfix (/suffix). - * * `text` - Escape HTML to help prevent XSS attacks. It has no optional - * parameters. - * - * @example - * // Column definition using the number renderer - * { - * data: "salary", - * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) - * } - * - * @namespace - */ - DataTable.render = { - number: function ( thousands, decimal, precision, prefix, postfix ) { - return { - display: function ( d ) { - if ( typeof d !== 'number' && typeof d !== 'string' ) { - return d; - } - - var negative = d < 0 ? '-' : ''; - var flo = parseFloat( d ); - - // If NaN then there isn't much formatting that we can do - just - // return immediately, escaping any HTML (this was supposed to - // be a number after all) - if ( isNaN( flo ) ) { - return __htmlEscapeEntities( d ); - } - - flo = flo.toFixed( precision ); - d = Math.abs( flo ); - - var intPart = parseInt( d, 10 ); - var floatPart = precision ? - decimal+(d - intPart).toFixed( precision ).substring( 2 ): - ''; - - // If zero, then can't have a negative prefix - if (intPart === 0 && parseFloat(floatPart) === 0) { - negative = ''; - } - - return negative + (prefix||'') + - intPart.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, thousands - ) + - floatPart + - (postfix||''); - } - }; - }, - - text: function () { - return { - display: __htmlEscapeEntities, - filter: __htmlEscapeEntities - }; + } + } + + /** + * Helpers for `columns.render`. + * + * The options defined here can be used with the `columns.render` initialisation + * option to provide a display renderer. The following functions are defined: + * + * * `number` - Will format numeric data (defined by `columns.data`) for + * display, retaining the original unformatted data for sorting and filtering. + * It takes 5 parameters: + * * `string` - Thousands grouping separator + * * `string` - Decimal point indicator + * * `integer` - Number of decimal points to show + * * `string` (optional) - Prefix. + * * `string` (optional) - Postfix (/suffix). + * * `text` - Escape HTML to help prevent XSS attacks. It has no optional + * parameters. + * + * @example + * // Column definition using the number renderer + * { + * data: "salary", + * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) + * } + * + * @namespace + */ + DataTable.render = { + date: __mlHelper('toLocaleDateString'), + datetime: __mlHelper('toLocaleString'), + time: __mlHelper('toLocaleTimeString'), + number: function ( thousands, decimal, precision, prefix, postfix ) { + // Auto locale detection + if (thousands === null || thousands === undefined) { + thousands = __thousands; } - }; - - - /* - * This is really a good bit rubbish this method of exposing the internal methods - * publicly... - To be fixed in 2.0 using methods on the prototype - */ - - - /** - * Create a wrapper function for exporting an internal functions to an external API. - * @param {string} fn API function name - * @returns {function} wrapped function - * @memberof DataTable#internal - */ - function _fnExternApiFunc (fn) - { - return function() { - var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( - Array.prototype.slice.call(arguments) - ); - return DataTable.ext.internal[fn].apply( this, args ); + + if (decimal === null || decimal === undefined) { + decimal = __decimal; + } + + return { + display: function ( d ) { + if ( typeof d !== 'number' && typeof d !== 'string' ) { + return d; + } + + if (d === '' || d === null) { + return d; + } + + var negative = d < 0 ? '-' : ''; + var flo = parseFloat( d ); + + // If NaN then there isn't much formatting that we can do - just + // return immediately, escaping any HTML (this was supposed to + // be a number after all) + if ( isNaN( flo ) ) { + return __htmlEscapeEntities( d ); + } + + flo = flo.toFixed( precision ); + d = Math.abs( flo ); + + var intPart = parseInt( d, 10 ); + var floatPart = precision ? + decimal+(d - intPart).toFixed( precision ).substring( 2 ): + ''; + + // If zero, then can't have a negative prefix + if (intPart === 0 && parseFloat(floatPart) === 0) { + negative = ''; + } + + return negative + (prefix||'') + + intPart.toString().replace( + /\B(?=(\d{3})+(?!\d))/g, thousands + ) + + floatPart + + (postfix||''); + } + }; + }, + + text: function () { + return { + display: __htmlEscapeEntities, + filter: __htmlEscapeEntities }; } - - - /** - * Reference to internal functions for use by plug-in developers. Note that - * these methods are references to internal functions and are considered to be - * private. If you use these methods, be aware that they are liable to change - * between versions. - * @namespace - */ - $.extend( DataTable.ext.internal, { - _fnExternApiFunc: _fnExternApiFunc, - _fnBuildAjax: _fnBuildAjax, - _fnAjaxUpdate: _fnAjaxUpdate, - _fnAjaxParameters: _fnAjaxParameters, - _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, - _fnAjaxDataSrc: _fnAjaxDataSrc, - _fnAddColumn: _fnAddColumn, - _fnColumnOptions: _fnColumnOptions, - _fnAdjustColumnSizing: _fnAdjustColumnSizing, - _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, - _fnColumnIndexToVisible: _fnColumnIndexToVisible, - _fnVisbleColumns: _fnVisbleColumns, - _fnGetColumns: _fnGetColumns, - _fnColumnTypes: _fnColumnTypes, - _fnApplyColumnDefs: _fnApplyColumnDefs, - _fnHungarianMap: _fnHungarianMap, - _fnCamelToHungarian: _fnCamelToHungarian, - _fnLanguageCompat: _fnLanguageCompat, - _fnBrowserDetect: _fnBrowserDetect, - _fnAddData: _fnAddData, - _fnAddTr: _fnAddTr, - _fnNodeToDataIndex: _fnNodeToDataIndex, - _fnNodeToColumnIndex: _fnNodeToColumnIndex, - _fnGetCellData: _fnGetCellData, - _fnSetCellData: _fnSetCellData, - _fnSplitObjNotation: _fnSplitObjNotation, - _fnGetObjectDataFn: _fnGetObjectDataFn, - _fnSetObjectDataFn: _fnSetObjectDataFn, - _fnGetDataMaster: _fnGetDataMaster, - _fnClearTable: _fnClearTable, - _fnDeleteIndex: _fnDeleteIndex, - _fnInvalidate: _fnInvalidate, - _fnGetRowElements: _fnGetRowElements, - _fnCreateTr: _fnCreateTr, - _fnBuildHead: _fnBuildHead, - _fnDrawHead: _fnDrawHead, - _fnDraw: _fnDraw, - _fnReDraw: _fnReDraw, - _fnAddOptionsHtml: _fnAddOptionsHtml, - _fnDetectHeader: _fnDetectHeader, - _fnGetUniqueThs: _fnGetUniqueThs, - _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, - _fnFilterComplete: _fnFilterComplete, - _fnFilterCustom: _fnFilterCustom, - _fnFilterColumn: _fnFilterColumn, - _fnFilter: _fnFilter, - _fnFilterCreateSearch: _fnFilterCreateSearch, - _fnEscapeRegex: _fnEscapeRegex, - _fnFilterData: _fnFilterData, - _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, - _fnUpdateInfo: _fnUpdateInfo, - _fnInfoMacros: _fnInfoMacros, - _fnInitialise: _fnInitialise, - _fnInitComplete: _fnInitComplete, - _fnLengthChange: _fnLengthChange, - _fnFeatureHtmlLength: _fnFeatureHtmlLength, - _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, - _fnPageChange: _fnPageChange, - _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, - _fnProcessingDisplay: _fnProcessingDisplay, - _fnFeatureHtmlTable: _fnFeatureHtmlTable, - _fnScrollDraw: _fnScrollDraw, - _fnApplyToChildren: _fnApplyToChildren, - _fnCalculateColumnWidths: _fnCalculateColumnWidths, - _fnThrottle: _fnThrottle, - _fnConvertToWidth: _fnConvertToWidth, - _fnGetWidestNode: _fnGetWidestNode, - _fnGetMaxLenString: _fnGetMaxLenString, - _fnStringToCss: _fnStringToCss, - _fnSortFlatten: _fnSortFlatten, - _fnSort: _fnSort, - _fnSortAria: _fnSortAria, - _fnSortListener: _fnSortListener, - _fnSortAttachListener: _fnSortAttachListener, - _fnSortingClasses: _fnSortingClasses, - _fnSortData: _fnSortData, - _fnSaveState: _fnSaveState, - _fnLoadState: _fnLoadState, - _fnImplementState: _fnImplementState, - _fnSettingsFromNode: _fnSettingsFromNode, - _fnLog: _fnLog, - _fnMap: _fnMap, - _fnBindAction: _fnBindAction, - _fnCallbackReg: _fnCallbackReg, - _fnCallbackFire: _fnCallbackFire, - _fnLengthOverflow: _fnLengthOverflow, - _fnRenderer: _fnRenderer, - _fnDataSource: _fnDataSource, - _fnRowAttributes: _fnRowAttributes, - _fnExtend: _fnExtend, - _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant - // in 1.10, so this dead-end function is - // added to prevent errors - } ); - - - // jQuery access - $.fn.dataTable = DataTable; - - // Provide access to the host jQuery object (circular reference) - DataTable.$ = $; - - // Legacy aliases - $.fn.dataTableSettings = DataTable.settings; - $.fn.dataTableExt = DataTable.ext; - - // With a capital `D` we return a DataTables API instance rather than a - // jQuery object - $.fn.DataTable = function ( opts ) { - return $(this).dataTable( opts ).api(); + }; + + + /* + * This is really a good bit rubbish this method of exposing the internal methods + * publicly... - To be fixed in 2.0 using methods on the prototype + */ + + + /** + * Create a wrapper function for exporting an internal functions to an external API. + * @param {string} fn API function name + * @returns {function} wrapped function + * @memberof DataTable#internal + */ + function _fnExternApiFunc (fn) + { + return function() { + var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( + Array.prototype.slice.call(arguments) + ); + return DataTable.ext.internal[fn].apply( this, args ); }; - - // All properties that are available to $.fn.dataTable should also be - // available on $.fn.DataTable - $.each( DataTable, function ( prop, val ) { - $.fn.DataTable[ prop ] = val; - } ); + } + + + /** + * Reference to internal functions for use by plug-in developers. Note that + * these methods are references to internal functions and are considered to be + * private. If you use these methods, be aware that they are liable to change + * between versions. + * @namespace + */ + $.extend( DataTable.ext.internal, { + _fnExternApiFunc: _fnExternApiFunc, + _fnBuildAjax: _fnBuildAjax, + _fnAjaxUpdate: _fnAjaxUpdate, + _fnAjaxParameters: _fnAjaxParameters, + _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, + _fnAjaxDataSrc: _fnAjaxDataSrc, + _fnAddColumn: _fnAddColumn, + _fnColumnOptions: _fnColumnOptions, + _fnAdjustColumnSizing: _fnAdjustColumnSizing, + _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, + _fnColumnIndexToVisible: _fnColumnIndexToVisible, + _fnVisbleColumns: _fnVisbleColumns, + _fnGetColumns: _fnGetColumns, + _fnColumnTypes: _fnColumnTypes, + _fnApplyColumnDefs: _fnApplyColumnDefs, + _fnHungarianMap: _fnHungarianMap, + _fnCamelToHungarian: _fnCamelToHungarian, + _fnLanguageCompat: _fnLanguageCompat, + _fnBrowserDetect: _fnBrowserDetect, + _fnAddData: _fnAddData, + _fnAddTr: _fnAddTr, + _fnNodeToDataIndex: _fnNodeToDataIndex, + _fnNodeToColumnIndex: _fnNodeToColumnIndex, + _fnGetCellData: _fnGetCellData, + _fnSetCellData: _fnSetCellData, + _fnSplitObjNotation: _fnSplitObjNotation, + _fnGetObjectDataFn: _fnGetObjectDataFn, + _fnSetObjectDataFn: _fnSetObjectDataFn, + _fnGetDataMaster: _fnGetDataMaster, + _fnClearTable: _fnClearTable, + _fnDeleteIndex: _fnDeleteIndex, + _fnInvalidate: _fnInvalidate, + _fnGetRowElements: _fnGetRowElements, + _fnCreateTr: _fnCreateTr, + _fnBuildHead: _fnBuildHead, + _fnDrawHead: _fnDrawHead, + _fnDraw: _fnDraw, + _fnReDraw: _fnReDraw, + _fnAddOptionsHtml: _fnAddOptionsHtml, + _fnDetectHeader: _fnDetectHeader, + _fnGetUniqueThs: _fnGetUniqueThs, + _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, + _fnFilterComplete: _fnFilterComplete, + _fnFilterCustom: _fnFilterCustom, + _fnFilterColumn: _fnFilterColumn, + _fnFilter: _fnFilter, + _fnFilterCreateSearch: _fnFilterCreateSearch, + _fnEscapeRegex: _fnEscapeRegex, + _fnFilterData: _fnFilterData, + _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, + _fnUpdateInfo: _fnUpdateInfo, + _fnInfoMacros: _fnInfoMacros, + _fnInitialise: _fnInitialise, + _fnInitComplete: _fnInitComplete, + _fnLengthChange: _fnLengthChange, + _fnFeatureHtmlLength: _fnFeatureHtmlLength, + _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, + _fnPageChange: _fnPageChange, + _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, + _fnProcessingDisplay: _fnProcessingDisplay, + _fnFeatureHtmlTable: _fnFeatureHtmlTable, + _fnScrollDraw: _fnScrollDraw, + _fnApplyToChildren: _fnApplyToChildren, + _fnCalculateColumnWidths: _fnCalculateColumnWidths, + _fnThrottle: _fnThrottle, + _fnConvertToWidth: _fnConvertToWidth, + _fnGetWidestNode: _fnGetWidestNode, + _fnGetMaxLenString: _fnGetMaxLenString, + _fnStringToCss: _fnStringToCss, + _fnSortFlatten: _fnSortFlatten, + _fnSort: _fnSort, + _fnSortAria: _fnSortAria, + _fnSortListener: _fnSortListener, + _fnSortAttachListener: _fnSortAttachListener, + _fnSortingClasses: _fnSortingClasses, + _fnSortData: _fnSortData, + _fnSaveState: _fnSaveState, + _fnLoadState: _fnLoadState, + _fnImplementState: _fnImplementState, + _fnSettingsFromNode: _fnSettingsFromNode, + _fnLog: _fnLog, + _fnMap: _fnMap, + _fnBindAction: _fnBindAction, + _fnCallbackReg: _fnCallbackReg, + _fnCallbackFire: _fnCallbackFire, + _fnLengthOverflow: _fnLengthOverflow, + _fnRenderer: _fnRenderer, + _fnDataSource: _fnDataSource, + _fnRowAttributes: _fnRowAttributes, + _fnExtend: _fnExtend, + _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant + // in 1.10, so this dead-end function is + // added to prevent errors + } ); + + + // jQuery access + $.fn.dataTable = DataTable; + + // Provide access to the host jQuery object (circular reference) + DataTable.$ = $; + + // Legacy aliases + $.fn.dataTableSettings = DataTable.settings; + $.fn.dataTableExt = DataTable.ext; + + // With a capital `D` we return a DataTables API instance rather than a + // jQuery object + $.fn.DataTable = function ( opts ) { + return $(this).dataTable( opts ).api(); + }; + + // All properties that are available to $.fn.dataTable should also be + // available on $.fn.DataTable + $.each( DataTable, function ( prop, val ) { + $.fn.DataTable[ prop ] = val; + } ); - return DataTable; + return DataTable; })); diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs index d385cdcd..c63be3e1 100644 --- a/src/static/templates/admin/base.hbs +++ b/src/static/templates/admin/base.hbs @@ -20,8 +20,15 @@ width: auto; margin: -5px 0 0 0; } + /* Special alert-row class to use Bootstrap v5.2+ variable colors */ + .alert-row { + --bs-alert-border: 1px solid var(--bs-alert-border-color); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + } - + - + diff --git a/src/static/templates/admin/settings.hbs b/src/static/templates/admin/settings.hbs index 454891ce..04142785 100644 --- a/src/static/templates/admin/settings.hbs +++ b/src/static/templates/admin/settings.hbs @@ -5,7 +5,7 @@
NOTE: The settings here override the environment variables. Once saved, it's recommended to stop setting them to avoid confusion.
This does not apply to the read-only section, which can only be set via environment variables.
- Settings which are overridden are shown with double underscores. + Settings which are overridden are shown with a yellow colored background.
@@ -16,7 +16,7 @@
{{#each elements}} {{#if editable}} -
+
{{#case type "text" "number" "password"}}
@@ -71,16 +71,25 @@ {{#each config}} {{#each elements}} {{#unless editable}} -
+
{{#case type "text" "number" "password"}}
- - {{#case type "password"}} + {{!-- + Also set the database_url input as password here. + If we would set it to password in config.rs it will not be character masked for the support string. + And sometimes this is more useful for providing support than just 3 asterisk. + --}} + {{#if (eq name "database_url")}} + - {{/case}} + {{else}} + + {{#case type "password"}} + + {{/case}} + {{/if}}
{{/case}} @@ -134,7 +143,9 @@ } .is-overridden-true { - text-decoration: underline double; + --bs-alert-color: #664d03; + --bs-alert-bg: #fff3cd; + --bs-alert-border-color: #ffecb5; } @@ -238,19 +249,45 @@ return Array.from(form).some(el => 'origValue' in el.dataset && ( el.dataset.origValue !== el.value)); } - // Trigger Form Change Detection + // This function will prevent submitting a from when someone presses enter. + function preventFormSubmitOnEnter(form) { + form.onkeypress = function(e) { + let key = e.charCode || e.keyCode || 0; + if (key == 13) { + e.preventDefault(); + } + } + } + + // Initialize Form Change Detection const config_form = document.getElementById('config-form'); initChangeDetection(config_form); + // Prevent enter to submitting the form and save the config. + // Users need to really click on save, this also to prevent accidental submits. + preventFormSubmitOnEnter(config_form); + + // This function will hook into the smtp-test-email input field and will call the smtpTest() function when enter is pressed. + function submitTestEmailOnEnter() { + const smtp_test_email_input = document.getElementById('smtp-test-email'); + smtp_test_email_input.onkeypress = function(e) { + let key = e.charCode || e.keyCode || 0; + if (key == 13) { + e.preventDefault(); + smtpTest(); + } + } + } + submitTestEmailOnEnter(); // Colorize some settings which are high risk - const risk_items = document.getElementsByClassName('col-form-label'); - function colorRiskSettings(risk_el) { - Array.from(risk_el).forEach((el) => { + function colorRiskSettings() { + const risk_items = document.getElementsByClassName('col-form-label'); + Array.from(risk_items).forEach((el) => { if (el.innerText.toLowerCase().includes('risks') ) { el.parentElement.className += ' alert-danger' } }); } - colorRiskSettings(risk_items); + colorRiskSettings(); diff --git a/src/util.rs b/src/util.rs index 677e5860..55208aeb 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,21 +29,48 @@ impl Fairing for AppHeaders { } } - async fn on_response<'r>(&self, _req: &'r Request<'_>, res: &mut Response<'r>) { - res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(self \"https://haveibeenpwned.com\" \"https://2fa.directory\"), usb=(), vr=()"); + async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) { + res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"); res.set_raw_header("Referrer-Policy", "same-origin"); - res.set_raw_header("X-Frame-Options", "SAMEORIGIN"); res.set_raw_header("X-Content-Type-Options", "nosniff"); // Obsolete in modern browsers, unsafe (XS-Leak), and largely replaced by CSP res.set_raw_header("X-XSS-Protection", "0"); - let csp = format!( - // Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb - // Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US - // Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/ - "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};", - CONFIG.allowed_iframe_ancestors() - ); - res.set_raw_header("Content-Security-Policy", csp); + + let req_uri_path = req.uri().path(); + + // Check if we are requesting an admin page, if so, allow unsafe-inline for scripts. + // TODO: In the future maybe we need to see if we can generate a sha256 hash or have no scripts inline at all. + let admin_path = format!("{}/admin", CONFIG.domain_path()); + let mut script_src = ""; + if req_uri_path.starts_with(admin_path.as_str()) { + script_src = " 'unsafe-inline'"; + } + + // Do not send the Content-Security-Policy (CSP) Header and X-Frame-Options for the *-connector.html files. + // This can cause issues when some MFA requests needs to open a popup or page within the clients like WebAuthn, or Duo. + // This is the same behaviour as upstream Bitwarden. + if !req_uri_path.ends_with("connector.html") { + let csp = format!( + // Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb + // Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US + // Firefox Browser Add-ons: https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/ + "default-src 'self'; \ + script-src 'self'{script_src}; \ + style-src 'self' 'unsafe-inline'; \ + img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com; \ + child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \ + frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \ + connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/; \ + object-src 'self' blob:; \ + frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};", + CONFIG.allowed_iframe_ancestors() + ); + res.set_raw_header("Content-Security-Policy", csp); + res.set_raw_header("X-Frame-Options", "SAMEORIGIN"); + } else { + // It looks like this header get's set somewhere else also, make sure this is not sent for these files, it will cause MFA issues. + res.remove_header("X-Frame-Options"); + } // Disable cache unless otherwise specified if !res.headers().contains("cache-control") {