diff --git a/apps/home/templates/index.html b/apps/home/templates/home/index.html similarity index 100% rename from apps/home/templates/index.html rename to apps/home/templates/home/index.html diff --git a/apps/home/urls.py b/apps/home/urls.py index c486297..833c058 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -1,6 +1,9 @@ +"""URLs for the home app.""" + from django.urls import path + from . import views urlpatterns = [ - path('', views.index, name='index'), + path("", views.IndexView.as_view(), name="index") ] \ No newline at end of file diff --git a/apps/home/views.py b/apps/home/views.py index 6d51786..40967c0 100644 --- a/apps/home/views.py +++ b/apps/home/views.py @@ -1,6 +1,10 @@ """Views for the home app.""" from django.shortcuts import render +from django.views.generic import TemplateView -def index(request): - return render(request, 'index.html') + +class IndexView(TemplateView): + """The index view of the home app.""" + + template_name = "home/index.html" diff --git a/core/settings.py b/core/settings.py index d0c7b12..68d4083 100644 --- a/core/settings.py +++ b/core/settings.py @@ -174,7 +174,7 @@ COMPRESS_ENABLED = True COMPRESS_OFFLINE = not DEBUG COMPRESS_PRECOMPILERS = [("text/x-scss", "django_libsass.SassCompiler")] COMPRESS_CSS_FILTERS = ["compressor.filters.css_default.CssAbsoluteIdentifier"] -# LIBSASS_ADDITIONAL_INCLUDE_PATHS = [str(BASE_DIR / "static/bootstrap-5.3.3/scss")] +LIBSASS_ADDITIONAL_INCLUDE_PATHS = [str(BASE_DIR / "static/")] # Media Files diff --git a/static/bootstrap-5.3.3/.babelrc.js b/static/bootstrap-5.3.3/.babelrc.js new file mode 100644 index 0000000..5139969 --- /dev/null +++ b/static/bootstrap-5.3.3/.babelrc.js @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + bugfixes: true, + modules: false + } + ] + ] +}; diff --git a/static/bootstrap-5.3.3/.browserslistrc b/static/bootstrap-5.3.3/.browserslistrc new file mode 100644 index 0000000..c71c8b9 --- /dev/null +++ b/static/bootstrap-5.3.3/.browserslistrc @@ -0,0 +1,11 @@ +# https://github.com/browserslist/browserslist#readme + +>= 0.5% +last 2 major versions +not dead +Chrome >= 60 +Firefox >= 60 +Firefox ESR +iOS >= 12 +Safari >= 12 +not Explorer <= 11 diff --git a/static/bootstrap-5.3.3/.bundlewatch.config.json b/static/bootstrap-5.3.3/.bundlewatch.config.json new file mode 100644 index 0000000..bb0a5a8 --- /dev/null +++ b/static/bootstrap-5.3.3/.bundlewatch.config.json @@ -0,0 +1,66 @@ +{ + "files": [ + { + "path": "./dist/css/bootstrap-grid.css", + "maxSize": "6.5 kB" + }, + { + "path": "./dist/css/bootstrap-grid.min.css", + "maxSize": "6.0 kB" + }, + { + "path": "./dist/css/bootstrap-reboot.css", + "maxSize": "3.5 kB" + }, + { + "path": "./dist/css/bootstrap-reboot.min.css", + "maxSize": "3.25 kB" + }, + { + "path": "./dist/css/bootstrap-utilities.css", + "maxSize": "11.75 kB" + }, + { + "path": "./dist/css/bootstrap-utilities.min.css", + "maxSize": "10.75 kB" + }, + { + "path": "./dist/css/bootstrap.css", + "maxSize": "32.5 kB" + }, + { + "path": "./dist/css/bootstrap.min.css", + "maxSize": "30.25 kB" + }, + { + "path": "./dist/js/bootstrap.bundle.js", + "maxSize": "43.0 kB" + }, + { + "path": "./dist/js/bootstrap.bundle.min.js", + "maxSize": "23.25 kB" + }, + { + "path": "./dist/js/bootstrap.esm.js", + "maxSize": "28.0 kB" + }, + { + "path": "./dist/js/bootstrap.esm.min.js", + "maxSize": "18.25 kB" + }, + { + "path": "./dist/js/bootstrap.js", + "maxSize": "28.75 kB" + }, + { + "path": "./dist/js/bootstrap.min.js", + "maxSize": "16.25 kB" + } + ], + "ci": { + "trackBranches": [ + "main", + "v4-dev" + ] + } +} diff --git a/static/bootstrap-5.3.3/.cspell.json b/static/bootstrap-5.3.3/.cspell.json new file mode 100644 index 0000000..484af2a --- /dev/null +++ b/static/bootstrap-5.3.3/.cspell.json @@ -0,0 +1,133 @@ +{ + "version": "0.2", + "words": [ + "affordance", + "allowfullscreen", + "Analyser", + "autohide", + "autohiding", + "autoplay", + "autoplays", + "autoplaying", + "blazingly", + "Blockquotes", + "Bootstrappers", + "borderless", + "Brotli", + "browserslist", + "browserslistrc", + "btncheck", + "btnradio", + "callout", + "callouts", + "camelCase", + "clearfix", + "Codesniffer", + "combinator", + "Contentful", + "Cpath", + "Crossfade", + "crossfading", + "cssgrid", + "Csvg", + "Datalists", + "Deque", + "discoverability", + "docsearch", + "docsref", + "dropend", + "dropleft", + "dropright", + "dropstart", + "dropup", + "dgst", + "errorf", + "favicon", + "favicons", + "fieldsets", + "flexbox", + "fullscreen", + "getbootstrap", + "Grayscale", + "Hoverable", + "hreflang", + "hstack", + "importmap", + "jsdelivr", + "Jumpstart", + "keyframes", + "libera", + "libman", + "Libsass", + "lightboxes", + "Lowercased", + "markdownify", + "mediaqueries", + "minifiers", + "misfunction", + "mkdir", + "monospace", + "mouseleave", + "navbars", + "navs", + "Neue", + "noindex", + "Noto", + "offcanvas", + "offcanvases", + "Packagist", + "popperjs", + "prebuild", + "prefersreducedmotion", + "prepended", + "printf", + "rects", + "relref", + "rgba", + "roboto", + "RTLCSS", + "ruleset", + "screenreaders", + "scrollbars", + "scrollspy", + "Segoe", + "semibold", + "socio", + "srcset", + "stackblitz", + "stickied", + "Stylelint", + "subnav", + "tabbable", + "textareas", + "toggleable", + "topbar", + "touchend", + "twbs", + "unitless", + "unstylable", + "unstyled", + "Uppercased", + "urlize", + "urlquery", + "vbtn", + "viewports", + "Vite", + "vstack", + "walkthroughs", + "WCAG", + "zindex" + ], + "language": "en-US", + "files": [ + "**/*.md" + ], + "ignorePaths": [ + ".cspell.json", + "dist/", + "*.min.*", + "**/*rtl*", + "**/tests/**" + ], + "useGitignore": true +} diff --git a/static/bootstrap-5.3.3/.editorconfig b/static/bootstrap-5.3.3/.editorconfig new file mode 100644 index 0000000..f29d257 --- /dev/null +++ b/static/bootstrap-5.3.3/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/static/bootstrap-5.3.3/.eslintignore b/static/bootstrap-5.3.3/.eslintignore new file mode 100644 index 0000000..4c5b84f --- /dev/null +++ b/static/bootstrap-5.3.3/.eslintignore @@ -0,0 +1,7 @@ +**/*.min.js +**/dist/ +**/vendor/ +/_site/ +/js/coverage/ +/site/static/sw.js +/site/layouts/partials/ diff --git a/static/bootstrap-5.3.3/.eslintrc.json b/static/bootstrap-5.3.3/.eslintrc.json new file mode 100644 index 0000000..2efe773 --- /dev/null +++ b/static/bootstrap-5.3.3/.eslintrc.json @@ -0,0 +1,222 @@ +{ + "root": true, + "extends": [ + "plugin:import/errors", + "plugin:import/warnings", + "plugin:unicorn/recommended", + "xo", + "xo/browser" + ], + "rules": { + "arrow-body-style": "off", + "capitalized-comments": "off", + "comma-dangle": [ + "error", + "never" + ], + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "always" + } + ], + "import/first": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-amd": "error", + "import/no-cycle": [ + "error", + { + "ignoreExternal": true + } + ], + "import/no-duplicates": "error", + "import/no-extraneous-dependencies": "error", + "import/no-mutable-exports": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-named-default": "error", + "import/no-self-import": "error", + "import/no-unassigned-import": [ + "error" + ], + "import/no-useless-path-segments": "error", + "import/order": "error", + "indent": [ + "error", + 2, + { + "MemberExpression": "off", + "SwitchCase": 1 + } + ], + "logical-assignment-operators": "off", + "max-params": [ + "warn", + 5 + ], + "multiline-ternary": [ + "error", + "always-multiline" + ], + "new-cap": [ + "error", + { + "properties": false + } + ], + "no-console": "error", + "no-negated-condition": "off", + "object-curly-spacing": [ + "error", + "always" + ], + "operator-linebreak": [ + "error", + "after" + ], + "prefer-object-has-own": "off", + "prefer-template": "error", + "semi": [ + "error", + "never" + ], + "strict": "error", + "unicorn/explicit-length-check": "off", + "unicorn/filename-case": "off", + "unicorn/no-array-callback-reference": "off", + "unicorn/no-array-method-this-argument": "off", + "unicorn/no-null": "off", + "unicorn/no-typeof-undefined": "off", + "unicorn/no-unused-properties": "error", + "unicorn/numeric-separators-style": "off", + "unicorn/prefer-array-flat": "off", + "unicorn/prefer-at": "off", + "unicorn/prefer-dom-node-dataset": "off", + "unicorn/prefer-module": "off", + "unicorn/prefer-query-selector": "off", + "unicorn/prefer-spread": "off", + "unicorn/prefer-string-replace-all": "off", + "unicorn/prevent-abbreviations": "off" + }, + "overrides": [ + { + "files": [ + "build/**" + ], + "env": { + "browser": false, + "node": true + }, + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-console": "off", + "unicorn/prefer-top-level-await": "off" + } + }, + { + "files": [ + "js/**" + ], + "parserOptions": { + "sourceType": "module" + } + }, + { + "files": [ + "js/tests/*.js", + "js/tests/integration/rollup*.js" + ], + "env": { + "node": true + }, + "parserOptions": { + "sourceType": "script" + } + }, + { + "files": [ + "js/tests/unit/**" + ], + "env": { + "jasmine": true + }, + "rules": { + "no-console": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/no-useless-undefined": "off", + "unicorn/prefer-add-event-listener": "off" + } + }, + { + "files": [ + "js/tests/visual/**" + ], + "plugins": [ + "html" + ], + "settings": { + "html/html-extensions": [ + ".html" + ] + }, + "rules": { + "no-console": "off", + "no-new": "off", + "unicorn/no-array-for-each": "off" + } + }, + { + "files": [ + "scss/tests/**" + ], + "env": { + "node": true + }, + "parserOptions": { + "sourceType": "script" + } + }, + { + "files": [ + "site/**" + ], + "env": { + "browser": true, + "node": false + }, + "parserOptions": { + "sourceType": "script", + "ecmaVersion": 2019 + }, + "rules": { + "no-new": "off", + "unicorn/no-array-for-each": "off" + } + }, + { + "files": [ + "**/*.md" + ], + "plugins": [ + "markdown" + ], + "processor": "markdown/markdown" + }, + { + "files": [ + "**/*.md/*.js" + ], + "extends": "plugin:markdown/recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "unicorn/prefer-node-protocol": "off" + } + } + ] +} diff --git a/static/bootstrap-5.3.3/.gitattributes b/static/bootstrap-5.3.3/.gitattributes new file mode 100644 index 0000000..40b1c37 --- /dev/null +++ b/static/bootstrap-5.3.3/.gitattributes @@ -0,0 +1,8 @@ +# Enforce Unix newlines +* text=auto eol=lf + +# Don't diff or textually merge source maps +*.map binary + +bootstrap.css linguist-vendored=false +bootstrap.js linguist-vendored=false diff --git a/static/bootstrap-5.3.3/.github/CODEOWNERS b/static/bootstrap-5.3.3/.github/CODEOWNERS new file mode 100644 index 0000000..7d3fa99 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/CODEOWNERS @@ -0,0 +1,3 @@ +*.js @twbs/js-review +*.css @twbs/css-review +*.scss @twbs/css-review diff --git a/static/bootstrap-5.3.3/.github/CONTRIBUTING.md b/static/bootstrap-5.3.3/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d18210b --- /dev/null +++ b/static/bootstrap-5.3.3/.github/CONTRIBUTING.md @@ -0,0 +1,244 @@ +# Contributing to Bootstrap + +Looking to contribute something to Bootstrap? **Here's how you can help.** + +Please take a moment to review this document in order to make the contribution +process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of +the developers managing and developing this open source project. In return, +they should reciprocate that respect in addressing your issue or assessing +patches and features. + + +## Using the issue tracker + +The [issue tracker](https://github.com/twbs/bootstrap/issues) is +the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests) +and [submitting pull requests](#pull-requests), but please respect the following +restrictions: + +- Please **do not** use the issue tracker for personal support requests. Stack Overflow ([`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5) tag), [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions) or [IRC](/README.md#community) are better places to get help. + +- Please **do not** derail or troll issues. Keep the discussion on topic and + respect the opinions of others. + +- Please **do not** post comments consisting solely of "+1" or ":thumbsup:". + Use [GitHub's "reactions" feature](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) + instead. We reserve the right to delete comments which violate this rule. + +- Please **do not** open issues regarding the official themes offered on . + Instead, please email any questions or feedback regarding those themes to `themes AT getbootstrap DOT com`. + + +## Issues assignment + +The core team will be looking at the open issues, analyze them, and provide guidance on how to proceed. **Issues won't be assigned to anyone outside the core team.** However, contributors are welcome to participate in the discussion and provide their input on how to best solve the issue, and even submit a PR if they want to. Please wait that the issue is ready to be worked on before submitting a PR, we don't want to waste your time. + +Please keep in mind that the core team is small, has limited resources and that we are not always able to respond immediately. We will try to provide feedback as soon as possible, but please be patient. If you don't get a response immediately, it doesn't mean that we are ignoring you or that we don't care about your issue or PR. We will get back to you as soon as we can. + + +## Issues and labels + +Our bug tracker utilizes several labels to help organize and identify issues. Here's what they represent and how we use them: + +- `browser bug` - Issues that are reported to us, but actually are the result of a browser-specific bug. These are diagnosed with reduced test cases and result in an issue opened on that browser's own bug tracker. +- `confirmed` - Issues that have been confirmed with a reduced test case and identify a bug in Bootstrap. +- `css` - Issues stemming from our compiled CSS or source Sass files. +- `docs` - Issues for improving or updating our documentation. +- `examples` - Issues involving the example templates included in our docs. +- `feature` - Issues asking for a new feature to be added, or an existing one to be extended or modified. New features require a minor version bump (e.g., `v3.0.0` to `v3.1.0`). +- `build` - Issues with our build system, which is used to run all our tests, concatenate and compile source files, and more. +- `help wanted` - Issues we need or would love help from the community to resolve. +- `js` - Issues stemming from our compiled or source JavaScript files. +- `meta` - Issues with the project itself or our GitHub repository. + +For a complete look at our labels, see the [project labels page](https://github.com/twbs/bootstrap/labels). + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful, so thanks! + +Guidelines for bug reports: + +0. **[Validate your HTML](https://html5.validator.nu/)** to ensure your + problem isn't caused by a simple error in your own code. + +1. **Use the GitHub issue search** — check if the issue has already been + reported. + +2. **Check if the issue has been fixed** — try to reproduce it using the + latest `main` (or `v4-dev` branch if the issue is about v4) in the repository. + +3. **Isolate the problem** — ideally create a [reduced test + case](https://css-tricks.com/reduced-test-cases/) and a live example. + These [v4 CodePen](https://codepen.io/team/bootstrap/pen/yLabNQL) and [v5 CodePen](https://codepen.io/team/bootstrap/pen/qBamdLj) are helpful templates. + + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? What browser(s) and OS +experience the problem? Do other browsers show the bug differently? What +would you expect to be the outcome? All these details will help people to fix +any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the browser/OS environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + +### Reporting upstream browser bugs + +Sometimes bugs reported to us are actually caused by bugs in the browser(s) themselves, not bugs in Bootstrap per se. + +| Vendor(s) | Browser(s) | Rendering engine | Bug reporting website(s) | Notes | +| ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- | +| Mozilla | Firefox | Gecko | | "Core" is normally the right product option to choose. | +| Apple | Safari | WebKit | | In Apple's bug reporter, choose "Safari" as the product. | +| Google, Opera | Chrome, Chromium, Opera v15+ | Blink | | Click the "New issue" button. | +| Microsoft | Edge | Blink | | Go to "Help > Send Feedback" from the browser | + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to _you_ to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + + +## Pull requests + +Good pull requests—patches, improvements, new features—are a fantastic +help. They should remain focused in scope and avoid containing unrelated +commits. + +**Please ask first** before embarking on any **significant** pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. For trivial +things, or things that don't require a lot of your time, you can go ahead and +make a PR. + +Please adhere to the [coding guidelines](#code-guidelines) used throughout the +project (indentation, accurate comments, etc.) and any other requirements +(such as test coverage). + +**Do not edit `bootstrap.css` or `bootstrap.js`, and do not commit +any dist files (`dist/` or `js/dist`).** Those files are automatically generated by our build tools. You should +edit the source files in [`/bootstrap/scss/`](https://github.com/twbs/bootstrap/tree/main/scss) +and/or [`/bootstrap/js/src/`](https://github.com/twbs/bootstrap/tree/main/js/src) instead. + +Similarly, when contributing to Bootstrap's documentation, you should edit the +documentation source files in +[the `/bootstrap/site/content/docs/` directory of the `main` branch](https://github.com/twbs/bootstrap/tree/main/site/content/docs). +**Do not edit the `gh-pages` branch.** That branch is generated from the +documentation source files and is managed separately by the Bootstrap Core Team. + +Adhering to the following process is the best way to get your work +included in the project: + +1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, + and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com//bootstrap.git + # Navigate to the newly cloned directory + cd bootstrap + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com/twbs/bootstrap.git + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout main + git pull upstream main + ``` + +3. Create a new topic branch (off the main project development branch) to + contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Please adhere to these [git commit + message guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + or your code is unlikely be merged into the main project. Use Git's + [interactive rebase](https://help.github.com/articles/about-git-rebase/) + feature to tidy up your commits before making them public. + +5. Locally merge (or rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream main + ``` + +6. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/) + with a clear title and description against the `main` branch. + +**IMPORTANT**: By submitting a patch, you agree to allow the project owners to +license your work under the terms of the [MIT License](../LICENSE) (if it +includes code changes) and under the terms of the +[Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/) +(if it includes documentation changes). + + +## Code guidelines + +### HTML + +[Adhere to the Code Guide.](https://codeguide.co/#html) + +- Use tags and elements appropriate for an HTML5 doctype (e.g., self-closing tags). +- Use CDNs and HTTPS for third-party JS when possible. We don't use protocol-relative URLs in this case because they break when viewing the page locally via `file://`. +- Use [WAI-ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes in documentation examples to promote accessibility. + +### CSS + +[Adhere to the Code Guide.](https://codeguide.co/#css) + +- When feasible, default color palettes should comply with [WCAG color contrast guidelines](https://www.w3.org/TR/WCAG20/#visual-audio-contrast). +- Except in rare cases, don't remove default `:focus` styles (via e.g. `outline: none;`) without providing alternative styles. See [this A11Y Project post](https://www.a11yproject.com/posts/2013-01-25-never-remove-css-outlines/) for more details. + +### JS + +- No semicolons (in client-side JS) +- 2 spaces (no tabs) +- strict mode +- "Attractive" + +### Checking coding style + +Run `npm run test` before committing to ensure your changes follow our coding standards. + + +## License + +By contributing your code, you agree to license your contribution under the [MIT License](../LICENSE). +By contributing to the documentation, you agree to license your contribution under the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/). + +Prior to v3.1.0, Bootstrap's code was released under the Apache License v2.0. diff --git a/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/bug_report.yml b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..3e3d6b9 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,62 @@ +name: Report a bug +description: Tell us about a bug or issue you may have identified in Bootstrap. +title: "Provide a general summary of the issue" +labels: [bug] +assignees: "-" +body: + - type: checkboxes + attributes: + label: Prerequisites + description: Take a couple minutes to help our maintainers work faster. + options: + - label: I have [searched](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue) for duplicate or closed issues + required: true + - label: I have [validated](https://html5.validator.nu/) any HTML to avoid common problems + required: true + - label: I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md) + required: true + - type: textarea + id: what-happened + attributes: + label: Describe the issue + description: Provide a summary of the issue and what you expected to happen, including specific steps to reproduce. + validations: + required: true + - type: textarea + id: reduced-test-case + attributes: + label: Reduced test cases + description: Include links [reduced test case](https://css-tricks.com/reduced-test-cases/) links or suggested fixes using CodePen ([v4 template](https://codepen.io/team/bootstrap/pen/yLabNQL) or [v5 template](https://codepen.io/team/bootstrap/pen/qBamdLj)). + validations: + required: true + - type: dropdown + id: os + attributes: + label: What operating system(s) are you seeing the problem on? + multiple: true + options: + - Windows + - macOS + - Android + - iOS + - Linux + validations: + required: true + - type: dropdown + id: browser + attributes: + label: What browser(s) are you seeing the problem on? + multiple: true + options: + - Chrome + - Safari + - Firefox + - Microsoft Edge + - Opera + - type: input + id: version + attributes: + label: What version of Bootstrap are you using? + placeholder: "e.g., v5.1.0 or v4.5.2" + validations: + required: true diff --git a/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/config.yml b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f152071 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Ask the community + url: https://github.com/twbs/bootstrap/discussions/new + about: Ask and discuss questions with other Bootstrap community members. diff --git a/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/feature_request.yml b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..4b757b1 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,29 @@ +name: Feature request +description: Suggest new or updated features to include in Bootstrap. +title: "Suggest a new feature" +labels: [feature] +assignees: [] +body: + - type: checkboxes + attributes: + label: Prerequisites + description: Take a couple minutes to help our maintainers work faster. + options: + - label: I have [searched](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue) for duplicate or closed feature requests + required: true + - label: I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md) + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: Provide detailed information for what we should add, including relevant links to prior art, screenshots, or live demos whenever possible. + validations: + required: true + - type: textarea + id: motivation + attributes: + label: Motivation and context + description: Tell us why this change is needed or helpful, and what problems it may help solve. + validations: + required: true diff --git a/static/bootstrap-5.3.3/.github/PULL_REQUEST_TEMPLATE.md b/static/bootstrap-5.3.3/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..98e45c5 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,38 @@ +### Description + + + +### Motivation & Context + + + +### Type of changes + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Refactoring (non-breaking change) +- [ ] Breaking change (fix or feature that would change existing functionality) + +### Checklist + + + + +- [ ] I have read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md) +- [ ] My code follows the code style of the project _(using `npm run lint`)_ +- [ ] My change introduces changes to the documentation +- [ ] I have updated the documentation accordingly +- [ ] I have added tests to cover my changes +- [ ] All new and existing tests passed + +#### Live previews + + + +- + +### Related issues + + diff --git a/static/bootstrap-5.3.3/.github/SUPPORT.md b/static/bootstrap-5.3.3/.github/SUPPORT.md new file mode 100644 index 0000000..26b3be4 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/SUPPORT.md @@ -0,0 +1,11 @@ +### Bug reports + +See the [contributing guidelines](CONTRIBUTING.md) for sharing bug reports. + +### How-to + +For general troubleshooting or help getting started: + +- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions). +- Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel. +- Ask and explore Stack Overflow with the [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5) tag. diff --git a/static/bootstrap-5.3.3/.github/codeql/codeql-config.yml b/static/bootstrap-5.3.3/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..9578772 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/codeql/codeql-config.yml @@ -0,0 +1,3 @@ +name: "CodeQL config" +paths-ignore: + - dist diff --git a/static/bootstrap-5.3.3/.github/dependabot.yml b/static/bootstrap-5.3.3/.github/dependabot.yml new file mode 100644 index 0000000..f54ba89 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + day: tuesday + time: "12:00" + timezone: Europe/Athens + - package-ecosystem: npm + directory: "/" + reviewers: + - XhmikosR + labels: + - dependencies + - v5 + schedule: + interval: weekly + day: tuesday + time: "12:00" + timezone: Europe/Athens + versioning-strategy: increase + rebase-strategy: disabled diff --git a/static/bootstrap-5.3.3/.github/release-drafter.yml b/static/bootstrap-5.3.3/.github/release-drafter.yml new file mode 100644 index 0000000..0289984 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/release-drafter.yml @@ -0,0 +1,60 @@ +name-template: 'v$NEXT_MAJOR_VERSION' +tag-template: 'v$NEXT_MAJOR_VERSION' +prerelease: true +exclude-labels: + - 'skip-changelog' +categories: + - title: '❗ Breaking Changes' + labels: + - 'breaking-change' + - title: '🚀 Highlights' + labels: + - 'release-highlight' + - title: '🚀 Features' + labels: + - 'new-feature' + - 'feature' + - 'enhancement' + - title: '🐛 Bug fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '⚡ Performance improvements' + labels: + - 'performance' + - title: '🎨 CSS' + labels: + - 'css' + - title: '☕️ JavaScript' + labels: + - 'js' + - title: '📖 Docs' + labels: + - 'docs' + - title: '🛠 Examples' + labels: + - 'examples' + - title: '🌎 Accessibility' + labels: + - 'accessibility' + - title: '🔧 Utility API' + labels: + - 'utility API' + - 'utilities' + - title: '🏭 Tests' + labels: + - 'tests' + - title: '🧰 Misc' + labels: + - 'build' + - 'meta' + - 'chore' + - 'CI' + - title: '📦 Dependencies' + labels: + - 'dependencies' +change-template: '- #$NUMBER: $TITLE' +template: | + ## Changes + $CHANGES diff --git a/static/bootstrap-5.3.3/.github/workflows/browserstack.yml b/static/bootstrap-5.3.3/.github/workflows/browserstack.yml new file mode 100644 index 0000000..a53c233 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/browserstack.yml @@ -0,0 +1,46 @@ +name: BrowserStack + +on: + push: + branches: + - "**" + - "!dependabot/**" + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + browserstack: + runs-on: ubuntu-latest + if: github.repository == 'twbs/bootstrap' + timeout-minutes: 30 + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Run dist + run: npm run dist + + - name: Run BrowserStack tests + run: npm run js-test-cloud + env: + BROWSER_STACK_ACCESS_KEY: "${{ secrets.BROWSER_STACK_ACCESS_KEY }}" + BROWSER_STACK_USERNAME: "${{ secrets.BROWSER_STACK_USERNAME }}" + GITHUB_SHA: "${{ github.sha }}" diff --git a/static/bootstrap-5.3.3/.github/workflows/bundlewatch.yml b/static/bootstrap-5.3.3/.github/workflows/bundlewatch.yml new file mode 100644 index 0000000..99ba060 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/bundlewatch.yml @@ -0,0 +1,43 @@ +name: Bundlewatch + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + bundlewatch: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Run dist + run: npm run dist + + - name: Run bundlewatch + run: npm run bundlewatch + env: + BUNDLEWATCH_GITHUB_TOKEN: "${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}" + CI_BRANCH_BASE: main diff --git a/static/bootstrap-5.3.3/.github/workflows/calibreapp-image-actions.yml b/static/bootstrap-5.3.3/.github/workflows/calibreapp-image-actions.yml new file mode 100644 index 0000000..08987b3 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/calibreapp-image-actions.yml @@ -0,0 +1,32 @@ +name: Compress Images + +on: + pull_request: + paths: + - '**.jpg' + - '**.jpeg' + - '**.png' + - '**.webp' + +permissions: + contents: read + +jobs: + build: + # Only run on Pull Requests within the same repository, and not from forks. + if: github.event.pull_request.head.repo.full_name == github.repository + name: calibreapp/image-actions + runs-on: ubuntu-latest + permissions: + # allow calibreapp/image-actions to update PRs + pull-requests: write + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Compress Images + uses: calibreapp/image-actions@1.1.0 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/static/bootstrap-5.3.3/.github/workflows/codeql.yml b/static/bootstrap-5.3.3/.github/workflows/codeql.yml new file mode 100644 index 0000000..dd7f6e7 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/codeql.yml @@ -0,0 +1,44 @@ +name: "CodeQL" + +on: + push: + branches: + - main + - v4-dev + - "!dependabot/**" + pull_request: + branches: + - main + - v4-dev + - "!dependabot/**" + schedule: + - cron: "0 2 * * 4" + workflow_dispatch: + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + config-file: ./.github/codeql/codeql-config.yml + languages: "javascript" + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:javascript" diff --git a/static/bootstrap-5.3.3/.github/workflows/cspell.yml b/static/bootstrap-5.3.3/.github/workflows/cspell.yml new file mode 100644 index 0000000..c671fde --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/cspell.yml @@ -0,0 +1,36 @@ +name: cspell + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + +permissions: + contents: read + +jobs: + cspell: + permissions: + # allow streetsidesoftware/cspell-action to fetch files for commits and PRs + contents: read + pull-requests: read + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Run cspell + uses: streetsidesoftware/cspell-action@v5 + with: + config: ".cspell.json" + files: "**/*.md" + inline: error + incremental_files_only: false diff --git a/static/bootstrap-5.3.3/.github/workflows/css.yml b/static/bootstrap-5.3.3/.github/workflows/css.yml new file mode 100644 index 0000000..52e93e2 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/css.yml @@ -0,0 +1,40 @@ +name: CSS + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + css: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Build CSS + run: npm run css + + - name: Run CSS tests + run: npm run css-test diff --git a/static/bootstrap-5.3.3/.github/workflows/docs.yml b/static/bootstrap-5.3.3/.github/workflows/docs.yml new file mode 100644 index 0000000..0822205 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/docs.yml @@ -0,0 +1,50 @@ +name: Docs + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + docs: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + cache: npm + + - run: java -version + + - name: Install npm dependencies + run: npm ci + + - name: Build docs + run: npm run docs-build + + - name: Validate HTML + run: npm run docs-vnu + + - name: Run linkinator + uses: JustinBeckwith/linkinator-action@v1 + with: + paths: _site + recurse: true + verbosity: error + skip: "^(?!http://localhost)" diff --git a/static/bootstrap-5.3.3/.github/workflows/issue-close-require.yml b/static/bootstrap-5.3.3/.github/workflows/issue-close-require.yml new file mode 100644 index 0000000..b5000d8 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/issue-close-require.yml @@ -0,0 +1,26 @@ +name: Close Issue Awaiting Reply + +on: + schedule: + - cron: "0 0 * * *" + +permissions: + contents: read + +jobs: + issue-close-require: + permissions: + # allow actions-cool/issues-helper to update issues and PRs + issues: write + pull-requests: write + runs-on: ubuntu-latest + if: github.repository == 'twbs/bootstrap' + steps: + - name: awaiting reply + uses: actions-cool/issues-helper@v3 + with: + actions: "close-issues" + labels: "awaiting-reply" + inactive-day: 14 + body: | + As the issue was labeled with `awaiting-reply`, but there has been no response in 14 days, this issue will be closed. If you have any questions, you can comment/reply. diff --git a/static/bootstrap-5.3.3/.github/workflows/issue-labeled.yml b/static/bootstrap-5.3.3/.github/workflows/issue-labeled.yml new file mode 100644 index 0000000..584879d --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/issue-labeled.yml @@ -0,0 +1,26 @@ +name: Issue Labeled + +on: + issues: + types: [labeled] + +permissions: + contents: read + +jobs: + issue-labeled: + permissions: + # allow actions-cool/issues-helper to update issues and PRs + issues: write + pull-requests: write + if: github.repository == 'twbs/bootstrap' + runs-on: ubuntu-latest + steps: + - name: awaiting reply + if: github.event.label.name == 'needs-example' + uses: actions-cool/issues-helper@v3 + with: + actions: "create-comment" + token: ${{ secrets.GITHUB_TOKEN }} + body: | + Hello @${{ github.event.issue.user.login }}. Bug reports must include a **live demo** of the issue. Per our [contributing guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md), please create a reduced test case on [CodePen](https://codepen.io/) or [StackBlitz](https://stackblitz.com/) and report back with your link, Bootstrap version, and specific browser and Operating System details. diff --git a/static/bootstrap-5.3.3/.github/workflows/js.yml b/static/bootstrap-5.3.3/.github/workflows/js.yml new file mode 100644 index 0000000..1b672aa --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/js.yml @@ -0,0 +1,52 @@ +name: JS Tests + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + run: + permissions: + # allow coverallsapp/github-action to create new checks issues and fetch code + checks: write + contents: read + name: JS Tests + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE }} + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Run dist + run: npm run js + + - name: Run JS tests + run: npm run js-test + + - name: Run Coveralls + uses: coverallsapp/github-action@v2 + if: ${{ !github.event.repository.fork }} + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + path-to-lcov: "./js/coverage/lcov.info" diff --git a/static/bootstrap-5.3.3/.github/workflows/lint.yml b/static/bootstrap-5.3.3/.github/workflows/lint.yml new file mode 100644 index 0000000..213f9ec --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/lint.yml @@ -0,0 +1,37 @@ +name: Lint + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Lint + run: npm run lint diff --git a/static/bootstrap-5.3.3/.github/workflows/node-sass.yml b/static/bootstrap-5.3.3/.github/workflows/node-sass.yml new file mode 100644 index 0000000..493cc35 --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/node-sass.yml @@ -0,0 +1,49 @@ +name: CSS (node-sass) + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +env: + FORCE_COLOR: 2 + NODE: 20 + +permissions: + contents: read + +jobs: + css: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ env.NODE }}" + + - name: Build CSS with node-sass + run: | + npx --package node-sass@latest node-sass --version + npx --package node-sass@latest node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 scss/ -o dist-sass/css/ + ls -Al dist-sass/css + + - name: Check built CSS files for Sass variables + shell: bash + run: | + SASS_VARS_FOUND=$(find "dist-sass/css/" -type f -name "*.css" -print0 | xargs -0 --no-run-if-empty grep -F "\$" || true) + if [[ -z "$SASS_VARS_FOUND" ]]; then + echo "All good, no Sass variables found!" + exit 0 + else + echo "Found $(echo "$SASS_VARS_FOUND" | wc -l | bc) Sass variables:" + echo "$SASS_VARS_FOUND" + exit 1 + fi diff --git a/static/bootstrap-5.3.3/.github/workflows/release-notes.yml b/static/bootstrap-5.3.3/.github/workflows/release-notes.yml new file mode 100644 index 0000000..813956a --- /dev/null +++ b/static/bootstrap-5.3.3/.github/workflows/release-notes.yml @@ -0,0 +1,23 @@ +name: Release notes + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # allow release-drafter/release-drafter to create GitHub releases and add labels to PRs + contents: write + pull-requests: write + runs-on: ubuntu-latest + if: github.repository == 'twbs/bootstrap' + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/static/bootstrap-5.3.3/.gitignore b/static/bootstrap-5.3.3/.gitignore new file mode 100644 index 0000000..0c9b6f5 --- /dev/null +++ b/static/bootstrap-5.3.3/.gitignore @@ -0,0 +1,43 @@ +# Ignore docs files +/_site/ +# Hugo files +/resources/ +/.hugo_build.lock + +# Numerous always-ignore extensions +*.diff +*.err +*.log +*.orig +*.rej +*.swo +*.swp +*.vi +*.zip +*~ + +# OS or Editor folders +._* +.cache +.DS_Store +.idea +.project +.settings +.tmproj +*.esproj +*.sublime-project +*.sublime-workspace +nbproject +Thumbs.db +/.vscode/ +# Local Netlify folder +.netlify + +# Komodo +.komodotools +*.komodoproject + +# Folders to ignore +/dist-sass/ +/js/coverage/ +/node_modules/ diff --git a/static/bootstrap-5.3.3/.npmrc b/static/bootstrap-5.3.3/.npmrc new file mode 100644 index 0000000..4812751 --- /dev/null +++ b/static/bootstrap-5.3.3/.npmrc @@ -0,0 +1 @@ +lockfile-version=2 diff --git a/static/bootstrap-5.3.3/.stylelintignore b/static/bootstrap-5.3.3/.stylelintignore new file mode 100644 index 0000000..0759a69 --- /dev/null +++ b/static/bootstrap-5.3.3/.stylelintignore @@ -0,0 +1,5 @@ +**/*.min.css +**/dist/ +**/vendor/ +/_site/ +/js/coverage/ diff --git a/static/bootstrap-5.3.3/.stylelintrc.json b/static/bootstrap-5.3.3/.stylelintrc.json new file mode 100644 index 0000000..589884a --- /dev/null +++ b/static/bootstrap-5.3.3/.stylelintrc.json @@ -0,0 +1,60 @@ +{ + "extends": [ + "stylelint-config-twbs-bootstrap" + ], + "reportInvalidScopeDisables": true, + "reportNeedlessDisables": true, + "overrides": [ + { + "files": "**/*.scss", + "rules": { + "declaration-property-value-disallowed-list": { + "border": "none", + "outline": "none" + }, + "function-disallowed-list": [ + "calc", + "lighten", + "darken" + ], + "property-disallowed-list": [ + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius", + "transition" + ], + "scss/dollar-variable-default": [ + true, + { + "ignore": "local" + } + ], + "scss/selector-no-union-class-name": true + } + }, + { + "files": "scss/**/*.{test,spec}.scss", + "rules": { + "scss/dollar-variable-default": null, + "declaration-no-important": null + } + }, + { + "files": "site/**/*.scss", + "rules": { + "scss/dollar-variable-default": null + } + }, + { + "files": "site/**/examples/**/*.css", + "rules": { + "comment-empty-line-before": null, + "property-no-vendor-prefix": null, + "selector-no-qualifying-type": null, + "value-no-vendor-prefix": null + } + } + ] +} diff --git a/static/bootstrap-5.3.3/js/index.esm.js b/static/bootstrap-5.3.3/js/index.esm.js new file mode 100644 index 0000000..155d9fb --- /dev/null +++ b/static/bootstrap-5.3.3/js/index.esm.js @@ -0,0 +1,19 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap index.esm.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +export { default as Alert } from './src/alert.js' +export { default as Button } from './src/button.js' +export { default as Carousel } from './src/carousel.js' +export { default as Collapse } from './src/collapse.js' +export { default as Dropdown } from './src/dropdown.js' +export { default as Modal } from './src/modal.js' +export { default as Offcanvas } from './src/offcanvas.js' +export { default as Popover } from './src/popover.js' +export { default as ScrollSpy } from './src/scrollspy.js' +export { default as Tab } from './src/tab.js' +export { default as Toast } from './src/toast.js' +export { default as Tooltip } from './src/tooltip.js' diff --git a/static/bootstrap-5.3.3/js/index.umd.js b/static/bootstrap-5.3.3/js/index.umd.js new file mode 100644 index 0000000..a33df74 --- /dev/null +++ b/static/bootstrap-5.3.3/js/index.umd.js @@ -0,0 +1,34 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap index.umd.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import Alert from './src/alert.js' +import Button from './src/button.js' +import Carousel from './src/carousel.js' +import Collapse from './src/collapse.js' +import Dropdown from './src/dropdown.js' +import Modal from './src/modal.js' +import Offcanvas from './src/offcanvas.js' +import Popover from './src/popover.js' +import ScrollSpy from './src/scrollspy.js' +import Tab from './src/tab.js' +import Toast from './src/toast.js' +import Tooltip from './src/tooltip.js' + +export default { + Alert, + Button, + Carousel, + Collapse, + Dropdown, + Modal, + Offcanvas, + Popover, + ScrollSpy, + Tab, + Toast, + Tooltip +} diff --git a/static/bootstrap-5.3.3/js/src/alert.js b/static/bootstrap-5.3.3/js/src/alert.js new file mode 100644 index 0000000..88232bc --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/alert.js @@ -0,0 +1,87 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { enableDismissTrigger } from './util/component-functions.js' +import { defineJQueryPlugin } from './util/index.js' + +/** + * Constants + */ + +const NAME = 'alert' +const DATA_KEY = 'bs.alert' +const EVENT_KEY = `.${DATA_KEY}` + +const EVENT_CLOSE = `close${EVENT_KEY}` +const EVENT_CLOSED = `closed${EVENT_KEY}` +const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_SHOW = 'show' + +/** + * Class definition + */ + +class Alert extends BaseComponent { + // Getters + static get NAME() { + return NAME + } + + // Public + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE) + + if (closeEvent.defaultPrevented) { + return + } + + this._element.classList.remove(CLASS_NAME_SHOW) + + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE) + this._queueCallback(() => this._destroyElement(), this._element, isAnimated) + } + + // Private + _destroyElement() { + this._element.remove() + EventHandler.trigger(this._element, EVENT_CLOSED) + this.dispose() + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Alert.getOrCreateInstance(this) + + if (typeof config !== 'string') { + return + } + + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](this) + }) + } +} + +/** + * Data API implementation + */ + +enableDismissTrigger(Alert, 'close') + +/** + * jQuery + */ + +defineJQueryPlugin(Alert) + +export default Alert diff --git a/static/bootstrap-5.3.3/js/src/base-component.js b/static/bootstrap-5.3.3/js/src/base-component.js new file mode 100644 index 0000000..82bf770 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/base-component.js @@ -0,0 +1,85 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import Data from './dom/data.js' +import EventHandler from './dom/event-handler.js' +import Config from './util/config.js' +import { executeAfterTransition, getElement } from './util/index.js' + +/** + * Constants + */ + +const VERSION = '5.3.3' + +/** + * Class definition + */ + +class BaseComponent extends Config { + constructor(element, config) { + super() + + element = getElement(element) + if (!element) { + return + } + + this._element = element + this._config = this._getConfig(config) + + Data.set(this._element, this.constructor.DATA_KEY, this) + } + + // Public + dispose() { + Data.remove(this._element, this.constructor.DATA_KEY) + EventHandler.off(this._element, this.constructor.EVENT_KEY) + + for (const propertyName of Object.getOwnPropertyNames(this)) { + this[propertyName] = null + } + } + + _queueCallback(callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated) + } + + _getConfig(config) { + config = this._mergeConfigObj(config, this._element) + config = this._configAfterMerge(config) + this._typeCheckConfig(config) + return config + } + + // Static + static getInstance(element) { + return Data.get(getElement(element), this.DATA_KEY) + } + + static getOrCreateInstance(element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null) + } + + static get VERSION() { + return VERSION + } + + static get DATA_KEY() { + return `bs.${this.NAME}` + } + + static get EVENT_KEY() { + return `.${this.DATA_KEY}` + } + + static eventName(name) { + return `${name}${this.EVENT_KEY}` + } +} + +export default BaseComponent diff --git a/static/bootstrap-5.3.3/js/src/button.js b/static/bootstrap-5.3.3/js/src/button.js new file mode 100644 index 0000000..a797f50 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/button.js @@ -0,0 +1,72 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { defineJQueryPlugin } from './util/index.js' + +/** + * Constants + */ + +const NAME = 'button' +const DATA_KEY = 'bs.button' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const CLASS_NAME_ACTIVE = 'active' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]' +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + +/** + * Class definition + */ + +class Button extends BaseComponent { + // Getters + static get NAME() { + return NAME + } + + // Public + toggle() { + // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method + this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE)) + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Button.getOrCreateInstance(this) + + if (config === 'toggle') { + data[config]() + } + }) + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { + event.preventDefault() + + const button = event.target.closest(SELECTOR_DATA_TOGGLE) + const data = Button.getOrCreateInstance(button) + + data.toggle() +}) + +/** + * jQuery + */ + +defineJQueryPlugin(Button) + +export default Button diff --git a/static/bootstrap-5.3.3/js/src/carousel.js b/static/bootstrap-5.3.3/js/src/carousel.js new file mode 100644 index 0000000..68d11a3 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/carousel.js @@ -0,0 +1,474 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' +import { + defineJQueryPlugin, + getNextActiveElement, + isRTL, + isVisible, + reflow, + triggerTransitionEnd +} from './util/index.js' +import Swipe from './util/swipe.js' + +/** + * Constants + */ + +const NAME = 'carousel' +const DATA_KEY = 'bs.carousel' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const ARROW_LEFT_KEY = 'ArrowLeft' +const ARROW_RIGHT_KEY = 'ArrowRight' +const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch + +const ORDER_NEXT = 'next' +const ORDER_PREV = 'prev' +const DIRECTION_LEFT = 'left' +const DIRECTION_RIGHT = 'right' + +const EVENT_SLIDE = `slide${EVENT_KEY}` +const EVENT_SLID = `slid${EVENT_KEY}` +const EVENT_KEYDOWN = `keydown${EVENT_KEY}` +const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}` +const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}` +const EVENT_DRAG_START = `dragstart${EVENT_KEY}` +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + +const CLASS_NAME_CAROUSEL = 'carousel' +const CLASS_NAME_ACTIVE = 'active' +const CLASS_NAME_SLIDE = 'slide' +const CLASS_NAME_END = 'carousel-item-end' +const CLASS_NAME_START = 'carousel-item-start' +const CLASS_NAME_NEXT = 'carousel-item-next' +const CLASS_NAME_PREV = 'carousel-item-prev' + +const SELECTOR_ACTIVE = '.active' +const SELECTOR_ITEM = '.carousel-item' +const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM +const SELECTOR_ITEM_IMG = '.carousel-item img' +const SELECTOR_INDICATORS = '.carousel-indicators' +const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]' +const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]' + +const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY]: DIRECTION_LEFT +} + +const Default = { + interval: 5000, + keyboard: true, + pause: 'hover', + ride: false, + touch: true, + wrap: true +} + +const DefaultType = { + interval: '(number|boolean)', // TODO:v6 remove boolean support + keyboard: 'boolean', + pause: '(string|boolean)', + ride: '(boolean|string)', + touch: 'boolean', + wrap: 'boolean' +} + +/** + * Class definition + */ + +class Carousel extends BaseComponent { + constructor(element, config) { + super(element, config) + + this._interval = null + this._activeElement = null + this._isSliding = false + this.touchTimeout = null + this._swipeHelper = null + + this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element) + this._addEventListeners() + + if (this._config.ride === CLASS_NAME_CAROUSEL) { + this.cycle() + } + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + next() { + this._slide(ORDER_NEXT) + } + + nextWhenVisible() { + // FIXME TODO use `document.visibilityState` + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && isVisible(this._element)) { + this.next() + } + } + + prev() { + this._slide(ORDER_PREV) + } + + pause() { + if (this._isSliding) { + triggerTransitionEnd(this._element) + } + + this._clearInterval() + } + + cycle() { + this._clearInterval() + this._updateInterval() + + this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval) + } + + _maybeEnableCycle() { + if (!this._config.ride) { + return + } + + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.cycle()) + return + } + + this.cycle() + } + + to(index) { + const items = this._getItems() + if (index > items.length - 1 || index < 0) { + return + } + + if (this._isSliding) { + EventHandler.one(this._element, EVENT_SLID, () => this.to(index)) + return + } + + const activeIndex = this._getItemIndex(this._getActive()) + if (activeIndex === index) { + return + } + + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV + + this._slide(order, items[index]) + } + + dispose() { + if (this._swipeHelper) { + this._swipeHelper.dispose() + } + + super.dispose() + } + + // Private + _configAfterMerge(config) { + config.defaultInterval = config.interval + return config + } + + _addEventListeners() { + if (this._config.keyboard) { + EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)) + } + + if (this._config.pause === 'hover') { + EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause()) + EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle()) + } + + if (this._config.touch && Swipe.isSupported()) { + this._addTouchEventListeners() + } + } + + _addTouchEventListeners() { + for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { + EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()) + } + + const endCallBack = () => { + if (this._config.pause !== 'hover') { + return + } + + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + + this.pause() + if (this.touchTimeout) { + clearTimeout(this.touchTimeout) + } + + this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval) + } + + const swipeConfig = { + leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), + rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), + endCallback: endCallBack + } + + this._swipeHelper = new Swipe(this._element, swipeConfig) + } + + _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return + } + + const direction = KEY_TO_DIRECTION[event.key] + if (direction) { + event.preventDefault() + this._slide(this._directionToOrder(direction)) + } + } + + _getItemIndex(element) { + return this._getItems().indexOf(element) + } + + _setActiveIndicatorElement(index) { + if (!this._indicatorsElement) { + return + } + + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement) + + activeIndicator.classList.remove(CLASS_NAME_ACTIVE) + activeIndicator.removeAttribute('aria-current') + + const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement) + + if (newActiveIndicator) { + newActiveIndicator.classList.add(CLASS_NAME_ACTIVE) + newActiveIndicator.setAttribute('aria-current', 'true') + } + } + + _updateInterval() { + const element = this._activeElement || this._getActive() + + if (!element) { + return + } + + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10) + + this._config.interval = elementInterval || this._config.defaultInterval + } + + _slide(order, element = null) { + if (this._isSliding) { + return + } + + const activeElement = this._getActive() + const isNext = order === ORDER_NEXT + const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) + + if (nextElement === activeElement) { + return + } + + const nextElementIndex = this._getItemIndex(nextElement) + + const triggerEvent = eventName => { + return EventHandler.trigger(this._element, eventName, { + relatedTarget: nextElement, + direction: this._orderToDirection(order), + from: this._getItemIndex(activeElement), + to: nextElementIndex + }) + } + + const slideEvent = triggerEvent(EVENT_SLIDE) + + if (slideEvent.defaultPrevented) { + return + } + + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + // TODO: change tests that use empty divs to avoid this check + return + } + + const isCycling = Boolean(this._interval) + this.pause() + + this._isSliding = true + + this._setActiveIndicatorElement(nextElementIndex) + this._activeElement = nextElement + + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV + + nextElement.classList.add(orderClassName) + + reflow(nextElement) + + activeElement.classList.add(directionalClassName) + nextElement.classList.add(directionalClassName) + + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName) + nextElement.classList.add(CLASS_NAME_ACTIVE) + + activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName) + + this._isSliding = false + + triggerEvent(EVENT_SLID) + } + + this._queueCallback(completeCallBack, activeElement, this._isAnimated()) + + if (isCycling) { + this.cycle() + } + } + + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_SLIDE) + } + + _getActive() { + return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) + } + + _getItems() { + return SelectorEngine.find(SELECTOR_ITEM, this._element) + } + + _clearInterval() { + if (this._interval) { + clearInterval(this._interval) + this._interval = null + } + } + + _directionToOrder(direction) { + if (isRTL()) { + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT + } + + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV + } + + _orderToDirection(order) { + if (isRTL()) { + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT + } + + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Carousel.getOrCreateInstance(this, config) + + if (typeof config === 'number') { + data.to(config) + return + } + + if (typeof config === 'string') { + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + } + }) + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) { + const target = SelectorEngine.getElementFromSelector(this) + + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { + return + } + + event.preventDefault() + + const carousel = Carousel.getOrCreateInstance(target) + const slideIndex = this.getAttribute('data-bs-slide-to') + + if (slideIndex) { + carousel.to(slideIndex) + carousel._maybeEnableCycle() + return + } + + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { + carousel.next() + carousel._maybeEnableCycle() + return + } + + carousel.prev() + carousel._maybeEnableCycle() +}) + +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) + + for (const carousel of carousels) { + Carousel.getOrCreateInstance(carousel) + } +}) + +/** + * jQuery + */ + +defineJQueryPlugin(Carousel) + +export default Carousel diff --git a/static/bootstrap-5.3.3/js/src/collapse.js b/static/bootstrap-5.3.3/js/src/collapse.js new file mode 100644 index 0000000..9f0c60c --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/collapse.js @@ -0,0 +1,297 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { + defineJQueryPlugin, + getElement, + reflow +} from './util/index.js' + +/** + * Constants + */ + +const NAME = 'collapse' +const DATA_KEY = 'bs.collapse' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_COLLAPSE = 'collapse' +const CLASS_NAME_COLLAPSING = 'collapsing' +const CLASS_NAME_COLLAPSED = 'collapsed' +const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}` +const CLASS_NAME_HORIZONTAL = 'collapse-horizontal' + +const WIDTH = 'width' +const HEIGHT = 'height' + +const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"]' + +const Default = { + parent: null, + toggle: true +} + +const DefaultType = { + parent: '(null|element)', + toggle: 'boolean' +} + +/** + * Class definition + */ + +class Collapse extends BaseComponent { + constructor(element, config) { + super(element, config) + + this._isTransitioning = false + this._triggerArray = [] + + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) + + for (const elem of toggleList) { + const selector = SelectorEngine.getSelectorFromElement(elem) + const filterElement = SelectorEngine.find(selector) + .filter(foundElement => foundElement === this._element) + + if (selector !== null && filterElement.length) { + this._triggerArray.push(elem) + } + } + + this._initializeChildren() + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()) + } + + if (this._config.toggle) { + this.toggle() + } + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + toggle() { + if (this._isShown()) { + this.hide() + } else { + this.show() + } + } + + show() { + if (this._isTransitioning || this._isShown()) { + return + } + + let activeChildren = [] + + // find active children + if (this._config.parent) { + activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES) + .filter(element => element !== this._element) + .map(element => Collapse.getOrCreateInstance(element, { toggle: false })) + } + + if (activeChildren.length && activeChildren[0]._isTransitioning) { + return + } + + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW) + if (startEvent.defaultPrevented) { + return + } + + for (const activeInstance of activeChildren) { + activeInstance.hide() + } + + const dimension = this._getDimension() + + this._element.classList.remove(CLASS_NAME_COLLAPSE) + this._element.classList.add(CLASS_NAME_COLLAPSING) + + this._element.style[dimension] = 0 + + this._addAriaAndCollapsedClass(this._triggerArray, true) + this._isTransitioning = true + + const complete = () => { + this._isTransitioning = false + + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) + + this._element.style[dimension] = '' + + EventHandler.trigger(this._element, EVENT_SHOWN) + } + + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) + const scrollSize = `scroll${capitalizedDimension}` + + this._queueCallback(complete, this._element, true) + this._element.style[dimension] = `${this._element[scrollSize]}px` + } + + hide() { + if (this._isTransitioning || !this._isShown()) { + return + } + + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE) + if (startEvent.defaultPrevented) { + return + } + + const dimension = this._getDimension() + + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px` + + reflow(this._element) + + this._element.classList.add(CLASS_NAME_COLLAPSING) + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) + + for (const trigger of this._triggerArray) { + const element = SelectorEngine.getElementFromSelector(trigger) + + if (element && !this._isShown(element)) { + this._addAriaAndCollapsedClass([trigger], false) + } + } + + this._isTransitioning = true + + const complete = () => { + this._isTransitioning = false + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE) + EventHandler.trigger(this._element, EVENT_HIDDEN) + } + + this._element.style[dimension] = '' + + this._queueCallback(complete, this._element, true) + } + + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) + } + + // Private + _configAfterMerge(config) { + config.toggle = Boolean(config.toggle) // Coerce string values + config.parent = getElement(config.parent) + return config + } + + _getDimension() { + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT + } + + _initializeChildren() { + if (!this._config.parent) { + return + } + + const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE) + + for (const element of children) { + const selected = SelectorEngine.getElementFromSelector(element) + + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)) + } + } + } + + _getFirstLevelChildren(selector) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) + // remove children if greater depth + return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)) + } + + _addAriaAndCollapsedClass(triggerArray, isOpen) { + if (!triggerArray.length) { + return + } + + for (const element of triggerArray) { + element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen) + element.setAttribute('aria-expanded', isOpen) + } + } + + // Static + static jQueryInterface(config) { + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false + } + + return this.each(function () { + const data = Collapse.getOrCreateInstance(this, _config) + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + } + }) + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + // preventDefault only for elements (which change the URL) not inside the collapsible element + if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) { + event.preventDefault() + } + + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { + Collapse.getOrCreateInstance(element, { toggle: false }).toggle() + } +}) + +/** + * jQuery + */ + +defineJQueryPlugin(Collapse) + +export default Collapse diff --git a/static/bootstrap-5.3.3/js/src/dom/data.js b/static/bootstrap-5.3.3/js/src/dom/data.js new file mode 100644 index 0000000..407f67e --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/dom/data.js @@ -0,0 +1,55 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap dom/data.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +/** + * Constants + */ + +const elementMap = new Map() + +export default { + set(element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()) + } + + const instanceMap = elementMap.get(element) + + // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + if (!instanceMap.has(key) && instanceMap.size !== 0) { + // eslint-disable-next-line no-console + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`) + return + } + + instanceMap.set(key, instance) + }, + + get(element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null + } + + return null + }, + + remove(element, key) { + if (!elementMap.has(element)) { + return + } + + const instanceMap = elementMap.get(element) + + instanceMap.delete(key) + + // free up element references if there are no instances left for an element + if (instanceMap.size === 0) { + elementMap.delete(element) + } + } +} diff --git a/static/bootstrap-5.3.3/js/src/dom/event-handler.js b/static/bootstrap-5.3.3/js/src/dom/event-handler.js new file mode 100644 index 0000000..561d875 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/dom/event-handler.js @@ -0,0 +1,317 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap dom/event-handler.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { getjQuery } from '../util/index.js' + +/** + * Constants + */ + +const namespaceRegex = /[^.]*(?=\..*)\.|.*/ +const stripNameRegex = /\..*/ +const stripUidRegex = /::\d+$/ +const eventRegistry = {} // Events storage +let uidEvent = 1 +const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' +} + +const nativeEvents = new Set([ + 'click', + 'dblclick', + 'mouseup', + 'mousedown', + 'contextmenu', + 'mousewheel', + 'DOMMouseScroll', + 'mouseover', + 'mouseout', + 'mousemove', + 'selectstart', + 'selectend', + 'keydown', + 'keypress', + 'keyup', + 'orientationchange', + 'touchstart', + 'touchmove', + 'touchend', + 'touchcancel', + 'pointerdown', + 'pointermove', + 'pointerup', + 'pointerleave', + 'pointercancel', + 'gesturestart', + 'gesturechange', + 'gestureend', + 'focus', + 'blur', + 'change', + 'reset', + 'select', + 'submit', + 'focusin', + 'focusout', + 'load', + 'unload', + 'beforeunload', + 'resize', + 'move', + 'DOMContentLoaded', + 'readystatechange', + 'error', + 'abort', + 'scroll' +]) + +/** + * Private methods + */ + +function makeEventUid(element, uid) { + return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++ +} + +function getElementEvents(element) { + const uid = makeEventUid(element) + + element.uidEvent = uid + eventRegistry[uid] = eventRegistry[uid] || {} + + return eventRegistry[uid] +} + +function bootstrapHandler(element, fn) { + return function handler(event) { + hydrateObj(event, { delegateTarget: element }) + + if (handler.oneOff) { + EventHandler.off(element, event.type, fn) + } + + return fn.apply(element, [event]) + } +} + +function bootstrapDelegationHandler(element, selector, fn) { + return function handler(event) { + const domElements = element.querySelectorAll(selector) + + for (let { target } = event; target && target !== this; target = target.parentNode) { + for (const domElement of domElements) { + if (domElement !== target) { + continue + } + + hydrateObj(event, { delegateTarget: target }) + + if (handler.oneOff) { + EventHandler.off(element, event.type, selector, fn) + } + + return fn.apply(target, [event]) + } + } + } +} + +function findHandler(events, callable, delegationSelector = null) { + return Object.values(events) + .find(event => event.callable === callable && event.delegationSelector === delegationSelector) +} + +function normalizeParameters(originalTypeEvent, handler, delegationFunction) { + const isDelegated = typeof handler === 'string' + // TODO: tooltip passes `false` instead of selector, so we need to check + const callable = isDelegated ? delegationFunction : (handler || delegationFunction) + let typeEvent = getTypeEvent(originalTypeEvent) + + if (!nativeEvents.has(typeEvent)) { + typeEvent = originalTypeEvent + } + + return [isDelegated, callable, typeEvent] +} + +function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { + if (typeof originalTypeEvent !== 'string' || !element) { + return + } + + let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) + + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (originalTypeEvent in customEvents) { + const wrapFunction = fn => { + return function (event) { + if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { + return fn.call(this, event) + } + } + } + + callable = wrapFunction(callable) + } + + const events = getElementEvents(element) + const handlers = events[typeEvent] || (events[typeEvent] = {}) + const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null) + + if (previousFunction) { + previousFunction.oneOff = previousFunction.oneOff && oneOff + + return + } + + const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, '')) + const fn = isDelegated ? + bootstrapDelegationHandler(element, handler, callable) : + bootstrapHandler(element, callable) + + fn.delegationSelector = isDelegated ? handler : null + fn.callable = callable + fn.oneOff = oneOff + fn.uidEvent = uid + handlers[uid] = fn + + element.addEventListener(typeEvent, fn, isDelegated) +} + +function removeHandler(element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector) + + if (!fn) { + return + } + + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)) + delete events[typeEvent][fn.uidEvent] +} + +function removeNamespacedHandlers(element, events, typeEvent, namespace) { + const storeElementEvent = events[typeEvent] || {} + + for (const [handlerKey, event] of Object.entries(storeElementEvent)) { + if (handlerKey.includes(namespace)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) + } + } +} + +function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, '') + return customEvents[event] || event +} + +const EventHandler = { + on(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, false) + }, + + one(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, true) + }, + + off(element, originalTypeEvent, handler, delegationFunction) { + if (typeof originalTypeEvent !== 'string' || !element) { + return + } + + const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) + const inNamespace = typeEvent !== originalTypeEvent + const events = getElementEvents(element) + const storeElementEvent = events[typeEvent] || {} + const isNamespace = originalTypeEvent.startsWith('.') + + if (typeof callable !== 'undefined') { + // Simplest case: handler is passed, remove that listener ONLY. + if (!Object.keys(storeElementEvent).length) { + return + } + + removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null) + return + } + + if (isNamespace) { + for (const elementEvent of Object.keys(events)) { + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)) + } + } + + for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { + const handlerKey = keyHandlers.replace(stripUidRegex, '') + + if (!inNamespace || originalTypeEvent.includes(handlerKey)) { + removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) + } + } + }, + + trigger(element, event, args) { + if (typeof event !== 'string' || !element) { + return null + } + + const $ = getjQuery() + const typeEvent = getTypeEvent(event) + const inNamespace = event !== typeEvent + + let jQueryEvent = null + let bubbles = true + let nativeDispatch = true + let defaultPrevented = false + + if (inNamespace && $) { + jQueryEvent = $.Event(event, args) + + $(element).trigger(jQueryEvent) + bubbles = !jQueryEvent.isPropagationStopped() + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() + defaultPrevented = jQueryEvent.isDefaultPrevented() + } + + const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args) + + if (defaultPrevented) { + evt.preventDefault() + } + + if (nativeDispatch) { + element.dispatchEvent(evt) + } + + if (evt.defaultPrevented && jQueryEvent) { + jQueryEvent.preventDefault() + } + + return evt + } +} + +function hydrateObj(obj, meta = {}) { + for (const [key, value] of Object.entries(meta)) { + try { + obj[key] = value + } catch { + Object.defineProperty(obj, key, { + configurable: true, + get() { + return value + } + }) + } + } + + return obj +} + +export default EventHandler diff --git a/static/bootstrap-5.3.3/js/src/dom/manipulator.js b/static/bootstrap-5.3.3/js/src/dom/manipulator.js new file mode 100644 index 0000000..dd86a9f --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/dom/manipulator.js @@ -0,0 +1,71 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +function normalizeData(value) { + if (value === 'true') { + return true + } + + if (value === 'false') { + return false + } + + if (value === Number(value).toString()) { + return Number(value) + } + + if (value === '' || value === 'null') { + return null + } + + if (typeof value !== 'string') { + return value + } + + try { + return JSON.parse(decodeURIComponent(value)) + } catch { + return value + } +} + +function normalizeDataKey(key) { + return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`) +} + +const Manipulator = { + setDataAttribute(element, key, value) { + element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value) + }, + + removeDataAttribute(element, key) { + element.removeAttribute(`data-bs-${normalizeDataKey(key)}`) + }, + + getDataAttributes(element) { + if (!element) { + return {} + } + + const attributes = {} + const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')) + + for (const key of bsKeys) { + let pureKey = key.replace(/^bs/, '') + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length) + attributes[pureKey] = normalizeData(element.dataset[key]) + } + + return attributes + }, + + getDataAttribute(element, key) { + return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)) + } +} + +export default Manipulator diff --git a/static/bootstrap-5.3.3/js/src/dom/selector-engine.js b/static/bootstrap-5.3.3/js/src/dom/selector-engine.js new file mode 100644 index 0000000..a4d81f3 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/dom/selector-engine.js @@ -0,0 +1,126 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap dom/selector-engine.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { isDisabled, isVisible, parseSelector } from '../util/index.js' + +const getSelector = element => { + let selector = element.getAttribute('data-bs-target') + + if (!selector || selector === '#') { + let hrefAttribute = element.getAttribute('href') + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { + return null + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}` + } + + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null + } + + return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null +} + +const SelectorEngine = { + find(selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)) + }, + + findOne(selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector) + }, + + children(element, selector) { + return [].concat(...element.children).filter(child => child.matches(selector)) + }, + + parents(element, selector) { + const parents = [] + let ancestor = element.parentNode.closest(selector) + + while (ancestor) { + parents.push(ancestor) + ancestor = ancestor.parentNode.closest(selector) + } + + return parents + }, + + prev(element, selector) { + let previous = element.previousElementSibling + + while (previous) { + if (previous.matches(selector)) { + return [previous] + } + + previous = previous.previousElementSibling + } + + return [] + }, + // TODO: this is now unused; remove later along with prev() + next(element, selector) { + let next = element.nextElementSibling + + while (next) { + if (next.matches(selector)) { + return [next] + } + + next = next.nextElementSibling + } + + return [] + }, + + focusableChildren(element) { + const focusables = [ + 'a', + 'button', + 'input', + 'textarea', + 'select', + 'details', + '[tabindex]', + '[contenteditable="true"]' + ].map(selector => `${selector}:not([tabindex^="-"])`).join(',') + + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) + }, + + getSelectorFromElement(element) { + const selector = getSelector(element) + + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null + } + + return null + }, + + getElementFromSelector(element) { + const selector = getSelector(element) + + return selector ? SelectorEngine.findOne(selector) : null + }, + + getMultipleElementsFromSelector(element) { + const selector = getSelector(element) + + return selector ? SelectorEngine.find(selector) : [] + } +} + +export default SelectorEngine diff --git a/static/bootstrap-5.3.3/js/src/dropdown.js b/static/bootstrap-5.3.3/js/src/dropdown.js new file mode 100644 index 0000000..af5fd16 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/dropdown.js @@ -0,0 +1,455 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import * as Popper from '@popperjs/core' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' +import { + defineJQueryPlugin, + execute, + getElement, + getNextActiveElement, + isDisabled, + isElement, + isRTL, + isVisible, + noop +} from './util/index.js' + +/** + * Constants + */ + +const NAME = 'dropdown' +const DATA_KEY = 'bs.dropdown' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const ESCAPE_KEY = 'Escape' +const TAB_KEY = 'Tab' +const ARROW_UP_KEY = 'ArrowUp' +const ARROW_DOWN_KEY = 'ArrowDown' +const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button + +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` + +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_DROPUP = 'dropup' +const CLASS_NAME_DROPEND = 'dropend' +const CLASS_NAME_DROPSTART = 'dropstart' +const CLASS_NAME_DROPUP_CENTER = 'dropup-center' +const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center' + +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)' +const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` +const SELECTOR_MENU = '.dropdown-menu' +const SELECTOR_NAVBAR = '.navbar' +const SELECTOR_NAVBAR_NAV = '.navbar-nav' +const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' + +const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start' +const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end' +const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' +const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' +const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' +const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' +const PLACEMENT_TOPCENTER = 'top' +const PLACEMENT_BOTTOMCENTER = 'bottom' + +const Default = { + autoClose: true, + boundary: 'clippingParents', + display: 'dynamic', + offset: [0, 2], + popperConfig: null, + reference: 'toggle' +} + +const DefaultType = { + autoClose: '(boolean|string)', + boundary: '(string|element)', + display: 'string', + offset: '(array|string|function)', + popperConfig: '(null|object|function)', + reference: '(string|element|object)' +} + +/** + * Class definition + */ + +class Dropdown extends BaseComponent { + constructor(element, config) { + super(element, config) + + this._popper = null + this._parent = this._element.parentNode // dropdown wrapper + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || + SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || + SelectorEngine.findOne(SELECTOR_MENU, this._parent) + this._inNavbar = this._detectNavbar() + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + toggle() { + return this._isShown() ? this.hide() : this.show() + } + + show() { + if (isDisabled(this._element) || this._isShown()) { + return + } + + const relatedTarget = { + relatedTarget: this._element + } + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget) + + if (showEvent.defaultPrevented) { + return + } + + this._createPopper() + + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop) + } + } + + this._element.focus() + this._element.setAttribute('aria-expanded', true) + + this._menu.classList.add(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_SHOW) + EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget) + } + + hide() { + if (isDisabled(this._element) || !this._isShown()) { + return + } + + const relatedTarget = { + relatedTarget: this._element + } + + this._completeHide(relatedTarget) + } + + dispose() { + if (this._popper) { + this._popper.destroy() + } + + super.dispose() + } + + update() { + this._inNavbar = this._detectNavbar() + if (this._popper) { + this._popper.update() + } + } + + // Private + _completeHide(relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget) + if (hideEvent.defaultPrevented) { + return + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop) + } + } + + if (this._popper) { + this._popper.destroy() + } + + this._menu.classList.remove(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOW) + this._element.setAttribute('aria-expanded', 'false') + Manipulator.removeDataAttribute(this._menu, 'popper') + EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) + } + + _getConfig(config) { + config = super._getConfig(config) + + if (typeof config.reference === 'object' && !isElement(config.reference) && + typeof config.reference.getBoundingClientRect !== 'function' + ) { + // Popper virtual elements require a getBoundingClientRect method + throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`) + } + + return config + } + + _createPopper() { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') + } + + let referenceElement = this._element + + if (this._config.reference === 'parent') { + referenceElement = this._parent + } else if (isElement(this._config.reference)) { + referenceElement = getElement(this._config.reference) + } else if (typeof this._config.reference === 'object') { + referenceElement = this._config.reference + } + + const popperConfig = this._getPopperConfig() + this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) + } + + _isShown() { + return this._menu.classList.contains(CLASS_NAME_SHOW) + } + + _getPlacement() { + const parentDropdown = this._parent + + if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { + return PLACEMENT_RIGHT + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { + return PLACEMENT_LEFT + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { + return PLACEMENT_TOPCENTER + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { + return PLACEMENT_BOTTOMCENTER + } + + // We need to trim the value because custom properties can also include spaces + const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end' + + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { + return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP + } + + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM + } + + _detectNavbar() { + return this._element.closest(SELECTOR_NAVBAR) !== null + } + + _getOffset() { + const { offset } = this._config + + if (typeof offset === 'string') { + return offset.split(',').map(value => Number.parseInt(value, 10)) + } + + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element) + } + + return offset + } + + _getPopperConfig() { + const defaultBsPopperConfig = { + placement: this._getPlacement(), + modifiers: [{ + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, + { + name: 'offset', + options: { + offset: this._getOffset() + } + }] + } + + // Disable Popper if we have a static display or Dropdown is in Navbar + if (this._inNavbar || this._config.display === 'static') { + Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove + defaultBsPopperConfig.modifiers = [{ + name: 'applyStyles', + enabled: false + }] + } + + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [defaultBsPopperConfig]) + } + } + + _selectMenuItem({ key, target }) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)) + + if (!items.length) { + return + } + + // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY + getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus() + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Dropdown.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } + + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + }) + } + + static clearMenus(event) { + if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) { + return + } + + const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN) + + for (const toggle of openToggles) { + const context = Dropdown.getInstance(toggle) + if (!context || context._config.autoClose === false) { + continue + } + + const composedPath = event.composedPath() + const isMenuTarget = composedPath.includes(context._menu) + if ( + composedPath.includes(context._element) || + (context._config.autoClose === 'inside' && !isMenuTarget) || + (context._config.autoClose === 'outside' && isMenuTarget) + ) { + continue + } + + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) { + continue + } + + const relatedTarget = { relatedTarget: context._element } + + if (event.type === 'click') { + relatedTarget.clickEvent = event + } + + context._completeHide(relatedTarget) + } + } + + static dataApiKeydownHandler(event) { + // If not an UP | DOWN | ESCAPE key => not a dropdown command + // If input/textarea && if key is other than ESCAPE => not a dropdown command + + const isInput = /input|textarea/i.test(event.target.tagName) + const isEscapeEvent = event.key === ESCAPE_KEY + const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key) + + if (!isUpOrDownEvent && !isEscapeEvent) { + return + } + + if (isInput && !isEscapeEvent) { + return + } + + event.preventDefault() + + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? + this : + (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || + SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || + SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode)) + + const instance = Dropdown.getOrCreateInstance(getToggleButton) + + if (isUpOrDownEvent) { + event.stopPropagation() + instance.show() + instance._selectMenuItem(event) + return + } + + if (instance._isShown()) { // else is escape and we check if it is shown + event.stopPropagation() + instance.hide() + getToggleButton.focus() + } + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler) +EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler) +EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) +EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + event.preventDefault() + Dropdown.getOrCreateInstance(this).toggle() +}) + +/** + * jQuery + */ + +defineJQueryPlugin(Dropdown) + +export default Dropdown diff --git a/static/bootstrap-5.3.3/js/src/modal.js b/static/bootstrap-5.3.3/js/src/modal.js new file mode 100644 index 0000000..dd61649 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/modal.js @@ -0,0 +1,378 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' +import { + defineJQueryPlugin, isRTL, isVisible, reflow +} from './util/index.js' +import ScrollBarHelper from './util/scrollbar.js' + +/** + * Constants + */ + +const NAME = 'modal' +const DATA_KEY = 'bs.modal' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const ESCAPE_KEY = 'Escape' + +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_RESIZE = `resize${EVENT_KEY}` +const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` +const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}` +const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` + +const CLASS_NAME_OPEN = 'modal-open' +const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_STATIC = 'modal-static' + +const OPEN_SELECTOR = '.modal.show' +const SELECTOR_DIALOG = '.modal-dialog' +const SELECTOR_MODAL_BODY = '.modal-body' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' + +const Default = { + backdrop: true, + focus: true, + keyboard: true +} + +const DefaultType = { + backdrop: '(boolean|string)', + focus: 'boolean', + keyboard: 'boolean' +} + +/** + * Class definition + */ + +class Modal extends BaseComponent { + constructor(element, config) { + super(element, config) + + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) + this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() + this._isShown = false + this._isTransitioning = false + this._scrollBar = new ScrollBarHelper() + + this._addEventListeners() + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + if (this._isShown || this._isTransitioning) { + return + } + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { + relatedTarget + }) + + if (showEvent.defaultPrevented) { + return + } + + this._isShown = true + this._isTransitioning = true + + this._scrollBar.hide() + + document.body.classList.add(CLASS_NAME_OPEN) + + this._adjustDialog() + + this._backdrop.show(() => this._showElement(relatedTarget)) + } + + hide() { + if (!this._isShown || this._isTransitioning) { + return + } + + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) + + if (hideEvent.defaultPrevented) { + return + } + + this._isShown = false + this._isTransitioning = true + this._focustrap.deactivate() + + this._element.classList.remove(CLASS_NAME_SHOW) + + this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()) + } + + dispose() { + EventHandler.off(window, EVENT_KEY) + EventHandler.off(this._dialog, EVENT_KEY) + + this._backdrop.dispose() + this._focustrap.deactivate() + + super.dispose() + } + + handleUpdate() { + this._adjustDialog() + } + + // Private + _initializeBackDrop() { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value, + isAnimated: this._isAnimated() + }) + } + + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }) + } + + _showElement(relatedTarget) { + // try to append dynamic modal + if (!document.body.contains(this._element)) { + document.body.append(this._element) + } + + this._element.style.display = 'block' + this._element.removeAttribute('aria-hidden') + this._element.setAttribute('aria-modal', true) + this._element.setAttribute('role', 'dialog') + this._element.scrollTop = 0 + + const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog) + if (modalBody) { + modalBody.scrollTop = 0 + } + + reflow(this._element) + + this._element.classList.add(CLASS_NAME_SHOW) + + const transitionComplete = () => { + if (this._config.focus) { + this._focustrap.activate() + } + + this._isTransitioning = false + EventHandler.trigger(this._element, EVENT_SHOWN, { + relatedTarget + }) + } + + this._queueCallback(transitionComplete, this._dialog, this._isAnimated()) + } + + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (event.key !== ESCAPE_KEY) { + return + } + + if (this._config.keyboard) { + this.hide() + return + } + + this._triggerBackdropTransition() + }) + + EventHandler.on(window, EVENT_RESIZE, () => { + if (this._isShown && !this._isTransitioning) { + this._adjustDialog() + } + }) + + EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { + // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks + EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { + if (this._element !== event.target || this._element !== event2.target) { + return + } + + if (this._config.backdrop === 'static') { + this._triggerBackdropTransition() + return + } + + if (this._config.backdrop) { + this.hide() + } + }) + }) + } + + _hideModal() { + this._element.style.display = 'none' + this._element.setAttribute('aria-hidden', true) + this._element.removeAttribute('aria-modal') + this._element.removeAttribute('role') + this._isTransitioning = false + + this._backdrop.hide(() => { + document.body.classList.remove(CLASS_NAME_OPEN) + this._resetAdjustments() + this._scrollBar.reset() + EventHandler.trigger(this._element, EVENT_HIDDEN) + }) + } + + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_FADE) + } + + _triggerBackdropTransition() { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + if (hideEvent.defaultPrevented) { + return + } + + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const initialOverflowY = this._element.style.overflowY + // return if the following background transition hasn't yet completed + if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { + return + } + + if (!isModalOverflowing) { + this._element.style.overflowY = 'hidden' + } + + this._element.classList.add(CLASS_NAME_STATIC) + this._queueCallback(() => { + this._element.classList.remove(CLASS_NAME_STATIC) + this._queueCallback(() => { + this._element.style.overflowY = initialOverflowY + }, this._dialog) + }, this._dialog) + + this._element.focus() + } + + /** + * The following methods are used to handle overflowing modals + */ + + _adjustDialog() { + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const scrollbarWidth = this._scrollBar.getWidth() + const isBodyOverflowing = scrollbarWidth > 0 + + if (isBodyOverflowing && !isModalOverflowing) { + const property = isRTL() ? 'paddingLeft' : 'paddingRight' + this._element.style[property] = `${scrollbarWidth}px` + } + + if (!isBodyOverflowing && isModalOverflowing) { + const property = isRTL() ? 'paddingRight' : 'paddingLeft' + this._element.style[property] = `${scrollbarWidth}px` + } + } + + _resetAdjustments() { + this._element.style.paddingLeft = '' + this._element.style.paddingRight = '' + } + + // Static + static jQueryInterface(config, relatedTarget) { + return this.each(function () { + const data = Modal.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } + + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](relatedTarget) + }) + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + const target = SelectorEngine.getElementFromSelector(this) + + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + EventHandler.one(target, EVENT_SHOW, showEvent => { + if (showEvent.defaultPrevented) { + // only register focus restorer if modal will actually get shown + return + } + + EventHandler.one(target, EVENT_HIDDEN, () => { + if (isVisible(this)) { + this.focus() + } + }) + }) + + // avoid conflict when clicking modal toggler while another one is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (alreadyOpen) { + Modal.getInstance(alreadyOpen).hide() + } + + const data = Modal.getOrCreateInstance(target) + + data.toggle(this) +}) + +enableDismissTrigger(Modal) + +/** + * jQuery + */ + +defineJQueryPlugin(Modal) + +export default Modal diff --git a/static/bootstrap-5.3.3/js/src/offcanvas.js b/static/bootstrap-5.3.3/js/src/offcanvas.js new file mode 100644 index 0000000..8d1feb1 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/offcanvas.js @@ -0,0 +1,282 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap offcanvas.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' +import { + defineJQueryPlugin, + isDisabled, + isVisible +} from './util/index.js' +import ScrollBarHelper from './util/scrollbar.js' + +/** + * Constants + */ + +const NAME = 'offcanvas' +const DATA_KEY = 'bs.offcanvas' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` +const ESCAPE_KEY = 'Escape' + +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_SHOWING = 'showing' +const CLASS_NAME_HIDING = 'hiding' +const CLASS_NAME_BACKDROP = 'offcanvas-backdrop' +const OPEN_SELECTOR = '.offcanvas.show' + +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_RESIZE = `resize${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` + +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' + +const Default = { + backdrop: true, + keyboard: true, + scroll: false +} + +const DefaultType = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + scroll: 'boolean' +} + +/** + * Class definition + */ + +class Offcanvas extends BaseComponent { + constructor(element, config) { + super(element, config) + + this._isShown = false + this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() + this._addEventListeners() + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + if (this._isShown) { + return + } + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) + + if (showEvent.defaultPrevented) { + return + } + + this._isShown = true + this._backdrop.show() + + if (!this._config.scroll) { + new ScrollBarHelper().hide() + } + + this._element.setAttribute('aria-modal', true) + this._element.setAttribute('role', 'dialog') + this._element.classList.add(CLASS_NAME_SHOWING) + + const completeCallBack = () => { + if (!this._config.scroll || this._config.backdrop) { + this._focustrap.activate() + } + + this._element.classList.add(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOWING) + EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) + } + + this._queueCallback(completeCallBack, this._element, true) + } + + hide() { + if (!this._isShown) { + return + } + + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) + + if (hideEvent.defaultPrevented) { + return + } + + this._focustrap.deactivate() + this._element.blur() + this._isShown = false + this._element.classList.add(CLASS_NAME_HIDING) + this._backdrop.hide() + + const completeCallback = () => { + this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING) + this._element.removeAttribute('aria-modal') + this._element.removeAttribute('role') + + if (!this._config.scroll) { + new ScrollBarHelper().reset() + } + + EventHandler.trigger(this._element, EVENT_HIDDEN) + } + + this._queueCallback(completeCallback, this._element, true) + } + + dispose() { + this._backdrop.dispose() + this._focustrap.deactivate() + super.dispose() + } + + // Private + _initializeBackDrop() { + const clickCallback = () => { + if (this._config.backdrop === 'static') { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + return + } + + this.hide() + } + + // 'static' option will be translated to true, and booleans will keep their value + const isVisible = Boolean(this._config.backdrop) + + return new Backdrop({ + className: CLASS_NAME_BACKDROP, + isVisible, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: isVisible ? clickCallback : null + }) + } + + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }) + } + + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (event.key !== ESCAPE_KEY) { + return + } + + if (this._config.keyboard) { + this.hide() + return + } + + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + }) + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Offcanvas.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } + + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](this) + }) + } +} + +/** + * Data API implementation + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + const target = SelectorEngine.getElementFromSelector(this) + + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + EventHandler.one(target, EVENT_HIDDEN, () => { + // focus on trigger when it is closed + if (isVisible(this)) { + this.focus() + } + }) + + // avoid conflict when clicking a toggler of an offcanvas, while another is open + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (alreadyOpen && alreadyOpen !== target) { + Offcanvas.getInstance(alreadyOpen).hide() + } + + const data = Offcanvas.getOrCreateInstance(target) + data.toggle(this) +}) + +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { + Offcanvas.getOrCreateInstance(selector).show() + } +}) + +EventHandler.on(window, EVENT_RESIZE, () => { + for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) { + if (getComputedStyle(element).position !== 'fixed') { + Offcanvas.getOrCreateInstance(element).hide() + } + } +}) + +enableDismissTrigger(Offcanvas) + +/** + * jQuery + */ + +defineJQueryPlugin(Offcanvas) + +export default Offcanvas diff --git a/static/bootstrap-5.3.3/js/src/popover.js b/static/bootstrap-5.3.3/js/src/popover.js new file mode 100644 index 0000000..612c521 --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/popover.js @@ -0,0 +1,97 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import Tooltip from './tooltip.js' +import { defineJQueryPlugin } from './util/index.js' + +/** + * Constants + */ + +const NAME = 'popover' + +const SELECTOR_TITLE = '.popover-header' +const SELECTOR_CONTENT = '.popover-body' + +const Default = { + ...Tooltip.Default, + content: '', + offset: [0, 8], + placement: 'right', + template: '', + trigger: 'click' +} + +const DefaultType = { + ...Tooltip.DefaultType, + content: '(null|string|element|function)' +} + +/** + * Class definition + */ + +class Popover extends Tooltip { + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Overrides + _isWithContent() { + return this._getTitle() || this._getContent() + } + + // Private + _getContentForTemplate() { + return { + [SELECTOR_TITLE]: this._getTitle(), + [SELECTOR_CONTENT]: this._getContent() + } + } + + _getContent() { + return this._resolvePossibleFunction(this._config.content) + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Popover.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } + + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[config]() + }) + } +} + +/** + * jQuery + */ + +defineJQueryPlugin(Popover) + +export default Popover diff --git a/static/bootstrap-5.3.3/js/src/scrollspy.js b/static/bootstrap-5.3.3/js/src/scrollspy.js new file mode 100644 index 0000000..368092d --- /dev/null +++ b/static/bootstrap-5.3.3/js/src/scrollspy.js @@ -0,0 +1,296 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { + defineJQueryPlugin, getElement, isDisabled, isVisible +} from './util/index.js' + +/** + * Constants + */ + +const NAME = 'scrollspy' +const DATA_KEY = 'bs.scrollspy' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const EVENT_ACTIVATE = `activate${EVENT_KEY}` +const EVENT_CLICK = `click${EVENT_KEY}` +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` + +const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item' +const CLASS_NAME_ACTIVE = 'active' + +const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]' +const SELECTOR_TARGET_LINKS = '[href]' +const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' +const SELECTOR_NAV_LINKS = '.nav-link' +const SELECTOR_NAV_ITEMS = '.nav-item' +const SELECTOR_LIST_ITEMS = '.list-group-item' +const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}` +const SELECTOR_DROPDOWN = '.dropdown' +const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' + +const Default = { + offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: '0px 0px -25%', + smoothScroll: false, + target: null, + threshold: [0.1, 0.5, 1] +} + +const DefaultType = { + offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: 'string', + smoothScroll: 'boolean', + target: 'element', + threshold: 'array' +} + +/** + * Class definition + */ + +class ScrollSpy extends BaseComponent { + constructor(element, config) { + super(element, config) + + // this._element is the observablesContainer and config.target the menu links wrapper + this._targetLinks = new Map() + this._observableSections = new Map() + this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element + this._activeTarget = null + this._observer = null + this._previousScrollData = { + visibleEntryTop: 0, + parentScrollTop: 0 + } + this.refresh() // initialize + } + + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + + // Public + refresh() { + this._initializeTargetsAndObservables() + this._maybeEnableSmoothScroll() + + if (this._observer) { + this._observer.disconnect() + } else { + this._observer = this._getNewObserver() + } + + for (const section of this._observableSections.values()) { + this._observer.observe(section) + } + } + + dispose() { + this._observer.disconnect() + super.dispose() + } + + // Private + _configAfterMerge(config) { + // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case + config.target = getElement(config.target) || document.body + + // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only + config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin + + if (typeof config.threshold === 'string') { + config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)) + } + + return config + } + + _maybeEnableSmoothScroll() { + if (!this._config.smoothScroll) { + return + } + + // unregister any previous listeners + EventHandler.off(this._config.target, EVENT_CLICK) + + EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { + const observableSection = this._observableSections.get(event.target.hash) + if (observableSection) { + event.preventDefault() + const root = this._rootElement || window + const height = observableSection.offsetTop - this._element.offsetTop + if (root.scrollTo) { + root.scrollTo({ top: height, behavior: 'smooth' }) + return + } + + // Chrome 60 doesn't support `scrollTo` + root.scrollTop = height + } + }) + } + + _getNewObserver() { + const options = { + root: this._rootElement, + threshold: this._config.threshold, + rootMargin: this._config.rootMargin + } + + return new IntersectionObserver(entries => this._observerCallback(entries), options) + } + + // The logic of selection + _observerCallback(entries) { + const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`) + const activate = entry => { + this._previousScrollData.visibleEntryTop = entry.target.offsetTop + this._process(targetElement(entry)) + } + + const parentScrollTop = (this._rootElement || document.documentElement).scrollTop + const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop + this._previousScrollData.parentScrollTop = parentScrollTop + + for (const entry of entries) { + if (!entry.isIntersecting) { + this._activeTarget = null + this._clearActiveClass(targetElement(entry)) + + continue + } + + const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop + // if we are scrolling down, pick the bigger offsetTop + if (userScrollsDown && entryIsLowerThanPrevious) { + activate(entry) + // if parent isn't scrolled, let's keep the first visible item, breaking the iteration + if (!parentScrollTop) { + return + } + + continue + } + + // if we are scrolling up, pick the smallest offsetTop + if (!userScrollsDown && !entryIsLowerThanPrevious) { + activate(entry) + } + } + } + + _initializeTargetsAndObservables() { + this._targetLinks = new Map() + this._observableSections = new Map() + + const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target) + + for (const anchor of targetLinks) { + // ensure that the anchor has an id and is not disabled + if (!anchor.hash || isDisabled(anchor)) { + continue + } + + const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element) + + // ensure that the observableSection exists & is visible + if (isVisible(observableSection)) { + this._targetLinks.set(decodeURI(anchor.hash), anchor) + this._observableSections.set(anchor.hash, observableSection) + } + } + } + + _process(target) { + if (this._activeTarget === target) { + return + } + + this._clearActiveClass(this._config.target) + this._activeTarget = target + target.classList.add(CLASS_NAME_ACTIVE) + this._activateParents(target) + + EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target }) + } + + _activateParents(target) { + // Activate dropdown parents + if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)) + .classList.add(CLASS_NAME_ACTIVE) + return + } + + for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { + // Set triggered links parents as active + // With both