1
0
Fork 0

Merging upstream version 5.3.0+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-17 07:08:19 +01:00
parent a87fc4fcc7
commit e92720b6a7
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
605 changed files with 15320 additions and 9495 deletions

View file

@ -2,43 +2,43 @@
"files": [ "files": [
{ {
"path": "./dist/css/bootstrap-grid.css", "path": "./dist/css/bootstrap-grid.css",
"maxSize": "7.5 kB" "maxSize": "6.5 kB"
}, },
{ {
"path": "./dist/css/bootstrap-grid.min.css", "path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "6.55 kB" "maxSize": "6.0 kB"
}, },
{ {
"path": "./dist/css/bootstrap-reboot.css", "path": "./dist/css/bootstrap-reboot.css",
"maxSize": "2.75 kB" "maxSize": "3.5 kB"
}, },
{ {
"path": "./dist/css/bootstrap-reboot.min.css", "path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "2.5 kB" "maxSize": "3.25 kB"
}, },
{ {
"path": "./dist/css/bootstrap-utilities.css", "path": "./dist/css/bootstrap-utilities.css",
"maxSize": "9.25 kB" "maxSize": "11.75 kB"
}, },
{ {
"path": "./dist/css/bootstrap-utilities.min.css", "path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "8.5 kB" "maxSize": "10.75 kB"
}, },
{ {
"path": "./dist/css/bootstrap.css", "path": "./dist/css/bootstrap.css",
"maxSize": "28.75 kB" "maxSize": "32.5 kB"
}, },
{ {
"path": "./dist/css/bootstrap.min.css", "path": "./dist/css/bootstrap.min.css",
"maxSize": "26.75 kB" "maxSize": "30.25 kB"
}, },
{ {
"path": "./dist/js/bootstrap.bundle.js", "path": "./dist/js/bootstrap.bundle.js",
"maxSize": "43.25 kB" "maxSize": "43.0 kB"
}, },
{ {
"path": "./dist/js/bootstrap.bundle.min.js", "path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "22.75 kB" "maxSize": "23.0 kB"
}, },
{ {
"path": "./dist/js/bootstrap.esm.js", "path": "./dist/js/bootstrap.esm.js",
@ -46,7 +46,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.esm.min.js", "path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "18.5 kB" "maxSize": "18.25 kB"
}, },
{ {
"path": "./dist/js/bootstrap.js", "path": "./dist/js/bootstrap.js",
@ -54,7 +54,7 @@
}, },
{ {
"path": "./dist/js/bootstrap.min.js", "path": "./dist/js/bootstrap.min.js",
"maxSize": "16.25 kB" "maxSize": "16.0 kB"
} }
], ],
"ci": { "ci": {

View file

@ -8,6 +8,7 @@
"autohiding", "autohiding",
"autoplay", "autoplay",
"autoplays", "autoplays",
"autoplaying",
"blazingly", "blazingly",
"Blockquotes", "Blockquotes",
"Bootstrappers", "Bootstrappers",
@ -107,6 +108,7 @@
"unstyled", "unstyled",
"Uppercased", "Uppercased",
"urlize", "urlize",
"urlquery",
"vbtn", "vbtn",
"viewports", "viewports",
"Vite", "Vite",

View file

@ -3,6 +3,5 @@
**/vendor/ **/vendor/
/_site/ /_site/
/js/coverage/ /js/coverage/
/js/tests/integration/
/site/static/sw.js /site/static/sw.js
/site/layouts/ /site/layouts/partials/

View file

@ -14,6 +14,35 @@
"error", "error",
"never" "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": [ "indent": [
"error", "error",
2, 2,
@ -46,20 +75,146 @@
"error", "error",
"after" "after"
], ],
"prefer-template": "error",
"semi": [ "semi": [
"error", "error",
"never" "never"
], ],
"strict": "error",
"unicorn/explicit-length-check": "off", "unicorn/explicit-length-check": "off",
"unicorn/filename-case": "off",
"unicorn/no-array-callback-reference": "off", "unicorn/no-array-callback-reference": "off",
"unicorn/no-array-method-this-argument": "off", "unicorn/no-array-method-this-argument": "off",
"unicorn/no-null": "off", "unicorn/no-null": "off",
"unicorn/no-typeof-undefined": "off",
"unicorn/no-unused-properties": "error", "unicorn/no-unused-properties": "error",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-array-flat": "off", "unicorn/prefer-array-flat": "off",
"unicorn/prefer-at": "off",
"unicorn/prefer-dom-node-dataset": "off", "unicorn/prefer-dom-node-dataset": "off",
"unicorn/prefer-module": "off", "unicorn/prefer-module": "off",
"unicorn/prefer-query-selector": "off", "unicorn/prefer-query-selector": "off",
"unicorn/prefer-spread": "off", "unicorn/prefer-spread": "off",
"unicorn/prefer-string-replace-all": "off",
"unicorn/prevent-abbreviations": "off" "unicorn/prevent-abbreviations": "off"
},
"overrides": [
{
"files": [
"build/**"
],
"env": {
"browser": false,
"node": true
},
"parserOptions": {
"sourceType": "script"
},
"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"
} }
} }
]
}

View file

@ -18,16 +18,16 @@ the preferred channel for [bug reports](#bug-reports), [features requests](#feat
and [submitting pull requests](#pull-requests), but please respect the following and [submitting pull requests](#pull-requests), but please respect the following
restrictions: 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** 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 - Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others. respect the opinions of others.
* Please **do not** post comments consisting solely of "+1" or ":thumbsup:". - 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/) 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. instead. We reserve the right to delete comments which violate this rule.
* Please **do not** open issues regarding the official themes offered on <https://themes.getbootstrap.com/>. - Please **do not** open issues regarding the official themes offered on <https://themes.getbootstrap.com/>.
Instead, please email any questions or feedback regarding those themes to `themes AT getbootstrap DOT com`. Instead, please email any questions or feedback regarding those themes to `themes AT getbootstrap DOT com`.
@ -101,16 +101,16 @@ Sometimes bugs reported to us are actually caused by bugs in the browser(s) them
| Vendor(s) | Browser(s) | Rendering engine | Bug reporting website(s) | Notes | | Vendor(s) | Browser(s) | Rendering engine | Bug reporting website(s) | Notes |
| ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- | | ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- |
| Mozilla | Firefox | Gecko | https://bugzilla.mozilla.org/enter_bug.cgi | "Core" is normally the right product option to choose. | | Mozilla | Firefox | Gecko | <https://bugzilla.mozilla.org/enter_bug.cgi> | "Core" is normally the right product option to choose. |
| Apple | Safari | WebKit | https://bugs.webkit.org/enter_bug.cgi?product=WebKit | In Apple's bug reporter, choose "Safari" as the product. | | Apple | Safari | WebKit | <https://bugs.webkit.org/enter_bug.cgi?product=WebKit> | In Apple's bug reporter, choose "Safari" as the product. |
| Google, Opera | Chrome, Chromium, Opera v15+ | Blink | https://bugs.chromium.org/p/chromium/issues/list | Click the "New issue" button. | | Google, Opera | Chrome, Chromium, Opera v15+ | Blink | <https://bugs.chromium.org/p/chromium/issues/list> | Click the "New issue" button. |
| Microsoft | Edge | Blink | https://developer.microsoft.com/en-us/microsoft-edge/ | Go to "Help > Send Feedback" from the browser | | Microsoft | Edge | Blink | <https://developer.microsoft.com/en-us/microsoft-edge/> | Go to "Help > Send Feedback" from the browser |
## Feature requests ## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea 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 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 case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible. provide as much detail and context as possible.

View file

@ -31,7 +31,7 @@
<!-- Please add direct links where your modifications can be seen in the documentation --> <!-- Please add direct links where your modifications can be seen in the documentation -->
* https://deploy-preview-{your pr number}--twbs-bootstrap.netlify.app/ - <https://deploy-preview-{your_pr_number}--twbs-bootstrap.netlify.app/>
### Related issues ### Related issues

3
.github/codeql/codeql-config.yml vendored Normal file
View file

@ -0,0 +1,3 @@
name: "CodeQL config"
paths-ignore:
- dist

View file

@ -1,20 +1,5 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
day: tuesday
time: "12:00"
timezone: Europe/Athens
open-pull-requests-limit: 10
reviewers:
- XhmikosR
labels:
- dependencies
- v5
versioning-strategy: increase
rebase-strategy: disabled
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
@ -22,3 +7,17 @@ updates:
day: tuesday day: tuesday
time: "12:00" time: "12:00"
timezone: Europe/Athens 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

View file

@ -2,21 +2,29 @@ name: BrowserStack
on: on:
push: push:
branches:
- "**"
- "!dependabot/**"
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
browserstack: browserstack:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'twbs/bootstrap' && (!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')) if: github.repository == 'twbs/bootstrap'
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View file

@ -2,14 +2,17 @@ name: Bundlewatch
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
bundlewatch: bundlewatch:
@ -18,6 +21,8 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View file

@ -17,6 +17,8 @@ jobs:
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Compress Images - name: Compress Images
uses: calibreapp/image-actions@1.1.0 uses: calibreapp/image-actions@1.1.0

View file

@ -7,13 +7,12 @@ on:
- v4-dev - v4-dev
- "!dependabot/**" - "!dependabot/**"
pull_request: pull_request:
# The branches below must be a subset of the branches above
branches: branches:
- main - main
- v4-dev - v4-dev
- "!dependabot/**" - "!dependabot/**"
schedule: schedule:
- cron: "0 2 * * 5" - cron: "0 2 * * 4"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -21,18 +20,25 @@ jobs:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
actions: read
contents: read
security-events: write security-events: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2
with: with:
config-file: ./.github/codeql/codeql-config.yml
languages: "javascript" languages: "javascript"
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v2
with:
category: "/language:javascript"

View file

@ -2,22 +2,30 @@ name: cspell
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16
permissions:
contents: read
jobs: jobs:
cspell: cspell:
permissions:
# allow streetsidesoftware/cspell-action to fetch files for commits and PRs
contents: read
pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Run cspell - name: Run cspell
uses: streetsidesoftware/cspell-action@v2 uses: streetsidesoftware/cspell-action@v2

View file

@ -2,14 +2,17 @@ name: CSS
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
css: css:
@ -18,6 +21,8 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@ -30,3 +35,6 @@ jobs:
- name: Build CSS - name: Build CSS
run: npm run css run: npm run css
- name: Run CSS tests
run: npm run css-test

View file

@ -2,14 +2,17 @@ name: Docs
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
docs: docs:
@ -18,6 +21,8 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View file

@ -4,8 +4,15 @@ on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
permissions:
contents: read
jobs: jobs:
issue-close-require: issue-close-require:
permissions:
# allow actions-cool/issues-helper to update issues and PRs
issues: write
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'twbs/bootstrap' if: github.repository == 'twbs/bootstrap'
steps: steps:

View file

@ -4,8 +4,15 @@ on:
issues: issues:
types: [labeled] types: [labeled]
permissions:
contents: read
jobs: jobs:
issue-labeled: issue-labeled:
permissions:
# allow actions-cool/issues-helper to update issues and PRs
issues: write
pull-requests: write
if: github.repository == 'twbs/bootstrap' if: github.repository == 'twbs/bootstrap'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View file

@ -2,23 +2,32 @@ name: JS Tests
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
run: run:
permissions:
# allow coverallsapp/github-action to create new checks issues and fetch code
checks: write
contents: read
name: JS Tests name: JS Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@ -36,7 +45,7 @@ jobs:
run: npm run js-test run: npm run js-test
- name: Run Coveralls - name: Run Coveralls
uses: coverallsapp/github-action@1.1.3 uses: coverallsapp/github-action@v2
with: with:
github-token: "${{ secrets.GITHUB_TOKEN }}" github-token: "${{ secrets.GITHUB_TOKEN }}"
path-to-lcov: "./js/coverage/lcov.info" path-to-lcov: "./js/coverage/lcov.info"

View file

@ -2,14 +2,17 @@ name: Lint
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
lint: lint:
@ -18,6 +21,8 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View file

@ -2,14 +2,17 @@ name: CSS (node-sass)
on: on:
push: push:
branches-ignore: branches:
- "dependabot/**" - main
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
env: env:
FORCE_COLOR: 2 FORCE_COLOR: 2
NODE: 16 NODE: 18
permissions:
contents: read
jobs: jobs:
css: css:
@ -18,6 +21,8 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@ -29,3 +34,16 @@ jobs:
npx --package node-sass@latest node-sass --version 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/ 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 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

View file

@ -6,8 +6,15 @@ on:
- main - main
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
update_release_draft: 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 runs-on: ubuntu-latest
if: github.repository == 'twbs/bootstrap' if: github.repository == 'twbs/bootstrap'
steps: steps:

1
.gitignore vendored
View file

@ -38,5 +38,6 @@ Thumbs.db
*.komodoproject *.komodoproject
# Folders to ignore # Folders to ignore
/dist-sass/
/js/coverage/ /js/coverage/
/node_modules/ /node_modules/

1
.npmrc Normal file
View file

@ -0,0 +1 @@
lockfile-version=2

View file

@ -1,31 +0,0 @@
{
"extends": [
"stylelint-config-twbs-bootstrap"
],
"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
}
}

60
.stylelintrc.json Normal file
View file

@ -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
}
}
]
}

View file

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall - Focusing on what is best not just for us as individuals, but for the overall
community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of - The use of sexualized language or imagery, and sexual attention or advances of
any kind any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email address, - Publishing others' private information, such as a physical or email address,
without their explicit permission without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities

View file

@ -1,7 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2011-2022 Twitter, Inc. Copyright (c) 2011-2023 The Bootstrap Authors
Copyright (c) 2011-2022 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://getbootstrap.com/"> <a href="https://getbootstrap.com/">
<img src="https://getbootstrap.com/docs/5.2/assets/brand/bootstrap-logo-shadow.png" alt="Bootstrap logo" width="200" height="165"> <img src="https://getbootstrap.com/docs/5.3/assets/brand/bootstrap-logo-shadow.png" alt="Bootstrap logo" width="200" height="165">
</a> </a>
</p> </p>
@ -9,7 +9,7 @@
<p align="center"> <p align="center">
Sleek, intuitive, and powerful front-end framework for faster and easier web development. Sleek, intuitive, and powerful front-end framework for faster and easier web development.
<br> <br>
<a href="https://getbootstrap.com/docs/5.2/"><strong>Explore Bootstrap docs »</strong></a> <a href="https://getbootstrap.com/docs/5.3/"><strong>Explore Bootstrap docs »</strong></a>
<br> <br>
<br> <br>
<a href="https://github.com/twbs/bootstrap/issues/new?assignees=-&labels=bug&template=bug_report.yml">Report bug</a> <a href="https://github.com/twbs/bootstrap/issues/new?assignees=-&labels=bug&template=bug_report.yml">Report bug</a>
@ -46,32 +46,32 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [`
Several quick start options are available: Several quick start options are available:
- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.2.3.zip) - [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.3.0.zip)
- Clone the repo: `git clone https://github.com/twbs/bootstrap.git` - Clone the repo: `git clone https://github.com/twbs/bootstrap.git`
- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.2.3` - Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.3.0`
- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.2.3` - Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.3.0`
- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.2.3` - Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.3.0`
- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass` - Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`
Read the [Getting started page](https://getbootstrap.com/docs/5.2/getting-started/introduction/) for information on the framework contents, templates, examples, and more. Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
## Status ## Status
[![Build Status](https://img.shields.io/github/workflow/status/twbs/bootstrap/JS%20Tests/main?label=JS%20Tests&logo=github)](https://github.com/twbs/bootstrap/actions?query=workflow%3AJS+Tests+branch%3Amain) [![Build Status](https://img.shields.io/github/actions/workflow/status/twbs/bootstrap/js.yml?branch=main&label=JS%20Tests&logo=github)](https://github.com/twbs/bootstrap/actions/workflows/js.yml?query=workflow%3AJS+branch%3Amain)
[![npm version](https://img.shields.io/npm/v/bootstrap)](https://www.npmjs.com/package/bootstrap) [![npm version](https://img.shields.io/npm/v/bootstrap?logo=npm&logoColor=fff)](https://www.npmjs.com/package/bootstrap)
[![Gem version](https://img.shields.io/gem/v/bootstrap)](https://rubygems.org/gems/bootstrap) [![Gem version](https://img.shields.io/gem/v/bootstrap?logo=rubygems&logoColor=fff)](https://rubygems.org/gems/bootstrap)
[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap) [![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue?logo=meteor&logoColor=fff)](https://atmospherejs.com/twbs/bootstrap)
[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap) [![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap?logo=packagist&logoColor=fff)](https://packagist.org/packages/twbs/bootstrap)
[![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](https://www.nuget.org/packages/bootstrap/absoluteLatest) [![NuGet](https://img.shields.io/nuget/vpre/bootstrap?logo=nuget&logoColor=fff)](https://www.nuget.org/packages/bootstrap/absoluteLatest)
[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main)](https://coveralls.io/github/twbs/bootstrap?branch=main) [![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main?logo=coveralls&logoColor=fff)](https://coveralls.io/github/twbs/bootstrap?branch=main)
[![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
[![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css)
[![JS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=gzip&label=JS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js) [![JS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=gzip&label=JS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)
[![JS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=brotli&label=JS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js) [![JS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=brotli&label=JS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js)
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229) [![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)
[![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap)](#backers) [![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap?logo=opencollective&logoColor=fff)](#backers)
[![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap)](#sponsors) [![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap?logo=opencollective&logoColor=fff)](#sponsors)
## What's included ## What's included
@ -144,7 +144,7 @@ Have a bug or a feature request? Please first read the [issue guidelines](https:
Bootstrap's documentation, included in this repo in the root directory, is built with [Hugo](https://gohugo.io/) and publicly hosted on GitHub Pages at <https://getbootstrap.com/>. The docs may also be run locally. Bootstrap's documentation, included in this repo in the root directory, is built with [Hugo](https://gohugo.io/) and publicly hosted on GitHub Pages at <https://getbootstrap.com/>. The docs may also be run locally.
Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). Working on our search? Be sure to set `debug: true` in `site/assets/js/search.js`. Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/).
### Running documentation locally ### Running documentation locally
@ -243,4 +243,4 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
## Copyright and license ## Copyright and license
Code and documentation copyright 20112022 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). Code and documentation copyright 20112023 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors). Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).

View file

@ -1,15 +0,0 @@
{
"env": {
"browser": false,
"node": true
},
"parserOptions": {
"sourceType": "script"
},
"extends": "../.eslintrc.json",
"rules": {
"no-console": "off",
"strict": "error",
"unicorn/prefer-top-level-await": "off"
}
}

View file

@ -1,6 +1,7 @@
'use strict' 'use strict'
const pkg = require('../package.json') const pkg = require('../package.json')
const year = new Date().getFullYear() const year = new Date().getFullYear()
function getBanner(pluginFilename) { function getBanner(pluginFilename) {

View file

@ -2,8 +2,7 @@
/*! /*!
* Script to build our plugins to use them separately. * Script to build our plugins to use them separately.
* Copyright 2020-2022 The Bootstrap Authors * Copyright 2020-2023 The Bootstrap Authors
* Copyright 2020-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -16,7 +15,7 @@ const { babel } = require('@rollup/plugin-babel')
const banner = require('./banner.js') const banner = require('./banner.js')
const sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\/g, '/') const sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\/g, '/')
const jsFiles = globby.sync(sourcePath + '/**/*.js') const jsFiles = globby.sync(`${sourcePath}/**/*.js`)
// Array which holds the resolved plugins // Array which holds the resolved plugins
const resolvedPlugins = [] const resolvedPlugins = []
@ -27,7 +26,7 @@ const filenameToEntity = filename => filename.replace('.js', '')
for (const file of jsFiles) { for (const file of jsFiles) {
resolvedPlugins.push({ resolvedPlugins.push({
src: file.replace('.js', ''), src: file,
dist: file.replace('src', 'dist'), dist: file.replace('src', 'dist'),
fileName: path.basename(file), fileName: path.basename(file),
className: filenameToEntity(path.basename(file)) className: filenameToEntity(path.basename(file))

View file

@ -2,8 +2,7 @@
/*! /*!
* Script to update version number references in the project. * Script to update version number references in the project.
* Copyright 2017-2022 The Bootstrap Authors * Copyright 2017-2023 The Bootstrap Authors
* Copyright 2017-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -36,8 +35,16 @@ function regExpQuoteReplacement(string) {
async function replaceRecursively(file, oldVersion, newVersion) { async function replaceRecursively(file, oldVersion, newVersion) {
const originalString = await fs.readFile(file, 'utf8') const originalString = await fs.readFile(file, 'utf8')
const newString = originalString.replace( const newString = originalString
new RegExp(regExpQuote(oldVersion), 'g'), regExpQuoteReplacement(newVersion) .replace(
new RegExp(regExpQuote(oldVersion), 'g'),
regExpQuoteReplacement(newVersion)
)
// Also replace the version used by the rubygem,
// which is using periods (`.`) instead of hyphens (`-`)
.replace(
new RegExp(regExpQuote(oldVersion.replace(/-/g, '.')), 'g'),
regExpQuoteReplacement(newVersion.replace(/-/g, '.'))
) )
// No need to move any further if the strings are identical // No need to move any further if the strings are identical
@ -56,22 +63,35 @@ async function replaceRecursively(file, oldVersion, newVersion) {
await fs.writeFile(file, newString, 'utf8') await fs.writeFile(file, newString, 'utf8')
} }
async function main(args) { function showUsage(args) {
let [oldVersion, newVersion] = args
if (!oldVersion || !newVersion) {
console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]')
console.error('Got arguments:', args) console.error('Got arguments:', args)
process.exit(1) process.exit(1)
} }
// Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s async function main(args) {
[oldVersion, newVersion] = [oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg) let [oldVersion, newVersion] = args
if (!oldVersion || !newVersion) {
showUsage(args)
}
// Strip any leading `v` from arguments because
// otherwise we will end up with duplicate `v`s
[oldVersion, newVersion] = [oldVersion, newVersion].map(arg => {
return arg.startsWith('v') ? arg.slice(1) : arg
})
if (oldVersion === newVersion) {
showUsage(args)
}
try { try {
const files = await globby(GLOB, GLOBBY_OPTIONS) const files = await globby(GLOB, GLOBBY_OPTIONS)
await Promise.all(files.map(file => replaceRecursively(file, oldVersion, newVersion))) await Promise.all(
files.map(file => replaceRecursively(file, oldVersion, newVersion))
)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
process.exit(1) process.exit(1)

View file

@ -5,8 +5,7 @@
* Remember to use the same vendor files as the CDN ones, * Remember to use the same vendor files as the CDN ones,
* otherwise the hashes won't match! * otherwise the hashes won't match!
* *
* Copyright 2017-2022 The Bootstrap Authors * Copyright 2017-2023 The Bootstrap Authors
* Copyright 2017-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -19,11 +18,11 @@ const sh = require('shelljs')
sh.config.fatal = true sh.config.fatal = true
const configFile = path.join(__dirname, '../config.yml') const configFile = path.join(__dirname, '../hugo.yml')
// Array of objects which holds the files to generate SRI hashes for. // Array of objects which holds the files to generate SRI hashes for.
// `file` is the path from the root folder // `file` is the path from the root folder
// `configPropertyName` is the config.yml variable's name of the file // `configPropertyName` is the hugo.yml variable's name of the file
const files = [ const files = [
{ {
file: 'dist/css/bootstrap.min.css', file: 'dist/css/bootstrap.min.css',
@ -47,8 +46,8 @@ const files = [
} }
] ]
for (const file of files) { for (const { file, configPropertyName } of files) {
fs.readFile(file.file, 'utf8', (error, data) => { fs.readFile(file, 'utf8', (error, data) => {
if (error) { if (error) {
throw error throw error
} }
@ -57,8 +56,8 @@ for (const file of files) {
const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64') const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64')
const integrity = `${algo}-${hash}` const integrity = `${algo}-${hash}`
console.log(`${file.configPropertyName}: ${integrity}`) console.log(`${configPropertyName}: ${integrity}`)
sh.sed('-i', new RegExp(`^(\\s+${file.configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile) sh.sed('-i', new RegExp(`^(\\s+${configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile)
}) })
} }

View file

@ -40,7 +40,7 @@ if (BUNDLE) {
const rollupConfig = { const rollupConfig = {
input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`), input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`),
output: { output: {
banner, banner: banner(),
file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`), file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`),
format: ESM ? 'esm' : 'umd', format: ESM ? 'esm' : 'umd',
globals, globals,

View file

@ -2,8 +2,7 @@
/*! /*!
* Script to run vnu-jar if Java is available. * Script to run vnu-jar if Java is available.
* Copyright 2017-2022 The Bootstrap Authors * Copyright 2017-2023 The Bootstrap Authors
* Copyright 2017-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -14,10 +13,13 @@ const vnu = require('vnu-jar')
execFile('java', ['-version'], (error, stdout, stderr) => { execFile('java', ['-version'], (error, stdout, stderr) => {
if (error) { if (error) {
console.error('Skipping vnu-jar test; Java is missing.') console.error('Skipping vnu-jar test; Java is probably missing.')
console.error(error)
return return
} }
console.log('Running vnu-jar validation...')
const is32bitJava = !/64-Bit/.test(stderr) const is32bitJava = !/64-Bit/.test(stderr)
// vnu-jar accepts multiple ignores joined with a `|`. // vnu-jar accepts multiple ignores joined with a `|`.
@ -49,6 +51,8 @@ execFile('java', ['-version'], (error, stdout, stderr) => {
args.splice(0, 0, '-Xss512k') args.splice(0, 0, '-Xss512k')
} }
console.log(`command used: java ${args.join(' ')}`)
return spawn('java', args, { return spawn('java', args, {
shell: true, shell: true,
stdio: 'inherit' stdio: 'inherit'

View file

@ -3,7 +3,7 @@
/*! /*!
* Script to create the built examples zip archive; * Script to create the built examples zip archive;
* requires the `zip` command to be present! * requires the `zip` command to be present!
* Copyright 2020-2022 The Bootstrap Authors * Copyright 2020-2023 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@ -84,7 +84,7 @@ for (const file of sh.find(`${distFolder}/**/*.html`)) {
} }
// create the zip file // create the zip file
sh.exec(`zip -r9 "${distFolder}.zip" "${distFolder}"`) sh.exec(`zip -qr9 "${distFolder}.zip" "${distFolder}"`)
// remove the folder we created // remove the folder we created
sh.rm('-rf', distFolder) sh.rm('-rf', distFolder)

View file

@ -31,7 +31,7 @@ publishDir: "_site"
module: module:
mounts: mounts:
- source: dist - source: dist
target: static/docs/5.2/dist target: static/docs/5.3/dist
- source: site/assets - source: site/assets
target: assets target: assets
- source: site/content - source: site/content
@ -42,9 +42,9 @@ module:
target: layouts target: layouts
- source: site/static - source: site/static
target: static target: static
- source: site/static/docs/5.2/assets/img/favicons/apple-touch-icon.png - source: site/static/docs/5.3/assets/img/favicons/apple-touch-icon.png
target: static/apple-touch-icon.png target: static/apple-touch-icon.png
- source: site/static/docs/5.2/assets/img/favicons/favicon.ico - source: site/static/docs/5.3/assets/img/favicons/favicon.ico
target: static/favicon.ico target: static/favicon.ico
params: params:
@ -52,10 +52,10 @@ params:
description: "Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins." description: "Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins."
authors: "Mark Otto, Jacob Thornton, and Bootstrap contributors" authors: "Mark Otto, Jacob Thornton, and Bootstrap contributors"
current_version: "5.2.3" current_version: "5.3.0"
current_ruby_version: "5.2.3" current_ruby_version: "5.3.0"
docs_version: "5.2" docs_version: "5.3"
rfs_version: "v9.0.6" rfs_version: "v10.0.0"
github_org: "https://github.com/twbs" github_org: "https://github.com/twbs"
repo: "https://github.com/twbs/bootstrap" repo: "https://github.com/twbs/bootstrap"
twitter: "getbootstrap" twitter: "getbootstrap"
@ -66,22 +66,23 @@ params:
swag: "https://cottonbureau.com/people/bootstrap" swag: "https://cottonbureau.com/people/bootstrap"
download: download:
source: "https://github.com/twbs/bootstrap/archive/v5.2.3.zip" source: "https://github.com/twbs/bootstrap/archive/v5.3.0.zip"
dist: "https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-dist.zip" dist: "https://github.com/twbs/bootstrap/releases/download/v5.3.0/bootstrap-5.3.0-dist.zip"
dist_examples: "https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-examples.zip" dist_examples: "https://github.com/twbs/bootstrap/releases/download/v5.3.0/bootstrap-5.3.0-examples.zip"
cdn: cdn:
# See https://www.srihash.org for info on how to generate the hashes # See https://www.srihash.org for info on how to generate the hashes
css: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
css_hash: "sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" css_hash: "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.rtl.min.css" css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css"
css_rtl_hash: "sha384-DOXMLfHhQkvFFp+rWTZwVlPVqdIhpDVYT9csOnHSgWQWPX0v5MCGtjCJbY6ERspU" css_rtl_hash: "sha384-PJsj/BTMqILvmcej7ulplguok8ag4xFTPryRq8xevL7eBYSmpXKcbNVuy+P0RMgq"
js: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"
js_hash: "sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" js_hash: "sha384-fbbOQedDUMZZ5KreZpsbe1LCZPVmfTnH7ois6mU1QK+m14rQ1l2bGBq41eYeM/fS"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" js_bundle_hash: "sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
popper_hash: "sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
anchors: anchors:
min: 2 min: 2

View file

@ -1,19 +1,19 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): index.esm.js * Bootstrap index.esm.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
export { default as Alert } from './src/alert' export { default as Alert } from './src/alert.js'
export { default as Button } from './src/button' export { default as Button } from './src/button.js'
export { default as Carousel } from './src/carousel' export { default as Carousel } from './src/carousel.js'
export { default as Collapse } from './src/collapse' export { default as Collapse } from './src/collapse.js'
export { default as Dropdown } from './src/dropdown' export { default as Dropdown } from './src/dropdown.js'
export { default as Modal } from './src/modal' export { default as Modal } from './src/modal.js'
export { default as Offcanvas } from './src/offcanvas' export { default as Offcanvas } from './src/offcanvas.js'
export { default as Popover } from './src/popover' export { default as Popover } from './src/popover.js'
export { default as ScrollSpy } from './src/scrollspy' export { default as ScrollSpy } from './src/scrollspy.js'
export { default as Tab } from './src/tab' export { default as Tab } from './src/tab.js'
export { default as Toast } from './src/toast' export { default as Toast } from './src/toast.js'
export { default as Tooltip } from './src/tooltip' export { default as Tooltip } from './src/tooltip.js'

View file

@ -1,22 +1,22 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): index.umd.js * Bootstrap index.umd.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import Alert from './src/alert' import Alert from './src/alert.js'
import Button from './src/button' import Button from './src/button.js'
import Carousel from './src/carousel' import Carousel from './src/carousel.js'
import Collapse from './src/collapse' import Collapse from './src/collapse.js'
import Dropdown from './src/dropdown' import Dropdown from './src/dropdown.js'
import Modal from './src/modal' import Modal from './src/modal.js'
import Offcanvas from './src/offcanvas' import Offcanvas from './src/offcanvas.js'
import Popover from './src/popover' import Popover from './src/popover.js'
import ScrollSpy from './src/scrollspy' import ScrollSpy from './src/scrollspy.js'
import Tab from './src/tab' import Tab from './src/tab.js'
import Toast from './src/toast' import Toast from './src/toast.js'
import Tooltip from './src/tooltip' import Tooltip from './src/tooltip.js'
export default { export default {
Alert, Alert,

View file

@ -1,14 +1,14 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): alert.js * Bootstrap alert.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import BaseComponent from './base-component' import { enableDismissTrigger } from './util/component-functions.js'
import { enableDismissTrigger } from './util/component-functions' import { defineJQueryPlugin } from './util/index.js'
/** /**
* Constants * Constants

View file

@ -1,20 +1,20 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): base-component.js * Bootstrap base-component.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import Data from './dom/data' import Data from './dom/data.js'
import { executeAfterTransition, getElement } from './util/index' import EventHandler from './dom/event-handler.js'
import EventHandler from './dom/event-handler' import Config from './util/config.js'
import Config from './util/config' import { executeAfterTransition, getElement } from './util/index.js'
/** /**
* Constants * Constants
*/ */
const VERSION = '5.2.3' const VERSION = '5.3.0'
/** /**
* Class definition * Class definition

View file

@ -1,13 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): button.js * Bootstrap button.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import BaseComponent from './base-component' import { defineJQueryPlugin } from './util/index.js'
/** /**
* Constants * Constants

View file

@ -1,24 +1,23 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): carousel.js * Bootstrap carousel.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * 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 { import {
defineJQueryPlugin, defineJQueryPlugin,
getElementFromSelector,
getNextActiveElement, getNextActiveElement,
isRTL, isRTL,
isVisible, isVisible,
reflow, reflow,
triggerTransitionEnd triggerTransitionEnd
} from './util/index' } from './util/index.js'
import EventHandler from './dom/event-handler' import Swipe from './util/swipe.js'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import Swipe from './util/swipe'
import BaseComponent from './base-component'
/** /**
* Constants * Constants
@ -330,7 +329,7 @@ class Carousel extends BaseComponent {
if (!activeElement || !nextElement) { if (!activeElement || !nextElement) {
// Some weirdness is happening, so we bail // Some weirdness is happening, so we bail
// todo: change tests that use empty divs to avoid this check // TODO: change tests that use empty divs to avoid this check
return return
} }
@ -431,7 +430,7 @@ class Carousel extends BaseComponent {
*/ */
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
const target = getElementFromSelector(this) const target = SelectorEngine.getElementFromSelector(this)
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
return return

View file

@ -1,20 +1,18 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): collapse.js * Bootstrap collapse.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * 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 { import {
defineJQueryPlugin, defineJQueryPlugin,
getElement, getElement,
getElementFromSelector,
getSelectorFromElement,
reflow reflow
} from './util/index' } from './util/index.js'
import EventHandler from './dom/event-handler'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'
/** /**
* Constants * Constants
@ -68,7 +66,7 @@ class Collapse extends BaseComponent {
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
for (const elem of toggleList) { for (const elem of toggleList) {
const selector = getSelectorFromElement(elem) const selector = SelectorEngine.getSelectorFromElement(elem)
const filterElement = SelectorEngine.find(selector) const filterElement = SelectorEngine.find(selector)
.filter(foundElement => foundElement === this._element) .filter(foundElement => foundElement === this._element)
@ -185,7 +183,7 @@ class Collapse extends BaseComponent {
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)
for (const trigger of this._triggerArray) { for (const trigger of this._triggerArray) {
const element = getElementFromSelector(trigger) const element = SelectorEngine.getElementFromSelector(trigger)
if (element && !this._isShown(element)) { if (element && !this._isShown(element)) {
this._addAriaAndCollapsedClass([trigger], false) this._addAriaAndCollapsedClass([trigger], false)
@ -229,7 +227,7 @@ class Collapse extends BaseComponent {
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE) const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)
for (const element of children) { for (const element of children) {
const selected = getElementFromSelector(element) const selected = SelectorEngine.getElementFromSelector(element)
if (selected) { if (selected) {
this._addAriaAndCollapsedClass([element], this._isShown(selected)) this._addAriaAndCollapsedClass([element], this._isShown(selected))
@ -285,10 +283,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
event.preventDefault() event.preventDefault()
} }
const selector = getSelectorFromElement(this) for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
const selectorElements = SelectorEngine.find(selector)
for (const element of selectorElements) {
Collapse.getOrCreateInstance(element, { toggle: false }).toggle() Collapse.getOrCreateInstance(element, { toggle: false }).toggle()
} }
}) })

View file

@ -1,6 +1,6 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): dom/data.js * Bootstrap dom/data.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */

View file

@ -1,11 +1,11 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): dom/event-handler.js * Bootstrap dom/event-handler.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { getjQuery } from '../util/index' import { getjQuery } from '../util/index.js'
/** /**
* Constants * Constants
@ -128,7 +128,7 @@ function findHandler(events, callable, delegationSelector = null) {
function normalizeParameters(originalTypeEvent, handler, delegationFunction) { function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
const isDelegated = typeof handler === 'string' const isDelegated = typeof handler === 'string'
// todo: tooltip passes `false` instead of selector, so we need to check // TODO: tooltip passes `false` instead of selector, so we need to check
const callable = isDelegated ? delegationFunction : (handler || delegationFunction) const callable = isDelegated ? delegationFunction : (handler || delegationFunction)
let typeEvent = getTypeEvent(originalTypeEvent) let typeEvent = getTypeEvent(originalTypeEvent)
@ -198,9 +198,8 @@ function removeHandler(element, events, typeEvent, handler, delegationSelector)
function removeNamespacedHandlers(element, events, typeEvent, namespace) { function removeNamespacedHandlers(element, events, typeEvent, namespace) {
const storeElementEvent = events[typeEvent] || {} const storeElementEvent = events[typeEvent] || {}
for (const handlerKey of Object.keys(storeElementEvent)) { for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
if (handlerKey.includes(namespace)) { if (handlerKey.includes(namespace)) {
const event = storeElementEvent[handlerKey]
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
} }
} }
@ -248,11 +247,10 @@ const EventHandler = {
} }
} }
for (const keyHandlers of Object.keys(storeElementEvent)) { for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
const handlerKey = keyHandlers.replace(stripUidRegex, '') const handlerKey = keyHandlers.replace(stripUidRegex, '')
if (!inNamespace || originalTypeEvent.includes(handlerKey)) { if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
const event = storeElementEvent[keyHandlers]
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
} }
} }
@ -281,8 +279,7 @@ const EventHandler = {
defaultPrevented = jQueryEvent.isDefaultPrevented() defaultPrevented = jQueryEvent.isDefaultPrevented()
} }
let evt = new Event(event, { bubbles, cancelable: true }) const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)
evt = hydrateObj(evt, args)
if (defaultPrevented) { if (defaultPrevented) {
evt.preventDefault() evt.preventDefault()
@ -300,8 +297,8 @@ const EventHandler = {
} }
} }
function hydrateObj(obj, meta) { function hydrateObj(obj, meta = {}) {
for (const [key, value] of Object.entries(meta || {})) { for (const [key, value] of Object.entries(meta)) {
try { try {
obj[key] = value obj[key] = value
} catch { } catch {

View file

@ -1,6 +1,6 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): dom/manipulator.js * Bootstrap dom/manipulator.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */

View file

@ -1,15 +1,36 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): dom/selector-engine.js * Bootstrap dom/selector-engine.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { isDisabled, isVisible } from '../util/index' import { isDisabled, isVisible, parseSelector } from '../util/index.js'
/** const getSelector = element => {
* Constants 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 parseSelector(selector)
}
const SelectorEngine = { const SelectorEngine = {
find(selector, element = document.documentElement) { find(selector, element = document.documentElement) {
@ -77,6 +98,28 @@ const SelectorEngine = {
].map(selector => `${selector}:not([tabindex^="-"])`).join(',') ].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) 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) : []
} }
} }

View file

@ -1,13 +1,18 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): dropdown.js * Bootstrap dropdown.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import * as Popper from '@popperjs/core' 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 { import {
defineJQueryPlugin, defineJQueryPlugin,
execute,
getElement, getElement,
getNextActiveElement, getNextActiveElement,
isDisabled, isDisabled,
@ -15,11 +20,7 @@ import {
isRTL, isRTL,
isVisible, isVisible,
noop noop
} from './util/index' } from './util/index.js'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import BaseComponent from './base-component'
/** /**
* Constants * Constants
@ -95,7 +96,7 @@ class Dropdown extends BaseComponent {
this._popper = null this._popper = null
this._parent = this._element.parentNode // dropdown wrapper this._parent = this._element.parentNode // dropdown wrapper
// todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
SelectorEngine.findOne(SELECTOR_MENU, this._parent) SelectorEngine.findOne(SELECTOR_MENU, this._parent)
@ -310,7 +311,7 @@ class Dropdown extends BaseComponent {
// Disable Popper if we have a static display or Dropdown is in Navbar // Disable Popper if we have a static display or Dropdown is in Navbar
if (this._inNavbar || this._config.display === 'static') { if (this._inNavbar || this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove
defaultBsPopperConfig.modifiers = [{ defaultBsPopperConfig.modifiers = [{
name: 'applyStyles', name: 'applyStyles',
enabled: false enabled: false
@ -319,7 +320,7 @@ class Dropdown extends BaseComponent {
return { return {
...defaultBsPopperConfig, ...defaultBsPopperConfig,
...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) ...execute(this._config.popperConfig, [defaultBsPopperConfig])
} }
} }
@ -408,7 +409,7 @@ class Dropdown extends BaseComponent {
event.preventDefault() event.preventDefault()
// todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?
this : this :
(SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||

View file

@ -1,18 +1,18 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): modal.js * Bootstrap modal.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import SelectorEngine from './dom/selector-engine' import SelectorEngine from './dom/selector-engine.js'
import ScrollBarHelper from './util/scrollbar' import Backdrop from './util/backdrop.js'
import BaseComponent from './base-component' import { enableDismissTrigger } from './util/component-functions.js'
import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap.js'
import FocusTrap from './util/focustrap' import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js'
import { enableDismissTrigger } from './util/component-functions' import ScrollBarHelper from './util/scrollbar.js'
/** /**
* Constants * Constants
@ -139,12 +139,12 @@ class Modal extends BaseComponent {
} }
dispose() { dispose() {
for (const htmlElement of [window, this._dialog]) { EventHandler.off(window, EVENT_KEY)
EventHandler.off(htmlElement, EVENT_KEY) EventHandler.off(this._dialog, EVENT_KEY)
}
this._backdrop.dispose() this._backdrop.dispose()
this._focustrap.deactivate() this._focustrap.deactivate()
super.dispose() super.dispose()
} }
@ -208,7 +208,6 @@ class Modal extends BaseComponent {
} }
if (this._config.keyboard) { if (this._config.keyboard) {
event.preventDefault()
this.hide() this.hide()
return return
} }
@ -336,7 +335,7 @@ class Modal extends BaseComponent {
*/ */
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = getElementFromSelector(this) const target = SelectorEngine.getElementFromSelector(this)
if (['A', 'AREA'].includes(this.tagName)) { if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault() event.preventDefault()

View file

@ -1,23 +1,22 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): offcanvas.js * Bootstrap offcanvas.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * 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 { import {
defineJQueryPlugin, defineJQueryPlugin,
getElementFromSelector,
isDisabled, isDisabled,
isVisible isVisible
} from './util/index' } from './util/index.js'
import ScrollBarHelper from './util/scrollbar' import ScrollBarHelper from './util/scrollbar.js'
import EventHandler from './dom/event-handler'
import BaseComponent from './base-component'
import SelectorEngine from './dom/selector-engine'
import Backdrop from './util/backdrop'
import FocusTrap from './util/focustrap'
import { enableDismissTrigger } from './util/component-functions'
/** /**
* Constants * Constants
@ -199,12 +198,12 @@ class Offcanvas extends BaseComponent {
return return
} }
if (!this._config.keyboard) { if (this._config.keyboard) {
EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) this.hide()
return return
} }
this.hide() EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
}) })
} }
@ -231,7 +230,7 @@ class Offcanvas extends BaseComponent {
*/ */
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = getElementFromSelector(this) const target = SelectorEngine.getElementFromSelector(this)
if (['A', 'AREA'].includes(this.tagName)) { if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault() event.preventDefault()

View file

@ -1,12 +1,12 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): popover.js * Bootstrap popover.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin } from './util/index' import Tooltip from './tooltip.js'
import Tooltip from './tooltip' import { defineJQueryPlugin } from './util/index.js'
/** /**
* Constants * Constants

View file

@ -1,14 +1,14 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): scrollspy.js * Bootstrap scrollspy.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import SelectorEngine from './dom/selector-engine' import SelectorEngine from './dom/selector-engine.js'
import BaseComponent from './base-component' import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js'
/** /**
* Constants * Constants
@ -208,11 +208,11 @@ class ScrollSpy extends BaseComponent {
continue continue
} }
const observableSection = SelectorEngine.findOne(anchor.hash, this._element) const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)
// ensure that the observableSection exists & is visible // ensure that the observableSection exists & is visible
if (isVisible(observableSection)) { if (isVisible(observableSection)) {
this._targetLinks.set(anchor.hash, anchor) this._targetLinks.set(decodeURI(anchor.hash), anchor)
this._observableSections.set(anchor.hash, observableSection) this._observableSections.set(anchor.hash, observableSection)
} }
} }

View file

@ -1,14 +1,14 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): tab.js * Bootstrap tab.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import SelectorEngine from './dom/selector-engine' import SelectorEngine from './dom/selector-engine.js'
import BaseComponent from './base-component' import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'
/** /**
* Constants * Constants
@ -43,7 +43,7 @@ const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)'
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]' const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]'
const SELECTOR_OUTER = '.nav-item, .list-group-item' const SELECTOR_OUTER = '.nav-item, .list-group-item'
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}` const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // todo:v6: could be only `tab` const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // TODO: could only be `tab` in v6
const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}` const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`
const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]` const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]`
@ -59,7 +59,7 @@ class Tab extends BaseComponent {
if (!this._parent) { if (!this._parent) {
return return
// todo: should Throw exception on v6 // TODO: should throw exception in v6
// throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`) // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)
} }
@ -106,7 +106,7 @@ class Tab extends BaseComponent {
element.classList.add(CLASS_NAME_ACTIVE) element.classList.add(CLASS_NAME_ACTIVE)
this._activate(getElementFromSelector(element)) // Search and activate/show the proper section this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section
const complete = () => { const complete = () => {
if (element.getAttribute('role') !== 'tab') { if (element.getAttribute('role') !== 'tab') {
@ -133,7 +133,7 @@ class Tab extends BaseComponent {
element.classList.remove(CLASS_NAME_ACTIVE) element.classList.remove(CLASS_NAME_ACTIVE)
element.blur() element.blur()
this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too
const complete = () => { const complete = () => {
if (element.getAttribute('role') !== 'tab') { if (element.getAttribute('role') !== 'tab') {
@ -203,7 +203,7 @@ class Tab extends BaseComponent {
} }
_setInitialAttributesOnTargetPanel(child) { _setInitialAttributesOnTargetPanel(child) {
const target = getElementFromSelector(child) const target = SelectorEngine.getElementFromSelector(child)
if (!target) { if (!target) {
return return
@ -212,7 +212,7 @@ class Tab extends BaseComponent {
this._setAttributeIfNotExists(target, 'role', 'tabpanel') this._setAttributeIfNotExists(target, 'role', 'tabpanel')
if (child.id) { if (child.id) {
this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`) this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)
} }
} }

View file

@ -1,14 +1,14 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): toast.js * Bootstrap toast.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { defineJQueryPlugin, reflow } from './util/index' import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler' import EventHandler from './dom/event-handler.js'
import BaseComponent from './base-component' import { enableDismissTrigger } from './util/component-functions.js'
import { enableDismissTrigger } from './util/component-functions' import { defineJQueryPlugin, reflow } from './util/index.js'
/** /**
* Constants * Constants

View file

@ -1,17 +1,17 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): tooltip.js * Bootstrap tooltip.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import * as Popper from '@popperjs/core' import * as Popper from '@popperjs/core'
import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index' import BaseComponent from './base-component.js'
import { DefaultAllowlist } from './util/sanitizer' import EventHandler from './dom/event-handler.js'
import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator.js'
import Manipulator from './dom/manipulator' import { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js'
import BaseComponent from './base-component' import { DefaultAllowlist } from './util/sanitizer.js'
import TemplateFactory from './util/template-factory' import TemplateFactory from './util/template-factory.js'
/** /**
* Constants * Constants
@ -62,7 +62,7 @@ const Default = {
delay: 0, delay: 0,
fallbackPlacements: ['top', 'right', 'bottom', 'left'], fallbackPlacements: ['top', 'right', 'bottom', 'left'],
html: false, html: false,
offset: [0, 0], offset: [0, 6],
placement: 'top', placement: 'top',
popperConfig: null, popperConfig: null,
sanitize: true, sanitize: true,
@ -197,7 +197,7 @@ class Tooltip extends BaseComponent {
return return
} }
// todo v6 remove this OR make it optional // TODO: v6 remove this or make it optional
this._disposePopper() this._disposePopper()
const tip = this._getTipElement() const tip = this._getTipElement()
@ -302,13 +302,13 @@ class Tooltip extends BaseComponent {
_createTipElement(content) { _createTipElement(content) {
const tip = this._getTemplateFactory(content).toHtml() const tip = this._getTemplateFactory(content).toHtml()
// todo: remove this check on v6 // TODO: remove this check in v6
if (!tip) { if (!tip) {
return null return null
} }
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
// todo: on v6 the following can be achieved with CSS only // TODO: v6 the following can be achieved with CSS only
tip.classList.add(`bs-${this.constructor.NAME}-auto`) tip.classList.add(`bs-${this.constructor.NAME}-auto`)
const tipId = getUID(this.constructor.NAME).toString() const tipId = getUID(this.constructor.NAME).toString()
@ -370,9 +370,7 @@ class Tooltip extends BaseComponent {
} }
_createPopper(tip) { _createPopper(tip) {
const placement = typeof this._config.placement === 'function' ? const placement = execute(this._config.placement, [this, tip, this._element])
this._config.placement.call(this, tip, this._element) :
this._config.placement
const attachment = AttachmentMap[placement.toUpperCase()] const attachment = AttachmentMap[placement.toUpperCase()]
return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
} }
@ -392,7 +390,7 @@ class Tooltip extends BaseComponent {
} }
_resolvePossibleFunction(arg) { _resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg.call(this._element) : arg return execute(arg, [this._element])
} }
_getPopperConfig(attachment) { _getPopperConfig(attachment) {
@ -438,7 +436,7 @@ class Tooltip extends BaseComponent {
return { return {
...defaultBsPopperConfig, ...defaultBsPopperConfig,
...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) ...execute(this._config.popperConfig, [defaultBsPopperConfig])
} }
} }
@ -579,9 +577,9 @@ class Tooltip extends BaseComponent {
_getDelegateConfig() { _getDelegateConfig() {
const config = {} const config = {}
for (const key in this._config) { for (const [key, value] of Object.entries(this._config)) {
if (this.constructor.Default[key] !== this._config[key]) { if (this.constructor.Default[key] !== value) {
config[key] = this._config[key] config[key] = value
} }
} }

View file

@ -1,13 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/backdrop.js * Bootstrap util/backdrop.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import EventHandler from '../dom/event-handler' import EventHandler from '../dom/event-handler.js'
import { execute, executeAfterTransition, getElement, reflow } from './index' import Config from './config.js'
import Config from './config' import { execute, executeAfterTransition, getElement, reflow } from './index.js'
/** /**
* Constants * Constants

View file

@ -1,12 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/component-functions.js * Bootstrap util/component-functions.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import EventHandler from '../dom/event-handler' import EventHandler from '../dom/event-handler.js'
import { getElementFromSelector, isDisabled } from './index' import SelectorEngine from '../dom/selector-engine.js'
import { isDisabled } from './index.js'
const enableDismissTrigger = (component, method = 'hide') => { const enableDismissTrigger = (component, method = 'hide') => {
const clickEvent = `click.dismiss${component.EVENT_KEY}` const clickEvent = `click.dismiss${component.EVENT_KEY}`
@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => {
return return
} }
const target = getElementFromSelector(this) || this.closest(`.${name}`) const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)
const instance = component.getOrCreateInstance(target) const instance = component.getOrCreateInstance(target)
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method

View file

@ -1,12 +1,12 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/config.js * Bootstrap util/config.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { isElement, toType } from './index' import Manipulator from '../dom/manipulator.js'
import Manipulator from '../dom/manipulator' import { isElement, toType } from './index.js'
/** /**
* Class definition * Class definition
@ -49,8 +49,7 @@ class Config {
} }
_typeCheckConfig(config, configTypes = this.constructor.DefaultType) { _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
for (const property of Object.keys(configTypes)) { for (const [property, expectedTypes] of Object.entries(configTypes)) {
const expectedTypes = configTypes[property]
const value = config[property] const value = config[property]
const valueType = isElement(value) ? 'element' : toType(value) const valueType = isElement(value) ? 'element' : toType(value)

View file

@ -1,13 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/focustrap.js * Bootstrap util/focustrap.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import EventHandler from '../dom/event-handler' import EventHandler from '../dom/event-handler.js'
import SelectorEngine from '../dom/selector-engine' import SelectorEngine from '../dom/selector-engine.js'
import Config from './config' import Config from './config.js'
/** /**
* Constants * Constants

View file

@ -1,6 +1,6 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/index.js * Bootstrap util/index.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
@ -9,6 +9,20 @@ const MAX_UID = 1_000_000
const MILLISECONDS_MULTIPLIER = 1000 const MILLISECONDS_MULTIPLIER = 1000
const TRANSITION_END = 'transitionend' const TRANSITION_END = 'transitionend'
/**
* Properly escape IDs selectors to handle weird IDs
* @param {string} selector
* @returns {string}
*/
const parseSelector = selector => {
if (selector && window.CSS && window.CSS.escape) {
// document.querySelector needs escaping to handle IDs (html5+) containing for instance /
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
}
return selector
}
// Shout-out Angus Croll (https://goo.gl/pxwQGp) // Shout-out Angus Croll (https://goo.gl/pxwQGp)
const toType = object => { const toType = object => {
if (object === null || object === undefined) { if (object === null || object === undefined) {
@ -30,47 +44,6 @@ const getUID = prefix => {
return prefix return prefix
} }
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
}
const getSelectorFromElement = element => {
const selector = getSelector(element)
if (selector) {
return document.querySelector(selector) ? selector : null
}
return null
}
const getElementFromSelector = element => {
const selector = getSelector(element)
return selector ? document.querySelector(selector) : null
}
const getTransitionDurationFromElement = element => { const getTransitionDurationFromElement = element => {
if (!element) { if (!element) {
return 0 return 0
@ -117,7 +90,7 @@ const getElement = object => {
} }
if (typeof object === 'string' && object.length > 0) { if (typeof object === 'string' && object.length > 0) {
return document.querySelector(object) return document.querySelector(parseSelector(object))
} }
return null return null
@ -249,10 +222,8 @@ const defineJQueryPlugin = plugin => {
}) })
} }
const execute = callback => { const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
if (typeof callback === 'function') { return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue
callback()
}
} }
const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
@ -318,10 +289,8 @@ export {
executeAfterTransition, executeAfterTransition,
findShadowRoot, findShadowRoot,
getElement, getElement,
getElementFromSelector,
getjQuery, getjQuery,
getNextActiveElement, getNextActiveElement,
getSelectorFromElement,
getTransitionDurationFromElement, getTransitionDurationFromElement,
getUID, getUID,
isDisabled, isDisabled,
@ -330,6 +299,7 @@ export {
isVisible, isVisible,
noop, noop,
onDOMContentLoaded, onDOMContentLoaded,
parseSelector,
reflow, reflow,
triggerTransitionEnd, triggerTransitionEnd,
toType toType

View file

@ -1,53 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/sanitizer.js * Bootstrap util/sanitizer.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
const uriAttributes = new Set([ // js-docs-start allow-list
'background',
'cite',
'href',
'itemtype',
'longdesc',
'poster',
'src',
'xlink:href'
])
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
/**
* A pattern that recognizes a commonly useful subset of URLs that are safe.
*
* Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts
*/
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i
/**
* A pattern that matches safe data URLs. Only matches image, video and audio types.
*
* Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts
*/
const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i
const allowedAttribute = (attribute, allowedAttributeList) => {
const attributeName = attribute.nodeName.toLowerCase()
if (allowedAttributeList.includes(attributeName)) {
if (uriAttributes.has(attributeName)) {
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))
}
return true
}
// Check if a regular expression validates the attribute.
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
.some(regex => regex.test(attributeName))
}
export const DefaultAllowlist = { export const DefaultAllowlist = {
// Global attributes allowed on any supplied element below. // Global attributes allowed on any supplied element below.
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
@ -81,6 +41,43 @@ export const DefaultAllowlist = {
u: [], u: [],
ul: [] ul: []
} }
// js-docs-end allow-list
const uriAttributes = new Set([
'background',
'cite',
'href',
'itemtype',
'longdesc',
'poster',
'src',
'xlink:href'
])
/**
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
* contexts.
*
* Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
*/
// eslint-disable-next-line unicorn/better-regex
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
const allowedAttribute = (attribute, allowedAttributeList) => {
const attributeName = attribute.nodeName.toLowerCase()
if (allowedAttributeList.includes(attributeName)) {
if (uriAttributes.has(attributeName)) {
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
}
return true
}
// Check if a regular expression validates the attribute.
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
.some(regex => regex.test(attributeName))
}
export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
if (!unsafeHtml.length) { if (!unsafeHtml.length) {
@ -100,7 +97,6 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
if (!Object.keys(allowList).includes(elementName)) { if (!Object.keys(allowList).includes(elementName)) {
element.remove() element.remove()
continue continue
} }

View file

@ -1,13 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/scrollBar.js * Bootstrap util/scrollBar.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import SelectorEngine from '../dom/selector-engine' import Manipulator from '../dom/manipulator.js'
import Manipulator from '../dom/manipulator' import SelectorEngine from '../dom/selector-engine.js'
import { isElement } from './index' import { isElement } from './index.js'
/** /**
* Constants * Constants

View file

@ -1,13 +1,13 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/swipe.js * Bootstrap util/swipe.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import Config from './config' import EventHandler from '../dom/event-handler.js'
import EventHandler from '../dom/event-handler' import Config from './config.js'
import { execute } from './index' import { execute } from './index.js'
/** /**
* Constants * Constants

View file

@ -1,14 +1,14 @@
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
* Bootstrap (v5.2.3): util/template-factory.js * Bootstrap util/template-factory.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import { DefaultAllowlist, sanitizeHtml } from './sanitizer' import SelectorEngine from '../dom/selector-engine.js'
import { getElement, isElement } from '../util/index' import Config from './config.js'
import SelectorEngine from '../dom/selector-engine' import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'
import Config from './config' import { execute, getElement, isElement } from './index.js'
/** /**
* Constants * Constants
@ -143,7 +143,7 @@ class TemplateFactory extends Config {
} }
_resolvePossibleFunction(arg) { _resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg(this) : arg return execute(arg, [this])
} }
_putElementInTemplate(element, templateElement) { _putElementInTemplate(element, templateElement) {

View file

@ -1,6 +1,7 @@
/* eslint-env node */
/* eslint-disable camelcase */ /* eslint-disable camelcase */
'use strict'
const browsers = { const browsers = {
safariMac: { safariMac: {
base: 'BrowserStack', base: 'BrowserStack',

View file

@ -1,3 +1,5 @@
/* eslint-disable import/extensions, import/no-unassigned-import */
import Tooltip from '../../dist/tooltip' import Tooltip from '../../dist/tooltip'
import '../../dist/carousel' import '../../dist/carousel'

View file

@ -1,7 +1,7 @@
/* eslint-env node */ 'use strict'
const commonjs = require('@rollup/plugin-commonjs') const commonjs = require('@rollup/plugin-commonjs')
const configRollup = require('./rollup.bundle') const configRollup = require('./rollup.bundle.js')
const config = { const config = {
...configRollup, ...configRollup,

View file

@ -1,4 +1,4 @@
/* eslint-env node */ 'use strict'
const { babel } = require('@rollup/plugin-babel') const { babel } = require('@rollup/plugin-babel')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')

View file

@ -1,5 +1,3 @@
/* eslint-env node */
'use strict' 'use strict'
const path = require('node:path') const path = require('node:path')
@ -8,7 +6,7 @@ const { babel } = require('@rollup/plugin-babel')
const istanbul = require('rollup-plugin-istanbul') const istanbul = require('rollup-plugin-istanbul')
const { nodeResolve } = require('@rollup/plugin-node-resolve') const { nodeResolve } = require('@rollup/plugin-node-resolve')
const replace = require('@rollup/plugin-replace') const replace = require('@rollup/plugin-replace')
const { browsers } = require('./browsers') const { browsers } = require('./browsers.js')
const ENV = process.env const ENV = process.env
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK) const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
@ -105,7 +103,7 @@ if (BROWSERSTACK) {
config.browserStack = { config.browserStack = {
username: ENV.BROWSER_STACK_USERNAME, username: ENV.BROWSER_STACK_USERNAME,
accessKey: ENV.BROWSER_STACK_ACCESS_KEY, accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
build: `bootstrap-${ENV.GITHUB_SHA ? ENV.GITHUB_SHA.slice(0, 7) + '-' : ''}${new Date().toISOString()}`, build: `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`,
project: 'Bootstrap', project: 'Bootstrap',
retryLimit: 2 retryLimit: 2
} }

View file

@ -1,13 +0,0 @@
{
"extends": [
"../../../.eslintrc.json"
],
"env": {
"jasmine": true
},
"rules": {
"unicorn/consistent-function-scoping": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/prefer-add-event-listener": "off"
}
}

View file

@ -1,6 +1,6 @@
import Alert from '../../src/alert' import Alert from '../../src/alert.js'
import { getTransitionDurationFromElement } from '../../src/util/index' import { getTransitionDurationFromElement } from '../../src/util/index.js'
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Alert', () => { describe('Alert', () => {
let fixtureEl let fixtureEl

View file

@ -1,7 +1,7 @@
import BaseComponent from '../../src/base-component' import BaseComponent from '../../src/base-component.js'
import { clearFixture, getFixture } from '../helpers/fixture' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import { noop } from '../../src/util/index.js'
import { noop } from '../../src/util' import { clearFixture, getFixture } from '../helpers/fixture.js'
class DummyClass extends BaseComponent { class DummyClass extends BaseComponent {
constructor(element) { constructor(element) {

View file

@ -1,5 +1,5 @@
import Button from '../../src/button' import Button from '../../src/button.js'
import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Button', () => { describe('Button', () => {
let fixtureEl let fixtureEl

View file

@ -1,8 +1,8 @@
import Carousel from '../../src/carousel' import Carousel from '../../src/carousel.js'
import EventHandler from '../../src/dom/event-handler' import EventHandler from '../../src/dom/event-handler.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { isRTL, noop } from '../../src/util/index.js'
import { isRTL, noop } from '../../src/util/index' import Swipe from '../../src/util/swipe.js'
import Swipe from '../../src/util/swipe' import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Carousel', () => { describe('Carousel', () => {
const { Simulator, PointerEvent } = window const { Simulator, PointerEvent } = window

View file

@ -1,6 +1,6 @@
import Collapse from '../../src/collapse' import Collapse from '../../src/collapse.js'
import EventHandler from '../../src/dom/event-handler' import EventHandler from '../../src/dom/event-handler.js'
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Collapse', () => { describe('Collapse', () => {
let fixtureEl let fixtureEl
@ -277,25 +277,25 @@ describe('Collapse', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div id="parentGroup" class="accordion">', '<div id="parentGroup" class="accordion">',
' <div id="parentHeader" class="accordion-header">', ' <div class="accordion-header">',
' <button data-bs-target="#parentContent" data-bs-toggle="collapse" role="button" class="accordion-toggle">Parent</button>', ' <button data-bs-target="#parentContent" data-bs-toggle="collapse" class="accordion-toggle">Parent</button>',
' </div>', ' </div>',
' <div id="parentContent" class="accordion-collapse collapse" aria-labelledby="parentHeader" data-bs-parent="#parentGroup">', ' <div id="parentContent" class="accordion-collapse collapse" data-bs-parent="#parentGroup">',
' <div class="accordion-body">', ' <div class="accordion-body">',
' <div id="childGroup" class="accordion">', ' <div id="childGroup" class="accordion">',
' <div class="accordion-item">', ' <div class="accordion-item">',
' <div id="childHeader1" class="accordion-header">', ' <div class="accordion-header">',
' <button data-bs-target="#childContent1" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 1</button>', ' <button data-bs-target="#childContent1" data-bs-toggle="collapse" class="accordion-toggle">Child 1</button>',
' </div>', ' </div>',
' <div id="childContent1" class="accordion-collapse collapse" aria-labelledby="childHeader1" data-bs-parent="#childGroup">', ' <div id="childContent1" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
' <div>content</div>', ' <div>content</div>',
' </div>', ' </div>',
' </div>', ' </div>',
' <div class="accordion-item">', ' <div class="accordion-item">',
' <div id="childHeader2" class="accordion-header">', ' <div class="accordion-header">',
' <button data-bs-target="#childContent2" data-bs-toggle="collapse" role="button" class="accordion-toggle">Child 2</button>', ' <button data-bs-target="#childContent2" data-bs-toggle="collapse" class="accordion-toggle">Child 2</button>',
' </div>', ' </div>',
' <div id="childContent2" class="accordion-collapse collapse" aria-labelledby="childHeader2" data-bs-parent="#childGroup">', ' <div id="childContent2" class="accordion-collapse collapse" data-bs-parent="#childGroup">',
' <div>content</div>', ' <div>content</div>',
' </div>', ' </div>',
' </div>', ' </div>',
@ -338,12 +338,12 @@ describe('Collapse', () => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<div class="accordion" id="accordionExample">', '<div class="accordion" id="accordionExample">',
' <div class="accordion-item">', ' <div class="accordion-item">',
' <h2 class="accordion-header" id="headingOne">', ' <h2 class="accordion-header">',
' <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">', ' <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">',
' Accordion Item #1', ' Accordion Item #1',
' </button>', ' </button>',
' </h2>', ' </h2>',
' <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">', ' <div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#accordionExample">',
' <div class="accordion-body">', ' <div class="accordion-body">',
' <nav>', ' <nav>',
' <div class="nav nav-tabs" id="nav-tab" role="tablist">', ' <div class="nav nav-tabs" id="nav-tab" role="tablist">',
@ -640,11 +640,11 @@ describe('Collapse', () => {
'<div id="accordion">', '<div id="accordion">',
' <div class="item">', ' <div class="item">',
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>', ' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
' </div>', ' </div>',
' <div class="item">', ' <div class="item">',
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>', ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
' </div>', ' </div>',
'</div>' '</div>'
].join('') ].join('')
@ -699,13 +699,13 @@ describe('Collapse', () => {
' <div class="col-lg-6">', ' <div class="col-lg-6">',
' <div class="item">', ' <div class="item">',
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-bs-parent="#accordion"></div>', ' <div id="collapseOne" class="collapse" role="tabpanel" data-bs-parent="#accordion"></div>',
' </div>', ' </div>',
' </div>', ' </div>',
' <div class="col-lg-6">', ' <div class="col-lg-6">',
' <div class="item">', ' <div class="item">',
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-bs-parent="#accordion"></div>', ' <div id="collapseTwo" class="collapse show" role="tabpanel" data-bs-parent="#accordion"></div>',
' </div>', ' </div>',
' </div>', ' </div>',
' </div>', ' </div>',
@ -829,18 +829,18 @@ describe('Collapse', () => {
'<div id="accordion">', '<div id="accordion">',
' <div class="item">', ' <div class="item">',
' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', ' <a id="linkTrigger" data-bs-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>',
' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel" aria-labelledby="headingThree">', ' <div id="collapseOne" data-bs-parent="#accordion" class="collapse" role="tabpanel">',
' <div id="nestedAccordion">', ' <div id="nestedAccordion">',
' <div class="item">', ' <div class="item">',
' <a id="nestedLinkTrigger" data-bs-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>', ' <a id="nestedLinkTrigger" data-bs-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>',
' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel" aria-labelledby="headingThree"></div>', ' <div id="nestedCollapseOne" data-bs-parent="#nestedAccordion" class="collapse" role="tabpanel"></div>',
' </div>', ' </div>',
' </div>', ' </div>',
' </div>', ' </div>',
' </div>', ' </div>',
' <div class="item">', ' <div class="item">',
' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', ' <a id="linkTriggerTwo" data-bs-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>',
' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel" aria-labelledby="headingTwo"></div>', ' <div id="collapseTwo" data-bs-parent="#accordion" class="collapse show" role="tabpanel"></div>',
' </div>', ' </div>',
'</div>' '</div>'
].join('') ].join('')
@ -887,17 +887,17 @@ describe('Collapse', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = [ fixtureEl.innerHTML = [
'<a id="trigger1" role="button" data-bs-toggle="collapse" href="#test1"></a>', '<a id="trigger1" role="button" data-bs-toggle="collapse" href="#test1"></a>',
'<a id="trigger2" role="button" data-bs-toggle="collapse" href="#test2"></a>', '<a id="trigger2" role="button" data-bs-toggle="collapse" href="#0/my/id"></a>',
'<a id="trigger3" role="button" data-bs-toggle="collapse" href=".multi"></a>', '<a id="trigger3" role="button" data-bs-toggle="collapse" href=".multi"></a>',
'<div id="test1" class="multi"></div>', '<div id="test1" class="multi"></div>',
'<div id="test2" class="multi"></div>' '<div id="0/my/id" class="multi"></div>'
].join('') ].join('')
const trigger1 = fixtureEl.querySelector('#trigger1') const trigger1 = fixtureEl.querySelector('#trigger1')
const trigger2 = fixtureEl.querySelector('#trigger2') const trigger2 = fixtureEl.querySelector('#trigger2')
const trigger3 = fixtureEl.querySelector('#trigger3') const trigger3 = fixtureEl.querySelector('#trigger3')
const target1 = fixtureEl.querySelector('#test1') const target1 = fixtureEl.querySelector('#test1')
const target2 = fixtureEl.querySelector('#test2') const target2 = fixtureEl.querySelector(`#${CSS.escape('0/my/id')}`)
const target2Shown = () => { const target2Shown = () => {
expect(trigger1).not.toHaveClass('collapsed') expect(trigger1).not.toHaveClass('collapsed')

View file

@ -1,5 +1,5 @@
import Data from '../../../src/dom/data' import Data from '../../../src/dom/data.js'
import { getFixture, clearFixture } from '../../helpers/fixture' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Data', () => { describe('Data', () => {
const TEST_KEY = 'bs.test' const TEST_KEY = 'bs.test'
@ -89,7 +89,6 @@ describe('Data', () => {
expect(Data.get(div, TEST_KEY)).toBeNull() expect(Data.get(div, TEST_KEY)).toBeNull()
}) })
/* eslint-disable no-console */
it('should console.error a message if called with multiple keys', () => { it('should console.error a message if called with multiple keys', () => {
console.error = jasmine.createSpy('console.error') console.error = jasmine.createSpy('console.error')
@ -102,5 +101,4 @@ describe('Data', () => {
expect(console.error).toHaveBeenCalled() expect(console.error).toHaveBeenCalled()
expect(Data.get(div, UNKNOWN_KEY)).toBeNull() expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
}) })
/* eslint-enable no-console */
}) })

View file

@ -1,6 +1,6 @@
import EventHandler from '../../../src/dom/event-handler' import EventHandler from '../../../src/dom/event-handler.js'
import { clearFixture, getFixture } from '../../helpers/fixture' import { noop } from '../../../src/util/index.js'
import { noop } from '../../../src/util' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('EventHandler', () => { describe('EventHandler', () => {
let fixtureEl let fixtureEl

View file

@ -1,5 +1,5 @@
import Manipulator from '../../../src/dom/manipulator' import Manipulator from '../../../src/dom/manipulator.js'
import { clearFixture, getFixture } from '../../helpers/fixture' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Manipulator', () => { describe('Manipulator', () => {
let fixtureEl let fixtureEl

View file

@ -1,5 +1,5 @@
import SelectorEngine from '../../../src/dom/selector-engine' import SelectorEngine from '../../../src/dom/selector-engine.js'
import { getFixture, clearFixture } from '../../helpers/fixture' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('SelectorEngine', () => { describe('SelectorEngine', () => {
let fixtureEl let fixtureEl
@ -232,5 +232,159 @@ describe('SelectorEngine', () => {
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
}) })
}) })
describe('getSelectorFromElement', () => {
it('should get selector from data-bs-target', () => {
fixtureEl.innerHTML = [
'<div id="test" data-bs-target=".target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
}) })
it('should get selector from href if no data-bs-target set', () => {
fixtureEl.innerHTML = [
'<a id="test" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
})
it('should get selector from href if data-bs-target equal to #', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
})
it('should return null if a selector from a href is a url without an anchor', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
})
it('should return the anchor if a selector from a href is a url', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
'<div id="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('#target')
})
it('should return null if selector not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
})
it('should return null if no selector', () => {
fixtureEl.innerHTML = '<div></div>'
const testEl = fixtureEl.querySelector('div')
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
})
})
describe('getElementFromSelector', () => {
it('should get element from data-bs-target', () => {
fixtureEl.innerHTML = [
'<div id="test" data-bs-target=".target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
})
it('should get element from href if no data-bs-target set', () => {
fixtureEl.innerHTML = [
'<a id="test" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
})
it('should return null if element not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
})
it('should return null if no selector', () => {
fixtureEl.innerHTML = '<div></div>'
const testEl = fixtureEl.querySelector('div')
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
})
})
describe('getMultipleElementsFromSelector', () => {
it('should get elements from data-bs-target', () => {
fixtureEl.innerHTML = [
'<div id="test" data-bs-target=".target"></div>',
'<div class="target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
})
it('should get elements in array, from href if no data-bs-target set', () => {
fixtureEl.innerHTML = [
'<a id="test" href=".target"></a>',
'<div class="target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
})
it('should return empty array if elements not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
const testEl = fixtureEl.querySelector('#test')
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
})
it('should return empty array if no selector', () => {
fixtureEl.innerHTML = '<div></div>'
const testEl = fixtureEl.querySelector('div')
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
})
})
})

View file

@ -1,7 +1,7 @@
import Dropdown from '../../src/dropdown' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import Dropdown from '../../src/dropdown.js'
import { noop } from '../../src/util/index' import { noop } from '../../src/util/index.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Dropdown', () => { describe('Dropdown', () => {
let fixtureEl let fixtureEl
@ -75,6 +75,7 @@ describe('Dropdown', () => {
resolve() resolve()
}) })
expect().nothing()
dropdown.show() dropdown.show()
}) })
}) })

View file

@ -1,18 +1,18 @@
/* eslint-env jquery */ /* eslint-env jquery */
import Alert from '../../src/alert' import Alert from '../../src/alert.js'
import Button from '../../src/button' import Button from '../../src/button.js'
import Carousel from '../../src/carousel' import Carousel from '../../src/carousel.js'
import Collapse from '../../src/collapse' import Collapse from '../../src/collapse.js'
import Dropdown from '../../src/dropdown' import Dropdown from '../../src/dropdown.js'
import Modal from '../../src/modal' import Modal from '../../src/modal.js'
import Offcanvas from '../../src/offcanvas' import Offcanvas from '../../src/offcanvas.js'
import Popover from '../../src/popover' import Popover from '../../src/popover.js'
import ScrollSpy from '../../src/scrollspy' import ScrollSpy from '../../src/scrollspy.js'
import Tab from '../../src/tab' import Tab from '../../src/tab.js'
import Toast from '../../src/toast' import Toast from '../../src/toast.js'
import Tooltip from '../../src/tooltip' import Tooltip from '../../src/tooltip.js'
import { clearFixture, getFixture } from '../helpers/fixture' import { clearFixture, getFixture } from '../helpers/fixture.js'
describe('jQuery', () => { describe('jQuery', () => {
let fixtureEl let fixtureEl

View file

@ -1,7 +1,7 @@
import Modal from '../../src/modal' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import Modal from '../../src/modal.js'
import ScrollBarHelper from '../../src/util/scrollbar' import ScrollBarHelper from '../../src/util/scrollbar.js'
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Modal', () => { describe('Modal', () => {
let fixtureEl let fixtureEl

View file

@ -1,8 +1,8 @@
import Offcanvas from '../../src/offcanvas' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import Offcanvas from '../../src/offcanvas.js'
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { isVisible } from '../../src/util/index.js'
import { isVisible } from '../../src/util/index' import ScrollBarHelper from '../../src/util/scrollbar.js'
import ScrollBarHelper from '../../src/util/scrollbar' import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Offcanvas', () => { describe('Offcanvas', () => {
let fixtureEl let fixtureEl

View file

@ -1,6 +1,6 @@
import Popover from '../../src/popover' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import Popover from '../../src/popover.js'
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Popover', () => { describe('Popover', () => {
let fixtureEl let fixtureEl

View file

@ -1,8 +1,6 @@
import ScrollSpy from '../../src/scrollspy' import EventHandler from '../../src/dom/event-handler.js'
import ScrollSpy from '../../src/scrollspy.js'
/** Test helpers */ import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
import EventHandler from '../../src/dom/event-handler'
describe('ScrollSpy', () => { describe('ScrollSpy', () => {
let fixtureEl let fixtureEl
@ -942,5 +940,39 @@ describe('ScrollSpy', () => {
}, 100) }, 100)
link.click() link.click()
}) })
it('should smoothscroll to observable with anchor link that contains a french word as id', done => {
fixtureEl.innerHTML = [
'<nav id="navBar" class="navbar">',
' <ul class="nav">',
' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#présentation">div 1</a></li>',
' </ul>',
'</nav>',
'<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
' <div id="présentation">div 1</div>',
'</div>'
].join('')
const div = fixtureEl.querySelector('.content')
const link = fixtureEl.querySelector('[href="#présentation"]')
const observable = fixtureEl.querySelector('#présentation')
const clickSpy = getElementScrollSpy(div)
// eslint-disable-next-line no-new
new ScrollSpy(div, {
offset: 1,
smoothScroll: true
})
setTimeout(() => {
if (div.scrollTo) {
expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })
} else {
expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)
}
done()
}, 100)
link.click()
})
}) })
}) })

View file

@ -1,5 +1,5 @@
import Tab from '../../src/tab' import Tab from '../../src/tab.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Tab', () => { describe('Tab', () => {
let fixtureEl let fixtureEl
@ -177,6 +177,43 @@ describe('Tab', () => {
}) })
}) })
it('should work with tab id being an int', done => {
fixtureEl.innerHTML = [
'<div class="card-header d-block d-inline-block">',
' <ul class="nav nav-tabs card-header-tabs" id="page_tabs">',
' <li class="nav-item">',
' <a class="nav-link" draggable="false" data-toggle="tab" href="#tab1">',
' Working Tab 1 (#tab1)',
' </a>',
' </li>',
' <li class="nav-item">',
' <a id="trigger2" class="nav-link" draggable="false" data-toggle="tab" href="#2">',
' Tab with numeric ID should work (#2)',
' </a>',
' </li>',
' </ul>',
'</div>',
'<div class="card-body">',
' <div class="tab-content" id="page_content">',
' <div class="tab-pane fade" id="tab1">',
' Working Tab 1 (#tab1) Content Here',
' </div>',
' <div class="tab-pane fade" id="2">',
' Working Tab 2 (#2) with numeric ID',
' </div>',
'</div>'
].join('')
const profileTriggerEl = fixtureEl.querySelector('#trigger2')
const tab = new Tab(profileTriggerEl)
profileTriggerEl.addEventListener('shown.bs.tab', () => {
expect(fixtureEl.querySelector(`#${CSS.escape('2')}`)).toHaveClass('active')
done()
})
tab.show()
})
it('should not fire shown when show is prevented', () => { it('should not fire shown when show is prevented', () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>' fixtureEl.innerHTML = '<div class="nav"><div class="nav-link"></div></div>'
@ -477,7 +514,7 @@ describe('Tab', () => {
expect(tabPanel.hasAttribute('tabindex')).toBeFalse() expect(tabPanel.hasAttribute('tabindex')).toBeFalse()
expect(tabPanel.hasAttribute('tabindex2')).toBeFalse() expect(tabPanel.hasAttribute('tabindex2')).toBeFalse()
expect(tabPanel.getAttribute('aria-labelledby')).toEqual('#foo') expect(tabPanel.getAttribute('aria-labelledby')).toEqual('foo')
expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse() expect(tabPanel2.hasAttribute('aria-labelledby')).toBeFalse()
}) })
}) })
@ -603,19 +640,19 @@ describe('Tab', () => {
'</div>' '</div>'
].join('') ].join('')
const tabEl = fixtureEl.querySelector('#tab1') const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2') const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3') const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4') const tabEl4 = fixtureEl.querySelector('#tab4')
const tab = new Tab(tabEl) const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2) const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3) const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4) const tab4 = new Tab(tabEl4)
const spy1 = spyOn(tab, 'show').and.callThrough() const spy1 = spyOn(tab1, 'show').and.callThrough()
const spy2 = spyOn(tab2, 'show').and.callThrough() const spy2 = spyOn(tab2, 'show').and.callThrough()
const spy3 = spyOn(tab3, 'show').and.callThrough() const spy3 = spyOn(tab3, 'show').and.callThrough()
const spy4 = spyOn(tab4, 'show').and.callThrough() const spy4 = spyOn(tab4, 'show').and.callThrough()
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough() const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough() const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough() const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough() const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
@ -623,7 +660,7 @@ describe('Tab', () => {
const keydown = createEvent('keydown') const keydown = createEvent('keydown')
keydown.key = 'ArrowRight' keydown.key = 'ArrowRight'
tabEl.dispatchEvent(keydown) tabEl1.dispatchEvent(keydown)
expect(spy1).not.toHaveBeenCalled() expect(spy1).not.toHaveBeenCalled()
expect(spy2).not.toHaveBeenCalled() expect(spy2).not.toHaveBeenCalled()
expect(spy3).not.toHaveBeenCalled() expect(spy3).not.toHaveBeenCalled()
@ -644,19 +681,19 @@ describe('Tab', () => {
'</div>' '</div>'
].join('') ].join('')
const tabEl = fixtureEl.querySelector('#tab1') const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2') const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3') const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4') const tabEl4 = fixtureEl.querySelector('#tab4')
const tab = new Tab(tabEl) const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2) const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3) const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4) const tab4 = new Tab(tabEl4)
const spy1 = spyOn(tab, 'show').and.callThrough() const spy1 = spyOn(tab1, 'show').and.callThrough()
const spy2 = spyOn(tab2, 'show').and.callThrough() const spy2 = spyOn(tab2, 'show').and.callThrough()
const spy3 = spyOn(tab3, 'show').and.callThrough() const spy3 = spyOn(tab3, 'show').and.callThrough()
const spy4 = spyOn(tab4, 'show').and.callThrough() const spy4 = spyOn(tab4, 'show').and.callThrough()
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough() const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough() const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough() const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough() const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()

View file

@ -1,5 +1,5 @@
import Toast from '../../src/toast' import Toast from '../../src/toast.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Toast', () => { describe('Toast', () => {
let fixtureEl let fixtureEl

View file

@ -1,7 +1,7 @@
import Tooltip from '../../src/tooltip' import EventHandler from '../../src/dom/event-handler.js'
import EventHandler from '../../src/dom/event-handler' import Tooltip from '../../src/tooltip.js'
import { noop } from '../../src/util/index' import { noop } from '../../src/util/index.js'
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js'
describe('Tooltip', () => { describe('Tooltip', () => {
let fixtureEl let fixtureEl
@ -56,7 +56,7 @@ describe('Tooltip', () => {
describe('constructor', () => { describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => { it('should take care of element either passed as a CSS selector or DOM element', () => {
fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title">' fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title"></a>'
const tooltipEl = fixtureEl.querySelector('#tooltipEl') const tooltipEl = fixtureEl.querySelector('#tooltipEl')
const tooltipBySelector = new Tooltip('#tooltipEl') const tooltipBySelector = new Tooltip('#tooltipEl')
@ -67,7 +67,7 @@ describe('Tooltip', () => {
}) })
it('should not take care of disallowed data attributes', () => { it('should not take care of disallowed data attributes', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -76,7 +76,7 @@ describe('Tooltip', () => {
}) })
it('should convert title and content to string if numbers', () => { it('should convert title and content to string if numbers', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -98,7 +98,7 @@ describe('Tooltip', () => {
trigger: 'click' trigger: 'click'
}) })
containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipInContainerEl = containerEl.querySelector('a') const tooltipInContainerEl = containerEl.querySelector('a')
@ -114,7 +114,7 @@ describe('Tooltip', () => {
it('should create offset modifier when offset is passed as a function', () => { it('should create offset modifier when offset is passed as a function', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Offset from function"></a>'
const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20]) const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
@ -141,7 +141,7 @@ describe('Tooltip', () => {
}) })
it('should create offset modifier when offset option is passed in data attribute', () => { it('should create offset modifier when offset option is passed in data attribute', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-offset="10,20" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -150,7 +150,7 @@ describe('Tooltip', () => {
}) })
it('should allow to pass config to Popper with `popperConfig`', () => { it('should allow to pass config to Popper with `popperConfig`', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -165,7 +165,7 @@ describe('Tooltip', () => {
}) })
it('should allow to pass config to Popper with `popperConfig` as a function', () => { it('should allow to pass config to Popper with `popperConfig` as a function', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' }) const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })
@ -192,7 +192,7 @@ describe('Tooltip', () => {
describe('enable', () => { describe('enable', () => {
it('should enable a tooltip', () => { it('should enable a tooltip', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -212,7 +212,7 @@ describe('Tooltip', () => {
describe('disable', () => { describe('disable', () => {
it('should disable tooltip', () => { it('should disable tooltip', () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -235,7 +235,7 @@ describe('Tooltip', () => {
describe('toggleEnabled', () => { describe('toggleEnabled', () => {
it('should toggle enabled', () => { it('should toggle enabled', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -251,7 +251,7 @@ describe('Tooltip', () => {
describe('toggle', () => { describe('toggle', () => {
it('should do nothing if disabled', () => { it('should do nothing if disabled', () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -273,7 +273,7 @@ describe('Tooltip', () => {
it('should show a tooltip', () => { it('should show a tooltip', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -289,7 +289,7 @@ describe('Tooltip', () => {
it('should call toggle and show the tooltip when trigger is "click"', () => { it('should call toggle and show the tooltip when trigger is "click"', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -309,7 +309,7 @@ describe('Tooltip', () => {
it('should hide a tooltip', () => { it('should hide a tooltip', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -329,7 +329,7 @@ describe('Tooltip', () => {
it('should call toggle and hide the tooltip when trigger is "click"', () => { it('should call toggle and hide the tooltip when trigger is "click"', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -354,7 +354,7 @@ describe('Tooltip', () => {
describe('dispose', () => { describe('dispose', () => {
it('should destroy a tooltip', () => { it('should destroy a tooltip', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough() const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()
@ -381,7 +381,7 @@ describe('Tooltip', () => {
it('should destroy a tooltip after it is shown and hidden', () => { it('should destroy a tooltip after it is shown and hidden', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -402,7 +402,7 @@ describe('Tooltip', () => {
it('should destroy a tooltip and remove it from the dom', () => { it('should destroy a tooltip and remove it from the dom', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -443,7 +443,7 @@ describe('Tooltip', () => {
describe('show', () => { describe('show', () => {
it('should show a tooltip', () => { it('should show a tooltip', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -488,7 +488,7 @@ describe('Tooltip', () => {
it('should show a tooltip on mobile', () => { it('should show a tooltip on mobile', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -509,7 +509,7 @@ describe('Tooltip', () => {
it('should show a tooltip relative to placement option', () => { it('should show a tooltip relative to placement option', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -532,7 +532,7 @@ describe('Tooltip', () => {
it('should not error when trying to show a tooltip that has been removed from the dom', () => { it('should not error when trying to show a tooltip that has been removed from the dom', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -561,7 +561,7 @@ describe('Tooltip', () => {
it('should show a tooltip with a dom element container', () => { it('should show a tooltip with a dom element container', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -579,7 +579,7 @@ describe('Tooltip', () => {
it('should show a tooltip with a jquery element container', () => { it('should show a tooltip with a jquery element container', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -600,7 +600,7 @@ describe('Tooltip', () => {
it('should show a tooltip with a selector in container', () => { it('should show a tooltip with a selector in container', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -618,7 +618,7 @@ describe('Tooltip', () => {
it('should show a tooltip with placement as a function', () => { it('should show a tooltip with placement as a function', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const spy = jasmine.createSpy('placement').and.returnValue('top') const spy = jasmine.createSpy('placement').and.returnValue('top')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
@ -638,7 +638,7 @@ describe('Tooltip', () => {
it('should show a tooltip without the animation', () => { it('should show a tooltip without the animation', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -658,7 +658,7 @@ describe('Tooltip', () => {
}) })
it('should throw an error the element is not visible', () => { it('should throw an error the element is not visible', () => {
fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -672,7 +672,7 @@ describe('Tooltip', () => {
it('should not show a tooltip if show.bs.tooltip is prevented', () => { it('should not show a tooltip if show.bs.tooltip is prevented', () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -699,7 +699,7 @@ describe('Tooltip', () => {
it('should show tooltip if leave event hasn\'t occurred before delay expires', () => { it('should show tooltip if leave event hasn\'t occurred before delay expires', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -723,7 +723,7 @@ describe('Tooltip', () => {
it('should not show tooltip if leave event occurs before delay expires', () => { it('should not show tooltip if leave event occurs before delay expires', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -845,7 +845,7 @@ describe('Tooltip', () => {
it('should only trigger inserted event if a new tooltip element was created', () => { it('should only trigger inserted event if a new tooltip element was created', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -878,7 +878,7 @@ describe('Tooltip', () => {
it('should show a tooltip with custom class provided in data attributes', () => { it('should show a tooltip with custom class provided in data attributes', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-custom-class="custom-class"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -896,7 +896,7 @@ describe('Tooltip', () => {
it('should show a tooltip with custom class provided as a string in config', () => { it('should show a tooltip with custom class provided as a string in config', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -917,7 +917,7 @@ describe('Tooltip', () => {
it('should show a tooltip with custom class provided as a function in config', () => { it('should show a tooltip with custom class provided as a function in config', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const spy = jasmine.createSpy('customClass').and.returnValue('custom-class') const spy = jasmine.createSpy('customClass').and.returnValue('custom-class')
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
@ -956,7 +956,7 @@ describe('Tooltip', () => {
describe('hide', () => { describe('hide', () => {
it('should hide a tooltip', () => { it('should hide a tooltip', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -974,7 +974,7 @@ describe('Tooltip', () => {
it('should hide a tooltip on mobile', () => { it('should hide a tooltip on mobile', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -998,7 +998,7 @@ describe('Tooltip', () => {
it('should hide a tooltip without animation', () => { it('should hide a tooltip without animation', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { const tooltip = new Tooltip(tooltipEl, {
@ -1018,7 +1018,7 @@ describe('Tooltip', () => {
it('should not hide a tooltip if hide event is prevented', () => { it('should not hide a tooltip if hide event is prevented', () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const assertDone = () => { const assertDone = () => {
setTimeout(() => { setTimeout(() => {
@ -1063,7 +1063,7 @@ describe('Tooltip', () => {
describe('update', () => { describe('update', () => {
it('should call popper update', () => { it('should call popper update', () => {
return new Promise(resolve => { return new Promise(resolve => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1082,7 +1082,7 @@ describe('Tooltip', () => {
}) })
it('should do nothing if the tooltip is not shown', () => { it('should do nothing if the tooltip is not shown', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1094,7 +1094,7 @@ describe('Tooltip', () => {
describe('_isWithContent', () => { describe('_isWithContent', () => {
it('should return true if there is content', () => { it('should return true if there is content', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1103,7 +1103,7 @@ describe('Tooltip', () => {
}) })
it('should return false if there is no content', () => { it('should return false if there is no content', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title=""></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1114,7 +1114,7 @@ describe('Tooltip', () => {
describe('_getTipElement', () => { describe('_getTipElement', () => {
it('should create the tip element and return it', () => { it('should create the tip element and return it', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1126,7 +1126,7 @@ describe('Tooltip', () => {
}) })
it('should return the created tip element', () => { it('should return the created tip element', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1145,7 +1145,7 @@ describe('Tooltip', () => {
describe('setContent', () => { describe('setContent', () => {
it('should set tip content', () => { it('should set tip content', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl, { animation: false }) const tooltip = new Tooltip(tooltipEl, { animation: false })
@ -1160,7 +1160,7 @@ describe('Tooltip', () => {
}) })
it('should re-show tip if it was already shown', () => { it('should re-show tip if it was already shown', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1175,7 +1175,7 @@ describe('Tooltip', () => {
}) })
it('should keep tip hidden, if it was already hidden before', () => { it('should keep tip hidden, if it was already hidden before', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1190,7 +1190,7 @@ describe('Tooltip', () => {
}) })
it('"setContent" should keep the initial template', () => { it('"setContent" should keep the initial template', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)
@ -1207,7 +1207,7 @@ describe('Tooltip', () => {
describe('setContent', () => { describe('setContent', () => {
it('should do nothing if the element is null', () => { it('should do nothing if the element is null', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">' fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
const tooltipEl = fixtureEl.querySelector('a') const tooltipEl = fixtureEl.querySelector('a')
const tooltip = new Tooltip(tooltipEl) const tooltip = new Tooltip(tooltipEl)

View file

@ -1,6 +1,6 @@
import Backdrop from '../../../src/util/backdrop' import Backdrop from '../../../src/util/backdrop.js'
import { getTransitionDurationFromElement } from '../../../src/util/index' import { getTransitionDurationFromElement } from '../../../src/util/index.js'
import { clearFixture, getFixture } from '../../helpers/fixture' import { clearFixture, getFixture } from '../../helpers/fixture.js'
const CLASS_BACKDROP = '.modal-backdrop' const CLASS_BACKDROP = '.modal-backdrop'
const CLASS_NAME_FADE = 'fade' const CLASS_NAME_FADE = 'fade'

View file

@ -1,8 +1,6 @@
/* Test helpers */ import BaseComponent from '../../../src/base-component.js'
import { enableDismissTrigger } from '../../../src/util/component-functions.js'
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
import { enableDismissTrigger } from '../../../src/util/component-functions'
import BaseComponent from '../../../src/base-component'
class DummyClass2 extends BaseComponent { class DummyClass2 extends BaseComponent {
static get NAME() { static get NAME() {

View file

@ -1,5 +1,5 @@
import Config from '../../../src/util/config' import Config from '../../../src/util/config.js'
import { clearFixture, getFixture } from '../../helpers/fixture' import { clearFixture, getFixture } from '../../helpers/fixture.js'
class DummyConfigClass extends Config { class DummyConfigClass extends Config {
static get NAME() { static get NAME() {
@ -128,7 +128,7 @@ describe('Config', () => {
const obj = new DummyConfigClass() const obj = new DummyConfigClass()
expect(() => { expect(() => {
obj._typeCheckConfig(config) obj._typeCheckConfig(config)
}).toThrowError(TypeError, obj.constructor.NAME.toUpperCase() + ': Option "parent" provided type "number" but expected type "(string|element)".') }).toThrowError(TypeError, `${obj.constructor.NAME.toUpperCase()}: Option "parent" provided type "number" but expected type "(string|element)".`)
}) })
it('should return null stringified when null is passed', () => { it('should return null stringified when null is passed', () => {

View file

@ -1,7 +1,7 @@
import FocusTrap from '../../../src/util/focustrap' import EventHandler from '../../../src/dom/event-handler.js'
import EventHandler from '../../../src/dom/event-handler' import SelectorEngine from '../../../src/dom/selector-engine.js'
import SelectorEngine from '../../../src/dom/selector-engine' import FocusTrap from '../../../src/util/focustrap.js'
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
describe('FocusTrap', () => { describe('FocusTrap', () => {
let fixtureEl let fixtureEl

View file

@ -1,6 +1,6 @@
import * as Util from '../../../src/util/index' import * as Util from '../../../src/util/index.js'
import { clearFixture, getFixture } from '../../helpers/fixture' import { noop } from '../../../src/util/index.js'
import { noop } from '../../../src/util/index' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Util', () => { describe('Util', () => {
let fixtureEl let fixtureEl
@ -22,119 +22,6 @@ describe('Util', () => {
}) })
}) })
describe('getSelectorFromElement', () => {
it('should get selector from data-bs-target', () => {
fixtureEl.innerHTML = [
'<div id="test" data-bs-target=".target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
})
it('should get selector from href if no data-bs-target set', () => {
fixtureEl.innerHTML = [
'<a id="test" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
})
it('should get selector from href if data-bs-target equal to #', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
})
it('should return null if a selector from a href is a url without an anchor', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toBeNull()
})
it('should return the anchor if a selector from a href is a url', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
'<div id="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toEqual('#target')
})
it('should return null if selector not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
const testEl = fixtureEl.querySelector('#test')
expect(Util.getSelectorFromElement(testEl)).toBeNull()
})
it('should return null if no selector', () => {
fixtureEl.innerHTML = '<div></div>'
const testEl = fixtureEl.querySelector('div')
expect(Util.getSelectorFromElement(testEl)).toBeNull()
})
})
describe('getElementFromSelector', () => {
it('should get element from data-bs-target', () => {
fixtureEl.innerHTML = [
'<div id="test" data-bs-target=".target"></div>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
})
it('should get element from href if no data-bs-target set', () => {
fixtureEl.innerHTML = [
'<a id="test" href=".target"></a>',
'<div class="target"></div>'
].join('')
const testEl = fixtureEl.querySelector('#test')
expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
})
it('should return null if element not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
const testEl = fixtureEl.querySelector('#test')
expect(Util.getElementFromSelector(testEl)).toBeNull()
})
it('should return null if no selector', () => {
fixtureEl.innerHTML = '<div></div>'
const testEl = fixtureEl.querySelector('div')
expect(Util.getElementFromSelector(testEl)).toBeNull()
})
})
describe('getTransitionDurationFromElement', () => { describe('getTransitionDurationFromElement', () => {
it('should get transition from element', () => { it('should get transition from element', () => {
fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>' fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>'
@ -631,6 +518,25 @@ describe('Util', () => {
Util.execute(spy) Util.execute(spy)
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
}) })
it('should execute if arg is function & return the result', () => {
const functionFoo = (num1, num2 = 10) => num1 + num2
const resultFoo = Util.execute(functionFoo, [4, 5])
expect(resultFoo).toBe(9)
const resultFoo1 = Util.execute(functionFoo, [4])
expect(resultFoo1).toBe(14)
const functionBar = () => 'foo'
const resultBar = Util.execute(functionBar)
expect(resultBar).toBe('foo')
})
it('should not execute if arg is not function & return default argument', () => {
const foo = 'bar'
expect(Util.execute(foo)).toBe('bar')
expect(Util.execute(foo, [], 4)).toBe(4)
})
}) })
describe('executeAfterTransition', () => { describe('executeAfterTransition', () => {

View file

@ -1,4 +1,4 @@
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer' import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js'
describe('Sanitizer', () => { describe('Sanitizer', () => {
describe('sanitizeHtml', () => { describe('sanitizeHtml', () => {
@ -10,17 +10,75 @@ describe('Sanitizer', () => {
expect(result).toEqual(empty) expect(result).toEqual(empty)
}) })
it('should sanitize template by removing tags with XSS', () => { it('should retain tags with valid URLs', () => {
const validUrls = [
'',
'http://abc',
'HTTP://abc',
'https://abc',
'HTTPS://abc',
'ftp://abc',
'FTP://abc',
'mailto:me@example.com',
'MAILTO:me@example.com',
'tel:123-123-1234',
'TEL:123-123-1234',
'sip:me@example.com',
'SIP:me@example.com',
'#anchor',
'/page1.md',
'http://JavaScript/my.js',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
'unknown-scheme:abc'
]
for (const url of validUrls) {
const template = [ const template = [
'<div>', '<div>',
' <a href="javascript:alert(7)">Click me</a>', ` <a href="${url}">Click me</a>`,
' <span>Some content</span>', ' <span>Some content</span>',
'</div>' '</div>'
].join('') ].join('')
const result = sanitizeHtml(template, DefaultAllowlist, null) const result = sanitizeHtml(template, DefaultAllowlist, null)
expect(result).not.toContain('href="javascript:alert(7)') expect(result).toContain(`href="${url}"`)
}
})
it('should sanitize template by removing tags with XSS', () => {
const invalidUrls = [
// eslint-disable-next-line no-script-url
'javascript:alert(7)',
// eslint-disable-next-line no-script-url
'javascript:evil()',
// eslint-disable-next-line no-script-url
'JavaScript:abc',
' javascript:abc',
' \n Java\n Script:abc',
'&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
'&#106&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
'&#106 &#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;',
'&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058',
'&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A;',
'jav&#x09;ascript:alert();',
'jav\u0000ascript:alert();'
]
for (const url of invalidUrls) {
const template = [
'<div>',
` <a href="${url}">Click me</a>`,
' <span>Some content</span>',
'</div>'
].join('')
const result = sanitizeHtml(template, DefaultAllowlist, null)
expect(result).not.toContain(`href="${url}"`)
}
}) })
it('should sanitize template and work with multiple regex', () => { it('should sanitize template and work with multiple regex', () => {

View file

@ -1,6 +1,6 @@
import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture' import Manipulator from '../../../src/dom/manipulator.js'
import Manipulator from '../../../src/dom/manipulator' import ScrollBarHelper from '../../../src/util/scrollbar.js'
import ScrollBarHelper from '../../../src/util/scrollbar' import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture.js'
describe('ScrollBar', () => { describe('ScrollBar', () => {
let fixtureEl let fixtureEl

View file

@ -1,7 +1,7 @@
import { clearFixture, getFixture } from '../../helpers/fixture' import EventHandler from '../../../src/dom/event-handler.js'
import EventHandler from '../../../src/dom/event-handler' import { noop } from '../../../src/util/index.js'
import Swipe from '../../../src/util/swipe' import Swipe from '../../../src/util/swipe.js'
import { noop } from '../../../src/util' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('Swipe', () => { describe('Swipe', () => {
const { Simulator, PointerEvent } = window const { Simulator, PointerEvent } = window

View file

@ -1,5 +1,5 @@
import { clearFixture, getFixture } from '../../helpers/fixture' import TemplateFactory from '../../../src/util/template-factory.js'
import TemplateFactory from '../../../src/util/template-factory' import { clearFixture, getFixture } from '../../helpers/fixture.js'
describe('TemplateFactory', () => { describe('TemplateFactory', () => {
let fixtureEl let fixtureEl

View file

@ -1,19 +0,0 @@
{
"plugins": [
"html"
],
"extends": "../../../.eslintrc.json",
"parserOptions": {
"sourceType": "module"
},
"settings": {
"html/html-extensions": [
".html"
]
},
"rules": {
"no-console": "off",
"no-new": "off",
"unicorn/no-array-for-each": "off"
}
}

View file

@ -15,7 +15,7 @@
</button> </button>
<p>For checkboxes and radio buttons, ensure that keyboard behavior is functioning correctly.</p> <p>For checkboxes and radio buttons, ensure that keyboard behavior is functioning correctly.</p>
<p>Navigate to the checkboxes with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>), and ensure that <kbd>SPACE</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>SPACE</kbd> toggles the checkbox again.</p> <p>Navigate to the checkboxes with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>), and ensure that <kbd>Space</kbd> toggles the currently focused checkbox. Click on one of the checkboxes using the mouse, ensure that focus was correctly set on the actual checkbox, and that <kbd>Space</kbd> toggles the checkbox again.</p>
<div class="btn-group" data-bs-toggle="buttons"> <div class="btn-group" data-bs-toggle="buttons">
<label class="btn btn-primary active"> <label class="btn btn-primary active">
@ -29,7 +29,7 @@
</label> </label>
</div> </div>
<p>Navigate to the radio button group with the keyboard (generally, using <kbd>TAB</kbd> / <kbd>SHIFT + TAB</kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>TAB</kbd> or "backwards" using <kbd>SHIFT + TAB</kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd></kbd> and <kbd></kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd></kbd> and <kbd></kbd> change the selected radio button again.</p> <p>Navigate to the radio button group with the keyboard (generally, using <kbd>Tab</kbd> / <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If no radio button was initially set to be selected, the first/last radio button should receive focus (depending on whether you navigated "forward" to the group with <kbd>Tab</kbd> or "backwards" using <kbd><kbd>Shift</kbd> + <kbd>Tab</kbd></kbd>). If a radio button was already selected, navigating with the keyboard should set focus to that particular radio button. Only one radio button in a group should receive focus at any given time. Ensure that the selected radio button can be changed by using the <kbd></kbd> and <kbd></kbd> arrow keys. Click on one of the radio buttons with the mouse, ensure that focus was correctly set on the actual radio button, and that <kbd></kbd> and <kbd></kbd> change the selected radio button again.</p>
<div class="btn-group" data-bs-toggle="buttons"> <div class="btn-group" data-bs-toggle="buttons">
<label class="btn btn-primary active"> <label class="btn btn-primary active">

Some files were not shown because too many files have changed in this diff Show more