├── .gitignore ├── LICENSE ├── README.md ├── docker-compose.yml ├── init ├── apache │ └── Dockerfile └── mysql │ └── init.sql └── src ├── .htaccess ├── classes ├── Controller.class.php ├── MunTemplate.class.php └── User.class.php ├── config ├── .htaccess ├── env.php └── function.php ├── index.php ├── static ├── css │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ └── bootstrap.min.css.map └── js │ └── bootstrap.js └── templates ├── footer.html ├── header.html ├── login.html ├── main.html ├── register.html └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Siwoo Mun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-always 2 | 3 | This is an ultra-simple skeleton php application for pure PHP users. 4 | The currently implemented classes are sample codes that implement basic login and signup feature. If you need a new feature, implement a new class and add it to `classes/Controller.class.php` to easily implement and manage your web application with MVC patterns. I've deployed many projects based on code from this repository. I'll be implementing some pretty good router class soon. Long live pure PHP. 5 | 6 | Classics never die. 7 | 8 | ## Spec 9 | \- [debian:11-slim](https://hub.docker.com/layers/library/debian/11-slim/images/sha256-139a42fa3bde3e5bad6ae912aaaf2103565558a7a73afe6ce6ceed6e46a6e519) 10 | \- [php8.1-apache](https://hub.docker.com/layers/library/php/8.1-apache/images/sha256-faf54b7511e54097fb5395aa8d03d50dae3f0010a21a5655030ff6c2a13dab17?context=explore) 11 | \- mariadb-server (latest, as of your build time) 12 | 13 | ## Usage 14 | ``` 15 | apt install docker docker-compose 16 | 17 | git clone https://github.com/munsiwoo/php-always 18 | cd ./php-always 19 | 20 | docker-compose up -d --build 21 | 22 | curl http://localhost:8080/ 23 | (default port is 8080) 24 | ``` 25 | 26 | ## History 27 | \- Sep 11, 2018: The `simple-mvc-php` repository has been made public. 28 | \- Mar 14, 2023: The project has been restructured. 29 | (The repository name has been changed from `simple-mvc-php` to `php-always`) 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | www: 5 | build: ./init/apache 6 | container_name: phpalways_app 7 | volumes: 8 | - ./src:/var/www/html 9 | ports: 10 | - "8080:80" 11 | environment: 12 | USE_DB: 1 13 | MYSQL_HOST: db 14 | MYSQL_USER: admin 15 | MYSQL_PASS: password 16 | MYSQL_DB: phpalways 17 | PWD_SALT: dddd18766b286daf538dd7cf774d0e22 18 | links: 19 | - db 20 | 21 | db: 22 | image: mariadb:latest 23 | restart: always 24 | container_name: phpalways_db 25 | volumes: 26 | - ./init/mysql:/docker-entrypoint-initdb.d 27 | environment: 28 | - MYSQL_ROOT_PASSWORD=password 29 | - MYSQL_USER=admin 30 | - MYSQL_PASSWORD=password 31 | - MYSQL_DATABASE=phpalways 32 | - TZ=Asia/Seoul 33 | command: 34 | - --character-set-server=utf8 35 | - --collation-server=utf8_general_ci -------------------------------------------------------------------------------- /init/apache/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-apache 2 | 3 | RUN sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf 4 | RUN a2enmod rewrite 5 | 6 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 7 | 8 | RUN sed -i 's/;extension=pdo_mysql/extension=pdo_mysql/g' $PHP_INI_DIR/php.ini 9 | RUN sed -i 's/;extension=openssl/extension=openssl/g' $PHP_INI_DIR/php.ini 10 | RUN sed -i 's/;extension=mbstring/extension=mbstring/g' $PHP_INI_DIR/php.ini 11 | RUN sed -i 's/;extension=curl/extension=curl/g' $PHP_INI_DIR/php.ini 12 | 13 | # Cannot display PHP version in HTTP response 14 | RUN sed -i 's/expose_php = On/expose_php = Off/g' $PHP_INI_DIR/php.ini 15 | 16 | # For debug 17 | RUN sed -i 's/display_errors = Off/display_errors = On/g' $PHP_INI_DIR/php.ini 18 | 19 | # For file upload 20 | RUN sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 10M/g' $PHP_INI_DIR/php.ini 21 | RUN sed -i 's/post_max_size = 8M/post_max_size = 12M/g' $PHP_INI_DIR/php.ini 22 | RUN sed -i 's/max_input_time = 60/max_input_time = 120/g' $PHP_INI_DIR/php.ini 23 | 24 | RUN apt update && apt install -y libzip-dev openssl libssl-dev libcurl4-openssl-dev vim 25 | 26 | RUN docker-php-ext-install mysqli pdo pdo_mysql 27 | RUN docker-php-ext-install zip 28 | RUN docker-php-ext-enable mysqli pdo pdo_mysql 29 | 30 | ENV TZ=Asia/Seoul 31 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -------------------------------------------------------------------------------- /init/mysql/init.sql: -------------------------------------------------------------------------------- 1 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 2 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 3 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 4 | /*!40101 SET NAMES utf8mb4 */; 5 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 6 | /*!40103 SET TIME_ZONE='+00:00' */; 7 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 8 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 9 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 10 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 11 | 12 | CREATE TABLE `users`( 13 | `userid` VARCHAR(64) BINARY NOT NULL, 14 | `password` VARCHAR(64) NOT NULL, 15 | `create_at` DATETIME NOT NULL, 16 | `is_admin` TINYINT NOT NULL DEFAULT 0, 17 | PRIMARY KEY (`userid`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -------------------------------------------------------------------------------- /src/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteRule "^static($|/.*)" - [L] 3 | RewriteRule "^(.*)$" "index.php" [QSA,NC,L] -------------------------------------------------------------------------------- /src/classes/Controller.class.php: -------------------------------------------------------------------------------- 1 | method = strtoupper($_SERVER['REQUEST_METHOD']); 8 | $this->request_uri = rawurldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); 9 | 10 | if(isset($_SERVER['CONTENT_TYPE'])){ 11 | if(strpos($_SERVER['CONTENT_TYPE'], 'application/json') === 0) 12 | $this->body = json_decode(file_get_contents('php://input'), true); 13 | if(strpos($_SERVER['CONTENT_TYPE'], 'application/x-www-form-urlencoded') === 0) 14 | $this->body = $_POST; 15 | if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') === 0){ // file upload 16 | $this->body = $_POST; 17 | $this->files = $_FILES; 18 | } 19 | } else { 20 | $this->body = json_decode(file_get_contents('php://input'), true); // default json 21 | } 22 | $this->route(); 23 | } 24 | function route(){ 25 | $User = new User(); 26 | $MunTemplate = new MunTemplate(TEMPLATE_DIR); 27 | 28 | switch($this->request_uri){ 29 | case '/': 30 | check_login(true); 31 | $MunTemplate->render_template('main.html'); 32 | break; 33 | 34 | case '/register': 35 | $MunTemplate->render_template('register.html'); 36 | break; 37 | 38 | case '/login': 39 | $MunTemplate->render_template('login.html'); 40 | break; 41 | 42 | case '/api/v1/login': // User sign in 43 | allow_method(['POST']); 44 | echo $User->login($this->body); 45 | break; 46 | 47 | case '/api/v1/register': // User sign up 48 | allow_method(['POST']); 49 | echo $User->register($this->body); 50 | break; 51 | 52 | case '/robots.txt': 53 | header('Content-Type: text/plain'); 54 | $MunTemplate->render_template('robots.txt'); 55 | break; 56 | 57 | default : 58 | header('HTTP/1.1 404 Not Found'); 59 | echo 'Not found'; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/classes/MunTemplate.class.php: -------------------------------------------------------------------------------- 1 | template_path = substr($template_path, 0, -1); 8 | else 9 | $this->template_path = $template_path; 10 | } 11 | 12 | private function process_import($html){ 13 | $parse_regexp = "/\@import(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 14 | preg_match_all($parse_regexp, $html, $matches); 15 | 16 | foreach($matches[2] as $filename){ 17 | $import_file = $this->template_path.'/'.trim($filename); 18 | if(!file_exists($import_file) && $this->debug){ 19 | throw new Exception("Muntemplate Error: file doesn't exist. ({$import_file})"); 20 | exit; 21 | } 22 | $filename = str_replace('.', '[.]', $filename); 23 | $replace_regexp = "/\@import(?:\s+)?(?\(((?:[^()]|(?&mun))*){$filename}\s*\))/mis"; 24 | $file_contents = file_get_contents($import_file); 25 | $html = preg_replace($replace_regexp, $file_contents, $html); 26 | } 27 | return $html; 28 | } 29 | private function process_var($html){ 30 | $parse_regexp = "/\@var(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 31 | $replace_code = ''; 32 | $retval = preg_replace($parse_regexp, $replace_code, $html); 33 | 34 | return $retval; 35 | } 36 | private function process_for($html){ 37 | $start_regexp = "/\@mun\s+for(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 38 | $end_regexp = "/\@endfor(?:\s+)?$/mis"; 39 | 40 | $html = preg_replace($start_regexp, '', $html); 41 | $retval = preg_replace($end_regexp, '', $html); 42 | 43 | return $retval; 44 | } 45 | private function process_foreach($html){ 46 | $start_regexp = "/\@mun\s+foreach(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 47 | $end_regexp = "/\@endforeach(?:\s+)?$/mis"; 48 | 49 | $html = preg_replace($start_regexp, '', $html); 50 | $retval = preg_replace($end_regexp, '', $html); 51 | 52 | return $retval; 53 | } 54 | private function process_if($html){ 55 | $if_regexp = "/\@mun\s+if(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 56 | $elif_regexp = "/\@mun\s+elif(?:\s+)?(?\(((?:[^()]|(?&mun))*)\))/mis"; 57 | $else_regexp = "/\@mun\s+else/mis"; 58 | $end_regexp = "/\@endif/mis"; 59 | 60 | $html = preg_replace($if_regexp, '', $html); 61 | $html = preg_replace($elif_regexp, '', $html); 62 | $html = preg_replace($else_regexp, '', $html); 63 | $retval = preg_replace($end_regexp, '', $html); 64 | 65 | return $retval; 66 | } 67 | private function remove_php_tag($html){ 68 | $retval = preg_replace('/(<\?(?!xml))/', '<?', $html); 69 | return $retval; 70 | } 71 | function render_template($template, $vars=[], $debug_mode=false, $php_tag=false){ 72 | $error_report = '' : '0); ?>'; 74 | $this->debug = $debug_mode; 75 | 76 | foreach($vars as $var_name=>$value){ 77 | ${$var_name} = $value; // ${variable_name} = value; 78 | } 79 | 80 | $exec_code = file_get_contents($this->template_path.'/'.$template); 81 | if(!$php_tag){ 82 | $exec_code = $this->remove_php_tag($exec_code); 83 | } 84 | 85 | $exec_code = $this->process_import($exec_code); 86 | $exec_code = $this->process_for($exec_code); 87 | $exec_code = $this->process_foreach($exec_code); 88 | $exec_code = $this->process_if($exec_code); 89 | $exec_code = $this->process_var($exec_code); 90 | $exec_code = $error_report.$exec_code; 91 | eval("?>$exec_code"); 92 | return true; 93 | } 94 | function render_template_string($exec_code, $vars=[], $debug_mode=false, $php_tag=false){ 95 | $error_report = '' : '0); ?>'; 97 | 98 | foreach($vars as $var_name=>$value){ 99 | ${$var_name} = $value; // ${variable_name} = value; 100 | } 101 | 102 | if(!$php_tag){ 103 | $exec_code = $this->remove_php_tag($exec_code); 104 | } 105 | 106 | $exec_code = $this->process_for($exec_code); 107 | $exec_code = $this->process_foreach($exec_code); 108 | $exec_code = $this->process_if($exec_code); 109 | $exec_code = $this->process_var($exec_code); 110 | $exec_code = $error_report.$exec_code; 111 | 112 | eval("?>$exec_code"); 113 | return true; 114 | } 115 | } -------------------------------------------------------------------------------- /src/classes/User.class.php: -------------------------------------------------------------------------------- 1 | db = new PDO("mysql:host={$host};dbname={$name};charset=UTF8", $user, $pass); 7 | } 8 | } 9 | function login($data){ 10 | $userid = $data['userid']; 11 | $password = password_hashing($data['password']); 12 | $retval = ['status' => false]; 13 | 14 | $sth = $this->db->prepare('SELECT * FROM users WHERE userid=? AND password=?'); 15 | $sth->execute([$userid, $password]); 16 | 17 | if($fetch = $sth->fetch(PDO::FETCH_ASSOC)){ 18 | $retval['status'] = true; 19 | $retval['message'] = 'You have successfully logged in.'; 20 | $_SESSION['userid'] = $fetch['userid']; 21 | 22 | if($fetch['is_admin']) $_SESSION['is_admin'] = 1; 23 | } else { 24 | $retval['message'] = 'The username or password doesn\'t match.'; 25 | } 26 | header('Content-Type: application/json'); 27 | return json_encode($retval); 28 | } 29 | function register($data){ 30 | header('Content-Type: application/json'); 31 | $retval = ['status' => false]; 32 | 33 | $userid = mb_strlen($data['userid']) ? trim(mb_substr($data['userid'], 0, 64)) : NULL; 34 | $password = password_hashing($data['password']); 35 | 36 | if(is_null($userid)){ 37 | $retval['message'] = 'The username is invalid.'; 38 | return json_encode($retval); 39 | } 40 | if(mb_strlen($data['password']) < 7){ 41 | $retval['message'] = 'Please set your password to at least 8 characters.'; 42 | return json_encode($retval); 43 | } 44 | $sth = $this->db->prepare("SELECT * FROM users WHERE userid=?"); 45 | $sth->execute([$userid]); 46 | if($sth->fetch(PDO::FETCH_ASSOC)){ 47 | $retval['message'] = 'A user that already exists.'; 48 | } 49 | else { 50 | $query = "INSERT INTO users (`userid`, `password`, `create_at`, `is_admin`) VALUES (?, ?, now(), 0)"; 51 | $this->db->beginTransaction(); 52 | $sth = $this->db->prepare($query); 53 | $sth->execute([$userid, $password]); 54 | 55 | $this->db->commit(); 56 | $retval['status'] = true; 57 | $retval['message'] = 'You have successfully signed up.'; 58 | } 59 | return json_encode($retval); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/config/.htaccess: -------------------------------------------------------------------------------- 1 | Order deny, allow 2 | Deny from all -------------------------------------------------------------------------------- /src/config/env.php: -------------------------------------------------------------------------------- 1 | getenv('MYSQL_HOST'), 6 | 'user'=> getenv('MYSQL_USER'), 7 | 'pass'=> getenv('MYSQL_PASS'), 8 | 'name'=> getenv('MYSQL_DB') 9 | ]); 10 | define('TEMPLATE_DIR', $_SERVER['DOCUMENT_ROOT'].'/templates'); 11 | define('PWD_SALT', getenv('PWD_SALT') ? getenv('PWD_SALT') : 'phpalwaysalt'); -------------------------------------------------------------------------------- /src/config/function.php: -------------------------------------------------------------------------------- 1 | location.href=\"{$url}\";"; 5 | if(strlen($msg)) 6 | $execute .= 'alert("'.str_replace('/', '\\/', addslashes($msg)).'");'; 7 | $execute .= ''; die($execute); 8 | } 9 | function backward_url($msg=""){ // history backward 10 | $execute = ''; die($execute); 14 | } 15 | function password_hashing($password){ 16 | return hash('sha256', sha1(sha1(md5($password).PWD_SALT)).PWD_SALT); 17 | } 18 | function allow_method($allowed_method){ 19 | $method = $_SERVER['REQUEST_METHOD']; 20 | $allow = true; 21 | if(is_string($allowed_method)) // typeof $allowed_method == String (exam: 'GET') 22 | if($method !== $allowed_method) $allow = false; 23 | else if(is_array($allowed_method)) // typeof $allowed_method == Array (exam: ['GET', 'POST']) 24 | if(!in_array($method, $allowed_method)) $allow = false; 25 | else 26 | $allow = false; 27 | if(!$allow){ 28 | header('HTTP/1.1 405 Method Not Allowed'); 29 | die('

405 Method Not Allowed

'); 30 | } 31 | return true; 32 | } 33 | function check_login($redirect=False){ 34 | if(!isset($_SESSION['userid'])) 35 | if($redirect) 36 | redirect_url('/login'); 37 | else 38 | die('unauthorized'); 39 | return true; 40 | } 41 | function check_admin(){ 42 | if(!isset($_SESSION['is_admin'])) 43 | die('unauthorized'); 44 | } 45 | function fetch_api($method, $uri, $data=[], $headers=['Content-Type: application/json']){ 46 | $ch = curl_init(); 47 | curl_setopt($ch, CURLOPT_URL, $uri); 48 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 49 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 50 | 51 | if(strtoupper($method) == 'POST'){ 52 | curl_setopt($ch, CURLOPT_POST, 1); 53 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 54 | } 55 | $response = curl_exec($ch); 56 | curl_close($ch); 57 | $retval = json_decode($response, true); 58 | return $retval; 59 | } -------------------------------------------------------------------------------- /src/index.php: -------------------------------------------------------------------------------- 1 | e[k] 21 | }); 22 | } 23 | } 24 | } 25 | n.default = e; 26 | return Object.freeze(n); 27 | } 28 | 29 | const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper); 30 | 31 | /** 32 | * -------------------------------------------------------------------------- 33 | * Bootstrap (v5.3.0-alpha1): util/index.js 34 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 35 | * -------------------------------------------------------------------------- 36 | */ 37 | 38 | const MAX_UID = 1000000; 39 | const MILLISECONDS_MULTIPLIER = 1000; 40 | const TRANSITION_END = 'transitionend'; 41 | 42 | /** 43 | * Properly escape IDs selectors to handle weird IDs 44 | * @param {string} selector 45 | * @returns {string} 46 | */ 47 | const parseSelector = selector => { 48 | if (selector && window.CSS && window.CSS.escape) { 49 | // document.querySelector needs escaping to handle IDs (html5+) containing for instance / 50 | selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`); 51 | } 52 | return selector; 53 | }; 54 | 55 | // Shout-out Angus Croll (https://goo.gl/pxwQGp) 56 | const toType = object => { 57 | if (object === null || object === undefined) { 58 | return `${object}`; 59 | } 60 | return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase(); 61 | }; 62 | 63 | /** 64 | * Public Util API 65 | */ 66 | 67 | const getUID = prefix => { 68 | do { 69 | prefix += Math.floor(Math.random() * MAX_UID); 70 | } while (document.getElementById(prefix)); 71 | return prefix; 72 | }; 73 | const getTransitionDurationFromElement = element => { 74 | if (!element) { 75 | return 0; 76 | } 77 | 78 | // Get transition-duration of the element 79 | let { 80 | transitionDuration, 81 | transitionDelay 82 | } = window.getComputedStyle(element); 83 | const floatTransitionDuration = Number.parseFloat(transitionDuration); 84 | const floatTransitionDelay = Number.parseFloat(transitionDelay); 85 | 86 | // Return 0 if element or transition duration is not found 87 | if (!floatTransitionDuration && !floatTransitionDelay) { 88 | return 0; 89 | } 90 | 91 | // If multiple durations are defined, take the first 92 | transitionDuration = transitionDuration.split(',')[0]; 93 | transitionDelay = transitionDelay.split(',')[0]; 94 | return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; 95 | }; 96 | const triggerTransitionEnd = element => { 97 | element.dispatchEvent(new Event(TRANSITION_END)); 98 | }; 99 | const isElement = object => { 100 | if (!object || typeof object !== 'object') { 101 | return false; 102 | } 103 | if (typeof object.jquery !== 'undefined') { 104 | object = object[0]; 105 | } 106 | return typeof object.nodeType !== 'undefined'; 107 | }; 108 | const getElement = object => { 109 | // it's a jQuery object or a node element 110 | if (isElement(object)) { 111 | return object.jquery ? object[0] : object; 112 | } 113 | if (typeof object === 'string' && object.length > 0) { 114 | return document.querySelector(parseSelector(object)); 115 | } 116 | return null; 117 | }; 118 | const isVisible = element => { 119 | if (!isElement(element) || element.getClientRects().length === 0) { 120 | return false; 121 | } 122 | const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; 123 | // Handle `details` element as its content may falsie appear visible when it is closed 124 | const closedDetails = element.closest('details:not([open])'); 125 | if (!closedDetails) { 126 | return elementIsVisible; 127 | } 128 | if (closedDetails !== element) { 129 | const summary = element.closest('summary'); 130 | if (summary && summary.parentNode !== closedDetails) { 131 | return false; 132 | } 133 | if (summary === null) { 134 | return false; 135 | } 136 | } 137 | return elementIsVisible; 138 | }; 139 | const isDisabled = element => { 140 | if (!element || element.nodeType !== Node.ELEMENT_NODE) { 141 | return true; 142 | } 143 | if (element.classList.contains('disabled')) { 144 | return true; 145 | } 146 | if (typeof element.disabled !== 'undefined') { 147 | return element.disabled; 148 | } 149 | return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; 150 | }; 151 | const findShadowRoot = element => { 152 | if (!document.documentElement.attachShadow) { 153 | return null; 154 | } 155 | 156 | // Can find the shadow root otherwise it'll return the document 157 | if (typeof element.getRootNode === 'function') { 158 | const root = element.getRootNode(); 159 | return root instanceof ShadowRoot ? root : null; 160 | } 161 | if (element instanceof ShadowRoot) { 162 | return element; 163 | } 164 | 165 | // when we don't find a shadow root 166 | if (!element.parentNode) { 167 | return null; 168 | } 169 | return findShadowRoot(element.parentNode); 170 | }; 171 | const noop = () => {}; 172 | 173 | /** 174 | * Trick to restart an element's animation 175 | * 176 | * @param {HTMLElement} element 177 | * @return void 178 | * 179 | * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation 180 | */ 181 | const reflow = element => { 182 | element.offsetHeight; // eslint-disable-line no-unused-expressions 183 | }; 184 | 185 | const getjQuery = () => { 186 | if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { 187 | return window.jQuery; 188 | } 189 | return null; 190 | }; 191 | const DOMContentLoadedCallbacks = []; 192 | const onDOMContentLoaded = callback => { 193 | if (document.readyState === 'loading') { 194 | // add listener on the first call when the document is in loading state 195 | if (!DOMContentLoadedCallbacks.length) { 196 | document.addEventListener('DOMContentLoaded', () => { 197 | for (const callback of DOMContentLoadedCallbacks) { 198 | callback(); 199 | } 200 | }); 201 | } 202 | DOMContentLoadedCallbacks.push(callback); 203 | } else { 204 | callback(); 205 | } 206 | }; 207 | const isRTL = () => document.documentElement.dir === 'rtl'; 208 | const defineJQueryPlugin = plugin => { 209 | onDOMContentLoaded(() => { 210 | const $ = getjQuery(); 211 | /* istanbul ignore if */ 212 | if ($) { 213 | const name = plugin.NAME; 214 | const JQUERY_NO_CONFLICT = $.fn[name]; 215 | $.fn[name] = plugin.jQueryInterface; 216 | $.fn[name].Constructor = plugin; 217 | $.fn[name].noConflict = () => { 218 | $.fn[name] = JQUERY_NO_CONFLICT; 219 | return plugin.jQueryInterface; 220 | }; 221 | } 222 | }); 223 | }; 224 | const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { 225 | return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue; 226 | }; 227 | const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { 228 | if (!waitForTransition) { 229 | execute(callback); 230 | return; 231 | } 232 | const durationPadding = 5; 233 | const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; 234 | let called = false; 235 | const handler = ({ 236 | target 237 | }) => { 238 | if (target !== transitionElement) { 239 | return; 240 | } 241 | called = true; 242 | transitionElement.removeEventListener(TRANSITION_END, handler); 243 | execute(callback); 244 | }; 245 | transitionElement.addEventListener(TRANSITION_END, handler); 246 | setTimeout(() => { 247 | if (!called) { 248 | triggerTransitionEnd(transitionElement); 249 | } 250 | }, emulatedDuration); 251 | }; 252 | 253 | /** 254 | * Return the previous/next element of a list. 255 | * 256 | * @param {array} list The list of elements 257 | * @param activeElement The active element 258 | * @param shouldGetNext Choose to get next or previous element 259 | * @param isCycleAllowed 260 | * @return {Element|elem} The proper element 261 | */ 262 | const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { 263 | const listLength = list.length; 264 | let index = list.indexOf(activeElement); 265 | 266 | // if the element does not exist in the list return an element 267 | // depending on the direction and if cycle is allowed 268 | if (index === -1) { 269 | return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]; 270 | } 271 | index += shouldGetNext ? 1 : -1; 272 | if (isCycleAllowed) { 273 | index = (index + listLength) % listLength; 274 | } 275 | return list[Math.max(0, Math.min(index, listLength - 1))]; 276 | }; 277 | 278 | /** 279 | * -------------------------------------------------------------------------- 280 | * Bootstrap (v5.3.0-alpha1): dom/event-handler.js 281 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 282 | * -------------------------------------------------------------------------- 283 | */ 284 | 285 | /** 286 | * Constants 287 | */ 288 | 289 | const namespaceRegex = /[^.]*(?=\..*)\.|.*/; 290 | const stripNameRegex = /\..*/; 291 | const stripUidRegex = /::\d+$/; 292 | const eventRegistry = {}; // Events storage 293 | let uidEvent = 1; 294 | const customEvents = { 295 | mouseenter: 'mouseover', 296 | mouseleave: 'mouseout' 297 | }; 298 | const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); 299 | 300 | /** 301 | * Private methods 302 | */ 303 | 304 | function makeEventUid(element, uid) { 305 | return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; 306 | } 307 | function getElementEvents(element) { 308 | const uid = makeEventUid(element); 309 | element.uidEvent = uid; 310 | eventRegistry[uid] = eventRegistry[uid] || {}; 311 | return eventRegistry[uid]; 312 | } 313 | function bootstrapHandler(element, fn) { 314 | return function handler(event) { 315 | hydrateObj(event, { 316 | delegateTarget: element 317 | }); 318 | if (handler.oneOff) { 319 | EventHandler.off(element, event.type, fn); 320 | } 321 | return fn.apply(element, [event]); 322 | }; 323 | } 324 | function bootstrapDelegationHandler(element, selector, fn) { 325 | return function handler(event) { 326 | const domElements = element.querySelectorAll(selector); 327 | for (let { 328 | target 329 | } = event; target && target !== this; target = target.parentNode) { 330 | for (const domElement of domElements) { 331 | if (domElement !== target) { 332 | continue; 333 | } 334 | hydrateObj(event, { 335 | delegateTarget: target 336 | }); 337 | if (handler.oneOff) { 338 | EventHandler.off(element, event.type, selector, fn); 339 | } 340 | return fn.apply(target, [event]); 341 | } 342 | } 343 | }; 344 | } 345 | function findHandler(events, callable, delegationSelector = null) { 346 | return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector); 347 | } 348 | function normalizeParameters(originalTypeEvent, handler, delegationFunction) { 349 | const isDelegated = typeof handler === 'string'; 350 | // todo: tooltip passes `false` instead of selector, so we need to check 351 | const callable = isDelegated ? delegationFunction : handler || delegationFunction; 352 | let typeEvent = getTypeEvent(originalTypeEvent); 353 | if (!nativeEvents.has(typeEvent)) { 354 | typeEvent = originalTypeEvent; 355 | } 356 | return [isDelegated, callable, typeEvent]; 357 | } 358 | function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { 359 | if (typeof originalTypeEvent !== 'string' || !element) { 360 | return; 361 | } 362 | let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); 363 | 364 | // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position 365 | // this prevents the handler from being dispatched the same way as mouseover or mouseout does 366 | if (originalTypeEvent in customEvents) { 367 | const wrapFunction = fn => { 368 | return function (event) { 369 | if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) { 370 | return fn.call(this, event); 371 | } 372 | }; 373 | }; 374 | callable = wrapFunction(callable); 375 | } 376 | const events = getElementEvents(element); 377 | const handlers = events[typeEvent] || (events[typeEvent] = {}); 378 | const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null); 379 | if (previousFunction) { 380 | previousFunction.oneOff = previousFunction.oneOff && oneOff; 381 | return; 382 | } 383 | const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, '')); 384 | const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable); 385 | fn.delegationSelector = isDelegated ? handler : null; 386 | fn.callable = callable; 387 | fn.oneOff = oneOff; 388 | fn.uidEvent = uid; 389 | handlers[uid] = fn; 390 | element.addEventListener(typeEvent, fn, isDelegated); 391 | } 392 | function removeHandler(element, events, typeEvent, handler, delegationSelector) { 393 | const fn = findHandler(events[typeEvent], handler, delegationSelector); 394 | if (!fn) { 395 | return; 396 | } 397 | element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); 398 | delete events[typeEvent][fn.uidEvent]; 399 | } 400 | function removeNamespacedHandlers(element, events, typeEvent, namespace) { 401 | const storeElementEvent = events[typeEvent] || {}; 402 | for (const [handlerKey, event] of Object.entries(storeElementEvent)) { 403 | if (handlerKey.includes(namespace)) { 404 | removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); 405 | } 406 | } 407 | } 408 | function getTypeEvent(event) { 409 | // allow to get the native events from namespaced events ('click.bs.button' --> 'click') 410 | event = event.replace(stripNameRegex, ''); 411 | return customEvents[event] || event; 412 | } 413 | const EventHandler = { 414 | on(element, event, handler, delegationFunction) { 415 | addHandler(element, event, handler, delegationFunction, false); 416 | }, 417 | one(element, event, handler, delegationFunction) { 418 | addHandler(element, event, handler, delegationFunction, true); 419 | }, 420 | off(element, originalTypeEvent, handler, delegationFunction) { 421 | if (typeof originalTypeEvent !== 'string' || !element) { 422 | return; 423 | } 424 | const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); 425 | const inNamespace = typeEvent !== originalTypeEvent; 426 | const events = getElementEvents(element); 427 | const storeElementEvent = events[typeEvent] || {}; 428 | const isNamespace = originalTypeEvent.startsWith('.'); 429 | if (typeof callable !== 'undefined') { 430 | // Simplest case: handler is passed, remove that listener ONLY. 431 | if (!Object.keys(storeElementEvent).length) { 432 | return; 433 | } 434 | removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null); 435 | return; 436 | } 437 | if (isNamespace) { 438 | for (const elementEvent of Object.keys(events)) { 439 | removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); 440 | } 441 | } 442 | for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { 443 | const handlerKey = keyHandlers.replace(stripUidRegex, ''); 444 | if (!inNamespace || originalTypeEvent.includes(handlerKey)) { 445 | removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); 446 | } 447 | } 448 | }, 449 | trigger(element, event, args) { 450 | if (typeof event !== 'string' || !element) { 451 | return null; 452 | } 453 | const $ = getjQuery(); 454 | const typeEvent = getTypeEvent(event); 455 | const inNamespace = event !== typeEvent; 456 | let jQueryEvent = null; 457 | let bubbles = true; 458 | let nativeDispatch = true; 459 | let defaultPrevented = false; 460 | if (inNamespace && $) { 461 | jQueryEvent = $.Event(event, args); 462 | $(element).trigger(jQueryEvent); 463 | bubbles = !jQueryEvent.isPropagationStopped(); 464 | nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); 465 | defaultPrevented = jQueryEvent.isDefaultPrevented(); 466 | } 467 | let evt = new Event(event, { 468 | bubbles, 469 | cancelable: true 470 | }); 471 | evt = hydrateObj(evt, args); 472 | if (defaultPrevented) { 473 | evt.preventDefault(); 474 | } 475 | if (nativeDispatch) { 476 | element.dispatchEvent(evt); 477 | } 478 | if (evt.defaultPrevented && jQueryEvent) { 479 | jQueryEvent.preventDefault(); 480 | } 481 | return evt; 482 | } 483 | }; 484 | function hydrateObj(obj, meta = {}) { 485 | for (const [key, value] of Object.entries(meta)) { 486 | try { 487 | obj[key] = value; 488 | } catch (_unused) { 489 | Object.defineProperty(obj, key, { 490 | configurable: true, 491 | get() { 492 | return value; 493 | } 494 | }); 495 | } 496 | } 497 | return obj; 498 | } 499 | 500 | /** 501 | * -------------------------------------------------------------------------- 502 | * Bootstrap (v5.3.0-alpha1): dom/data.js 503 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 504 | * -------------------------------------------------------------------------- 505 | */ 506 | 507 | /** 508 | * Constants 509 | */ 510 | 511 | const elementMap = new Map(); 512 | const Data = { 513 | set(element, key, instance) { 514 | if (!elementMap.has(element)) { 515 | elementMap.set(element, new Map()); 516 | } 517 | const instanceMap = elementMap.get(element); 518 | 519 | // make it clear we only want one instance per element 520 | // can be removed later when multiple key/instances are fine to be used 521 | if (!instanceMap.has(key) && instanceMap.size !== 0) { 522 | // eslint-disable-next-line no-console 523 | console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); 524 | return; 525 | } 526 | instanceMap.set(key, instance); 527 | }, 528 | get(element, key) { 529 | if (elementMap.has(element)) { 530 | return elementMap.get(element).get(key) || null; 531 | } 532 | return null; 533 | }, 534 | remove(element, key) { 535 | if (!elementMap.has(element)) { 536 | return; 537 | } 538 | const instanceMap = elementMap.get(element); 539 | instanceMap.delete(key); 540 | 541 | // free up element references if there are no instances left for an element 542 | if (instanceMap.size === 0) { 543 | elementMap.delete(element); 544 | } 545 | } 546 | }; 547 | 548 | /** 549 | * -------------------------------------------------------------------------- 550 | * Bootstrap (v5.3.0-alpha1): dom/manipulator.js 551 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 552 | * -------------------------------------------------------------------------- 553 | */ 554 | 555 | function normalizeData(value) { 556 | if (value === 'true') { 557 | return true; 558 | } 559 | if (value === 'false') { 560 | return false; 561 | } 562 | if (value === Number(value).toString()) { 563 | return Number(value); 564 | } 565 | if (value === '' || value === 'null') { 566 | return null; 567 | } 568 | if (typeof value !== 'string') { 569 | return value; 570 | } 571 | try { 572 | return JSON.parse(decodeURIComponent(value)); 573 | } catch (_unused) { 574 | return value; 575 | } 576 | } 577 | function normalizeDataKey(key) { 578 | return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); 579 | } 580 | const Manipulator = { 581 | setDataAttribute(element, key, value) { 582 | element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); 583 | }, 584 | removeDataAttribute(element, key) { 585 | element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); 586 | }, 587 | getDataAttributes(element) { 588 | if (!element) { 589 | return {}; 590 | } 591 | const attributes = {}; 592 | const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); 593 | for (const key of bsKeys) { 594 | let pureKey = key.replace(/^bs/, ''); 595 | pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); 596 | attributes[pureKey] = normalizeData(element.dataset[key]); 597 | } 598 | return attributes; 599 | }, 600 | getDataAttribute(element, key) { 601 | return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); 602 | } 603 | }; 604 | 605 | /** 606 | * -------------------------------------------------------------------------- 607 | * Bootstrap (v5.3.0-alpha1): util/config.js 608 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 609 | * -------------------------------------------------------------------------- 610 | */ 611 | 612 | /** 613 | * Class definition 614 | */ 615 | 616 | class Config { 617 | // Getters 618 | static get Default() { 619 | return {}; 620 | } 621 | static get DefaultType() { 622 | return {}; 623 | } 624 | static get NAME() { 625 | throw new Error('You have to implement the static method "NAME", for each component!'); 626 | } 627 | _getConfig(config) { 628 | config = this._mergeConfigObj(config); 629 | config = this._configAfterMerge(config); 630 | this._typeCheckConfig(config); 631 | return config; 632 | } 633 | _configAfterMerge(config) { 634 | return config; 635 | } 636 | _mergeConfigObj(config, element) { 637 | const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse 638 | 639 | return { 640 | ...this.constructor.Default, 641 | ...(typeof jsonConfig === 'object' ? jsonConfig : {}), 642 | ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}), 643 | ...(typeof config === 'object' ? config : {}) 644 | }; 645 | } 646 | _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { 647 | for (const [property, expectedTypes] of Object.entries(configTypes)) { 648 | const value = config[property]; 649 | const valueType = isElement(value) ? 'element' : toType(value); 650 | if (!new RegExp(expectedTypes).test(valueType)) { 651 | throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); 652 | } 653 | } 654 | } 655 | } 656 | 657 | /** 658 | * -------------------------------------------------------------------------- 659 | * Bootstrap (v5.3.0-alpha1): base-component.js 660 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 661 | * -------------------------------------------------------------------------- 662 | */ 663 | 664 | /** 665 | * Constants 666 | */ 667 | 668 | const VERSION = '5.3.0-alpha1'; 669 | 670 | /** 671 | * Class definition 672 | */ 673 | 674 | class BaseComponent extends Config { 675 | constructor(element, config) { 676 | super(); 677 | element = getElement(element); 678 | if (!element) { 679 | return; 680 | } 681 | this._element = element; 682 | this._config = this._getConfig(config); 683 | Data.set(this._element, this.constructor.DATA_KEY, this); 684 | } 685 | 686 | // Public 687 | dispose() { 688 | Data.remove(this._element, this.constructor.DATA_KEY); 689 | EventHandler.off(this._element, this.constructor.EVENT_KEY); 690 | for (const propertyName of Object.getOwnPropertyNames(this)) { 691 | this[propertyName] = null; 692 | } 693 | } 694 | _queueCallback(callback, element, isAnimated = true) { 695 | executeAfterTransition(callback, element, isAnimated); 696 | } 697 | _getConfig(config) { 698 | config = this._mergeConfigObj(config, this._element); 699 | config = this._configAfterMerge(config); 700 | this._typeCheckConfig(config); 701 | return config; 702 | } 703 | 704 | // Static 705 | static getInstance(element) { 706 | return Data.get(getElement(element), this.DATA_KEY); 707 | } 708 | static getOrCreateInstance(element, config = {}) { 709 | return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); 710 | } 711 | static get VERSION() { 712 | return VERSION; 713 | } 714 | static get DATA_KEY() { 715 | return `bs.${this.NAME}`; 716 | } 717 | static get EVENT_KEY() { 718 | return `.${this.DATA_KEY}`; 719 | } 720 | static eventName(name) { 721 | return `${name}${this.EVENT_KEY}`; 722 | } 723 | } 724 | 725 | /** 726 | * -------------------------------------------------------------------------- 727 | * Bootstrap (v5.3.0-alpha1): dom/selector-engine.js 728 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 729 | * -------------------------------------------------------------------------- 730 | */ 731 | const getSelector = element => { 732 | let selector = element.getAttribute('data-bs-target'); 733 | if (!selector || selector === '#') { 734 | let hrefAttribute = element.getAttribute('href'); 735 | 736 | // The only valid content that could double as a selector are IDs or classes, 737 | // so everything starting with `#` or `.`. If a "real" URL is used as the selector, 738 | // `document.querySelector` will rightfully complain it is invalid. 739 | // See https://github.com/twbs/bootstrap/issues/32273 740 | if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { 741 | return null; 742 | } 743 | 744 | // Just in case some CMS puts out a full URL with the anchor appended 745 | if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { 746 | hrefAttribute = `#${hrefAttribute.split('#')[1]}`; 747 | } 748 | selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; 749 | } 750 | return parseSelector(selector); 751 | }; 752 | const SelectorEngine = { 753 | find(selector, element = document.documentElement) { 754 | return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); 755 | }, 756 | findOne(selector, element = document.documentElement) { 757 | return Element.prototype.querySelector.call(element, selector); 758 | }, 759 | children(element, selector) { 760 | return [].concat(...element.children).filter(child => child.matches(selector)); 761 | }, 762 | parents(element, selector) { 763 | const parents = []; 764 | let ancestor = element.parentNode.closest(selector); 765 | while (ancestor) { 766 | parents.push(ancestor); 767 | ancestor = ancestor.parentNode.closest(selector); 768 | } 769 | return parents; 770 | }, 771 | prev(element, selector) { 772 | let previous = element.previousElementSibling; 773 | while (previous) { 774 | if (previous.matches(selector)) { 775 | return [previous]; 776 | } 777 | previous = previous.previousElementSibling; 778 | } 779 | return []; 780 | }, 781 | // TODO: this is now unused; remove later along with prev() 782 | next(element, selector) { 783 | let next = element.nextElementSibling; 784 | while (next) { 785 | if (next.matches(selector)) { 786 | return [next]; 787 | } 788 | next = next.nextElementSibling; 789 | } 790 | return []; 791 | }, 792 | focusableChildren(element) { 793 | const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); 794 | return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); 795 | }, 796 | getSelectorFromElement(element) { 797 | const selector = getSelector(element); 798 | if (selector) { 799 | return SelectorEngine.findOne(selector) ? selector : null; 800 | } 801 | return null; 802 | }, 803 | getElementFromSelector(element) { 804 | const selector = getSelector(element); 805 | return selector ? SelectorEngine.findOne(selector) : null; 806 | }, 807 | getMultipleElementsFromSelector(element) { 808 | const selector = getSelector(element); 809 | return selector ? SelectorEngine.find(selector) : []; 810 | } 811 | }; 812 | 813 | /** 814 | * -------------------------------------------------------------------------- 815 | * Bootstrap (v5.3.0-alpha1): util/component-functions.js 816 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 817 | * -------------------------------------------------------------------------- 818 | */ 819 | const enableDismissTrigger = (component, method = 'hide') => { 820 | const clickEvent = `click.dismiss${component.EVENT_KEY}`; 821 | const name = component.NAME; 822 | EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { 823 | if (['A', 'AREA'].includes(this.tagName)) { 824 | event.preventDefault(); 825 | } 826 | if (isDisabled(this)) { 827 | return; 828 | } 829 | const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); 830 | const instance = component.getOrCreateInstance(target); 831 | 832 | // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method 833 | instance[method](); 834 | }); 835 | }; 836 | 837 | /** 838 | * -------------------------------------------------------------------------- 839 | * Bootstrap (v5.3.0-alpha1): alert.js 840 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 841 | * -------------------------------------------------------------------------- 842 | */ 843 | 844 | /** 845 | * Constants 846 | */ 847 | 848 | const NAME$f = 'alert'; 849 | const DATA_KEY$a = 'bs.alert'; 850 | const EVENT_KEY$b = `.${DATA_KEY$a}`; 851 | const EVENT_CLOSE = `close${EVENT_KEY$b}`; 852 | const EVENT_CLOSED = `closed${EVENT_KEY$b}`; 853 | const CLASS_NAME_FADE$5 = 'fade'; 854 | const CLASS_NAME_SHOW$8 = 'show'; 855 | 856 | /** 857 | * Class definition 858 | */ 859 | 860 | class Alert extends BaseComponent { 861 | // Getters 862 | static get NAME() { 863 | return NAME$f; 864 | } 865 | 866 | // Public 867 | close() { 868 | const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); 869 | if (closeEvent.defaultPrevented) { 870 | return; 871 | } 872 | this._element.classList.remove(CLASS_NAME_SHOW$8); 873 | const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); 874 | this._queueCallback(() => this._destroyElement(), this._element, isAnimated); 875 | } 876 | 877 | // Private 878 | _destroyElement() { 879 | this._element.remove(); 880 | EventHandler.trigger(this._element, EVENT_CLOSED); 881 | this.dispose(); 882 | } 883 | 884 | // Static 885 | static jQueryInterface(config) { 886 | return this.each(function () { 887 | const data = Alert.getOrCreateInstance(this); 888 | if (typeof config !== 'string') { 889 | return; 890 | } 891 | if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { 892 | throw new TypeError(`No method named "${config}"`); 893 | } 894 | data[config](this); 895 | }); 896 | } 897 | } 898 | 899 | /** 900 | * Data API implementation 901 | */ 902 | 903 | enableDismissTrigger(Alert, 'close'); 904 | 905 | /** 906 | * jQuery 907 | */ 908 | 909 | defineJQueryPlugin(Alert); 910 | 911 | /** 912 | * -------------------------------------------------------------------------- 913 | * Bootstrap (v5.3.0-alpha1): button.js 914 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 915 | * -------------------------------------------------------------------------- 916 | */ 917 | 918 | /** 919 | * Constants 920 | */ 921 | 922 | const NAME$e = 'button'; 923 | const DATA_KEY$9 = 'bs.button'; 924 | const EVENT_KEY$a = `.${DATA_KEY$9}`; 925 | const DATA_API_KEY$6 = '.data-api'; 926 | const CLASS_NAME_ACTIVE$3 = 'active'; 927 | const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; 928 | const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; 929 | 930 | /** 931 | * Class definition 932 | */ 933 | 934 | class Button extends BaseComponent { 935 | // Getters 936 | static get NAME() { 937 | return NAME$e; 938 | } 939 | 940 | // Public 941 | toggle() { 942 | // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method 943 | this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); 944 | } 945 | 946 | // Static 947 | static jQueryInterface(config) { 948 | return this.each(function () { 949 | const data = Button.getOrCreateInstance(this); 950 | if (config === 'toggle') { 951 | data[config](); 952 | } 953 | }); 954 | } 955 | } 956 | 957 | /** 958 | * Data API implementation 959 | */ 960 | 961 | EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { 962 | event.preventDefault(); 963 | const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); 964 | const data = Button.getOrCreateInstance(button); 965 | data.toggle(); 966 | }); 967 | 968 | /** 969 | * jQuery 970 | */ 971 | 972 | defineJQueryPlugin(Button); 973 | 974 | /** 975 | * -------------------------------------------------------------------------- 976 | * Bootstrap (v5.3.0-alpha1): util/swipe.js 977 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 978 | * -------------------------------------------------------------------------- 979 | */ 980 | 981 | /** 982 | * Constants 983 | */ 984 | 985 | const NAME$d = 'swipe'; 986 | const EVENT_KEY$9 = '.bs.swipe'; 987 | const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`; 988 | const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`; 989 | const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`; 990 | const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`; 991 | const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`; 992 | const POINTER_TYPE_TOUCH = 'touch'; 993 | const POINTER_TYPE_PEN = 'pen'; 994 | const CLASS_NAME_POINTER_EVENT = 'pointer-event'; 995 | const SWIPE_THRESHOLD = 40; 996 | const Default$c = { 997 | endCallback: null, 998 | leftCallback: null, 999 | rightCallback: null 1000 | }; 1001 | const DefaultType$c = { 1002 | endCallback: '(function|null)', 1003 | leftCallback: '(function|null)', 1004 | rightCallback: '(function|null)' 1005 | }; 1006 | 1007 | /** 1008 | * Class definition 1009 | */ 1010 | 1011 | class Swipe extends Config { 1012 | constructor(element, config) { 1013 | super(); 1014 | this._element = element; 1015 | if (!element || !Swipe.isSupported()) { 1016 | return; 1017 | } 1018 | this._config = this._getConfig(config); 1019 | this._deltaX = 0; 1020 | this._supportPointerEvents = Boolean(window.PointerEvent); 1021 | this._initEvents(); 1022 | } 1023 | 1024 | // Getters 1025 | static get Default() { 1026 | return Default$c; 1027 | } 1028 | static get DefaultType() { 1029 | return DefaultType$c; 1030 | } 1031 | static get NAME() { 1032 | return NAME$d; 1033 | } 1034 | 1035 | // Public 1036 | dispose() { 1037 | EventHandler.off(this._element, EVENT_KEY$9); 1038 | } 1039 | 1040 | // Private 1041 | _start(event) { 1042 | if (!this._supportPointerEvents) { 1043 | this._deltaX = event.touches[0].clientX; 1044 | return; 1045 | } 1046 | if (this._eventIsPointerPenTouch(event)) { 1047 | this._deltaX = event.clientX; 1048 | } 1049 | } 1050 | _end(event) { 1051 | if (this._eventIsPointerPenTouch(event)) { 1052 | this._deltaX = event.clientX - this._deltaX; 1053 | } 1054 | this._handleSwipe(); 1055 | execute(this._config.endCallback); 1056 | } 1057 | _move(event) { 1058 | this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; 1059 | } 1060 | _handleSwipe() { 1061 | const absDeltaX = Math.abs(this._deltaX); 1062 | if (absDeltaX <= SWIPE_THRESHOLD) { 1063 | return; 1064 | } 1065 | const direction = absDeltaX / this._deltaX; 1066 | this._deltaX = 0; 1067 | if (!direction) { 1068 | return; 1069 | } 1070 | execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); 1071 | } 1072 | _initEvents() { 1073 | if (this._supportPointerEvents) { 1074 | EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); 1075 | EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); 1076 | this._element.classList.add(CLASS_NAME_POINTER_EVENT); 1077 | } else { 1078 | EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); 1079 | EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event)); 1080 | EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); 1081 | } 1082 | } 1083 | _eventIsPointerPenTouch(event) { 1084 | return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); 1085 | } 1086 | 1087 | // Static 1088 | static isSupported() { 1089 | return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; 1090 | } 1091 | } 1092 | 1093 | /** 1094 | * -------------------------------------------------------------------------- 1095 | * Bootstrap (v5.3.0-alpha1): carousel.js 1096 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 1097 | * -------------------------------------------------------------------------- 1098 | */ 1099 | 1100 | /** 1101 | * Constants 1102 | */ 1103 | 1104 | const NAME$c = 'carousel'; 1105 | const DATA_KEY$8 = 'bs.carousel'; 1106 | const EVENT_KEY$8 = `.${DATA_KEY$8}`; 1107 | const DATA_API_KEY$5 = '.data-api'; 1108 | const ARROW_LEFT_KEY$1 = 'ArrowLeft'; 1109 | const ARROW_RIGHT_KEY$1 = 'ArrowRight'; 1110 | const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch 1111 | 1112 | const ORDER_NEXT = 'next'; 1113 | const ORDER_PREV = 'prev'; 1114 | const DIRECTION_LEFT = 'left'; 1115 | const DIRECTION_RIGHT = 'right'; 1116 | const EVENT_SLIDE = `slide${EVENT_KEY$8}`; 1117 | const EVENT_SLID = `slid${EVENT_KEY$8}`; 1118 | const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`; 1119 | const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`; 1120 | const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`; 1121 | const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`; 1122 | const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`; 1123 | const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`; 1124 | const CLASS_NAME_CAROUSEL = 'carousel'; 1125 | const CLASS_NAME_ACTIVE$2 = 'active'; 1126 | const CLASS_NAME_SLIDE = 'slide'; 1127 | const CLASS_NAME_END = 'carousel-item-end'; 1128 | const CLASS_NAME_START = 'carousel-item-start'; 1129 | const CLASS_NAME_NEXT = 'carousel-item-next'; 1130 | const CLASS_NAME_PREV = 'carousel-item-prev'; 1131 | const SELECTOR_ACTIVE = '.active'; 1132 | const SELECTOR_ITEM = '.carousel-item'; 1133 | const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM; 1134 | const SELECTOR_ITEM_IMG = '.carousel-item img'; 1135 | const SELECTOR_INDICATORS = '.carousel-indicators'; 1136 | const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; 1137 | const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; 1138 | const KEY_TO_DIRECTION = { 1139 | [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT, 1140 | [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT 1141 | }; 1142 | const Default$b = { 1143 | interval: 5000, 1144 | keyboard: true, 1145 | pause: 'hover', 1146 | ride: false, 1147 | touch: true, 1148 | wrap: true 1149 | }; 1150 | const DefaultType$b = { 1151 | interval: '(number|boolean)', 1152 | // TODO:v6 remove boolean support 1153 | keyboard: 'boolean', 1154 | pause: '(string|boolean)', 1155 | ride: '(boolean|string)', 1156 | touch: 'boolean', 1157 | wrap: 'boolean' 1158 | }; 1159 | 1160 | /** 1161 | * Class definition 1162 | */ 1163 | 1164 | class Carousel extends BaseComponent { 1165 | constructor(element, config) { 1166 | super(element, config); 1167 | this._interval = null; 1168 | this._activeElement = null; 1169 | this._isSliding = false; 1170 | this.touchTimeout = null; 1171 | this._swipeHelper = null; 1172 | this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); 1173 | this._addEventListeners(); 1174 | if (this._config.ride === CLASS_NAME_CAROUSEL) { 1175 | this.cycle(); 1176 | } 1177 | } 1178 | 1179 | // Getters 1180 | static get Default() { 1181 | return Default$b; 1182 | } 1183 | static get DefaultType() { 1184 | return DefaultType$b; 1185 | } 1186 | static get NAME() { 1187 | return NAME$c; 1188 | } 1189 | 1190 | // Public 1191 | next() { 1192 | this._slide(ORDER_NEXT); 1193 | } 1194 | nextWhenVisible() { 1195 | // FIXME TODO use `document.visibilityState` 1196 | // Don't call next when the page isn't visible 1197 | // or the carousel or its parent isn't visible 1198 | if (!document.hidden && isVisible(this._element)) { 1199 | this.next(); 1200 | } 1201 | } 1202 | prev() { 1203 | this._slide(ORDER_PREV); 1204 | } 1205 | pause() { 1206 | if (this._isSliding) { 1207 | triggerTransitionEnd(this._element); 1208 | } 1209 | this._clearInterval(); 1210 | } 1211 | cycle() { 1212 | this._clearInterval(); 1213 | this._updateInterval(); 1214 | this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval); 1215 | } 1216 | _maybeEnableCycle() { 1217 | if (!this._config.ride) { 1218 | return; 1219 | } 1220 | if (this._isSliding) { 1221 | EventHandler.one(this._element, EVENT_SLID, () => this.cycle()); 1222 | return; 1223 | } 1224 | this.cycle(); 1225 | } 1226 | to(index) { 1227 | const items = this._getItems(); 1228 | if (index > items.length - 1 || index < 0) { 1229 | return; 1230 | } 1231 | if (this._isSliding) { 1232 | EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); 1233 | return; 1234 | } 1235 | const activeIndex = this._getItemIndex(this._getActive()); 1236 | if (activeIndex === index) { 1237 | return; 1238 | } 1239 | const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; 1240 | this._slide(order, items[index]); 1241 | } 1242 | dispose() { 1243 | if (this._swipeHelper) { 1244 | this._swipeHelper.dispose(); 1245 | } 1246 | super.dispose(); 1247 | } 1248 | 1249 | // Private 1250 | _configAfterMerge(config) { 1251 | config.defaultInterval = config.interval; 1252 | return config; 1253 | } 1254 | _addEventListeners() { 1255 | if (this._config.keyboard) { 1256 | EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event)); 1257 | } 1258 | if (this._config.pause === 'hover') { 1259 | EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause()); 1260 | EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle()); 1261 | } 1262 | if (this._config.touch && Swipe.isSupported()) { 1263 | this._addTouchEventListeners(); 1264 | } 1265 | } 1266 | _addTouchEventListeners() { 1267 | for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { 1268 | EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()); 1269 | } 1270 | const endCallBack = () => { 1271 | if (this._config.pause !== 'hover') { 1272 | return; 1273 | } 1274 | 1275 | // If it's a touch-enabled device, mouseenter/leave are fired as 1276 | // part of the mouse compatibility events on first tap - the carousel 1277 | // would stop cycling until user tapped out of it; 1278 | // here, we listen for touchend, explicitly pause the carousel 1279 | // (as if it's the second time we tap on it, mouseenter compat event 1280 | // is NOT fired) and after a timeout (to allow for mouse compatibility 1281 | // events to fire) we explicitly restart cycling 1282 | 1283 | this.pause(); 1284 | if (this.touchTimeout) { 1285 | clearTimeout(this.touchTimeout); 1286 | } 1287 | this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval); 1288 | }; 1289 | const swipeConfig = { 1290 | leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), 1291 | rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), 1292 | endCallback: endCallBack 1293 | }; 1294 | this._swipeHelper = new Swipe(this._element, swipeConfig); 1295 | } 1296 | _keydown(event) { 1297 | if (/input|textarea/i.test(event.target.tagName)) { 1298 | return; 1299 | } 1300 | const direction = KEY_TO_DIRECTION[event.key]; 1301 | if (direction) { 1302 | event.preventDefault(); 1303 | this._slide(this._directionToOrder(direction)); 1304 | } 1305 | } 1306 | _getItemIndex(element) { 1307 | return this._getItems().indexOf(element); 1308 | } 1309 | _setActiveIndicatorElement(index) { 1310 | if (!this._indicatorsElement) { 1311 | return; 1312 | } 1313 | const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement); 1314 | activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); 1315 | activeIndicator.removeAttribute('aria-current'); 1316 | const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement); 1317 | if (newActiveIndicator) { 1318 | newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2); 1319 | newActiveIndicator.setAttribute('aria-current', 'true'); 1320 | } 1321 | } 1322 | _updateInterval() { 1323 | const element = this._activeElement || this._getActive(); 1324 | if (!element) { 1325 | return; 1326 | } 1327 | const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); 1328 | this._config.interval = elementInterval || this._config.defaultInterval; 1329 | } 1330 | _slide(order, element = null) { 1331 | if (this._isSliding) { 1332 | return; 1333 | } 1334 | const activeElement = this._getActive(); 1335 | const isNext = order === ORDER_NEXT; 1336 | const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap); 1337 | if (nextElement === activeElement) { 1338 | return; 1339 | } 1340 | const nextElementIndex = this._getItemIndex(nextElement); 1341 | const triggerEvent = eventName => { 1342 | return EventHandler.trigger(this._element, eventName, { 1343 | relatedTarget: nextElement, 1344 | direction: this._orderToDirection(order), 1345 | from: this._getItemIndex(activeElement), 1346 | to: nextElementIndex 1347 | }); 1348 | }; 1349 | const slideEvent = triggerEvent(EVENT_SLIDE); 1350 | if (slideEvent.defaultPrevented) { 1351 | return; 1352 | } 1353 | if (!activeElement || !nextElement) { 1354 | // Some weirdness is happening, so we bail 1355 | // todo: change tests that use empty divs to avoid this check 1356 | return; 1357 | } 1358 | const isCycling = Boolean(this._interval); 1359 | this.pause(); 1360 | this._isSliding = true; 1361 | this._setActiveIndicatorElement(nextElementIndex); 1362 | this._activeElement = nextElement; 1363 | const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; 1364 | const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; 1365 | nextElement.classList.add(orderClassName); 1366 | reflow(nextElement); 1367 | activeElement.classList.add(directionalClassName); 1368 | nextElement.classList.add(directionalClassName); 1369 | const completeCallBack = () => { 1370 | nextElement.classList.remove(directionalClassName, orderClassName); 1371 | nextElement.classList.add(CLASS_NAME_ACTIVE$2); 1372 | activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); 1373 | this._isSliding = false; 1374 | triggerEvent(EVENT_SLID); 1375 | }; 1376 | this._queueCallback(completeCallBack, activeElement, this._isAnimated()); 1377 | if (isCycling) { 1378 | this.cycle(); 1379 | } 1380 | } 1381 | _isAnimated() { 1382 | return this._element.classList.contains(CLASS_NAME_SLIDE); 1383 | } 1384 | _getActive() { 1385 | return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); 1386 | } 1387 | _getItems() { 1388 | return SelectorEngine.find(SELECTOR_ITEM, this._element); 1389 | } 1390 | _clearInterval() { 1391 | if (this._interval) { 1392 | clearInterval(this._interval); 1393 | this._interval = null; 1394 | } 1395 | } 1396 | _directionToOrder(direction) { 1397 | if (isRTL()) { 1398 | return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; 1399 | } 1400 | return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; 1401 | } 1402 | _orderToDirection(order) { 1403 | if (isRTL()) { 1404 | return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; 1405 | } 1406 | return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; 1407 | } 1408 | 1409 | // Static 1410 | static jQueryInterface(config) { 1411 | return this.each(function () { 1412 | const data = Carousel.getOrCreateInstance(this, config); 1413 | if (typeof config === 'number') { 1414 | data.to(config); 1415 | return; 1416 | } 1417 | if (typeof config === 'string') { 1418 | if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { 1419 | throw new TypeError(`No method named "${config}"`); 1420 | } 1421 | data[config](); 1422 | } 1423 | }); 1424 | } 1425 | } 1426 | 1427 | /** 1428 | * Data API implementation 1429 | */ 1430 | 1431 | EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) { 1432 | const target = SelectorEngine.getElementFromSelector(this); 1433 | if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { 1434 | return; 1435 | } 1436 | event.preventDefault(); 1437 | const carousel = Carousel.getOrCreateInstance(target); 1438 | const slideIndex = this.getAttribute('data-bs-slide-to'); 1439 | if (slideIndex) { 1440 | carousel.to(slideIndex); 1441 | carousel._maybeEnableCycle(); 1442 | return; 1443 | } 1444 | if (Manipulator.getDataAttribute(this, 'slide') === 'next') { 1445 | carousel.next(); 1446 | carousel._maybeEnableCycle(); 1447 | return; 1448 | } 1449 | carousel.prev(); 1450 | carousel._maybeEnableCycle(); 1451 | }); 1452 | EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => { 1453 | const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); 1454 | for (const carousel of carousels) { 1455 | Carousel.getOrCreateInstance(carousel); 1456 | } 1457 | }); 1458 | 1459 | /** 1460 | * jQuery 1461 | */ 1462 | 1463 | defineJQueryPlugin(Carousel); 1464 | 1465 | /** 1466 | * -------------------------------------------------------------------------- 1467 | * Bootstrap (v5.3.0-alpha1): collapse.js 1468 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 1469 | * -------------------------------------------------------------------------- 1470 | */ 1471 | 1472 | /** 1473 | * Constants 1474 | */ 1475 | 1476 | const NAME$b = 'collapse'; 1477 | const DATA_KEY$7 = 'bs.collapse'; 1478 | const EVENT_KEY$7 = `.${DATA_KEY$7}`; 1479 | const DATA_API_KEY$4 = '.data-api'; 1480 | const EVENT_SHOW$6 = `show${EVENT_KEY$7}`; 1481 | const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`; 1482 | const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`; 1483 | const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`; 1484 | const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`; 1485 | const CLASS_NAME_SHOW$7 = 'show'; 1486 | const CLASS_NAME_COLLAPSE = 'collapse'; 1487 | const CLASS_NAME_COLLAPSING = 'collapsing'; 1488 | const CLASS_NAME_COLLAPSED = 'collapsed'; 1489 | const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; 1490 | const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; 1491 | const WIDTH = 'width'; 1492 | const HEIGHT = 'height'; 1493 | const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; 1494 | const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; 1495 | const Default$a = { 1496 | parent: null, 1497 | toggle: true 1498 | }; 1499 | const DefaultType$a = { 1500 | parent: '(null|element)', 1501 | toggle: 'boolean' 1502 | }; 1503 | 1504 | /** 1505 | * Class definition 1506 | */ 1507 | 1508 | class Collapse extends BaseComponent { 1509 | constructor(element, config) { 1510 | super(element, config); 1511 | this._isTransitioning = false; 1512 | this._triggerArray = []; 1513 | const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); 1514 | for (const elem of toggleList) { 1515 | const selector = SelectorEngine.getSelectorFromElement(elem); 1516 | const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element); 1517 | if (selector !== null && filterElement.length) { 1518 | this._triggerArray.push(elem); 1519 | } 1520 | } 1521 | this._initializeChildren(); 1522 | if (!this._config.parent) { 1523 | this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); 1524 | } 1525 | if (this._config.toggle) { 1526 | this.toggle(); 1527 | } 1528 | } 1529 | 1530 | // Getters 1531 | static get Default() { 1532 | return Default$a; 1533 | } 1534 | static get DefaultType() { 1535 | return DefaultType$a; 1536 | } 1537 | static get NAME() { 1538 | return NAME$b; 1539 | } 1540 | 1541 | // Public 1542 | toggle() { 1543 | if (this._isShown()) { 1544 | this.hide(); 1545 | } else { 1546 | this.show(); 1547 | } 1548 | } 1549 | show() { 1550 | if (this._isTransitioning || this._isShown()) { 1551 | return; 1552 | } 1553 | let activeChildren = []; 1554 | 1555 | // find active children 1556 | if (this._config.parent) { 1557 | activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, { 1558 | toggle: false 1559 | })); 1560 | } 1561 | if (activeChildren.length && activeChildren[0]._isTransitioning) { 1562 | return; 1563 | } 1564 | const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6); 1565 | if (startEvent.defaultPrevented) { 1566 | return; 1567 | } 1568 | for (const activeInstance of activeChildren) { 1569 | activeInstance.hide(); 1570 | } 1571 | const dimension = this._getDimension(); 1572 | this._element.classList.remove(CLASS_NAME_COLLAPSE); 1573 | this._element.classList.add(CLASS_NAME_COLLAPSING); 1574 | this._element.style[dimension] = 0; 1575 | this._addAriaAndCollapsedClass(this._triggerArray, true); 1576 | this._isTransitioning = true; 1577 | const complete = () => { 1578 | this._isTransitioning = false; 1579 | this._element.classList.remove(CLASS_NAME_COLLAPSING); 1580 | this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); 1581 | this._element.style[dimension] = ''; 1582 | EventHandler.trigger(this._element, EVENT_SHOWN$6); 1583 | }; 1584 | const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); 1585 | const scrollSize = `scroll${capitalizedDimension}`; 1586 | this._queueCallback(complete, this._element, true); 1587 | this._element.style[dimension] = `${this._element[scrollSize]}px`; 1588 | } 1589 | hide() { 1590 | if (this._isTransitioning || !this._isShown()) { 1591 | return; 1592 | } 1593 | const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6); 1594 | if (startEvent.defaultPrevented) { 1595 | return; 1596 | } 1597 | const dimension = this._getDimension(); 1598 | this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; 1599 | reflow(this._element); 1600 | this._element.classList.add(CLASS_NAME_COLLAPSING); 1601 | this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); 1602 | for (const trigger of this._triggerArray) { 1603 | const element = SelectorEngine.getElementFromSelector(trigger); 1604 | if (element && !this._isShown(element)) { 1605 | this._addAriaAndCollapsedClass([trigger], false); 1606 | } 1607 | } 1608 | this._isTransitioning = true; 1609 | const complete = () => { 1610 | this._isTransitioning = false; 1611 | this._element.classList.remove(CLASS_NAME_COLLAPSING); 1612 | this._element.classList.add(CLASS_NAME_COLLAPSE); 1613 | EventHandler.trigger(this._element, EVENT_HIDDEN$6); 1614 | }; 1615 | this._element.style[dimension] = ''; 1616 | this._queueCallback(complete, this._element, true); 1617 | } 1618 | _isShown(element = this._element) { 1619 | return element.classList.contains(CLASS_NAME_SHOW$7); 1620 | } 1621 | 1622 | // Private 1623 | _configAfterMerge(config) { 1624 | config.toggle = Boolean(config.toggle); // Coerce string values 1625 | config.parent = getElement(config.parent); 1626 | return config; 1627 | } 1628 | _getDimension() { 1629 | return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; 1630 | } 1631 | _initializeChildren() { 1632 | if (!this._config.parent) { 1633 | return; 1634 | } 1635 | const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4); 1636 | for (const element of children) { 1637 | const selected = SelectorEngine.getElementFromSelector(element); 1638 | if (selected) { 1639 | this._addAriaAndCollapsedClass([element], this._isShown(selected)); 1640 | } 1641 | } 1642 | } 1643 | _getFirstLevelChildren(selector) { 1644 | const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); 1645 | // remove children if greater depth 1646 | return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)); 1647 | } 1648 | _addAriaAndCollapsedClass(triggerArray, isOpen) { 1649 | if (!triggerArray.length) { 1650 | return; 1651 | } 1652 | for (const element of triggerArray) { 1653 | element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen); 1654 | element.setAttribute('aria-expanded', isOpen); 1655 | } 1656 | } 1657 | 1658 | // Static 1659 | static jQueryInterface(config) { 1660 | const _config = {}; 1661 | if (typeof config === 'string' && /show|hide/.test(config)) { 1662 | _config.toggle = false; 1663 | } 1664 | return this.each(function () { 1665 | const data = Collapse.getOrCreateInstance(this, _config); 1666 | if (typeof config === 'string') { 1667 | if (typeof data[config] === 'undefined') { 1668 | throw new TypeError(`No method named "${config}"`); 1669 | } 1670 | data[config](); 1671 | } 1672 | }); 1673 | } 1674 | } 1675 | 1676 | /** 1677 | * Data API implementation 1678 | */ 1679 | 1680 | EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { 1681 | // preventDefault only for elements (which change the URL) not inside the collapsible element 1682 | if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { 1683 | event.preventDefault(); 1684 | } 1685 | for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { 1686 | Collapse.getOrCreateInstance(element, { 1687 | toggle: false 1688 | }).toggle(); 1689 | } 1690 | }); 1691 | 1692 | /** 1693 | * jQuery 1694 | */ 1695 | 1696 | defineJQueryPlugin(Collapse); 1697 | 1698 | /** 1699 | * -------------------------------------------------------------------------- 1700 | * Bootstrap (v5.3.0-alpha1): dropdown.js 1701 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 1702 | * -------------------------------------------------------------------------- 1703 | */ 1704 | 1705 | /** 1706 | * Constants 1707 | */ 1708 | 1709 | const NAME$a = 'dropdown'; 1710 | const DATA_KEY$6 = 'bs.dropdown'; 1711 | const EVENT_KEY$6 = `.${DATA_KEY$6}`; 1712 | const DATA_API_KEY$3 = '.data-api'; 1713 | const ESCAPE_KEY$2 = 'Escape'; 1714 | const TAB_KEY$1 = 'Tab'; 1715 | const ARROW_UP_KEY$1 = 'ArrowUp'; 1716 | const ARROW_DOWN_KEY$1 = 'ArrowDown'; 1717 | const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button 1718 | 1719 | const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`; 1720 | const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`; 1721 | const EVENT_SHOW$5 = `show${EVENT_KEY$6}`; 1722 | const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`; 1723 | const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; 1724 | const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`; 1725 | const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`; 1726 | const CLASS_NAME_SHOW$6 = 'show'; 1727 | const CLASS_NAME_DROPUP = 'dropup'; 1728 | const CLASS_NAME_DROPEND = 'dropend'; 1729 | const CLASS_NAME_DROPSTART = 'dropstart'; 1730 | const CLASS_NAME_DROPUP_CENTER = 'dropup-center'; 1731 | const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'; 1732 | const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'; 1733 | const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`; 1734 | const SELECTOR_MENU = '.dropdown-menu'; 1735 | const SELECTOR_NAVBAR = '.navbar'; 1736 | const SELECTOR_NAVBAR_NAV = '.navbar-nav'; 1737 | const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; 1738 | const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; 1739 | const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; 1740 | const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; 1741 | const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; 1742 | const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; 1743 | const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; 1744 | const PLACEMENT_TOPCENTER = 'top'; 1745 | const PLACEMENT_BOTTOMCENTER = 'bottom'; 1746 | const Default$9 = { 1747 | autoClose: true, 1748 | boundary: 'clippingParents', 1749 | display: 'dynamic', 1750 | offset: [0, 2], 1751 | popperConfig: null, 1752 | reference: 'toggle' 1753 | }; 1754 | const DefaultType$9 = { 1755 | autoClose: '(boolean|string)', 1756 | boundary: '(string|element)', 1757 | display: 'string', 1758 | offset: '(array|string|function)', 1759 | popperConfig: '(null|object|function)', 1760 | reference: '(string|element|object)' 1761 | }; 1762 | 1763 | /** 1764 | * Class definition 1765 | */ 1766 | 1767 | class Dropdown extends BaseComponent { 1768 | constructor(element, config) { 1769 | super(element, config); 1770 | this._popper = null; 1771 | this._parent = this._element.parentNode; // dropdown wrapper 1772 | // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ 1773 | this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent); 1774 | this._inNavbar = this._detectNavbar(); 1775 | } 1776 | 1777 | // Getters 1778 | static get Default() { 1779 | return Default$9; 1780 | } 1781 | static get DefaultType() { 1782 | return DefaultType$9; 1783 | } 1784 | static get NAME() { 1785 | return NAME$a; 1786 | } 1787 | 1788 | // Public 1789 | toggle() { 1790 | return this._isShown() ? this.hide() : this.show(); 1791 | } 1792 | show() { 1793 | if (isDisabled(this._element) || this._isShown()) { 1794 | return; 1795 | } 1796 | const relatedTarget = { 1797 | relatedTarget: this._element 1798 | }; 1799 | const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget); 1800 | if (showEvent.defaultPrevented) { 1801 | return; 1802 | } 1803 | this._createPopper(); 1804 | 1805 | // If this is a touch-enabled device we add extra 1806 | // empty mouseover listeners to the body's immediate children; 1807 | // only needed because of broken event delegation on iOS 1808 | // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html 1809 | if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { 1810 | for (const element of [].concat(...document.body.children)) { 1811 | EventHandler.on(element, 'mouseover', noop); 1812 | } 1813 | } 1814 | this._element.focus(); 1815 | this._element.setAttribute('aria-expanded', true); 1816 | this._menu.classList.add(CLASS_NAME_SHOW$6); 1817 | this._element.classList.add(CLASS_NAME_SHOW$6); 1818 | EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget); 1819 | } 1820 | hide() { 1821 | if (isDisabled(this._element) || !this._isShown()) { 1822 | return; 1823 | } 1824 | const relatedTarget = { 1825 | relatedTarget: this._element 1826 | }; 1827 | this._completeHide(relatedTarget); 1828 | } 1829 | dispose() { 1830 | if (this._popper) { 1831 | this._popper.destroy(); 1832 | } 1833 | super.dispose(); 1834 | } 1835 | update() { 1836 | this._inNavbar = this._detectNavbar(); 1837 | if (this._popper) { 1838 | this._popper.update(); 1839 | } 1840 | } 1841 | 1842 | // Private 1843 | _completeHide(relatedTarget) { 1844 | const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget); 1845 | if (hideEvent.defaultPrevented) { 1846 | return; 1847 | } 1848 | 1849 | // If this is a touch-enabled device we remove the extra 1850 | // empty mouseover listeners we added for iOS support 1851 | if ('ontouchstart' in document.documentElement) { 1852 | for (const element of [].concat(...document.body.children)) { 1853 | EventHandler.off(element, 'mouseover', noop); 1854 | } 1855 | } 1856 | if (this._popper) { 1857 | this._popper.destroy(); 1858 | } 1859 | this._menu.classList.remove(CLASS_NAME_SHOW$6); 1860 | this._element.classList.remove(CLASS_NAME_SHOW$6); 1861 | this._element.setAttribute('aria-expanded', 'false'); 1862 | Manipulator.removeDataAttribute(this._menu, 'popper'); 1863 | EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); 1864 | } 1865 | _getConfig(config) { 1866 | config = super._getConfig(config); 1867 | if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { 1868 | // Popper virtual elements require a getBoundingClientRect method 1869 | throw new TypeError(`${NAME$a.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); 1870 | } 1871 | return config; 1872 | } 1873 | _createPopper() { 1874 | if (typeof Popper__namespace === 'undefined') { 1875 | throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); 1876 | } 1877 | let referenceElement = this._element; 1878 | if (this._config.reference === 'parent') { 1879 | referenceElement = this._parent; 1880 | } else if (isElement(this._config.reference)) { 1881 | referenceElement = getElement(this._config.reference); 1882 | } else if (typeof this._config.reference === 'object') { 1883 | referenceElement = this._config.reference; 1884 | } 1885 | const popperConfig = this._getPopperConfig(); 1886 | this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig); 1887 | } 1888 | _isShown() { 1889 | return this._menu.classList.contains(CLASS_NAME_SHOW$6); 1890 | } 1891 | _getPlacement() { 1892 | const parentDropdown = this._parent; 1893 | if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { 1894 | return PLACEMENT_RIGHT; 1895 | } 1896 | if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { 1897 | return PLACEMENT_LEFT; 1898 | } 1899 | if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { 1900 | return PLACEMENT_TOPCENTER; 1901 | } 1902 | if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { 1903 | return PLACEMENT_BOTTOMCENTER; 1904 | } 1905 | 1906 | // We need to trim the value because custom properties can also include spaces 1907 | const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; 1908 | if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { 1909 | return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; 1910 | } 1911 | return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; 1912 | } 1913 | _detectNavbar() { 1914 | return this._element.closest(SELECTOR_NAVBAR) !== null; 1915 | } 1916 | _getOffset() { 1917 | const { 1918 | offset 1919 | } = this._config; 1920 | if (typeof offset === 'string') { 1921 | return offset.split(',').map(value => Number.parseInt(value, 10)); 1922 | } 1923 | if (typeof offset === 'function') { 1924 | return popperData => offset(popperData, this._element); 1925 | } 1926 | return offset; 1927 | } 1928 | _getPopperConfig() { 1929 | const defaultBsPopperConfig = { 1930 | placement: this._getPlacement(), 1931 | modifiers: [{ 1932 | name: 'preventOverflow', 1933 | options: { 1934 | boundary: this._config.boundary 1935 | } 1936 | }, { 1937 | name: 'offset', 1938 | options: { 1939 | offset: this._getOffset() 1940 | } 1941 | }] 1942 | }; 1943 | 1944 | // Disable Popper if we have a static display or Dropdown is in Navbar 1945 | if (this._inNavbar || this._config.display === 'static') { 1946 | Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove 1947 | defaultBsPopperConfig.modifiers = [{ 1948 | name: 'applyStyles', 1949 | enabled: false 1950 | }]; 1951 | } 1952 | return { 1953 | ...defaultBsPopperConfig, 1954 | ...execute(this._config.popperConfig, [defaultBsPopperConfig]) 1955 | }; 1956 | } 1957 | _selectMenuItem({ 1958 | key, 1959 | target 1960 | }) { 1961 | const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)); 1962 | if (!items.length) { 1963 | return; 1964 | } 1965 | 1966 | // if target isn't included in items (e.g. when expanding the dropdown) 1967 | // allow cycling to get the last item in case key equals ARROW_UP_KEY 1968 | getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); 1969 | } 1970 | 1971 | // Static 1972 | static jQueryInterface(config) { 1973 | return this.each(function () { 1974 | const data = Dropdown.getOrCreateInstance(this, config); 1975 | if (typeof config !== 'string') { 1976 | return; 1977 | } 1978 | if (typeof data[config] === 'undefined') { 1979 | throw new TypeError(`No method named "${config}"`); 1980 | } 1981 | data[config](); 1982 | }); 1983 | } 1984 | static clearMenus(event) { 1985 | if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) { 1986 | return; 1987 | } 1988 | const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN); 1989 | for (const toggle of openToggles) { 1990 | const context = Dropdown.getInstance(toggle); 1991 | if (!context || context._config.autoClose === false) { 1992 | continue; 1993 | } 1994 | const composedPath = event.composedPath(); 1995 | const isMenuTarget = composedPath.includes(context._menu); 1996 | if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { 1997 | continue; 1998 | } 1999 | 2000 | // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu 2001 | if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { 2002 | continue; 2003 | } 2004 | const relatedTarget = { 2005 | relatedTarget: context._element 2006 | }; 2007 | if (event.type === 'click') { 2008 | relatedTarget.clickEvent = event; 2009 | } 2010 | context._completeHide(relatedTarget); 2011 | } 2012 | } 2013 | static dataApiKeydownHandler(event) { 2014 | // If not an UP | DOWN | ESCAPE key => not a dropdown command 2015 | // If input/textarea && if key is other than ESCAPE => not a dropdown command 2016 | 2017 | const isInput = /input|textarea/i.test(event.target.tagName); 2018 | const isEscapeEvent = event.key === ESCAPE_KEY$2; 2019 | const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key); 2020 | if (!isUpOrDownEvent && !isEscapeEvent) { 2021 | return; 2022 | } 2023 | if (isInput && !isEscapeEvent) { 2024 | return; 2025 | } 2026 | event.preventDefault(); 2027 | 2028 | // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ 2029 | const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode); 2030 | const instance = Dropdown.getOrCreateInstance(getToggleButton); 2031 | if (isUpOrDownEvent) { 2032 | event.stopPropagation(); 2033 | instance.show(); 2034 | instance._selectMenuItem(event); 2035 | return; 2036 | } 2037 | if (instance._isShown()) { 2038 | // else is escape and we check if it is shown 2039 | event.stopPropagation(); 2040 | instance.hide(); 2041 | getToggleButton.focus(); 2042 | } 2043 | } 2044 | } 2045 | 2046 | /** 2047 | * Data API implementation 2048 | */ 2049 | 2050 | EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); 2051 | EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); 2052 | EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); 2053 | EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); 2054 | EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { 2055 | event.preventDefault(); 2056 | Dropdown.getOrCreateInstance(this).toggle(); 2057 | }); 2058 | 2059 | /** 2060 | * jQuery 2061 | */ 2062 | 2063 | defineJQueryPlugin(Dropdown); 2064 | 2065 | /** 2066 | * -------------------------------------------------------------------------- 2067 | * Bootstrap (v5.3.0-alpha1): util/scrollBar.js 2068 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2069 | * -------------------------------------------------------------------------- 2070 | */ 2071 | 2072 | /** 2073 | * Constants 2074 | */ 2075 | 2076 | const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; 2077 | const SELECTOR_STICKY_CONTENT = '.sticky-top'; 2078 | const PROPERTY_PADDING = 'padding-right'; 2079 | const PROPERTY_MARGIN = 'margin-right'; 2080 | 2081 | /** 2082 | * Class definition 2083 | */ 2084 | 2085 | class ScrollBarHelper { 2086 | constructor() { 2087 | this._element = document.body; 2088 | } 2089 | 2090 | // Public 2091 | getWidth() { 2092 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes 2093 | const documentWidth = document.documentElement.clientWidth; 2094 | return Math.abs(window.innerWidth - documentWidth); 2095 | } 2096 | hide() { 2097 | const width = this.getWidth(); 2098 | this._disableOverFlow(); 2099 | // give padding to element to balance the hidden scrollbar width 2100 | this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); 2101 | // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth 2102 | this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); 2103 | this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); 2104 | } 2105 | reset() { 2106 | this._resetElementAttributes(this._element, 'overflow'); 2107 | this._resetElementAttributes(this._element, PROPERTY_PADDING); 2108 | this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); 2109 | this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); 2110 | } 2111 | isOverflowing() { 2112 | return this.getWidth() > 0; 2113 | } 2114 | 2115 | // Private 2116 | _disableOverFlow() { 2117 | this._saveInitialAttribute(this._element, 'overflow'); 2118 | this._element.style.overflow = 'hidden'; 2119 | } 2120 | _setElementAttributes(selector, styleProperty, callback) { 2121 | const scrollbarWidth = this.getWidth(); 2122 | const manipulationCallBack = element => { 2123 | if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { 2124 | return; 2125 | } 2126 | this._saveInitialAttribute(element, styleProperty); 2127 | const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); 2128 | element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); 2129 | }; 2130 | this._applyManipulationCallback(selector, manipulationCallBack); 2131 | } 2132 | _saveInitialAttribute(element, styleProperty) { 2133 | const actualValue = element.style.getPropertyValue(styleProperty); 2134 | if (actualValue) { 2135 | Manipulator.setDataAttribute(element, styleProperty, actualValue); 2136 | } 2137 | } 2138 | _resetElementAttributes(selector, styleProperty) { 2139 | const manipulationCallBack = element => { 2140 | const value = Manipulator.getDataAttribute(element, styleProperty); 2141 | // We only want to remove the property if the value is `null`; the value can also be zero 2142 | if (value === null) { 2143 | element.style.removeProperty(styleProperty); 2144 | return; 2145 | } 2146 | Manipulator.removeDataAttribute(element, styleProperty); 2147 | element.style.setProperty(styleProperty, value); 2148 | }; 2149 | this._applyManipulationCallback(selector, manipulationCallBack); 2150 | } 2151 | _applyManipulationCallback(selector, callBack) { 2152 | if (isElement(selector)) { 2153 | callBack(selector); 2154 | return; 2155 | } 2156 | for (const sel of SelectorEngine.find(selector, this._element)) { 2157 | callBack(sel); 2158 | } 2159 | } 2160 | } 2161 | 2162 | /** 2163 | * -------------------------------------------------------------------------- 2164 | * Bootstrap (v5.3.0-alpha1): util/backdrop.js 2165 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2166 | * -------------------------------------------------------------------------- 2167 | */ 2168 | 2169 | /** 2170 | * Constants 2171 | */ 2172 | 2173 | const NAME$9 = 'backdrop'; 2174 | const CLASS_NAME_FADE$4 = 'fade'; 2175 | const CLASS_NAME_SHOW$5 = 'show'; 2176 | const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`; 2177 | const Default$8 = { 2178 | className: 'modal-backdrop', 2179 | clickCallback: null, 2180 | isAnimated: false, 2181 | isVisible: true, 2182 | // if false, we use the backdrop helper without adding any element to the dom 2183 | rootElement: 'body' // give the choice to place backdrop under different elements 2184 | }; 2185 | 2186 | const DefaultType$8 = { 2187 | className: 'string', 2188 | clickCallback: '(function|null)', 2189 | isAnimated: 'boolean', 2190 | isVisible: 'boolean', 2191 | rootElement: '(element|string)' 2192 | }; 2193 | 2194 | /** 2195 | * Class definition 2196 | */ 2197 | 2198 | class Backdrop extends Config { 2199 | constructor(config) { 2200 | super(); 2201 | this._config = this._getConfig(config); 2202 | this._isAppended = false; 2203 | this._element = null; 2204 | } 2205 | 2206 | // Getters 2207 | static get Default() { 2208 | return Default$8; 2209 | } 2210 | static get DefaultType() { 2211 | return DefaultType$8; 2212 | } 2213 | static get NAME() { 2214 | return NAME$9; 2215 | } 2216 | 2217 | // Public 2218 | show(callback) { 2219 | if (!this._config.isVisible) { 2220 | execute(callback); 2221 | return; 2222 | } 2223 | this._append(); 2224 | const element = this._getElement(); 2225 | if (this._config.isAnimated) { 2226 | reflow(element); 2227 | } 2228 | element.classList.add(CLASS_NAME_SHOW$5); 2229 | this._emulateAnimation(() => { 2230 | execute(callback); 2231 | }); 2232 | } 2233 | hide(callback) { 2234 | if (!this._config.isVisible) { 2235 | execute(callback); 2236 | return; 2237 | } 2238 | this._getElement().classList.remove(CLASS_NAME_SHOW$5); 2239 | this._emulateAnimation(() => { 2240 | this.dispose(); 2241 | execute(callback); 2242 | }); 2243 | } 2244 | dispose() { 2245 | if (!this._isAppended) { 2246 | return; 2247 | } 2248 | EventHandler.off(this._element, EVENT_MOUSEDOWN); 2249 | this._element.remove(); 2250 | this._isAppended = false; 2251 | } 2252 | 2253 | // Private 2254 | _getElement() { 2255 | if (!this._element) { 2256 | const backdrop = document.createElement('div'); 2257 | backdrop.className = this._config.className; 2258 | if (this._config.isAnimated) { 2259 | backdrop.classList.add(CLASS_NAME_FADE$4); 2260 | } 2261 | this._element = backdrop; 2262 | } 2263 | return this._element; 2264 | } 2265 | _configAfterMerge(config) { 2266 | // use getElement() with the default "body" to get a fresh Element on each instantiation 2267 | config.rootElement = getElement(config.rootElement); 2268 | return config; 2269 | } 2270 | _append() { 2271 | if (this._isAppended) { 2272 | return; 2273 | } 2274 | const element = this._getElement(); 2275 | this._config.rootElement.append(element); 2276 | EventHandler.on(element, EVENT_MOUSEDOWN, () => { 2277 | execute(this._config.clickCallback); 2278 | }); 2279 | this._isAppended = true; 2280 | } 2281 | _emulateAnimation(callback) { 2282 | executeAfterTransition(callback, this._getElement(), this._config.isAnimated); 2283 | } 2284 | } 2285 | 2286 | /** 2287 | * -------------------------------------------------------------------------- 2288 | * Bootstrap (v5.3.0-alpha1): util/focustrap.js 2289 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2290 | * -------------------------------------------------------------------------- 2291 | */ 2292 | 2293 | /** 2294 | * Constants 2295 | */ 2296 | 2297 | const NAME$8 = 'focustrap'; 2298 | const DATA_KEY$5 = 'bs.focustrap'; 2299 | const EVENT_KEY$5 = `.${DATA_KEY$5}`; 2300 | const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`; 2301 | const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`; 2302 | const TAB_KEY = 'Tab'; 2303 | const TAB_NAV_FORWARD = 'forward'; 2304 | const TAB_NAV_BACKWARD = 'backward'; 2305 | const Default$7 = { 2306 | autofocus: true, 2307 | trapElement: null // The element to trap focus inside of 2308 | }; 2309 | 2310 | const DefaultType$7 = { 2311 | autofocus: 'boolean', 2312 | trapElement: 'element' 2313 | }; 2314 | 2315 | /** 2316 | * Class definition 2317 | */ 2318 | 2319 | class FocusTrap extends Config { 2320 | constructor(config) { 2321 | super(); 2322 | this._config = this._getConfig(config); 2323 | this._isActive = false; 2324 | this._lastTabNavDirection = null; 2325 | } 2326 | 2327 | // Getters 2328 | static get Default() { 2329 | return Default$7; 2330 | } 2331 | static get DefaultType() { 2332 | return DefaultType$7; 2333 | } 2334 | static get NAME() { 2335 | return NAME$8; 2336 | } 2337 | 2338 | // Public 2339 | activate() { 2340 | if (this._isActive) { 2341 | return; 2342 | } 2343 | if (this._config.autofocus) { 2344 | this._config.trapElement.focus(); 2345 | } 2346 | EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop 2347 | EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event)); 2348 | EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); 2349 | this._isActive = true; 2350 | } 2351 | deactivate() { 2352 | if (!this._isActive) { 2353 | return; 2354 | } 2355 | this._isActive = false; 2356 | EventHandler.off(document, EVENT_KEY$5); 2357 | } 2358 | 2359 | // Private 2360 | _handleFocusin(event) { 2361 | const { 2362 | trapElement 2363 | } = this._config; 2364 | if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { 2365 | return; 2366 | } 2367 | const elements = SelectorEngine.focusableChildren(trapElement); 2368 | if (elements.length === 0) { 2369 | trapElement.focus(); 2370 | } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { 2371 | elements[elements.length - 1].focus(); 2372 | } else { 2373 | elements[0].focus(); 2374 | } 2375 | } 2376 | _handleKeydown(event) { 2377 | if (event.key !== TAB_KEY) { 2378 | return; 2379 | } 2380 | this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; 2381 | } 2382 | } 2383 | 2384 | /** 2385 | * -------------------------------------------------------------------------- 2386 | * Bootstrap (v5.3.0-alpha1): modal.js 2387 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2388 | * -------------------------------------------------------------------------- 2389 | */ 2390 | 2391 | /** 2392 | * Constants 2393 | */ 2394 | 2395 | const NAME$7 = 'modal'; 2396 | const DATA_KEY$4 = 'bs.modal'; 2397 | const EVENT_KEY$4 = `.${DATA_KEY$4}`; 2398 | const DATA_API_KEY$2 = '.data-api'; 2399 | const ESCAPE_KEY$1 = 'Escape'; 2400 | const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`; 2401 | const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`; 2402 | const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`; 2403 | const EVENT_SHOW$4 = `show${EVENT_KEY$4}`; 2404 | const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`; 2405 | const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`; 2406 | const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`; 2407 | const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`; 2408 | const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`; 2409 | const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`; 2410 | const CLASS_NAME_OPEN = 'modal-open'; 2411 | const CLASS_NAME_FADE$3 = 'fade'; 2412 | const CLASS_NAME_SHOW$4 = 'show'; 2413 | const CLASS_NAME_STATIC = 'modal-static'; 2414 | const OPEN_SELECTOR$1 = '.modal.show'; 2415 | const SELECTOR_DIALOG = '.modal-dialog'; 2416 | const SELECTOR_MODAL_BODY = '.modal-body'; 2417 | const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; 2418 | const Default$6 = { 2419 | backdrop: true, 2420 | focus: true, 2421 | keyboard: true 2422 | }; 2423 | const DefaultType$6 = { 2424 | backdrop: '(boolean|string)', 2425 | focus: 'boolean', 2426 | keyboard: 'boolean' 2427 | }; 2428 | 2429 | /** 2430 | * Class definition 2431 | */ 2432 | 2433 | class Modal extends BaseComponent { 2434 | constructor(element, config) { 2435 | super(element, config); 2436 | this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); 2437 | this._backdrop = this._initializeBackDrop(); 2438 | this._focustrap = this._initializeFocusTrap(); 2439 | this._isShown = false; 2440 | this._isTransitioning = false; 2441 | this._scrollBar = new ScrollBarHelper(); 2442 | this._addEventListeners(); 2443 | } 2444 | 2445 | // Getters 2446 | static get Default() { 2447 | return Default$6; 2448 | } 2449 | static get DefaultType() { 2450 | return DefaultType$6; 2451 | } 2452 | static get NAME() { 2453 | return NAME$7; 2454 | } 2455 | 2456 | // Public 2457 | toggle(relatedTarget) { 2458 | return this._isShown ? this.hide() : this.show(relatedTarget); 2459 | } 2460 | show(relatedTarget) { 2461 | if (this._isShown || this._isTransitioning) { 2462 | return; 2463 | } 2464 | const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, { 2465 | relatedTarget 2466 | }); 2467 | if (showEvent.defaultPrevented) { 2468 | return; 2469 | } 2470 | this._isShown = true; 2471 | this._isTransitioning = true; 2472 | this._scrollBar.hide(); 2473 | document.body.classList.add(CLASS_NAME_OPEN); 2474 | this._adjustDialog(); 2475 | this._backdrop.show(() => this._showElement(relatedTarget)); 2476 | } 2477 | hide() { 2478 | if (!this._isShown || this._isTransitioning) { 2479 | return; 2480 | } 2481 | const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4); 2482 | if (hideEvent.defaultPrevented) { 2483 | return; 2484 | } 2485 | this._isShown = false; 2486 | this._isTransitioning = true; 2487 | this._focustrap.deactivate(); 2488 | this._element.classList.remove(CLASS_NAME_SHOW$4); 2489 | this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); 2490 | } 2491 | dispose() { 2492 | for (const htmlElement of [window, this._dialog]) { 2493 | EventHandler.off(htmlElement, EVENT_KEY$4); 2494 | } 2495 | this._backdrop.dispose(); 2496 | this._focustrap.deactivate(); 2497 | super.dispose(); 2498 | } 2499 | handleUpdate() { 2500 | this._adjustDialog(); 2501 | } 2502 | 2503 | // Private 2504 | _initializeBackDrop() { 2505 | return new Backdrop({ 2506 | isVisible: Boolean(this._config.backdrop), 2507 | // 'static' option will be translated to true, and booleans will keep their value, 2508 | isAnimated: this._isAnimated() 2509 | }); 2510 | } 2511 | _initializeFocusTrap() { 2512 | return new FocusTrap({ 2513 | trapElement: this._element 2514 | }); 2515 | } 2516 | _showElement(relatedTarget) { 2517 | // try to append dynamic modal 2518 | if (!document.body.contains(this._element)) { 2519 | document.body.append(this._element); 2520 | } 2521 | this._element.style.display = 'block'; 2522 | this._element.removeAttribute('aria-hidden'); 2523 | this._element.setAttribute('aria-modal', true); 2524 | this._element.setAttribute('role', 'dialog'); 2525 | this._element.scrollTop = 0; 2526 | const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); 2527 | if (modalBody) { 2528 | modalBody.scrollTop = 0; 2529 | } 2530 | reflow(this._element); 2531 | this._element.classList.add(CLASS_NAME_SHOW$4); 2532 | const transitionComplete = () => { 2533 | if (this._config.focus) { 2534 | this._focustrap.activate(); 2535 | } 2536 | this._isTransitioning = false; 2537 | EventHandler.trigger(this._element, EVENT_SHOWN$4, { 2538 | relatedTarget 2539 | }); 2540 | }; 2541 | this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); 2542 | } 2543 | _addEventListeners() { 2544 | EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { 2545 | if (event.key !== ESCAPE_KEY$1) { 2546 | return; 2547 | } 2548 | if (this._config.keyboard) { 2549 | event.preventDefault(); 2550 | this.hide(); 2551 | return; 2552 | } 2553 | this._triggerBackdropTransition(); 2554 | }); 2555 | EventHandler.on(window, EVENT_RESIZE$1, () => { 2556 | if (this._isShown && !this._isTransitioning) { 2557 | this._adjustDialog(); 2558 | } 2559 | }); 2560 | EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { 2561 | // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks 2562 | EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { 2563 | if (this._element !== event.target || this._element !== event2.target) { 2564 | return; 2565 | } 2566 | if (this._config.backdrop === 'static') { 2567 | this._triggerBackdropTransition(); 2568 | return; 2569 | } 2570 | if (this._config.backdrop) { 2571 | this.hide(); 2572 | } 2573 | }); 2574 | }); 2575 | } 2576 | _hideModal() { 2577 | this._element.style.display = 'none'; 2578 | this._element.setAttribute('aria-hidden', true); 2579 | this._element.removeAttribute('aria-modal'); 2580 | this._element.removeAttribute('role'); 2581 | this._isTransitioning = false; 2582 | this._backdrop.hide(() => { 2583 | document.body.classList.remove(CLASS_NAME_OPEN); 2584 | this._resetAdjustments(); 2585 | this._scrollBar.reset(); 2586 | EventHandler.trigger(this._element, EVENT_HIDDEN$4); 2587 | }); 2588 | } 2589 | _isAnimated() { 2590 | return this._element.classList.contains(CLASS_NAME_FADE$3); 2591 | } 2592 | _triggerBackdropTransition() { 2593 | const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1); 2594 | if (hideEvent.defaultPrevented) { 2595 | return; 2596 | } 2597 | const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; 2598 | const initialOverflowY = this._element.style.overflowY; 2599 | // return if the following background transition hasn't yet completed 2600 | if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { 2601 | return; 2602 | } 2603 | if (!isModalOverflowing) { 2604 | this._element.style.overflowY = 'hidden'; 2605 | } 2606 | this._element.classList.add(CLASS_NAME_STATIC); 2607 | this._queueCallback(() => { 2608 | this._element.classList.remove(CLASS_NAME_STATIC); 2609 | this._queueCallback(() => { 2610 | this._element.style.overflowY = initialOverflowY; 2611 | }, this._dialog); 2612 | }, this._dialog); 2613 | this._element.focus(); 2614 | } 2615 | 2616 | /** 2617 | * The following methods are used to handle overflowing modals 2618 | */ 2619 | 2620 | _adjustDialog() { 2621 | const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; 2622 | const scrollbarWidth = this._scrollBar.getWidth(); 2623 | const isBodyOverflowing = scrollbarWidth > 0; 2624 | if (isBodyOverflowing && !isModalOverflowing) { 2625 | const property = isRTL() ? 'paddingLeft' : 'paddingRight'; 2626 | this._element.style[property] = `${scrollbarWidth}px`; 2627 | } 2628 | if (!isBodyOverflowing && isModalOverflowing) { 2629 | const property = isRTL() ? 'paddingRight' : 'paddingLeft'; 2630 | this._element.style[property] = `${scrollbarWidth}px`; 2631 | } 2632 | } 2633 | _resetAdjustments() { 2634 | this._element.style.paddingLeft = ''; 2635 | this._element.style.paddingRight = ''; 2636 | } 2637 | 2638 | // Static 2639 | static jQueryInterface(config, relatedTarget) { 2640 | return this.each(function () { 2641 | const data = Modal.getOrCreateInstance(this, config); 2642 | if (typeof config !== 'string') { 2643 | return; 2644 | } 2645 | if (typeof data[config] === 'undefined') { 2646 | throw new TypeError(`No method named "${config}"`); 2647 | } 2648 | data[config](relatedTarget); 2649 | }); 2650 | } 2651 | } 2652 | 2653 | /** 2654 | * Data API implementation 2655 | */ 2656 | 2657 | EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { 2658 | const target = SelectorEngine.getElementFromSelector(this); 2659 | if (['A', 'AREA'].includes(this.tagName)) { 2660 | event.preventDefault(); 2661 | } 2662 | EventHandler.one(target, EVENT_SHOW$4, showEvent => { 2663 | if (showEvent.defaultPrevented) { 2664 | // only register focus restorer if modal will actually get shown 2665 | return; 2666 | } 2667 | EventHandler.one(target, EVENT_HIDDEN$4, () => { 2668 | if (isVisible(this)) { 2669 | this.focus(); 2670 | } 2671 | }); 2672 | }); 2673 | 2674 | // avoid conflict when clicking modal toggler while another one is open 2675 | const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); 2676 | if (alreadyOpen) { 2677 | Modal.getInstance(alreadyOpen).hide(); 2678 | } 2679 | const data = Modal.getOrCreateInstance(target); 2680 | data.toggle(this); 2681 | }); 2682 | enableDismissTrigger(Modal); 2683 | 2684 | /** 2685 | * jQuery 2686 | */ 2687 | 2688 | defineJQueryPlugin(Modal); 2689 | 2690 | /** 2691 | * -------------------------------------------------------------------------- 2692 | * Bootstrap (v5.3.0-alpha1): offcanvas.js 2693 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2694 | * -------------------------------------------------------------------------- 2695 | */ 2696 | 2697 | /** 2698 | * Constants 2699 | */ 2700 | 2701 | const NAME$6 = 'offcanvas'; 2702 | const DATA_KEY$3 = 'bs.offcanvas'; 2703 | const EVENT_KEY$3 = `.${DATA_KEY$3}`; 2704 | const DATA_API_KEY$1 = '.data-api'; 2705 | const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`; 2706 | const ESCAPE_KEY = 'Escape'; 2707 | const CLASS_NAME_SHOW$3 = 'show'; 2708 | const CLASS_NAME_SHOWING$1 = 'showing'; 2709 | const CLASS_NAME_HIDING = 'hiding'; 2710 | const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; 2711 | const OPEN_SELECTOR = '.offcanvas.show'; 2712 | const EVENT_SHOW$3 = `show${EVENT_KEY$3}`; 2713 | const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`; 2714 | const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`; 2715 | const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`; 2716 | const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`; 2717 | const EVENT_RESIZE = `resize${EVENT_KEY$3}`; 2718 | const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`; 2719 | const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`; 2720 | const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; 2721 | const Default$5 = { 2722 | backdrop: true, 2723 | keyboard: true, 2724 | scroll: false 2725 | }; 2726 | const DefaultType$5 = { 2727 | backdrop: '(boolean|string)', 2728 | keyboard: 'boolean', 2729 | scroll: 'boolean' 2730 | }; 2731 | 2732 | /** 2733 | * Class definition 2734 | */ 2735 | 2736 | class Offcanvas extends BaseComponent { 2737 | constructor(element, config) { 2738 | super(element, config); 2739 | this._isShown = false; 2740 | this._backdrop = this._initializeBackDrop(); 2741 | this._focustrap = this._initializeFocusTrap(); 2742 | this._addEventListeners(); 2743 | } 2744 | 2745 | // Getters 2746 | static get Default() { 2747 | return Default$5; 2748 | } 2749 | static get DefaultType() { 2750 | return DefaultType$5; 2751 | } 2752 | static get NAME() { 2753 | return NAME$6; 2754 | } 2755 | 2756 | // Public 2757 | toggle(relatedTarget) { 2758 | return this._isShown ? this.hide() : this.show(relatedTarget); 2759 | } 2760 | show(relatedTarget) { 2761 | if (this._isShown) { 2762 | return; 2763 | } 2764 | const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { 2765 | relatedTarget 2766 | }); 2767 | if (showEvent.defaultPrevented) { 2768 | return; 2769 | } 2770 | this._isShown = true; 2771 | this._backdrop.show(); 2772 | if (!this._config.scroll) { 2773 | new ScrollBarHelper().hide(); 2774 | } 2775 | this._element.setAttribute('aria-modal', true); 2776 | this._element.setAttribute('role', 'dialog'); 2777 | this._element.classList.add(CLASS_NAME_SHOWING$1); 2778 | const completeCallBack = () => { 2779 | if (!this._config.scroll || this._config.backdrop) { 2780 | this._focustrap.activate(); 2781 | } 2782 | this._element.classList.add(CLASS_NAME_SHOW$3); 2783 | this._element.classList.remove(CLASS_NAME_SHOWING$1); 2784 | EventHandler.trigger(this._element, EVENT_SHOWN$3, { 2785 | relatedTarget 2786 | }); 2787 | }; 2788 | this._queueCallback(completeCallBack, this._element, true); 2789 | } 2790 | hide() { 2791 | if (!this._isShown) { 2792 | return; 2793 | } 2794 | const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); 2795 | if (hideEvent.defaultPrevented) { 2796 | return; 2797 | } 2798 | this._focustrap.deactivate(); 2799 | this._element.blur(); 2800 | this._isShown = false; 2801 | this._element.classList.add(CLASS_NAME_HIDING); 2802 | this._backdrop.hide(); 2803 | const completeCallback = () => { 2804 | this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING); 2805 | this._element.removeAttribute('aria-modal'); 2806 | this._element.removeAttribute('role'); 2807 | if (!this._config.scroll) { 2808 | new ScrollBarHelper().reset(); 2809 | } 2810 | EventHandler.trigger(this._element, EVENT_HIDDEN$3); 2811 | }; 2812 | this._queueCallback(completeCallback, this._element, true); 2813 | } 2814 | dispose() { 2815 | this._backdrop.dispose(); 2816 | this._focustrap.deactivate(); 2817 | super.dispose(); 2818 | } 2819 | 2820 | // Private 2821 | _initializeBackDrop() { 2822 | const clickCallback = () => { 2823 | if (this._config.backdrop === 'static') { 2824 | EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); 2825 | return; 2826 | } 2827 | this.hide(); 2828 | }; 2829 | 2830 | // 'static' option will be translated to true, and booleans will keep their value 2831 | const isVisible = Boolean(this._config.backdrop); 2832 | return new Backdrop({ 2833 | className: CLASS_NAME_BACKDROP, 2834 | isVisible, 2835 | isAnimated: true, 2836 | rootElement: this._element.parentNode, 2837 | clickCallback: isVisible ? clickCallback : null 2838 | }); 2839 | } 2840 | _initializeFocusTrap() { 2841 | return new FocusTrap({ 2842 | trapElement: this._element 2843 | }); 2844 | } 2845 | _addEventListeners() { 2846 | EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { 2847 | if (event.key !== ESCAPE_KEY) { 2848 | return; 2849 | } 2850 | if (!this._config.keyboard) { 2851 | EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); 2852 | return; 2853 | } 2854 | this.hide(); 2855 | }); 2856 | } 2857 | 2858 | // Static 2859 | static jQueryInterface(config) { 2860 | return this.each(function () { 2861 | const data = Offcanvas.getOrCreateInstance(this, config); 2862 | if (typeof config !== 'string') { 2863 | return; 2864 | } 2865 | if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { 2866 | throw new TypeError(`No method named "${config}"`); 2867 | } 2868 | data[config](this); 2869 | }); 2870 | } 2871 | } 2872 | 2873 | /** 2874 | * Data API implementation 2875 | */ 2876 | 2877 | EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { 2878 | const target = SelectorEngine.getElementFromSelector(this); 2879 | if (['A', 'AREA'].includes(this.tagName)) { 2880 | event.preventDefault(); 2881 | } 2882 | if (isDisabled(this)) { 2883 | return; 2884 | } 2885 | EventHandler.one(target, EVENT_HIDDEN$3, () => { 2886 | // focus on trigger when it is closed 2887 | if (isVisible(this)) { 2888 | this.focus(); 2889 | } 2890 | }); 2891 | 2892 | // avoid conflict when clicking a toggler of an offcanvas, while another is open 2893 | const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); 2894 | if (alreadyOpen && alreadyOpen !== target) { 2895 | Offcanvas.getInstance(alreadyOpen).hide(); 2896 | } 2897 | const data = Offcanvas.getOrCreateInstance(target); 2898 | data.toggle(this); 2899 | }); 2900 | EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { 2901 | for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { 2902 | Offcanvas.getOrCreateInstance(selector).show(); 2903 | } 2904 | }); 2905 | EventHandler.on(window, EVENT_RESIZE, () => { 2906 | for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) { 2907 | if (getComputedStyle(element).position !== 'fixed') { 2908 | Offcanvas.getOrCreateInstance(element).hide(); 2909 | } 2910 | } 2911 | }); 2912 | enableDismissTrigger(Offcanvas); 2913 | 2914 | /** 2915 | * jQuery 2916 | */ 2917 | 2918 | defineJQueryPlugin(Offcanvas); 2919 | 2920 | /** 2921 | * -------------------------------------------------------------------------- 2922 | * Bootstrap (v5.3.0-alpha1): util/sanitizer.js 2923 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 2924 | * -------------------------------------------------------------------------- 2925 | */ 2926 | 2927 | const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); 2928 | const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; 2929 | 2930 | /** 2931 | * A pattern that recognizes a commonly useful subset of URLs that are safe. 2932 | * 2933 | * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts 2934 | */ 2935 | const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i; 2936 | 2937 | /** 2938 | * A pattern that matches safe data URLs. Only matches image, video and audio types. 2939 | * 2940 | * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts 2941 | */ 2942 | const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i; 2943 | const allowedAttribute = (attribute, allowedAttributeList) => { 2944 | const attributeName = attribute.nodeName.toLowerCase(); 2945 | if (allowedAttributeList.includes(attributeName)) { 2946 | if (uriAttributes.has(attributeName)) { 2947 | return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)); 2948 | } 2949 | return true; 2950 | } 2951 | 2952 | // Check if a regular expression validates the attribute. 2953 | return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); 2954 | }; 2955 | const DefaultAllowlist = { 2956 | // Global attributes allowed on any supplied element below. 2957 | '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], 2958 | a: ['target', 'href', 'title', 'rel'], 2959 | area: [], 2960 | b: [], 2961 | br: [], 2962 | col: [], 2963 | code: [], 2964 | div: [], 2965 | em: [], 2966 | hr: [], 2967 | h1: [], 2968 | h2: [], 2969 | h3: [], 2970 | h4: [], 2971 | h5: [], 2972 | h6: [], 2973 | i: [], 2974 | img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], 2975 | li: [], 2976 | ol: [], 2977 | p: [], 2978 | pre: [], 2979 | s: [], 2980 | small: [], 2981 | span: [], 2982 | sub: [], 2983 | sup: [], 2984 | strong: [], 2985 | u: [], 2986 | ul: [] 2987 | }; 2988 | function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { 2989 | if (!unsafeHtml.length) { 2990 | return unsafeHtml; 2991 | } 2992 | if (sanitizeFunction && typeof sanitizeFunction === 'function') { 2993 | return sanitizeFunction(unsafeHtml); 2994 | } 2995 | const domParser = new window.DOMParser(); 2996 | const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); 2997 | const elements = [].concat(...createdDocument.body.querySelectorAll('*')); 2998 | for (const element of elements) { 2999 | const elementName = element.nodeName.toLowerCase(); 3000 | if (!Object.keys(allowList).includes(elementName)) { 3001 | element.remove(); 3002 | continue; 3003 | } 3004 | const attributeList = [].concat(...element.attributes); 3005 | const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); 3006 | for (const attribute of attributeList) { 3007 | if (!allowedAttribute(attribute, allowedAttributes)) { 3008 | element.removeAttribute(attribute.nodeName); 3009 | } 3010 | } 3011 | } 3012 | return createdDocument.body.innerHTML; 3013 | } 3014 | 3015 | /** 3016 | * -------------------------------------------------------------------------- 3017 | * Bootstrap (v5.3.0-alpha1): util/template-factory.js 3018 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 3019 | * -------------------------------------------------------------------------- 3020 | */ 3021 | 3022 | /** 3023 | * Constants 3024 | */ 3025 | 3026 | const NAME$5 = 'TemplateFactory'; 3027 | const Default$4 = { 3028 | allowList: DefaultAllowlist, 3029 | content: {}, 3030 | // { selector : text , selector2 : text2 , } 3031 | extraClass: '', 3032 | html: false, 3033 | sanitize: true, 3034 | sanitizeFn: null, 3035 | template: '
' 3036 | }; 3037 | const DefaultType$4 = { 3038 | allowList: 'object', 3039 | content: 'object', 3040 | extraClass: '(string|function)', 3041 | html: 'boolean', 3042 | sanitize: 'boolean', 3043 | sanitizeFn: '(null|function)', 3044 | template: 'string' 3045 | }; 3046 | const DefaultContentType = { 3047 | entry: '(string|element|function|null)', 3048 | selector: '(string|element)' 3049 | }; 3050 | 3051 | /** 3052 | * Class definition 3053 | */ 3054 | 3055 | class TemplateFactory extends Config { 3056 | constructor(config) { 3057 | super(); 3058 | this._config = this._getConfig(config); 3059 | } 3060 | 3061 | // Getters 3062 | static get Default() { 3063 | return Default$4; 3064 | } 3065 | static get DefaultType() { 3066 | return DefaultType$4; 3067 | } 3068 | static get NAME() { 3069 | return NAME$5; 3070 | } 3071 | 3072 | // Public 3073 | getContent() { 3074 | return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); 3075 | } 3076 | hasContent() { 3077 | return this.getContent().length > 0; 3078 | } 3079 | changeContent(content) { 3080 | this._checkContent(content); 3081 | this._config.content = { 3082 | ...this._config.content, 3083 | ...content 3084 | }; 3085 | return this; 3086 | } 3087 | toHtml() { 3088 | const templateWrapper = document.createElement('div'); 3089 | templateWrapper.innerHTML = this._maybeSanitize(this._config.template); 3090 | for (const [selector, text] of Object.entries(this._config.content)) { 3091 | this._setContent(templateWrapper, text, selector); 3092 | } 3093 | const template = templateWrapper.children[0]; 3094 | const extraClass = this._resolvePossibleFunction(this._config.extraClass); 3095 | if (extraClass) { 3096 | template.classList.add(...extraClass.split(' ')); 3097 | } 3098 | return template; 3099 | } 3100 | 3101 | // Private 3102 | _typeCheckConfig(config) { 3103 | super._typeCheckConfig(config); 3104 | this._checkContent(config.content); 3105 | } 3106 | _checkContent(arg) { 3107 | for (const [selector, content] of Object.entries(arg)) { 3108 | super._typeCheckConfig({ 3109 | selector, 3110 | entry: content 3111 | }, DefaultContentType); 3112 | } 3113 | } 3114 | _setContent(template, content, selector) { 3115 | const templateElement = SelectorEngine.findOne(selector, template); 3116 | if (!templateElement) { 3117 | return; 3118 | } 3119 | content = this._resolvePossibleFunction(content); 3120 | if (!content) { 3121 | templateElement.remove(); 3122 | return; 3123 | } 3124 | if (isElement(content)) { 3125 | this._putElementInTemplate(getElement(content), templateElement); 3126 | return; 3127 | } 3128 | if (this._config.html) { 3129 | templateElement.innerHTML = this._maybeSanitize(content); 3130 | return; 3131 | } 3132 | templateElement.textContent = content; 3133 | } 3134 | _maybeSanitize(arg) { 3135 | return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; 3136 | } 3137 | _resolvePossibleFunction(arg) { 3138 | return execute(arg, [this]); 3139 | } 3140 | _putElementInTemplate(element, templateElement) { 3141 | if (this._config.html) { 3142 | templateElement.innerHTML = ''; 3143 | templateElement.append(element); 3144 | return; 3145 | } 3146 | templateElement.textContent = element.textContent; 3147 | } 3148 | } 3149 | 3150 | /** 3151 | * -------------------------------------------------------------------------- 3152 | * Bootstrap (v5.3.0-alpha1): tooltip.js 3153 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 3154 | * -------------------------------------------------------------------------- 3155 | */ 3156 | 3157 | /** 3158 | * Constants 3159 | */ 3160 | 3161 | const NAME$4 = 'tooltip'; 3162 | const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); 3163 | const CLASS_NAME_FADE$2 = 'fade'; 3164 | const CLASS_NAME_MODAL = 'modal'; 3165 | const CLASS_NAME_SHOW$2 = 'show'; 3166 | const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; 3167 | const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; 3168 | const EVENT_MODAL_HIDE = 'hide.bs.modal'; 3169 | const TRIGGER_HOVER = 'hover'; 3170 | const TRIGGER_FOCUS = 'focus'; 3171 | const TRIGGER_CLICK = 'click'; 3172 | const TRIGGER_MANUAL = 'manual'; 3173 | const EVENT_HIDE$2 = 'hide'; 3174 | const EVENT_HIDDEN$2 = 'hidden'; 3175 | const EVENT_SHOW$2 = 'show'; 3176 | const EVENT_SHOWN$2 = 'shown'; 3177 | const EVENT_INSERTED = 'inserted'; 3178 | const EVENT_CLICK$1 = 'click'; 3179 | const EVENT_FOCUSIN$1 = 'focusin'; 3180 | const EVENT_FOCUSOUT$1 = 'focusout'; 3181 | const EVENT_MOUSEENTER = 'mouseenter'; 3182 | const EVENT_MOUSELEAVE = 'mouseleave'; 3183 | const AttachmentMap = { 3184 | AUTO: 'auto', 3185 | TOP: 'top', 3186 | RIGHT: isRTL() ? 'left' : 'right', 3187 | BOTTOM: 'bottom', 3188 | LEFT: isRTL() ? 'right' : 'left' 3189 | }; 3190 | const Default$3 = { 3191 | allowList: DefaultAllowlist, 3192 | animation: true, 3193 | boundary: 'clippingParents', 3194 | container: false, 3195 | customClass: '', 3196 | delay: 0, 3197 | fallbackPlacements: ['top', 'right', 'bottom', 'left'], 3198 | html: false, 3199 | offset: [0, 0], 3200 | placement: 'top', 3201 | popperConfig: null, 3202 | sanitize: true, 3203 | sanitizeFn: null, 3204 | selector: false, 3205 | template: '', 3206 | title: '', 3207 | trigger: 'hover focus' 3208 | }; 3209 | const DefaultType$3 = { 3210 | allowList: 'object', 3211 | animation: 'boolean', 3212 | boundary: '(string|element)', 3213 | container: '(string|element|boolean)', 3214 | customClass: '(string|function)', 3215 | delay: '(number|object)', 3216 | fallbackPlacements: 'array', 3217 | html: 'boolean', 3218 | offset: '(array|string|function)', 3219 | placement: '(string|function)', 3220 | popperConfig: '(null|object|function)', 3221 | sanitize: 'boolean', 3222 | sanitizeFn: '(null|function)', 3223 | selector: '(string|boolean)', 3224 | template: 'string', 3225 | title: '(string|element|function)', 3226 | trigger: 'string' 3227 | }; 3228 | 3229 | /** 3230 | * Class definition 3231 | */ 3232 | 3233 | class Tooltip extends BaseComponent { 3234 | constructor(element, config) { 3235 | if (typeof Popper__namespace === 'undefined') { 3236 | throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); 3237 | } 3238 | super(element, config); 3239 | 3240 | // Private 3241 | this._isEnabled = true; 3242 | this._timeout = 0; 3243 | this._isHovered = null; 3244 | this._activeTrigger = {}; 3245 | this._popper = null; 3246 | this._templateFactory = null; 3247 | this._newContent = null; 3248 | 3249 | // Protected 3250 | this.tip = null; 3251 | this._setListeners(); 3252 | if (!this._config.selector) { 3253 | this._fixTitle(); 3254 | } 3255 | } 3256 | 3257 | // Getters 3258 | static get Default() { 3259 | return Default$3; 3260 | } 3261 | static get DefaultType() { 3262 | return DefaultType$3; 3263 | } 3264 | static get NAME() { 3265 | return NAME$4; 3266 | } 3267 | 3268 | // Public 3269 | enable() { 3270 | this._isEnabled = true; 3271 | } 3272 | disable() { 3273 | this._isEnabled = false; 3274 | } 3275 | toggleEnabled() { 3276 | this._isEnabled = !this._isEnabled; 3277 | } 3278 | toggle() { 3279 | if (!this._isEnabled) { 3280 | return; 3281 | } 3282 | this._activeTrigger.click = !this._activeTrigger.click; 3283 | if (this._isShown()) { 3284 | this._leave(); 3285 | return; 3286 | } 3287 | this._enter(); 3288 | } 3289 | dispose() { 3290 | clearTimeout(this._timeout); 3291 | EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); 3292 | if (this._element.getAttribute('data-bs-original-title')) { 3293 | this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title')); 3294 | } 3295 | this._disposePopper(); 3296 | super.dispose(); 3297 | } 3298 | show() { 3299 | if (this._element.style.display === 'none') { 3300 | throw new Error('Please use show on visible elements'); 3301 | } 3302 | if (!(this._isWithContent() && this._isEnabled)) { 3303 | return; 3304 | } 3305 | const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2)); 3306 | const shadowRoot = findShadowRoot(this._element); 3307 | const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element); 3308 | if (showEvent.defaultPrevented || !isInTheDom) { 3309 | return; 3310 | } 3311 | 3312 | // todo v6 remove this OR make it optional 3313 | this._disposePopper(); 3314 | const tip = this._getTipElement(); 3315 | this._element.setAttribute('aria-describedby', tip.getAttribute('id')); 3316 | const { 3317 | container 3318 | } = this._config; 3319 | if (!this._element.ownerDocument.documentElement.contains(this.tip)) { 3320 | container.append(tip); 3321 | EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)); 3322 | } 3323 | this._popper = this._createPopper(tip); 3324 | tip.classList.add(CLASS_NAME_SHOW$2); 3325 | 3326 | // If this is a touch-enabled device we add extra 3327 | // empty mouseover listeners to the body's immediate children; 3328 | // only needed because of broken event delegation on iOS 3329 | // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html 3330 | if ('ontouchstart' in document.documentElement) { 3331 | for (const element of [].concat(...document.body.children)) { 3332 | EventHandler.on(element, 'mouseover', noop); 3333 | } 3334 | } 3335 | const complete = () => { 3336 | EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2)); 3337 | if (this._isHovered === false) { 3338 | this._leave(); 3339 | } 3340 | this._isHovered = false; 3341 | }; 3342 | this._queueCallback(complete, this.tip, this._isAnimated()); 3343 | } 3344 | hide() { 3345 | if (!this._isShown()) { 3346 | return; 3347 | } 3348 | const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2)); 3349 | if (hideEvent.defaultPrevented) { 3350 | return; 3351 | } 3352 | const tip = this._getTipElement(); 3353 | tip.classList.remove(CLASS_NAME_SHOW$2); 3354 | 3355 | // If this is a touch-enabled device we remove the extra 3356 | // empty mouseover listeners we added for iOS support 3357 | if ('ontouchstart' in document.documentElement) { 3358 | for (const element of [].concat(...document.body.children)) { 3359 | EventHandler.off(element, 'mouseover', noop); 3360 | } 3361 | } 3362 | this._activeTrigger[TRIGGER_CLICK] = false; 3363 | this._activeTrigger[TRIGGER_FOCUS] = false; 3364 | this._activeTrigger[TRIGGER_HOVER] = false; 3365 | this._isHovered = null; // it is a trick to support manual triggering 3366 | 3367 | const complete = () => { 3368 | if (this._isWithActiveTrigger()) { 3369 | return; 3370 | } 3371 | if (!this._isHovered) { 3372 | this._disposePopper(); 3373 | } 3374 | this._element.removeAttribute('aria-describedby'); 3375 | EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2)); 3376 | }; 3377 | this._queueCallback(complete, this.tip, this._isAnimated()); 3378 | } 3379 | update() { 3380 | if (this._popper) { 3381 | this._popper.update(); 3382 | } 3383 | } 3384 | 3385 | // Protected 3386 | _isWithContent() { 3387 | return Boolean(this._getTitle()); 3388 | } 3389 | _getTipElement() { 3390 | if (!this.tip) { 3391 | this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()); 3392 | } 3393 | return this.tip; 3394 | } 3395 | _createTipElement(content) { 3396 | const tip = this._getTemplateFactory(content).toHtml(); 3397 | 3398 | // todo: remove this check on v6 3399 | if (!tip) { 3400 | return null; 3401 | } 3402 | tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); 3403 | // todo: on v6 the following can be achieved with CSS only 3404 | tip.classList.add(`bs-${this.constructor.NAME}-auto`); 3405 | const tipId = getUID(this.constructor.NAME).toString(); 3406 | tip.setAttribute('id', tipId); 3407 | if (this._isAnimated()) { 3408 | tip.classList.add(CLASS_NAME_FADE$2); 3409 | } 3410 | return tip; 3411 | } 3412 | setContent(content) { 3413 | this._newContent = content; 3414 | if (this._isShown()) { 3415 | this._disposePopper(); 3416 | this.show(); 3417 | } 3418 | } 3419 | _getTemplateFactory(content) { 3420 | if (this._templateFactory) { 3421 | this._templateFactory.changeContent(content); 3422 | } else { 3423 | this._templateFactory = new TemplateFactory({ 3424 | ...this._config, 3425 | // the `content` var has to be after `this._config` 3426 | // to override config.content in case of popover 3427 | content, 3428 | extraClass: this._resolvePossibleFunction(this._config.customClass) 3429 | }); 3430 | } 3431 | return this._templateFactory; 3432 | } 3433 | _getContentForTemplate() { 3434 | return { 3435 | [SELECTOR_TOOLTIP_INNER]: this._getTitle() 3436 | }; 3437 | } 3438 | _getTitle() { 3439 | return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title'); 3440 | } 3441 | 3442 | // Private 3443 | _initializeOnDelegatedTarget(event) { 3444 | return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); 3445 | } 3446 | _isAnimated() { 3447 | return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2); 3448 | } 3449 | _isShown() { 3450 | return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2); 3451 | } 3452 | _createPopper(tip) { 3453 | const placement = execute(this._config.placement, [this, tip, this._element]); 3454 | const attachment = AttachmentMap[placement.toUpperCase()]; 3455 | return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment)); 3456 | } 3457 | _getOffset() { 3458 | const { 3459 | offset 3460 | } = this._config; 3461 | if (typeof offset === 'string') { 3462 | return offset.split(',').map(value => Number.parseInt(value, 10)); 3463 | } 3464 | if (typeof offset === 'function') { 3465 | return popperData => offset(popperData, this._element); 3466 | } 3467 | return offset; 3468 | } 3469 | _resolvePossibleFunction(arg) { 3470 | return execute(arg, [this._element]); 3471 | } 3472 | _getPopperConfig(attachment) { 3473 | const defaultBsPopperConfig = { 3474 | placement: attachment, 3475 | modifiers: [{ 3476 | name: 'flip', 3477 | options: { 3478 | fallbackPlacements: this._config.fallbackPlacements 3479 | } 3480 | }, { 3481 | name: 'offset', 3482 | options: { 3483 | offset: this._getOffset() 3484 | } 3485 | }, { 3486 | name: 'preventOverflow', 3487 | options: { 3488 | boundary: this._config.boundary 3489 | } 3490 | }, { 3491 | name: 'arrow', 3492 | options: { 3493 | element: `.${this.constructor.NAME}-arrow` 3494 | } 3495 | }, { 3496 | name: 'preSetPlacement', 3497 | enabled: true, 3498 | phase: 'beforeMain', 3499 | fn: data => { 3500 | // Pre-set Popper's placement attribute in order to read the arrow sizes properly. 3501 | // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement 3502 | this._getTipElement().setAttribute('data-popper-placement', data.state.placement); 3503 | } 3504 | }] 3505 | }; 3506 | return { 3507 | ...defaultBsPopperConfig, 3508 | ...execute(this._config.popperConfig, [defaultBsPopperConfig]) 3509 | }; 3510 | } 3511 | _setListeners() { 3512 | const triggers = this._config.trigger.split(' '); 3513 | for (const trigger of triggers) { 3514 | if (trigger === 'click') { 3515 | EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { 3516 | const context = this._initializeOnDelegatedTarget(event); 3517 | context.toggle(); 3518 | }); 3519 | } else if (trigger !== TRIGGER_MANUAL) { 3520 | const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1); 3521 | const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1); 3522 | EventHandler.on(this._element, eventIn, this._config.selector, event => { 3523 | const context = this._initializeOnDelegatedTarget(event); 3524 | context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; 3525 | context._enter(); 3526 | }); 3527 | EventHandler.on(this._element, eventOut, this._config.selector, event => { 3528 | const context = this._initializeOnDelegatedTarget(event); 3529 | context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); 3530 | context._leave(); 3531 | }); 3532 | } 3533 | } 3534 | this._hideModalHandler = () => { 3535 | if (this._element) { 3536 | this.hide(); 3537 | } 3538 | }; 3539 | EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); 3540 | } 3541 | _fixTitle() { 3542 | const title = this._element.getAttribute('title'); 3543 | if (!title) { 3544 | return; 3545 | } 3546 | if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) { 3547 | this._element.setAttribute('aria-label', title); 3548 | } 3549 | this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility 3550 | this._element.removeAttribute('title'); 3551 | } 3552 | _enter() { 3553 | if (this._isShown() || this._isHovered) { 3554 | this._isHovered = true; 3555 | return; 3556 | } 3557 | this._isHovered = true; 3558 | this._setTimeout(() => { 3559 | if (this._isHovered) { 3560 | this.show(); 3561 | } 3562 | }, this._config.delay.show); 3563 | } 3564 | _leave() { 3565 | if (this._isWithActiveTrigger()) { 3566 | return; 3567 | } 3568 | this._isHovered = false; 3569 | this._setTimeout(() => { 3570 | if (!this._isHovered) { 3571 | this.hide(); 3572 | } 3573 | }, this._config.delay.hide); 3574 | } 3575 | _setTimeout(handler, timeout) { 3576 | clearTimeout(this._timeout); 3577 | this._timeout = setTimeout(handler, timeout); 3578 | } 3579 | _isWithActiveTrigger() { 3580 | return Object.values(this._activeTrigger).includes(true); 3581 | } 3582 | _getConfig(config) { 3583 | const dataAttributes = Manipulator.getDataAttributes(this._element); 3584 | for (const dataAttribute of Object.keys(dataAttributes)) { 3585 | if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { 3586 | delete dataAttributes[dataAttribute]; 3587 | } 3588 | } 3589 | config = { 3590 | ...dataAttributes, 3591 | ...(typeof config === 'object' && config ? config : {}) 3592 | }; 3593 | config = this._mergeConfigObj(config); 3594 | config = this._configAfterMerge(config); 3595 | this._typeCheckConfig(config); 3596 | return config; 3597 | } 3598 | _configAfterMerge(config) { 3599 | config.container = config.container === false ? document.body : getElement(config.container); 3600 | if (typeof config.delay === 'number') { 3601 | config.delay = { 3602 | show: config.delay, 3603 | hide: config.delay 3604 | }; 3605 | } 3606 | if (typeof config.title === 'number') { 3607 | config.title = config.title.toString(); 3608 | } 3609 | if (typeof config.content === 'number') { 3610 | config.content = config.content.toString(); 3611 | } 3612 | return config; 3613 | } 3614 | _getDelegateConfig() { 3615 | const config = {}; 3616 | for (const [key, value] of Object.entries(this._config)) { 3617 | if (this.constructor.Default[key] !== value) { 3618 | config[key] = value; 3619 | } 3620 | } 3621 | config.selector = false; 3622 | config.trigger = 'manual'; 3623 | 3624 | // In the future can be replaced with: 3625 | // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) 3626 | // `Object.fromEntries(keysWithDifferentValues)` 3627 | return config; 3628 | } 3629 | _disposePopper() { 3630 | if (this._popper) { 3631 | this._popper.destroy(); 3632 | this._popper = null; 3633 | } 3634 | if (this.tip) { 3635 | this.tip.remove(); 3636 | this.tip = null; 3637 | } 3638 | } 3639 | 3640 | // Static 3641 | static jQueryInterface(config) { 3642 | return this.each(function () { 3643 | const data = Tooltip.getOrCreateInstance(this, config); 3644 | if (typeof config !== 'string') { 3645 | return; 3646 | } 3647 | if (typeof data[config] === 'undefined') { 3648 | throw new TypeError(`No method named "${config}"`); 3649 | } 3650 | data[config](); 3651 | }); 3652 | } 3653 | } 3654 | 3655 | /** 3656 | * jQuery 3657 | */ 3658 | 3659 | defineJQueryPlugin(Tooltip); 3660 | 3661 | /** 3662 | * -------------------------------------------------------------------------- 3663 | * Bootstrap (v5.3.0-alpha1): popover.js 3664 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 3665 | * -------------------------------------------------------------------------- 3666 | */ 3667 | 3668 | /** 3669 | * Constants 3670 | */ 3671 | 3672 | const NAME$3 = 'popover'; 3673 | const SELECTOR_TITLE = '.popover-header'; 3674 | const SELECTOR_CONTENT = '.popover-body'; 3675 | const Default$2 = { 3676 | ...Tooltip.Default, 3677 | content: '', 3678 | offset: [0, 8], 3679 | placement: 'right', 3680 | template: '', 3681 | trigger: 'click' 3682 | }; 3683 | const DefaultType$2 = { 3684 | ...Tooltip.DefaultType, 3685 | content: '(null|string|element|function)' 3686 | }; 3687 | 3688 | /** 3689 | * Class definition 3690 | */ 3691 | 3692 | class Popover extends Tooltip { 3693 | // Getters 3694 | static get Default() { 3695 | return Default$2; 3696 | } 3697 | static get DefaultType() { 3698 | return DefaultType$2; 3699 | } 3700 | static get NAME() { 3701 | return NAME$3; 3702 | } 3703 | 3704 | // Overrides 3705 | _isWithContent() { 3706 | return this._getTitle() || this._getContent(); 3707 | } 3708 | 3709 | // Private 3710 | _getContentForTemplate() { 3711 | return { 3712 | [SELECTOR_TITLE]: this._getTitle(), 3713 | [SELECTOR_CONTENT]: this._getContent() 3714 | }; 3715 | } 3716 | _getContent() { 3717 | return this._resolvePossibleFunction(this._config.content); 3718 | } 3719 | 3720 | // Static 3721 | static jQueryInterface(config) { 3722 | return this.each(function () { 3723 | const data = Popover.getOrCreateInstance(this, config); 3724 | if (typeof config !== 'string') { 3725 | return; 3726 | } 3727 | if (typeof data[config] === 'undefined') { 3728 | throw new TypeError(`No method named "${config}"`); 3729 | } 3730 | data[config](); 3731 | }); 3732 | } 3733 | } 3734 | 3735 | /** 3736 | * jQuery 3737 | */ 3738 | 3739 | defineJQueryPlugin(Popover); 3740 | 3741 | /** 3742 | * -------------------------------------------------------------------------- 3743 | * Bootstrap (v5.3.0-alpha1): scrollspy.js 3744 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 3745 | * -------------------------------------------------------------------------- 3746 | */ 3747 | 3748 | /** 3749 | * Constants 3750 | */ 3751 | 3752 | const NAME$2 = 'scrollspy'; 3753 | const DATA_KEY$2 = 'bs.scrollspy'; 3754 | const EVENT_KEY$2 = `.${DATA_KEY$2}`; 3755 | const DATA_API_KEY = '.data-api'; 3756 | const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; 3757 | const EVENT_CLICK = `click${EVENT_KEY$2}`; 3758 | const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`; 3759 | const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; 3760 | const CLASS_NAME_ACTIVE$1 = 'active'; 3761 | const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; 3762 | const SELECTOR_TARGET_LINKS = '[href]'; 3763 | const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'; 3764 | const SELECTOR_NAV_LINKS = '.nav-link'; 3765 | const SELECTOR_NAV_ITEMS = '.nav-item'; 3766 | const SELECTOR_LIST_ITEMS = '.list-group-item'; 3767 | const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`; 3768 | const SELECTOR_DROPDOWN = '.dropdown'; 3769 | const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; 3770 | const Default$1 = { 3771 | offset: null, 3772 | // TODO: v6 @deprecated, keep it for backwards compatibility reasons 3773 | rootMargin: '0px 0px -25%', 3774 | smoothScroll: false, 3775 | target: null, 3776 | threshold: [0.1, 0.5, 1] 3777 | }; 3778 | const DefaultType$1 = { 3779 | offset: '(number|null)', 3780 | // TODO v6 @deprecated, keep it for backwards compatibility reasons 3781 | rootMargin: 'string', 3782 | smoothScroll: 'boolean', 3783 | target: 'element', 3784 | threshold: 'array' 3785 | }; 3786 | 3787 | /** 3788 | * Class definition 3789 | */ 3790 | 3791 | class ScrollSpy extends BaseComponent { 3792 | constructor(element, config) { 3793 | super(element, config); 3794 | 3795 | // this._element is the observablesContainer and config.target the menu links wrapper 3796 | this._targetLinks = new Map(); 3797 | this._observableSections = new Map(); 3798 | this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; 3799 | this._activeTarget = null; 3800 | this._observer = null; 3801 | this._previousScrollData = { 3802 | visibleEntryTop: 0, 3803 | parentScrollTop: 0 3804 | }; 3805 | this.refresh(); // initialize 3806 | } 3807 | 3808 | // Getters 3809 | static get Default() { 3810 | return Default$1; 3811 | } 3812 | static get DefaultType() { 3813 | return DefaultType$1; 3814 | } 3815 | static get NAME() { 3816 | return NAME$2; 3817 | } 3818 | 3819 | // Public 3820 | refresh() { 3821 | this._initializeTargetsAndObservables(); 3822 | this._maybeEnableSmoothScroll(); 3823 | if (this._observer) { 3824 | this._observer.disconnect(); 3825 | } else { 3826 | this._observer = this._getNewObserver(); 3827 | } 3828 | for (const section of this._observableSections.values()) { 3829 | this._observer.observe(section); 3830 | } 3831 | } 3832 | dispose() { 3833 | this._observer.disconnect(); 3834 | super.dispose(); 3835 | } 3836 | 3837 | // Private 3838 | _configAfterMerge(config) { 3839 | // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case 3840 | config.target = getElement(config.target) || document.body; 3841 | 3842 | // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only 3843 | config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; 3844 | if (typeof config.threshold === 'string') { 3845 | config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)); 3846 | } 3847 | return config; 3848 | } 3849 | _maybeEnableSmoothScroll() { 3850 | if (!this._config.smoothScroll) { 3851 | return; 3852 | } 3853 | 3854 | // unregister any previous listeners 3855 | EventHandler.off(this._config.target, EVENT_CLICK); 3856 | EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { 3857 | const observableSection = this._observableSections.get(event.target.hash); 3858 | if (observableSection) { 3859 | event.preventDefault(); 3860 | const root = this._rootElement || window; 3861 | const height = observableSection.offsetTop - this._element.offsetTop; 3862 | if (root.scrollTo) { 3863 | root.scrollTo({ 3864 | top: height, 3865 | behavior: 'smooth' 3866 | }); 3867 | return; 3868 | } 3869 | 3870 | // Chrome 60 doesn't support `scrollTo` 3871 | root.scrollTop = height; 3872 | } 3873 | }); 3874 | } 3875 | _getNewObserver() { 3876 | const options = { 3877 | root: this._rootElement, 3878 | threshold: this._config.threshold, 3879 | rootMargin: this._config.rootMargin 3880 | }; 3881 | return new IntersectionObserver(entries => this._observerCallback(entries), options); 3882 | } 3883 | 3884 | // The logic of selection 3885 | _observerCallback(entries) { 3886 | const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`); 3887 | const activate = entry => { 3888 | this._previousScrollData.visibleEntryTop = entry.target.offsetTop; 3889 | this._process(targetElement(entry)); 3890 | }; 3891 | const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; 3892 | const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; 3893 | this._previousScrollData.parentScrollTop = parentScrollTop; 3894 | for (const entry of entries) { 3895 | if (!entry.isIntersecting) { 3896 | this._activeTarget = null; 3897 | this._clearActiveClass(targetElement(entry)); 3898 | continue; 3899 | } 3900 | const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; 3901 | // if we are scrolling down, pick the bigger offsetTop 3902 | if (userScrollsDown && entryIsLowerThanPrevious) { 3903 | activate(entry); 3904 | // if parent isn't scrolled, let's keep the first visible item, breaking the iteration 3905 | if (!parentScrollTop) { 3906 | return; 3907 | } 3908 | continue; 3909 | } 3910 | 3911 | // if we are scrolling up, pick the smallest offsetTop 3912 | if (!userScrollsDown && !entryIsLowerThanPrevious) { 3913 | activate(entry); 3914 | } 3915 | } 3916 | } 3917 | _initializeTargetsAndObservables() { 3918 | this._targetLinks = new Map(); 3919 | this._observableSections = new Map(); 3920 | const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target); 3921 | for (const anchor of targetLinks) { 3922 | // ensure that the anchor has an id and is not disabled 3923 | if (!anchor.hash || isDisabled(anchor)) { 3924 | continue; 3925 | } 3926 | const observableSection = SelectorEngine.findOne(anchor.hash, this._element); 3927 | 3928 | // ensure that the observableSection exists & is visible 3929 | if (isVisible(observableSection)) { 3930 | this._targetLinks.set(anchor.hash, anchor); 3931 | this._observableSections.set(anchor.hash, observableSection); 3932 | } 3933 | } 3934 | } 3935 | _process(target) { 3936 | if (this._activeTarget === target) { 3937 | return; 3938 | } 3939 | this._clearActiveClass(this._config.target); 3940 | this._activeTarget = target; 3941 | target.classList.add(CLASS_NAME_ACTIVE$1); 3942 | this._activateParents(target); 3943 | EventHandler.trigger(this._element, EVENT_ACTIVATE, { 3944 | relatedTarget: target 3945 | }); 3946 | } 3947 | _activateParents(target) { 3948 | // Activate dropdown parents 3949 | if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { 3950 | SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1); 3951 | return; 3952 | } 3953 | for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { 3954 | // Set triggered links parents as active 3955 | // With both