From e92720b6a721787e792a06f3212be94d8a8179f1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 17 Feb 2025 07:08:19 +0100 Subject: [PATCH] Merging upstream version 5.3.0+dfsg. Signed-off-by: Daniel Baumann --- .bundlewatch.config.json | 24 +- .cspell.json | 2 + .eslintignore | 3 +- .eslintrc.json | 157 +- .github/CONTRIBUTING.md | 18 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/codeql/codeql-config.yml | 3 + .github/dependabot.yml | 29 +- .github/workflows/browserstack.yml | 12 +- .github/workflows/bundlewatch.yml | 11 +- .../workflows/calibreapp-image-actions.yml | 2 + .github/workflows/codeql.yml | 14 +- .github/workflows/cspell.yml | 14 +- .github/workflows/css.yml | 14 +- .github/workflows/docs.yml | 11 +- .github/workflows/issue-close-require.yml | 7 + .github/workflows/issue-labeled.yml | 7 + .github/workflows/js.yml | 17 +- .github/workflows/lint.yml | 11 +- .github/workflows/node-sass.yml | 24 +- .github/workflows/release-notes.yml | 7 + .gitignore | 1 + .npmrc | 1 + .stylelintrc | 31 - .stylelintrc.json | 60 + CODE_OF_CONDUCT.md | 20 +- LICENSE | 3 +- README.md | 36 +- build/.eslintrc.json | 15 - build/banner.js | 1 + build/build-plugins.js | 7 +- build/change-version.js | 42 +- build/generate-sri.js | 15 +- build/rollup.config.js | 2 +- build/vnu-jar.js | 10 +- build/zip-examples.js | 4 +- config.yml => hugo.yml | 41 +- js/index.esm.js | 26 +- js/index.umd.js | 26 +- js/src/alert.js | 10 +- js/src/base-component.js | 12 +- js/src/button.js | 8 +- js/src/carousel.js | 19 +- js/src/collapse.js | 23 +- js/src/dom/data.js | 2 +- js/src/dom/event-handler.js | 19 +- js/src/dom/manipulator.js | 2 +- js/src/dom/selector-engine.js | 53 +- js/src/dropdown.js | 21 +- js/src/modal.js | 27 +- js/src/offcanvas.js | 27 +- js/src/popover.js | 6 +- js/src/scrollspy.js | 14 +- js/src/tab.js | 22 +- js/src/toast.js | 10 +- js/src/tooltip.js | 36 +- js/src/util/backdrop.js | 8 +- js/src/util/component-functions.js | 9 +- js/src/util/config.js | 9 +- js/src/util/focustrap.js | 8 +- js/src/util/index.js | 68 +- js/src/util/sanitizer.js | 82 +- js/src/util/scrollbar.js | 8 +- js/src/util/swipe.js | 8 +- js/src/util/template-factory.js | 12 +- js/tests/browsers.js | 3 +- js/tests/integration/bundle-modularity.js | 2 + .../integration/rollup.bundle-modularity.js | 4 +- js/tests/integration/rollup.bundle.js | 2 +- js/tests/karma.conf.js | 6 +- js/tests/unit/.eslintrc.json | 13 - js/tests/unit/alert.spec.js | 6 +- js/tests/unit/base-component.spec.js | 8 +- js/tests/unit/button.spec.js | 4 +- js/tests/unit/carousel.spec.js | 10 +- js/tests/unit/collapse.spec.js | 48 +- js/tests/unit/dom/data.spec.js | 6 +- js/tests/unit/dom/event-handler.spec.js | 6 +- js/tests/unit/dom/manipulator.spec.js | 4 +- js/tests/unit/dom/selector-engine.spec.js | 160 +- js/tests/unit/dropdown.spec.js | 9 +- js/tests/unit/jquery.spec.js | 26 +- js/tests/unit/modal.spec.js | 8 +- js/tests/unit/offcanvas.spec.js | 10 +- js/tests/unit/popover.spec.js | 6 +- js/tests/unit/scrollspy.spec.js | 42 +- js/tests/unit/tab.spec.js | 61 +- js/tests/unit/toast.spec.js | 4 +- js/tests/unit/tooltip.spec.js | 110 +- js/tests/unit/util/backdrop.spec.js | 6 +- .../unit/util/component-functions.spec.js | 8 +- js/tests/unit/util/config.spec.js | 6 +- js/tests/unit/util/focustrap.spec.js | 8 +- js/tests/unit/util/index.spec.js | 138 +- js/tests/unit/util/sanitizer.spec.js | 76 +- js/tests/unit/util/scrollbar.spec.js | 6 +- js/tests/unit/util/swipe.spec.js | 8 +- js/tests/unit/util/template-factory.spec.js | 4 +- js/tests/visual/.eslintrc.json | 19 - js/tests/visual/button.html | 4 +- js/tests/visual/carousel.html | 2 +- js/tests/visual/input.html | 78 + js/tests/visual/modal.html | 2 +- js/tests/visual/scrollspy.html | 9 + js/tests/visual/toast.html | 2 +- js/tests/visual/tooltip.html | 2 +- nuget/bootstrap.nuspec | 5 +- nuget/bootstrap.png | Bin 6636 -> 6421 bytes nuget/bootstrap.sass.nuspec | 5 +- package-lock.json | 8816 +++++++++-------- package.js | 2 +- package.json | 95 +- scss/_accordion.scss | 9 + scss/_alert.scss | 19 +- scss/_button-group.scss | 4 +- scss/_buttons.scss | 6 +- scss/_card.scss | 5 + scss/_carousel.scss | 22 +- scss/_close.scss | 43 +- scss/_dropdown.scss | 1 + scss/_functions.scss | 2 +- scss/_grid.scss | 6 + scss/_helpers.scss | 2 + scss/_list-group.scss | 19 +- scss/_maps.scss | 120 + scss/_mixins.scss | 3 +- scss/_nav.scss | 43 +- scss/_navbar.scss | 19 +- scss/_offcanvas.scss | 6 +- scss/_pagination.scss | 2 +- scss/_progress.scss | 11 +- scss/_reboot.scss | 6 +- scss/_root.scss | 131 +- scss/_tables.scss | 29 +- scss/_tooltip.scss | 9 +- scss/_utilities.scss | 185 +- scss/_variables-dark.scss | 85 + scss/_variables.scss | 411 +- scss/bootstrap-grid.scss | 4 +- scss/bootstrap-reboot.scss | 1 + scss/bootstrap-utilities.scss | 1 + scss/bootstrap.scss | 1 + scss/forms/_floating-labels.scss | 25 +- scss/forms/_form-check.scss | 35 +- scss/forms/_form-control.scss | 26 +- scss/forms/_form-select.scss | 13 +- scss/forms/_input-group.scss | 2 +- scss/helpers/_color-bg.scss | 2 - scss/helpers/_colored-links.scss | 22 +- scss/helpers/_focus-ring.scss | 5 + scss/helpers/_icon-link.scss | 25 + scss/mixins/_alert.scss | 5 +- scss/mixins/_banner.scss | 6 +- scss/mixins/_caret.scss | 55 +- scss/mixins/_color-mode.scss | 21 + scss/mixins/_forms.scss | 15 +- scss/mixins/_list-group.scss | 2 + scss/mixins/_utilities.scss | 2 +- scss/mixins/_visually-hidden.scss | 6 +- scss/tests/jasmine.js | 16 + scss/tests/mixins/_color-modes.test.scss | 69 + .../_media-query-color-mode-full.test.scss | 8 + scss/tests/mixins/_utilities.test.scss | 393 + scss/tests/sass-true/register.js | 14 + scss/tests/sass-true/runner.js | 17 + scss/tests/utilities/_api.test.scss | 75 + scss/vendor/_rfs.scss | 52 +- site/.eslintrc.json | 54 - site/assets/js/application.js | 8 +- site/assets/js/code-examples.js | 11 +- site/assets/js/search.js | 4 +- site/assets/js/snippets.js | 42 +- site/assets/scss/_ads.scss | 6 +- site/assets/scss/_brand.scss | 4 +- site/assets/scss/_buttons.scss | 25 +- site/assets/scss/_callouts.scss | 9 +- site/assets/scss/_clipboard-js.scss | 8 +- site/assets/scss/_colors.scss | 1 - site/assets/scss/_component-examples.scss | 152 +- site/assets/scss/_content.scss | 95 +- site/assets/scss/_footer.scss | 4 +- site/assets/scss/_masthead.scss | 53 +- site/assets/scss/_navbar.scss | 64 +- site/assets/scss/_scrolling.scss | 13 + site/assets/scss/_search.scss | 33 +- site/assets/scss/_sidebar.scss | 19 +- site/assets/scss/_syntax.scss | 57 +- site/assets/scss/_toc.scss | 36 +- site/assets/scss/_variables.scss | 26 +- site/assets/scss/docs.scss | 8 +- site/content/docs/5.2/_index.html | 5 - .../docs/5.2/components/close-button.md | 38 - site/content/docs/5.2/components/progress.md | 154 - site/content/docs/5.2/customize/color.md | 151 - site/content/docs/5.2/examples/.stylelintrc | 15 - site/content/docs/5.2/examples/_index.md | 45 - .../5.2/examples/dashboard-rtl/index.html | 253 - .../docs/5.2/examples/dashboard/dashboard.css | 92 - .../5.2/examples/dashboard/dashboard.rtl.css | 88 - .../docs/5.2/examples/dashboard/index.html | 252 - .../docs/5.2/examples/dropdowns/index.html | 338 - site/content/docs/5.2/examples/grid/grid.css | 13 - .../docs/5.2/examples/list-groups/index.html | 222 - .../docs/5.2/examples/pricing/pricing.css | 11 - .../docs/5.2/examples/product/index.html | 148 - .../5.2/examples/starter-template/index.html | 52 - .../starter-template/starter-template.css | 9 - .../content/docs/5.2/helpers/colored-links.md | 21 - site/content/docs/5.2/utilities/overflow.md | 40 - site/content/docs/5.2/utilities/sizing.md | 60 - site/content/docs/5.3/_index.html | 5 + site/content/docs/{5.2 => 5.3}/about/brand.md | 20 +- .../docs/{5.2 => 5.3}/about/license.md | 6 +- .../docs/{5.2 => 5.3}/about/overview.md | 2 +- site/content/docs/{5.2 => 5.3}/about/team.md | 0 .../docs/{5.2 => 5.3}/about/translations.md | 0 .../docs/{5.2 => 5.3}/components/accordion.md | 44 +- .../docs/{5.2 => 5.3}/components/alerts.md | 35 +- .../docs/{5.2 => 5.3}/components/badge.md | 2 +- .../{5.2 => 5.3}/components/breadcrumb.md | 5 +- .../{5.2 => 5.3}/components/button-group.md | 38 +- .../docs/{5.2 => 5.3}/components/buttons.md | 20 +- .../docs/{5.2 => 5.3}/components/card.md | 38 +- .../docs/{5.2 => 5.3}/components/carousel.md | 179 +- .../docs/5.3/components/close-button.md | 54 + .../docs/{5.2 => 5.3}/components/collapse.md | 18 +- .../docs/{5.2 => 5.3}/components/dropdowns.md | 45 +- .../{5.2 => 5.3}/components/list-group.md | 32 +- .../docs/{5.2 => 5.3}/components/modal.md | 43 +- .../docs/{5.2 => 5.3}/components/navbar.md | 90 +- .../docs/{5.2 => 5.3}/components/navs-tabs.md | 31 +- .../docs/{5.2 => 5.3}/components/offcanvas.md | 16 +- .../{5.2 => 5.3}/components/pagination.md | 0 .../{5.2 => 5.3}/components/placeholders.md | 12 +- .../docs/{5.2 => 5.3}/components/popovers.md | 28 +- site/content/docs/5.3/components/progress.md | 202 + .../docs/{5.2 => 5.3}/components/scrollspy.md | 10 +- .../docs/{5.2 => 5.3}/components/spinners.md | 4 +- .../docs/{5.2 => 5.3}/components/toasts.md | 44 +- .../docs/{5.2 => 5.3}/components/tooltips.md | 31 +- .../docs/{5.2 => 5.3}/content/figures.md | 4 +- .../docs/{5.2 => 5.3}/content/images.md | 4 +- .../docs/{5.2 => 5.3}/content/reboot.md | 55 +- .../docs/{5.2 => 5.3}/content/tables.md | 18 +- .../docs/{5.2 => 5.3}/content/typography.md | 8 +- .../content/docs/5.3/customize/color-modes.md | 258 + site/content/docs/5.3/customize/color.md | 510 + .../docs/{5.2 => 5.3}/customize/components.md | 2 +- .../{5.2 => 5.3}/customize/css-variables.md | 35 +- .../docs/{5.2 => 5.3}/customize/optimize.md | 5 +- .../docs/{5.2 => 5.3}/customize/options.md | 1 + .../docs/{5.2 => 5.3}/customize/overview.md | 4 +- .../docs/{5.2 => 5.3}/customize/sass.md | 9 +- site/content/docs/5.3/docsref.md | 49 + site/content/docs/5.3/examples/_index.md | 66 + .../examples/album-rtl/index.html | 34 +- .../{5.2 => 5.3}/examples/album/index.html | 34 +- .../docs/5.3/examples/badges/badges.css | 3 + .../docs/5.3/examples/badges/index.html | 149 + .../{5.2 => 5.3}/examples/blog-rtl/index.html | 119 +- .../docs/{5.2 => 5.3}/examples/blog/blog.css | 32 +- .../{5.2 => 5.3}/examples/blog/blog.rtl.css | 32 +- .../{5.2 => 5.3}/examples/blog/index.html | 115 +- .../5.3/examples/breadcrumbs/breadcrumbs.css | 50 + .../docs/5.3/examples/breadcrumbs/index.html | 88 + .../docs/5.3/examples/buttons/index.html | 88 + .../examples/carousel-rtl/index.html | 30 +- .../examples/carousel/carousel.css | 2 +- .../examples/carousel/carousel.rtl.css | 2 +- .../{5.2 => 5.3}/examples/carousel/index.html | 30 +- .../examples/cheatsheet-rtl/index.html | 56 +- .../examples/cheatsheet/cheatsheet.css | 0 .../examples/cheatsheet/cheatsheet.js | 0 .../examples/cheatsheet/cheatsheet.rtl.css | 0 .../examples/cheatsheet/index.html | 56 +- .../examples/checkout-rtl/index.html | 30 +- .../examples/checkout/checkout.css} | 0 .../examples/checkout/checkout.js} | 0 .../{5.2 => 5.3}/examples/checkout/index.html | 28 +- .../{5.2 => 5.3}/examples/cover/cover.css | 6 +- .../{5.2 => 5.3}/examples/cover/index.html | 3 +- .../examples/dashboard-rtl/dashboard.js | 20 +- .../5.3/examples/dashboard-rtl/index.html | 333 + .../docs/5.3/examples/dashboard/dashboard.css | 48 + .../examples/dashboard/dashboard.js | 20 +- .../5.3/examples/dashboard/dashboard.rtl.css | 48 + .../docs/5.3/examples/dashboard/index.html | 332 + .../examples/dropdowns/dropdowns.css | 24 +- .../docs/5.3/examples/dropdowns/index.html | 462 + .../examples/features/features.css | 10 - .../{5.2 => 5.3}/examples/features/index.html | 147 +- .../examples/features/unsplash-photo-1.jpg | Bin .../examples/features/unsplash-photo-2.jpg | Bin .../examples/features/unsplash-photo-3.jpg | Bin .../{5.2 => 5.3}/examples/footers/index.html | 106 +- site/content/docs/5.3/examples/grid/grid.css | 13 + .../{5.2 => 5.3}/examples/grid/index.html | 1 - .../{5.2 => 5.3}/examples/headers/headers.css | 2 +- .../{5.2 => 5.3}/examples/headers/index.html | 56 +- .../examples/heroes/bootstrap-docs.png | Bin .../examples/heroes/bootstrap-themes.png | Bin .../{5.2 => 5.3}/examples/heroes/heroes.css | 0 .../{5.2 => 5.3}/examples/heroes/index.html | 16 +- .../examples/jumbotron/index.html | 9 +- .../docs/5.3/examples/jumbotrons/index.html | 82 + .../5.3/examples/jumbotrons/jumbotrons.css | 1 + .../docs/5.3/examples/list-groups/index.html | 225 + .../examples/list-groups/list-groups.css | 14 +- .../{5.2 => 5.3}/examples/masonry/index.html | 10 +- .../{5.2 => 5.3}/examples/modals/index.html | 49 +- .../{5.2 => 5.3}/examples/modals/modals.css | 7 - .../examples/navbar-bottom/index.html | 2 +- .../examples/navbar-fixed/index.html | 4 +- .../examples/navbar-fixed/navbar-fixed.css} | 0 .../examples/navbar-static/index.html | 4 +- .../examples/navbar-static/navbar-static.css} | 0 .../examples/navbars-offcanvas/index.html | 12 +- .../navbars-offcanvas/navbars-offcanvas.css} | 0 .../{5.2 => 5.3}/examples/navbars/index.html | 42 +- .../examples/navbars/navbars.css} | 0 .../examples/offcanvas-navbar/index.html | 20 +- .../offcanvas-navbar/offcanvas-navbar.css} | 0 .../offcanvas-navbar/offcanvas-navbar.js} | 0 .../{5.2 => 5.3}/examples/pricing/index.html | 23 +- .../docs/5.3/examples/pricing/pricing.css | 11 + .../docs/5.3/examples/product/index.html | 189 + .../{5.2 => 5.3}/examples/product/product.css | 5 + .../{5.2 => 5.3}/examples/sidebars/index.html | 107 +- .../examples/sidebars/sidebars.css | 14 +- .../examples/sidebars/sidebars.js | 0 .../{5.2 => 5.3}/examples/sign-in/index.html | 16 +- .../examples/sign-in/sign-in.css} | 10 +- .../5.3/examples/starter-template/index.html | 107 + .../examples/sticky-footer-navbar/index.html | 4 +- .../sticky-footer-navbar.css | 0 .../examples/sticky-footer/index.html | 5 +- .../examples/sticky-footer/sticky-footer.css | 0 .../docs/{5.2 => 5.3}/extend/approach.md | 6 +- .../content/docs/{5.2 => 5.3}/extend/icons.md | 0 .../docs/{5.2 => 5.3}/forms/checks-radios.md | 12 +- .../{5.2 => 5.3}/forms/floating-labels.md | 32 +- .../docs/{5.2 => 5.3}/forms/form-control.md | 42 +- .../docs/{5.2 => 5.3}/forms/input-group.md | 15 +- .../content/docs/{5.2 => 5.3}/forms/layout.md | 0 .../docs/{5.2 => 5.3}/forms/overview.md | 44 +- site/content/docs/{5.2 => 5.3}/forms/range.md | 4 +- .../content/docs/{5.2 => 5.3}/forms/select.md | 4 +- .../docs/{5.2 => 5.3}/forms/validation.md | 32 +- .../getting-started/accessibility.md | 0 .../getting-started/best-practices.md | 0 .../getting-started/browsers-devices.md | 6 +- .../{5.2 => 5.3}/getting-started/contents.md | 0 .../getting-started/contribute.md | 6 +- .../{5.2 => 5.3}/getting-started/download.md | 4 +- .../getting-started/introduction.md | 6 +- .../getting-started/javascript.md | 59 +- .../{5.2 => 5.3}/getting-started/parcel.md | 6 +- .../docs/{5.2 => 5.3}/getting-started/rfs.md | 0 .../docs/{5.2 => 5.3}/getting-started/rtl.md | 8 +- .../docs/{5.2 => 5.3}/getting-started/vite.md | 39 +- .../{5.2 => 5.3}/getting-started/webpack.md | 89 +- .../docs/{5.2 => 5.3}/helpers/clearfix.md | 2 +- .../{5.2 => 5.3}/helpers/color-background.md | 8 +- .../content/docs/5.3/helpers/colored-links.md | 43 + site/content/docs/5.3/helpers/focus-ring.md | 68 + site/content/docs/5.3/helpers/icon-link.md | 88 + .../docs/{5.2 => 5.3}/helpers/position.md | 0 .../docs/{5.2 => 5.3}/helpers/ratio.md | 2 +- .../docs/{5.2 => 5.3}/helpers/stacks.md | 34 +- .../{5.2 => 5.3}/helpers/stretched-link.md | 4 +- .../{5.2 => 5.3}/helpers/text-truncation.md | 0 .../{5.2 => 5.3}/helpers/vertical-rule.md | 8 +- .../{5.2 => 5.3}/helpers/visually-hidden.md | 2 +- .../docs/{5.2 => 5.3}/layout/breakpoints.md | 8 +- .../docs/{5.2 => 5.3}/layout/columns.md | 25 +- .../docs/{5.2 => 5.3}/layout/containers.md | 24 +- .../docs/{5.2 => 5.3}/layout/css-grid.md | 0 site/content/docs/{5.2 => 5.3}/layout/grid.md | 8 +- .../docs/{5.2 => 5.3}/layout/gutters.md | 56 +- .../docs/{5.2 => 5.3}/layout/utilities.md | 0 .../docs/{5.2 => 5.3}/layout/z-index.md | 0 site/content/docs/{5.2 => 5.3}/migration.md | 239 +- .../docs/{5.2 => 5.3}/utilities/api.md | 9 +- .../docs/{5.2 => 5.3}/utilities/background.md | 42 +- .../docs/{5.2 => 5.3}/utilities/borders.md | 30 +- .../docs/{5.2 => 5.3}/utilities/colors.md | 46 +- .../docs/{5.2 => 5.3}/utilities/display.md | 10 +- .../docs/{5.2 => 5.3}/utilities/flex.md | 100 +- .../docs/{5.2 => 5.3}/utilities/float.md | 4 +- .../{5.2 => 5.3}/utilities/interactions.md | 6 +- site/content/docs/5.3/utilities/link.md | 105 + site/content/docs/5.3/utilities/object-fit.md | 63 + .../docs/{5.2 => 5.3}/utilities/opacity.md | 4 +- site/content/docs/5.3/utilities/overflow.md | 99 + .../docs/{5.2 => 5.3}/utilities/position.md | 18 +- .../docs/{5.2 => 5.3}/utilities/shadows.md | 16 +- site/content/docs/5.3/utilities/sizing.md | 62 + .../docs/{5.2 => 5.3}/utilities/spacing.md | 49 +- .../docs/{5.2 => 5.3}/utilities/text.md | 17 +- .../{5.2 => 5.3}/utilities/vertical-align.md | 4 +- .../docs/{5.2 => 5.3}/utilities/visibility.md | 4 +- site/content/docs/5.3/utilities/z-index.md | 50 + site/content/docs/_index.html | 2 +- site/content/docs/versions.md | 8 +- site/data/core-team.yml | 15 +- site/data/docs-versions.yml | 65 +- site/data/examples.yml | 47 + site/data/sidebar.yml | 6 + site/data/theme-colors.yml | 1 + site/layouts/_default/baseof.html | 2 +- site/layouts/_default/docs.html | 22 +- site/layouts/_default/examples.html | 95 +- site/layouts/_default/home.html | 10 +- .../partials/callout-danger-async-methods.md | 5 - .../partials/callout-info-npm-starter.md | 1 - ...ut-warning-color-assistive-technologies.md | 3 - .../partials/callout-warning-input-support.md | 3 - .../partials/callouts/danger-async-methods.md | 1 + .../info-mediaqueries-breakpoints.md} | 0 .../partials/callouts/info-npm-starter.md | 1 + .../info-prefersreducedmotion.md} | 0 .../info-sanitizer.md} | 0 .../warning-color-assistive-technologies.md | 1 + .../warning-data-bs-title-vs-title.md} | 0 .../callouts/warning-input-support.md | 1 + site/layouts/partials/docs-navbar.html | 48 +- site/layouts/partials/docs-versions.html | 14 +- site/layouts/partials/footer.html | 10 +- site/layouts/partials/header.html | 3 + .../partials/home/components-utilities.html | 88 + site/layouts/partials/home/css-variables.html | 48 + site/layouts/partials/home/customize.html | 59 + site/layouts/partials/home/get-started.html | 58 + site/layouts/partials/home/icons.html | 23 + .../partials/home/masthead-followup.html | 356 - site/layouts/partials/home/masthead.html | 12 +- site/layouts/partials/home/plugins.html | 65 + site/layouts/partials/home/themes.html | 23 + site/layouts/partials/icons.html | 15 +- .../partials/icons/bootstrap-logo-solid.svg | 1 - site/layouts/partials/icons/bootstrap.svg | 1 - site/layouts/partials/icons/cloud-fill.svg | 3 - site/layouts/partials/icons/code.svg | 3 - site/layouts/partials/icons/collapse.svg | 4 - site/layouts/partials/icons/expand.svg | 4 - site/layouts/partials/icons/homepage-hero.svg | 1 - site/layouts/partials/icons/list.svg | 3 - site/layouts/partials/icons/menu.svg | 1 - site/layouts/partials/js-data-attributes.md | 2 + site/layouts/partials/social.html | 28 +- site/layouts/shortcodes/added-in.html | 2 +- .../callout-deprecated-dark-variants.html | 9 + site/layouts/shortcodes/deprecated-in.html | 5 + site/layouts/shortcodes/example.html | 14 +- site/layouts/shortcodes/js-dismiss.html | 4 +- site/layouts/shortcodes/js-docs.html | 64 + site/layouts/shortcodes/placeholder.html | 23 +- site/layouts/shortcodes/scss-docs.html | 35 +- .../assets/brand/bootstrap-social-logo.png | Bin 145590 -> 0 bytes .../assets/img/bootstrap-themes-collage.png | Bin 74829 -> 0 bytes .../5.2/assets/img/examples/album-rtl.png | Bin 6392 -> 0 bytes .../5.2/assets/img/examples/album-rtl@2x.png | Bin 15450 -> 0 bytes .../docs/5.2/assets/img/examples/album.png | Bin 10760 -> 0 bytes .../assets/img/examples/carousel-rtl@2x.png | Bin 24535 -> 0 bytes .../docs/5.2/assets/img/examples/carousel.png | Bin 13314 -> 0 bytes .../5.2/assets/img/examples/carousel@2x.png | Bin 31465 -> 0 bytes .../docs/5.2/assets/img/examples/footers.png | Bin 4324 -> 0 bytes .../docs/5.2/assets/img/examples/grid.png | Bin 14485 -> 0 bytes .../docs/5.2/assets/img/examples/grid@2x.png | Bin 34834 -> 0 bytes .../docs/5.2/assets/img/examples/headers.png | Bin 5197 -> 0 bytes .../5.2/assets/img/examples/masonry@2x.png | Bin 37733 -> 0 bytes .../5.2/assets/img/examples/navbar-bottom.png | Bin 4873 -> 0 bytes .../5.2/assets/img/examples/navbar-fixed.png | Bin 5911 -> 0 bytes .../assets/img/examples/navbar-fixed@2x.png | Bin 14103 -> 0 bytes .../5.2/assets/img/examples/navbar-static.png | Bin 6624 -> 0 bytes .../assets/img/examples/navbars-offcanvas.png | Bin 6864 -> 0 bytes .../img/examples/navbars-offcanvas@2x.png | Bin 17070 -> 0 bytes .../docs/5.2/assets/img/examples/navbars.png | Bin 13124 -> 0 bytes .../img/examples/sticky-footer-navbar.png | Bin 6979 -> 0 bytes .../img/examples/sticky-footer-navbar@2x.png | Bin 15836 -> 0 bytes .../5.2/assets/img/examples/sticky-footer.png | Bin 4280 -> 0 bytes .../5.2/assets/img/favicons/favicon-32x32.png | Bin 1159 -> 0 bytes .../guides/parcel-dev-server-bootstrap.png | Bin 102674 -> 0 bytes .../assets/img/guides/parcel-dev-server.png | Bin 75744 -> 0 bytes .../img/guides/vite-dev-server-bootstrap.png | Bin 75894 -> 0 bytes .../5.2/assets/img/guides/vite-dev-server.png | Bin 74851 -> 0 bytes .../guides/webpack-dev-server-bootstrap.png | Bin 77318 -> 0 bytes .../assets/img/guides/webpack-dev-server.png | Bin 76154 -> 0 bytes site/static/docs/5.2/assets/img/parcel.png | Bin 6042 -> 0 bytes .../assets/brand/bootstrap-logo-black.svg | 0 .../assets/brand/bootstrap-logo-shadow.png | Bin .../assets/brand/bootstrap-logo-white.svg | 0 .../assets/brand/bootstrap-logo.svg | 0 .../assets/brand/bootstrap-social.png | Bin .../assets/img/bootstrap-icons.png | Bin .../assets/img/bootstrap-icons@2x.png | Bin .../assets/img/bootstrap-themes-collage.png | Bin 0 -> 74442 bytes .../img/bootstrap-themes-collage@2x.png | Bin .../assets/img/bootstrap-themes.png | Bin .../assets/img/bootstrap-themes@2x.png | Bin .../5.3/assets/img/examples/album-rtl.png | Bin 0 -> 6391 bytes .../5.3/assets/img/examples/album-rtl@2x.png | Bin 0 -> 15347 bytes .../docs/5.3/assets/img/examples/album.png | Bin 0 -> 10678 bytes .../assets/img/examples/album@2x.png | Bin .../docs/5.3/assets/img/examples/badges.png | Bin 0 -> 6328 bytes .../5.3/assets/img/examples/badges@2x.png | Bin 0 -> 14798 bytes .../assets/img/examples/blog-rtl.png | Bin .../assets/img/examples/blog-rtl@2x.png | Bin .../{5.2 => 5.3}/assets/img/examples/blog.png | Bin .../assets/img/examples/blog@2x.png | Bin .../5.3/assets/img/examples/breadcrumbs.png | Bin 0 -> 2382 bytes .../assets/img/examples/breadcrumbs@2x.png | Bin 0 -> 6028 bytes .../docs/5.3/assets/img/examples/buttons.png | Bin 0 -> 4545 bytes .../5.3/assets/img/examples/buttons@2x.png | Bin 0 -> 9994 bytes .../assets/img/examples/carousel-rtl.png | Bin .../assets/img/examples/carousel-rtl@2x.png | Bin 0 -> 24460 bytes .../docs/5.3/assets/img/examples/carousel.png | Bin 0 -> 13219 bytes .../5.3/assets/img/examples/carousel@2x.png | Bin 0 -> 31320 bytes .../assets/img/examples/cheatsheet-rtl.png | Bin .../assets/img/examples/cheatsheet-rtl@2x.png | Bin .../assets/img/examples/cheatsheet.png | Bin .../assets/img/examples/cheatsheet@2x.png | Bin .../assets/img/examples/checkout-rtl.png | Bin .../assets/img/examples/checkout-rtl@2x.png | Bin .../assets/img/examples/checkout.png | Bin .../assets/img/examples/checkout@2x.png | Bin .../assets/img/examples/cover.png | Bin .../assets/img/examples/cover@2x.png | Bin .../assets/img/examples/dashboard-rtl.png | Bin .../assets/img/examples/dashboard-rtl@2x.png | Bin .../assets/img/examples/dashboard.png | Bin .../assets/img/examples/dashboard@2x.png | Bin .../assets/img/examples/dropdowns.png | Bin .../assets/img/examples/dropdowns@2x.png | Bin .../assets/img/examples/features.png | Bin .../assets/img/examples/features@2x.png | Bin .../docs/5.3/assets/img/examples/footers.png | Bin 0 -> 4297 bytes .../assets/img/examples/footers@2x.png | Bin .../docs/5.3/assets/img/examples/grid.png | Bin 0 -> 9395 bytes .../docs/5.3/assets/img/examples/grid@2x.png | Bin 0 -> 24996 bytes .../docs/5.3/assets/img/examples/headers.png | Bin 0 -> 5196 bytes .../assets/img/examples/headers@2x.png | Bin .../assets/img/examples/heroes.png | Bin .../assets/img/examples/heroes@2x.png | Bin .../assets/img/examples/jumbotron.png | Bin .../assets/img/examples/jumbotron@2x.png | Bin .../5.3/assets/img/examples/jumbotrons.png | Bin 0 -> 6463 bytes .../5.3/assets/img/examples/jumbotrons@2x.png | Bin 0 -> 13933 bytes .../assets/img/examples/list-groups.png | Bin .../assets/img/examples/list-groups@2x.png | Bin .../assets/img/examples/masonry.png | Bin .../5.3/assets/img/examples/masonry@2x.png | Bin 0 -> 37705 bytes .../assets/img/examples/modals.png | Bin .../assets/img/examples/modals@2x.png | Bin .../5.3/assets/img/examples/navbar-bottom.png | Bin 0 -> 4819 bytes .../assets/img/examples/navbar-bottom@2x.png | Bin .../5.3/assets/img/examples/navbar-fixed.png | Bin 0 -> 5876 bytes .../assets/img/examples/navbar-fixed@2x.png | Bin 0 -> 13979 bytes .../5.3/assets/img/examples/navbar-static.png | Bin 0 -> 6541 bytes .../assets/img/examples/navbar-static@2x.png | Bin .../assets/img/examples/navbars-offcanvas.png | Bin 0 -> 6850 bytes .../img/examples/navbars-offcanvas@2x.png | Bin 0 -> 16965 bytes .../docs/5.3/assets/img/examples/navbars.png | Bin 0 -> 12965 bytes .../assets/img/examples/navbars@2x.png | Bin .../assets/img/examples/offcanvas-navbar.png | Bin .../img/examples/offcanvas-navbar@2x.png | Bin .../assets/img/examples/pricing.png | Bin .../assets/img/examples/pricing@2x.png | Bin .../assets/img/examples/product.png | Bin .../assets/img/examples/product@2x.png | Bin .../assets/img/examples/sidebars.png | Bin .../assets/img/examples/sidebars@2x.png | Bin .../assets/img/examples/sign-in.png | Bin .../assets/img/examples/sign-in@2x.png | Bin .../assets/img/examples/starter-template.png | Bin .../img/examples/starter-template@2x.png | Bin .../img/examples/sticky-footer-navbar.png | Bin 0 -> 6966 bytes .../img/examples/sticky-footer-navbar@2x.png | Bin 0 -> 15744 bytes .../5.3/assets/img/examples/sticky-footer.png | Bin 0 -> 4279 bytes .../assets/img/examples/sticky-footer@2x.png | Bin .../img/favicons/android-chrome-192x192.png | Bin .../img/favicons/android-chrome-512x512.png | Bin .../assets/img/favicons/apple-touch-icon.png | Bin .../assets/img/favicons/favicon-16x16.png | Bin .../5.3/assets/img/favicons/favicon-32x32.png | Bin 0 -> 1156 bytes .../assets/img/favicons/favicon.ico | Bin .../assets/img/favicons/manifest.json | 0 .../assets/img/favicons/safari-pinned-tab.svg | 0 .../assets/img/guides/bootstrap-parcel.png | Bin .../assets/img/guides/bootstrap-parcel@2x.png | Bin .../assets/img/guides/bootstrap-vite.png | Bin .../assets/img/guides/bootstrap-vite@2x.png | Bin .../assets/img/guides/bootstrap-webpack.png | Bin .../img/guides/bootstrap-webpack@2x.png | Bin .../guides/parcel-dev-server-bootstrap.png | Bin 0 -> 15565 bytes .../assets/img/guides/parcel-dev-server.png | Bin 0 -> 13933 bytes .../img/guides/vite-dev-server-bootstrap.png | Bin 0 -> 14001 bytes .../5.3/assets/img/guides/vite-dev-server.png | Bin 0 -> 13584 bytes .../guides/webpack-dev-server-bootstrap.png | Bin 0 -> 14702 bytes .../assets/img/guides/webpack-dev-server.png | Bin 0 -> 14197 bytes site/static/docs/5.3/assets/img/parcel.png | Bin 0 -> 6003 bytes .../docs/{5.2 => 5.3}/assets/img/vite.svg | 0 .../docs/{5.2 => 5.3}/assets/img/webpack.svg | 0 site/static/docs/5.3/assets/js/color-modes.js | 80 + .../{5.2 => 5.3}/assets/js/validate-forms.js | 0 605 files changed, 15320 insertions(+), 9495 deletions(-) create mode 100644 .github/codeql/codeql-config.yml create mode 100644 .npmrc delete mode 100644 .stylelintrc create mode 100644 .stylelintrc.json delete mode 100644 build/.eslintrc.json rename config.yml => hugo.yml (69%) delete mode 100644 js/tests/unit/.eslintrc.json delete mode 100644 js/tests/visual/.eslintrc.json create mode 100644 js/tests/visual/input.html create mode 100644 scss/_variables-dark.scss create mode 100644 scss/helpers/_focus-ring.scss create mode 100644 scss/helpers/_icon-link.scss create mode 100644 scss/mixins/_color-mode.scss create mode 100644 scss/tests/jasmine.js create mode 100644 scss/tests/mixins/_color-modes.test.scss create mode 100644 scss/tests/mixins/_media-query-color-mode-full.test.scss create mode 100644 scss/tests/mixins/_utilities.test.scss create mode 100644 scss/tests/sass-true/register.js create mode 100644 scss/tests/sass-true/runner.js create mode 100644 scss/tests/utilities/_api.test.scss delete mode 100644 site/.eslintrc.json create mode 100644 site/assets/scss/_scrolling.scss delete mode 100644 site/content/docs/5.2/_index.html delete mode 100644 site/content/docs/5.2/components/close-button.md delete mode 100644 site/content/docs/5.2/components/progress.md delete mode 100644 site/content/docs/5.2/customize/color.md delete mode 100644 site/content/docs/5.2/examples/.stylelintrc delete mode 100644 site/content/docs/5.2/examples/_index.md delete mode 100644 site/content/docs/5.2/examples/dashboard-rtl/index.html delete mode 100644 site/content/docs/5.2/examples/dashboard/dashboard.css delete mode 100644 site/content/docs/5.2/examples/dashboard/dashboard.rtl.css delete mode 100644 site/content/docs/5.2/examples/dashboard/index.html delete mode 100644 site/content/docs/5.2/examples/dropdowns/index.html delete mode 100644 site/content/docs/5.2/examples/grid/grid.css delete mode 100644 site/content/docs/5.2/examples/list-groups/index.html delete mode 100644 site/content/docs/5.2/examples/pricing/pricing.css delete mode 100644 site/content/docs/5.2/examples/product/index.html delete mode 100644 site/content/docs/5.2/examples/starter-template/index.html delete mode 100644 site/content/docs/5.2/examples/starter-template/starter-template.css delete mode 100644 site/content/docs/5.2/helpers/colored-links.md delete mode 100644 site/content/docs/5.2/utilities/overflow.md delete mode 100644 site/content/docs/5.2/utilities/sizing.md create mode 100644 site/content/docs/5.3/_index.html rename site/content/docs/{5.2 => 5.3}/about/brand.md (69%) rename site/content/docs/{5.2 => 5.3}/about/license.md (80%) rename site/content/docs/{5.2 => 5.3}/about/overview.md (99%) rename site/content/docs/{5.2 => 5.3}/about/team.md (100%) rename site/content/docs/{5.2 => 5.3}/about/translations.md (100%) rename site/content/docs/{5.2 => 5.3}/components/accordion.md (85%) rename site/content/docs/{5.2 => 5.3}/components/alerts.md (93%) rename site/content/docs/{5.2 => 5.3}/components/badge.md (98%) rename site/content/docs/{5.2 => 5.3}/components/breadcrumb.md (90%) rename site/content/docs/{5.2 => 5.3}/components/button-group.md (85%) rename site/content/docs/{5.2 => 5.3}/components/buttons.md (93%) rename site/content/docs/{5.2 => 5.3}/components/card.md (95%) rename site/content/docs/{5.2 => 5.3}/components/carousel.md (70%) create mode 100644 site/content/docs/5.3/components/close-button.md rename site/content/docs/{5.2 => 5.3}/components/collapse.md (89%) rename site/content/docs/{5.2 => 5.3}/components/dropdowns.md (96%) rename site/content/docs/{5.2 => 5.3}/components/list-group.md (94%) rename site/content/docs/{5.2 => 5.3}/components/modal.md (95%) rename site/content/docs/{5.2 => 5.3}/components/navbar.md (91%) rename site/content/docs/{5.2 => 5.3}/components/navs-tabs.md (98%) rename site/content/docs/{5.2 => 5.3}/components/offcanvas.md (95%) rename site/content/docs/{5.2 => 5.3}/components/pagination.md (100%) rename site/content/docs/{5.2 => 5.3}/components/placeholders.md (93%) rename site/content/docs/{5.2 => 5.3}/components/popovers.md (84%) create mode 100644 site/content/docs/5.3/components/progress.md rename site/content/docs/{5.2 => 5.3}/components/scrollspy.md (97%) rename site/content/docs/{5.2 => 5.3}/components/spinners.md (97%) rename site/content/docs/{5.2 => 5.3}/components/toasts.md (94%) rename site/content/docs/{5.2 => 5.3}/components/tooltips.md (89%) rename site/content/docs/{5.2 => 5.3}/content/figures.md (97%) rename site/content/docs/{5.2 => 5.3}/content/images.md (98%) rename site/content/docs/{5.2 => 5.3}/content/reboot.md (90%) rename site/content/docs/{5.2 => 5.3}/content/tables.md (95%) rename site/content/docs/{5.2 => 5.3}/content/typography.md (98%) create mode 100644 site/content/docs/5.3/customize/color-modes.md create mode 100644 site/content/docs/5.3/customize/color.md rename site/content/docs/{5.2 => 5.3}/customize/components.md (96%) rename site/content/docs/{5.2 => 5.3}/customize/css-variables.md (72%) rename site/content/docs/{5.2 => 5.3}/customize/optimize.md (97%) rename site/content/docs/{5.2 => 5.3}/customize/options.md (94%) rename site/content/docs/{5.2 => 5.3}/customize/overview.md (95%) rename site/content/docs/{5.2 => 5.3}/customize/sass.md (96%) create mode 100644 site/content/docs/5.3/docsref.md create mode 100644 site/content/docs/5.3/examples/_index.md rename site/content/docs/{5.2 => 5.3}/examples/album-rtl/index.html (86%) rename site/content/docs/{5.2 => 5.3}/examples/album/index.html (87%) create mode 100644 site/content/docs/5.3/examples/badges/badges.css create mode 100644 site/content/docs/5.3/examples/badges/index.html rename site/content/docs/{5.2 => 5.3}/examples/blog-rtl/index.html (76%) rename site/content/docs/{5.2 => 5.3}/examples/blog/blog.css (59%) rename site/content/docs/{5.2 => 5.3}/examples/blog/blog.rtl.css (54%) rename site/content/docs/{5.2 => 5.3}/examples/blog/index.html (69%) create mode 100644 site/content/docs/5.3/examples/breadcrumbs/breadcrumbs.css create mode 100644 site/content/docs/5.3/examples/breadcrumbs/index.html create mode 100644 site/content/docs/5.3/examples/buttons/index.html rename site/content/docs/{5.2 => 5.3}/examples/carousel-rtl/index.html (81%) rename site/content/docs/{5.2 => 5.3}/examples/carousel/carousel.css (97%) rename site/content/docs/{5.2 => 5.3}/examples/carousel/carousel.rtl.css (96%) rename site/content/docs/{5.2 => 5.3}/examples/carousel/index.html (77%) rename site/content/docs/{5.2 => 5.3}/examples/cheatsheet-rtl/index.html (97%) rename site/content/docs/{5.2 => 5.3}/examples/cheatsheet/cheatsheet.css (100%) rename site/content/docs/{5.2 => 5.3}/examples/cheatsheet/cheatsheet.js (100%) rename site/content/docs/{5.2 => 5.3}/examples/cheatsheet/cheatsheet.rtl.css (100%) rename site/content/docs/{5.2 => 5.3}/examples/cheatsheet/index.html (97%) rename site/content/docs/{5.2 => 5.3}/examples/checkout-rtl/index.html (90%) rename site/content/docs/{5.2/examples/checkout/form-validation.css => 5.3/examples/checkout/checkout.css} (100%) rename site/content/docs/{5.2/examples/checkout/form-validation.js => 5.3/examples/checkout/checkout.js} (100%) rename site/content/docs/{5.2 => 5.3}/examples/checkout/index.html (91%) rename site/content/docs/{5.2 => 5.3}/examples/cover/cover.css (91%) rename site/content/docs/{5.2 => 5.3}/examples/cover/index.html (90%) rename site/content/docs/{5.2 => 5.3}/examples/dashboard-rtl/dashboard.js (76%) create mode 100644 site/content/docs/5.3/examples/dashboard-rtl/index.html create mode 100644 site/content/docs/5.3/examples/dashboard/dashboard.css rename site/content/docs/{5.2 => 5.3}/examples/dashboard/dashboard.js (75%) create mode 100644 site/content/docs/5.3/examples/dashboard/dashboard.rtl.css create mode 100644 site/content/docs/5.3/examples/dashboard/index.html rename site/content/docs/{5.2 => 5.3}/examples/dropdowns/dropdowns.css (73%) create mode 100644 site/content/docs/5.3/examples/dropdowns/index.html rename site/content/docs/{5.2 => 5.3}/examples/features/features.css (73%) rename site/content/docs/{5.2 => 5.3}/examples/features/index.html (72%) rename site/content/docs/{5.2 => 5.3}/examples/features/unsplash-photo-1.jpg (100%) rename site/content/docs/{5.2 => 5.3}/examples/features/unsplash-photo-2.jpg (100%) rename site/content/docs/{5.2 => 5.3}/examples/features/unsplash-photo-3.jpg (100%) rename site/content/docs/{5.2 => 5.3}/examples/footers/index.html (75%) create mode 100644 site/content/docs/5.3/examples/grid/grid.css rename site/content/docs/{5.2 => 5.3}/examples/grid/index.html (99%) rename site/content/docs/{5.2 => 5.3}/examples/headers/headers.css (87%) rename site/content/docs/{5.2 => 5.3}/examples/headers/index.html (85%) rename site/content/docs/{5.2 => 5.3}/examples/heroes/bootstrap-docs.png (100%) rename site/content/docs/{5.2 => 5.3}/examples/heroes/bootstrap-themes.png (100%) rename site/content/docs/{5.2 => 5.3}/examples/heroes/heroes.css (100%) rename site/content/docs/{5.2 => 5.3}/examples/heroes/index.html (89%) rename site/content/docs/{5.2 => 5.3}/examples/jumbotron/index.html (90%) create mode 100644 site/content/docs/5.3/examples/jumbotrons/index.html create mode 100644 site/content/docs/5.3/examples/jumbotrons/jumbotrons.css create mode 100644 site/content/docs/5.3/examples/list-groups/index.html rename site/content/docs/{5.2 => 5.3}/examples/list-groups/list-groups.css (80%) rename site/content/docs/{5.2 => 5.3}/examples/masonry/index.html (90%) rename site/content/docs/{5.2 => 5.3}/examples/modals/index.html (63%) rename site/content/docs/{5.2 => 5.3}/examples/modals/modals.css (60%) rename site/content/docs/{5.2 => 5.3}/examples/navbar-bottom/index.html (97%) rename site/content/docs/{5.2 => 5.3}/examples/navbar-fixed/index.html (95%) rename site/content/docs/{5.2/examples/navbar-fixed/navbar-top-fixed.css => 5.3/examples/navbar-fixed/navbar-fixed.css} (100%) rename site/content/docs/{5.2 => 5.3}/examples/navbar-static/index.html (95%) rename site/content/docs/{5.2/examples/navbar-static/navbar-top.css => 5.3/examples/navbar-static/navbar-static.css} (100%) rename site/content/docs/{5.2 => 5.3}/examples/navbars-offcanvas/index.html (95%) rename site/content/docs/{5.2/examples/navbars-offcanvas/navbar.css => 5.3/examples/navbars-offcanvas/navbars-offcanvas.css} (100%) rename site/content/docs/{5.2 => 5.3}/examples/navbars/index.html (90%) rename site/content/docs/{5.2/examples/navbars/navbar.css => 5.3/examples/navbars/navbars.css} (100%) rename site/content/docs/{5.2 => 5.3}/examples/offcanvas-navbar/index.html (92%) rename site/content/docs/{5.2/examples/offcanvas-navbar/offcanvas.css => 5.3/examples/offcanvas-navbar/offcanvas-navbar.css} (100%) rename site/content/docs/{5.2/examples/offcanvas-navbar/offcanvas.js => 5.3/examples/offcanvas-navbar/offcanvas-navbar.js} (100%) rename site/content/docs/{5.2 => 5.3}/examples/pricing/index.html (89%) create mode 100644 site/content/docs/5.3/examples/pricing/pricing.css create mode 100644 site/content/docs/5.3/examples/product/index.html rename site/content/docs/{5.2 => 5.3}/examples/product/product.css (95%) rename site/content/docs/{5.2 => 5.3}/examples/sidebars/index.html (66%) rename site/content/docs/{5.2 => 5.3}/examples/sidebars/sidebars.css (66%) rename site/content/docs/{5.2 => 5.3}/examples/sidebars/sidebars.js (100%) rename site/content/docs/{5.2 => 5.3}/examples/sign-in/index.html (61%) rename site/content/docs/{5.2/examples/sign-in/signin.css => 5.3/examples/sign-in/sign-in.css} (72%) create mode 100644 site/content/docs/5.3/examples/starter-template/index.html rename site/content/docs/{5.2 => 5.3}/examples/sticky-footer-navbar/index.html (93%) rename site/content/docs/{5.2 => 5.3}/examples/sticky-footer-navbar/sticky-footer-navbar.css (100%) rename site/content/docs/{5.2 => 5.3}/examples/sticky-footer/index.html (81%) rename site/content/docs/{5.2 => 5.3}/examples/sticky-footer/sticky-footer.css (100%) rename site/content/docs/{5.2 => 5.3}/extend/approach.md (97%) rename site/content/docs/{5.2 => 5.3}/extend/icons.md (100%) rename site/content/docs/{5.2 => 5.3}/forms/checks-radios.md (98%) rename site/content/docs/{5.2 => 5.3}/forms/floating-labels.md (81%) rename site/content/docs/{5.2 => 5.3}/forms/form-control.md (76%) rename site/content/docs/{5.2 => 5.3}/forms/input-group.md (96%) rename site/content/docs/{5.2 => 5.3}/forms/layout.md (100%) rename site/content/docs/{5.2 => 5.3}/forms/overview.md (79%) rename site/content/docs/{5.2 => 5.3}/forms/range.md (98%) rename site/content/docs/{5.2 => 5.3}/forms/select.md (98%) rename site/content/docs/{5.2 => 5.3}/forms/validation.md (94%) rename site/content/docs/{5.2 => 5.3}/getting-started/accessibility.md (100%) rename site/content/docs/{5.2 => 5.3}/getting-started/best-practices.md (100%) rename site/content/docs/{5.2 => 5.3}/getting-started/browsers-devices.md (96%) rename site/content/docs/{5.2 => 5.3}/getting-started/contents.md (100%) rename site/content/docs/{5.2 => 5.3}/getting-started/contribute.md (96%) rename site/content/docs/{5.2 => 5.3}/getting-started/download.md (98%) rename site/content/docs/{5.2 => 5.3}/getting-started/introduction.md (98%) rename site/content/docs/{5.2 => 5.3}/getting-started/javascript.md (92%) rename site/content/docs/{5.2 => 5.3}/getting-started/parcel.md (98%) rename site/content/docs/{5.2 => 5.3}/getting-started/rfs.md (100%) rename site/content/docs/{5.2 => 5.3}/getting-started/rtl.md (94%) rename site/content/docs/{5.2 => 5.3}/getting-started/vite.md (87%) rename site/content/docs/{5.2 => 5.3}/getting-started/webpack.md (82%) rename site/content/docs/{5.2 => 5.3}/helpers/clearfix.md (96%) rename site/content/docs/{5.2 => 5.3}/helpers/color-background.md (93%) create mode 100644 site/content/docs/5.3/helpers/colored-links.md create mode 100644 site/content/docs/5.3/helpers/focus-ring.md create mode 100644 site/content/docs/5.3/helpers/icon-link.md rename site/content/docs/{5.2 => 5.3}/helpers/position.md (100%) rename site/content/docs/{5.2 => 5.3}/helpers/ratio.md (99%) rename site/content/docs/{5.2 => 5.3}/helpers/stacks.md (76%) rename site/content/docs/{5.2 => 5.3}/helpers/stretched-link.md (96%) rename site/content/docs/{5.2 => 5.3}/helpers/text-truncation.md (100%) rename site/content/docs/{5.2 => 5.3}/helpers/vertical-rule.md (84%) rename site/content/docs/{5.2 => 5.3}/helpers/visually-hidden.md (96%) rename site/content/docs/{5.2 => 5.3}/layout/breakpoints.md (96%) rename site/content/docs/{5.2 => 5.3}/layout/columns.md (93%) rename site/content/docs/{5.2 => 5.3}/layout/containers.md (69%) rename site/content/docs/{5.2 => 5.3}/layout/css-grid.md (100%) rename site/content/docs/{5.2 => 5.3}/layout/grid.md (98%) rename site/content/docs/{5.2 => 5.3}/layout/gutters.md (71%) rename site/content/docs/{5.2 => 5.3}/layout/utilities.md (100%) rename site/content/docs/{5.2 => 5.3}/layout/z-index.md (100%) rename site/content/docs/{5.2 => 5.3}/migration.md (69%) rename site/content/docs/{5.2 => 5.3}/utilities/api.md (98%) rename site/content/docs/{5.2 => 5.3}/utilities/background.md (77%) rename site/content/docs/{5.2 => 5.3}/utilities/borders.md (80%) rename site/content/docs/{5.2 => 5.3}/utilities/colors.md (72%) rename site/content/docs/{5.2 => 5.3}/utilities/display.md (96%) rename site/content/docs/{5.2 => 5.3}/utilities/flex.md (90%) rename site/content/docs/{5.2 => 5.3}/utilities/float.md (98%) rename site/content/docs/{5.2 => 5.3}/utilities/interactions.md (95%) create mode 100644 site/content/docs/5.3/utilities/link.md create mode 100644 site/content/docs/5.3/utilities/object-fit.md rename site/content/docs/{5.2 => 5.3}/utilities/opacity.md (97%) create mode 100644 site/content/docs/5.3/utilities/overflow.md rename site/content/docs/{5.2 => 5.3}/utilities/position.md (90%) rename site/content/docs/{5.2 => 5.3}/utilities/shadows.md (66%) create mode 100644 site/content/docs/5.3/utilities/sizing.md rename site/content/docs/{5.2 => 5.3}/utilities/spacing.md (72%) rename site/content/docs/{5.2 => 5.3}/utilities/text.md (95%) rename site/content/docs/{5.2 => 5.3}/utilities/vertical-align.md (98%) rename site/content/docs/{5.2 => 5.3}/utilities/visibility.md (97%) create mode 100644 site/content/docs/5.3/utilities/z-index.md delete mode 100644 site/layouts/partials/callout-danger-async-methods.md delete mode 100644 site/layouts/partials/callout-info-npm-starter.md delete mode 100644 site/layouts/partials/callout-warning-color-assistive-technologies.md delete mode 100644 site/layouts/partials/callout-warning-input-support.md create mode 100644 site/layouts/partials/callouts/danger-async-methods.md rename site/layouts/partials/{callout-info-mediaqueries-breakpoints.md => callouts/info-mediaqueries-breakpoints.md} (100%) create mode 100644 site/layouts/partials/callouts/info-npm-starter.md rename site/layouts/partials/{callout-info-prefersreducedmotion.md => callouts/info-prefersreducedmotion.md} (100%) rename site/layouts/partials/{callout-info-sanitizer.md => callouts/info-sanitizer.md} (100%) create mode 100644 site/layouts/partials/callouts/warning-color-assistive-technologies.md rename site/layouts/partials/{callout-warning-data-bs-title-vs-title.md => callouts/warning-data-bs-title-vs-title.md} (100%) create mode 100644 site/layouts/partials/callouts/warning-input-support.md create mode 100644 site/layouts/partials/home/components-utilities.html create mode 100644 site/layouts/partials/home/css-variables.html create mode 100644 site/layouts/partials/home/customize.html create mode 100644 site/layouts/partials/home/get-started.html create mode 100644 site/layouts/partials/home/icons.html delete mode 100644 site/layouts/partials/home/masthead-followup.html create mode 100644 site/layouts/partials/home/plugins.html create mode 100644 site/layouts/partials/home/themes.html delete mode 100644 site/layouts/partials/icons/bootstrap-logo-solid.svg delete mode 100644 site/layouts/partials/icons/bootstrap.svg delete mode 100644 site/layouts/partials/icons/cloud-fill.svg delete mode 100644 site/layouts/partials/icons/code.svg delete mode 100644 site/layouts/partials/icons/collapse.svg delete mode 100644 site/layouts/partials/icons/expand.svg delete mode 100644 site/layouts/partials/icons/homepage-hero.svg delete mode 100644 site/layouts/partials/icons/list.svg delete mode 100644 site/layouts/partials/icons/menu.svg create mode 100644 site/layouts/shortcodes/callout-deprecated-dark-variants.html create mode 100644 site/layouts/shortcodes/deprecated-in.html create mode 100644 site/layouts/shortcodes/js-docs.html delete mode 100644 site/static/docs/5.2/assets/brand/bootstrap-social-logo.png delete mode 100644 site/static/docs/5.2/assets/img/bootstrap-themes-collage.png delete mode 100644 site/static/docs/5.2/assets/img/examples/album-rtl.png delete mode 100644 site/static/docs/5.2/assets/img/examples/album-rtl@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/album.png delete mode 100644 site/static/docs/5.2/assets/img/examples/carousel-rtl@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/carousel.png delete mode 100644 site/static/docs/5.2/assets/img/examples/carousel@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/footers.png delete mode 100644 site/static/docs/5.2/assets/img/examples/grid.png delete mode 100644 site/static/docs/5.2/assets/img/examples/grid@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/headers.png delete mode 100644 site/static/docs/5.2/assets/img/examples/masonry@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbar-bottom.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbar-fixed.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbar-fixed@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbar-static.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbars-offcanvas.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbars-offcanvas@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/navbars.png delete mode 100644 site/static/docs/5.2/assets/img/examples/sticky-footer-navbar.png delete mode 100644 site/static/docs/5.2/assets/img/examples/sticky-footer-navbar@2x.png delete mode 100644 site/static/docs/5.2/assets/img/examples/sticky-footer.png delete mode 100644 site/static/docs/5.2/assets/img/favicons/favicon-32x32.png delete mode 100644 site/static/docs/5.2/assets/img/guides/parcel-dev-server-bootstrap.png delete mode 100644 site/static/docs/5.2/assets/img/guides/parcel-dev-server.png delete mode 100644 site/static/docs/5.2/assets/img/guides/vite-dev-server-bootstrap.png delete mode 100644 site/static/docs/5.2/assets/img/guides/vite-dev-server.png delete mode 100644 site/static/docs/5.2/assets/img/guides/webpack-dev-server-bootstrap.png delete mode 100644 site/static/docs/5.2/assets/img/guides/webpack-dev-server.png delete mode 100644 site/static/docs/5.2/assets/img/parcel.png rename site/static/docs/{5.2 => 5.3}/assets/brand/bootstrap-logo-black.svg (100%) rename site/static/docs/{5.2 => 5.3}/assets/brand/bootstrap-logo-shadow.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/brand/bootstrap-logo-white.svg (100%) rename site/static/docs/{5.2 => 5.3}/assets/brand/bootstrap-logo.svg (100%) rename site/static/docs/{5.2 => 5.3}/assets/brand/bootstrap-social.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/bootstrap-icons.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/bootstrap-icons@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/bootstrap-themes-collage.png rename site/static/docs/{5.2 => 5.3}/assets/img/bootstrap-themes-collage@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/bootstrap-themes.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/bootstrap-themes@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/album-rtl.png create mode 100644 site/static/docs/5.3/assets/img/examples/album-rtl@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/album.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/album@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/badges.png create mode 100644 site/static/docs/5.3/assets/img/examples/badges@2x.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/blog-rtl.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/blog-rtl@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/blog.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/blog@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/breadcrumbs.png create mode 100644 site/static/docs/5.3/assets/img/examples/breadcrumbs@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/buttons.png create mode 100644 site/static/docs/5.3/assets/img/examples/buttons@2x.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/carousel-rtl.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/carousel-rtl@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/carousel.png create mode 100644 site/static/docs/5.3/assets/img/examples/carousel@2x.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cheatsheet-rtl.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cheatsheet-rtl@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cheatsheet.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cheatsheet@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/checkout-rtl.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/checkout-rtl@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/checkout.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/checkout@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cover.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/cover@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dashboard-rtl.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dashboard-rtl@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dashboard.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dashboard@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dropdowns.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/dropdowns@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/features.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/features@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/footers.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/footers@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/grid.png create mode 100644 site/static/docs/5.3/assets/img/examples/grid@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/headers.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/headers@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/heroes.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/heroes@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/jumbotron.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/jumbotron@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/jumbotrons.png create mode 100644 site/static/docs/5.3/assets/img/examples/jumbotrons@2x.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/list-groups.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/list-groups@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/masonry.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/masonry@2x.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/modals.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/modals@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/navbar-bottom.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/navbar-bottom@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/navbar-fixed.png create mode 100644 site/static/docs/5.3/assets/img/examples/navbar-fixed@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/navbar-static.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/navbar-static@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/navbars-offcanvas.png create mode 100644 site/static/docs/5.3/assets/img/examples/navbars-offcanvas@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/navbars.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/navbars@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/offcanvas-navbar.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/offcanvas-navbar@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/pricing.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/pricing@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/product.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/product@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/sidebars.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/sidebars@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/sign-in.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/sign-in@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/starter-template.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/examples/starter-template@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/examples/sticky-footer-navbar.png create mode 100644 site/static/docs/5.3/assets/img/examples/sticky-footer-navbar@2x.png create mode 100644 site/static/docs/5.3/assets/img/examples/sticky-footer.png rename site/static/docs/{5.2 => 5.3}/assets/img/examples/sticky-footer@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/android-chrome-192x192.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/android-chrome-512x512.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/apple-touch-icon.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/favicon-16x16.png (100%) create mode 100644 site/static/docs/5.3/assets/img/favicons/favicon-32x32.png rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/favicon.ico (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/manifest.json (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/favicons/safari-pinned-tab.svg (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-parcel.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-parcel@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-vite.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-vite@2x.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-webpack.png (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/guides/bootstrap-webpack@2x.png (100%) create mode 100644 site/static/docs/5.3/assets/img/guides/parcel-dev-server-bootstrap.png create mode 100644 site/static/docs/5.3/assets/img/guides/parcel-dev-server.png create mode 100644 site/static/docs/5.3/assets/img/guides/vite-dev-server-bootstrap.png create mode 100644 site/static/docs/5.3/assets/img/guides/vite-dev-server.png create mode 100644 site/static/docs/5.3/assets/img/guides/webpack-dev-server-bootstrap.png create mode 100644 site/static/docs/5.3/assets/img/guides/webpack-dev-server.png create mode 100644 site/static/docs/5.3/assets/img/parcel.png rename site/static/docs/{5.2 => 5.3}/assets/img/vite.svg (100%) rename site/static/docs/{5.2 => 5.3}/assets/img/webpack.svg (100%) create mode 100644 site/static/docs/5.3/assets/js/color-modes.js rename site/static/docs/{5.2 => 5.3}/assets/js/validate-forms.js (100%) diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json index 143753b..d7e1c80 100644 --- a/.bundlewatch.config.json +++ b/.bundlewatch.config.json @@ -2,43 +2,43 @@ "files": [ { "path": "./dist/css/bootstrap-grid.css", - "maxSize": "7.5 kB" + "maxSize": "6.5 kB" }, { "path": "./dist/css/bootstrap-grid.min.css", - "maxSize": "6.55 kB" + "maxSize": "6.0 kB" }, { "path": "./dist/css/bootstrap-reboot.css", - "maxSize": "2.75 kB" + "maxSize": "3.5 kB" }, { "path": "./dist/css/bootstrap-reboot.min.css", - "maxSize": "2.5 kB" + "maxSize": "3.25 kB" }, { "path": "./dist/css/bootstrap-utilities.css", - "maxSize": "9.25 kB" + "maxSize": "11.75 kB" }, { "path": "./dist/css/bootstrap-utilities.min.css", - "maxSize": "8.5 kB" + "maxSize": "10.75 kB" }, { "path": "./dist/css/bootstrap.css", - "maxSize": "28.75 kB" + "maxSize": "32.5 kB" }, { "path": "./dist/css/bootstrap.min.css", - "maxSize": "26.75 kB" + "maxSize": "30.25 kB" }, { "path": "./dist/js/bootstrap.bundle.js", - "maxSize": "43.25 kB" + "maxSize": "43.0 kB" }, { "path": "./dist/js/bootstrap.bundle.min.js", - "maxSize": "22.75 kB" + "maxSize": "23.0 kB" }, { "path": "./dist/js/bootstrap.esm.js", @@ -46,7 +46,7 @@ }, { "path": "./dist/js/bootstrap.esm.min.js", - "maxSize": "18.5 kB" + "maxSize": "18.25 kB" }, { "path": "./dist/js/bootstrap.js", @@ -54,7 +54,7 @@ }, { "path": "./dist/js/bootstrap.min.js", - "maxSize": "16.25 kB" + "maxSize": "16.0 kB" } ], "ci": { diff --git a/.cspell.json b/.cspell.json index d528823..e477ef8 100644 --- a/.cspell.json +++ b/.cspell.json @@ -8,6 +8,7 @@ "autohiding", "autoplay", "autoplays", + "autoplaying", "blazingly", "Blockquotes", "Bootstrappers", @@ -107,6 +108,7 @@ "unstyled", "Uppercased", "urlize", + "urlquery", "vbtn", "viewports", "Vite", diff --git a/.eslintignore b/.eslintignore index 04bae15..4c5b84f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,5 @@ **/vendor/ /_site/ /js/coverage/ -/js/tests/integration/ /site/static/sw.js -/site/layouts/ +/site/layouts/partials/ diff --git a/.eslintrc.json b/.eslintrc.json index d8e83a8..055acc7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,6 +14,35 @@ "error", "never" ], + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "always" + } + ], + "import/first": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-amd": "error", + "import/no-cycle": [ + "error", + { + "ignoreExternal": true + } + ], + "import/no-duplicates": "error", + "import/no-extraneous-dependencies": "error", + "import/no-mutable-exports": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-named-default": "error", + "import/no-self-import": "error", + "import/no-unassigned-import": [ + "error" + ], + "import/no-useless-path-segments": "error", + "import/order": "error", "indent": [ "error", 2, @@ -46,20 +75,146 @@ "error", "after" ], + "prefer-template": "error", "semi": [ "error", "never" ], + "strict": "error", "unicorn/explicit-length-check": "off", + "unicorn/filename-case": "off", "unicorn/no-array-callback-reference": "off", "unicorn/no-array-method-this-argument": "off", "unicorn/no-null": "off", + "unicorn/no-typeof-undefined": "off", "unicorn/no-unused-properties": "error", + "unicorn/numeric-separators-style": "off", "unicorn/prefer-array-flat": "off", + "unicorn/prefer-at": "off", "unicorn/prefer-dom-node-dataset": "off", "unicorn/prefer-module": "off", "unicorn/prefer-query-selector": "off", "unicorn/prefer-spread": "off", + "unicorn/prefer-string-replace-all": "off", "unicorn/prevent-abbreviations": "off" - } + }, + "overrides": [ + { + "files": [ + "build/**" + ], + "env": { + "browser": false, + "node": true + }, + "parserOptions": { + "sourceType": "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" + } + } + ] } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c7211e6..4463445 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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 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. -* 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/) instead. We reserve the right to delete comments which violate this rule. -* Please **do not** open issues regarding the official themes offered on . +- Please **do not** open issues regarding the official themes offered on . Instead, please email any questions or feedback regarding those themes to `themes AT getbootstrap DOT com`. @@ -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 | | ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- | -| 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. | -| 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 | +| Mozilla | Firefox | Gecko | | "Core" is normally the right product option to choose. | +| Apple | Safari | WebKit | | In Apple's bug reporter, choose "Safari" as the product. | +| Google, Opera | Chrome, Chromium, Opera v15+ | Blink | | Click the "New issue" button. | +| Microsoft | Edge | Blink | | Go to "Help > Send Feedback" from the browser | ## Feature requests Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong +fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4675f70..98e45c5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,7 +31,7 @@ -* https://deploy-preview-{your pr number}--twbs-bootstrap.netlify.app/ +- ### Related issues diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..9578772 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,3 @@ +name: "CodeQL config" +paths-ignore: + - dist diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 29135b4..f54ba89 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,5 @@ version: 2 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" directory: "/" schedule: @@ -22,3 +7,17 @@ updates: day: tuesday time: "12:00" timezone: Europe/Athens + - package-ecosystem: npm + directory: "/" + reviewers: + - XhmikosR + labels: + - dependencies + - v5 + schedule: + interval: weekly + day: tuesday + time: "12:00" + timezone: Europe/Athens + versioning-strategy: increase + rebase-strategy: disabled diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml index 425c566..e545d62 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -2,21 +2,29 @@ name: BrowserStack on: push: + branches: + - "**" + - "!dependabot/**" workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: browserstack: 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 steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/bundlewatch.yml b/.github/workflows/bundlewatch.yml index d1a1747..c02a37e 100644 --- a/.github/workflows/bundlewatch.yml +++ b/.github/workflows/bundlewatch.yml @@ -2,14 +2,17 @@ name: Bundlewatch on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: bundlewatch: @@ -18,6 +21,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/calibreapp-image-actions.yml b/.github/workflows/calibreapp-image-actions.yml index e23f562..21df1f6 100644 --- a/.github/workflows/calibreapp-image-actions.yml +++ b/.github/workflows/calibreapp-image-actions.yml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v3 + with: + persist-credentials: false - name: Compress Images uses: calibreapp/image-actions@1.1.0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 70be056..b1780ee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,13 +7,12 @@ on: - v4-dev - "!dependabot/**" pull_request: - # The branches below must be a subset of the branches above branches: - main - v4-dev - "!dependabot/**" schedule: - - cron: "0 2 * * 5" + - cron: "0 2 * * 4" workflow_dispatch: jobs: @@ -21,18 +20,25 @@ jobs: name: Analyze runs-on: ubuntu-latest permissions: - actions: read - contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: + config-file: ./.github/codeql/codeql-config.yml languages: "javascript" + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 + with: + category: "/language:javascript" diff --git a/.github/workflows/cspell.yml b/.github/workflows/cspell.yml index 3751ad3..11788e3 100644 --- a/.github/workflows/cspell.yml +++ b/.github/workflows/cspell.yml @@ -2,22 +2,30 @@ name: cspell on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + +permissions: + contents: read jobs: cspell: + permissions: + # allow streetsidesoftware/cspell-action to fetch files for commits and PRs + contents: read + pull-requests: read runs-on: ubuntu-latest steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Run cspell uses: streetsidesoftware/cspell-action@v2 diff --git a/.github/workflows/css.yml b/.github/workflows/css.yml index 857a567..66112a9 100644 --- a/.github/workflows/css.yml +++ b/.github/workflows/css.yml @@ -2,14 +2,17 @@ name: CSS on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: css: @@ -18,6 +21,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 @@ -30,3 +35,6 @@ jobs: - name: Build CSS run: npm run css + + - name: Run CSS tests + run: npm run css-test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f33413e..2a684f6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,14 +2,17 @@ name: Docs on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: docs: @@ -18,6 +21,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index b251cd7..b5000d8 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -4,8 +4,15 @@ on: schedule: - cron: "0 0 * * *" +permissions: + contents: read + jobs: issue-close-require: + permissions: + # allow actions-cool/issues-helper to update issues and PRs + issues: write + pull-requests: write runs-on: ubuntu-latest if: github.repository == 'twbs/bootstrap' steps: diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index fac5849..584879d 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -4,8 +4,15 @@ on: issues: types: [labeled] +permissions: + contents: read + jobs: issue-labeled: + permissions: + # allow actions-cool/issues-helper to update issues and PRs + issues: write + pull-requests: write if: github.repository == 'twbs/bootstrap' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index 82616c5..805b1b7 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -2,23 +2,32 @@ name: JS Tests on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: run: + permissions: + # allow coverallsapp/github-action to create new checks issues and fetch code + checks: write + contents: read name: JS Tests runs-on: ubuntu-latest steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 @@ -36,7 +45,7 @@ jobs: run: npm run js-test - name: Run Coveralls - uses: coverallsapp/github-action@1.1.3 + uses: coverallsapp/github-action@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" path-to-lcov: "./js/coverage/lcov.info" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 816694e..fd62b41 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,14 +2,17 @@ name: Lint on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: lint: @@ -18,6 +21,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/node-sass.yml b/.github/workflows/node-sass.yml index 465cee4..c558e44 100644 --- a/.github/workflows/node-sass.yml +++ b/.github/workflows/node-sass.yml @@ -2,14 +2,17 @@ name: CSS (node-sass) on: push: - branches-ignore: - - "dependabot/**" + branches: + - main pull_request: workflow_dispatch: env: FORCE_COLOR: 2 - NODE: 16 + NODE: 18 + +permissions: + contents: read jobs: css: @@ -18,6 +21,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v3 + with: + persist-credentials: false - name: Set up Node.js 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 --output-style expanded --source-map true --source-map-contents true --precision 6 scss/ -o dist-sass/css/ ls -Al dist-sass/css + + - name: Check built CSS files for Sass variables + shell: bash + run: | + SASS_VARS_FOUND=$(find "dist-sass/css/" -type f -name "*.css" -print0 | xargs -0 --no-run-if-empty grep -F "\$" || true) + if [[ -z "$SASS_VARS_FOUND" ]]; then + echo "All good, no Sass variables found!" + exit 0 + else + echo "Found $(echo "$SASS_VARS_FOUND" | wc -l | bc) Sass variables:" + echo "$SASS_VARS_FOUND" + exit 1 + fi diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml index bbd0a24..f620dd3 100644 --- a/.github/workflows/release-notes.yml +++ b/.github/workflows/release-notes.yml @@ -6,8 +6,15 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: update_release_draft: + permissions: + # allow release-drafter/release-drafter to create GitHub releases and add labels to PRs + contents: write + pull-requests: write runs-on: ubuntu-latest if: github.repository == 'twbs/bootstrap' steps: diff --git a/.gitignore b/.gitignore index 2215d63..0c9b6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,6 @@ Thumbs.db *.komodoproject # Folders to ignore +/dist-sass/ /js/coverage/ /node_modules/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..4812751 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +lockfile-version=2 diff --git a/.stylelintrc b/.stylelintrc deleted file mode 100644 index 94c8ec1..0000000 --- a/.stylelintrc +++ /dev/null @@ -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 - } -} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..589884a --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,60 @@ +{ + "extends": [ + "stylelint-config-twbs-bootstrap" + ], + "reportInvalidScopeDisables": true, + "reportNeedlessDisables": true, + "overrides": [ + { + "files": "**/*.scss", + "rules": { + "declaration-property-value-disallowed-list": { + "border": "none", + "outline": "none" + }, + "function-disallowed-list": [ + "calc", + "lighten", + "darken" + ], + "property-disallowed-list": [ + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius", + "transition" + ], + "scss/dollar-variable-default": [ + true, + { + "ignore": "local" + } + ], + "scss/selector-no-union-class-name": true + } + }, + { + "files": "scss/**/*.{test,spec}.scss", + "rules": { + "scss/dollar-variable-default": null, + "declaration-no-important": null + } + }, + { + "files": "site/**/*.scss", + "rules": { + "scss/dollar-variable-default": null + } + }, + { + "files": "site/**/examples/**/*.css", + "rules": { + "comment-empty-line-before": null, + "property-no-vendor-prefix": null, + "selector-no-qualifying-type": null, + "value-no-vendor-prefix": null + } + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 28fd5e8..b983cba 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, 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 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 -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, 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 ## Enforcement Responsibilities diff --git a/LICENSE b/LICENSE index dda75ca..6633b55 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2022 Twitter, Inc. -Copyright (c) 2011-2022 The Bootstrap Authors +Copyright (c) 2011-2023 The Bootstrap Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bd40192..529b0d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Bootstrap logo + Bootstrap logo

@@ -9,7 +9,7 @@

Sleek, intuitive, and powerful front-end framework for faster and easier web development.
- Explore Bootstrap docs » + Explore Bootstrap docs »

Report bug @@ -46,32 +46,32 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [` 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` -- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.2.3` -- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.2.3` -- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.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.3.0` +- 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` -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 -[![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) -[![npm version](https://img.shields.io/npm/v/bootstrap)](https://www.npmjs.com/package/bootstrap) -[![Gem version](https://img.shields.io/gem/v/bootstrap)](https://rubygems.org/gems/bootstrap) -[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap) -[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap) -[![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](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) +[![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?logo=npm&logoColor=fff)](https://www.npmjs.com/package/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?logo=meteor&logoColor=fff)](https://atmospherejs.com/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?logo=nuget&logoColor=fff)](https://www.nuget.org/packages/bootstrap/absoluteLatest) +[![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 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 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) -[![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap)](#backers) -[![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap)](#sponsors) +[![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?logo=opencollective&logoColor=fff)](#sponsors) ## 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 . 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 @@ -243,4 +243,4 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com ## Copyright and license -Code and documentation copyright 2011–2022 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 2011–2023 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/). diff --git a/build/.eslintrc.json b/build/.eslintrc.json deleted file mode 100644 index dec6323..0000000 --- a/build/.eslintrc.json +++ /dev/null @@ -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" - } -} diff --git a/build/banner.js b/build/banner.js index df82ff3..a022f1c 100644 --- a/build/banner.js +++ b/build/banner.js @@ -1,6 +1,7 @@ 'use strict' const pkg = require('../package.json') + const year = new Date().getFullYear() function getBanner(pluginFilename) { diff --git a/build/build-plugins.js b/build/build-plugins.js index a160209..b2833a3 100644 --- a/build/build-plugins.js +++ b/build/build-plugins.js @@ -2,8 +2,7 @@ /*! * Script to build our plugins to use them separately. - * Copyright 2020-2022 The Bootstrap Authors - * Copyright 2020-2022 Twitter, Inc. + * Copyright 2020-2023 The Bootstrap Authors * 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 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 const resolvedPlugins = [] @@ -27,7 +26,7 @@ const filenameToEntity = filename => filename.replace('.js', '') for (const file of jsFiles) { resolvedPlugins.push({ - src: file.replace('.js', ''), + src: file, dist: file.replace('src', 'dist'), fileName: path.basename(file), className: filenameToEntity(path.basename(file)) diff --git a/build/change-version.js b/build/change-version.js index 57c5fde..9685df5 100644 --- a/build/change-version.js +++ b/build/change-version.js @@ -2,8 +2,7 @@ /*! * Script to update version number references in the project. - * Copyright 2017-2022 The Bootstrap Authors - * Copyright 2017-2022 Twitter, Inc. + * Copyright 2017-2023 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ @@ -36,9 +35,17 @@ function regExpQuoteReplacement(string) { async function replaceRecursively(file, oldVersion, newVersion) { const originalString = await fs.readFile(file, 'utf8') - const newString = originalString.replace( - new RegExp(regExpQuote(oldVersion), 'g'), regExpQuoteReplacement(newVersion) - ) + const newString = originalString + .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 if (originalString === newString) { @@ -56,22 +63,35 @@ async function replaceRecursively(file, oldVersion, newVersion) { await fs.writeFile(file, newString, 'utf8') } +function showUsage(args) { + console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') + console.error('Got arguments:', args) + process.exit(1) +} + async function main(args) { let [oldVersion, newVersion] = args if (!oldVersion || !newVersion) { - console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') - console.error('Got arguments:', args) - process.exit(1) + showUsage(args) } - // Strip any leading `v` from arguments because otherwise we will end up with duplicate `v`s - [oldVersion, newVersion] = [oldVersion, newVersion].map(arg => arg.startsWith('v') ? arg.slice(1) : arg) + // 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 { 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) { console.error(error) process.exit(1) diff --git a/build/generate-sri.js b/build/generate-sri.js index ef1b39f..2e22924 100644 --- a/build/generate-sri.js +++ b/build/generate-sri.js @@ -5,8 +5,7 @@ * Remember to use the same vendor files as the CDN ones, * otherwise the hashes won't match! * - * Copyright 2017-2022 The Bootstrap Authors - * Copyright 2017-2022 Twitter, Inc. + * Copyright 2017-2023 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ @@ -19,11 +18,11 @@ const sh = require('shelljs') 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. // `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 = [ { file: 'dist/css/bootstrap.min.css', @@ -47,8 +46,8 @@ const files = [ } ] -for (const file of files) { - fs.readFile(file.file, 'utf8', (error, data) => { +for (const { file, configPropertyName } of files) { + fs.readFile(file, 'utf8', (error, data) => { if (error) { throw error } @@ -57,8 +56,8 @@ for (const file of files) { const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64') 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) }) } diff --git a/build/rollup.config.js b/build/rollup.config.js index 27f12ac..f01918e 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -40,7 +40,7 @@ if (BUNDLE) { const rollupConfig = { input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`), output: { - banner, + banner: banner(), file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`), format: ESM ? 'esm' : 'umd', globals, diff --git a/build/vnu-jar.js b/build/vnu-jar.js index f29eeb7..22956cb 100644 --- a/build/vnu-jar.js +++ b/build/vnu-jar.js @@ -2,8 +2,7 @@ /*! * Script to run vnu-jar if Java is available. - * Copyright 2017-2022 The Bootstrap Authors - * Copyright 2017-2022 Twitter, Inc. + * Copyright 2017-2023 The Bootstrap Authors * 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) => { 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 } + console.log('Running vnu-jar validation...') + const is32bitJava = !/64-Bit/.test(stderr) // vnu-jar accepts multiple ignores joined with a `|`. @@ -49,6 +51,8 @@ execFile('java', ['-version'], (error, stdout, stderr) => { args.splice(0, 0, '-Xss512k') } + console.log(`command used: java ${args.join(' ')}`) + return spawn('java', args, { shell: true, stdio: 'inherit' diff --git a/build/zip-examples.js b/build/zip-examples.js index 077901e..613376a 100644 --- a/build/zip-examples.js +++ b/build/zip-examples.js @@ -3,7 +3,7 @@ /*! * Script to create the built examples zip archive; * 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) */ @@ -84,7 +84,7 @@ for (const file of sh.find(`${distFolder}/**/*.html`)) { } // create the zip file -sh.exec(`zip -r9 "${distFolder}.zip" "${distFolder}"`) +sh.exec(`zip -qr9 "${distFolder}.zip" "${distFolder}"`) // remove the folder we created sh.rm('-rf', distFolder) diff --git a/config.yml b/hugo.yml similarity index 69% rename from config.yml rename to hugo.yml index 682f873..47da082 100644 --- a/config.yml +++ b/hugo.yml @@ -31,7 +31,7 @@ publishDir: "_site" module: mounts: - source: dist - target: static/docs/5.2/dist + target: static/docs/5.3/dist - source: site/assets target: assets - source: site/content @@ -42,9 +42,9 @@ module: target: layouts - source: site/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 - - 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 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." authors: "Mark Otto, Jacob Thornton, and Bootstrap contributors" - current_version: "5.2.3" - current_ruby_version: "5.2.3" - docs_version: "5.2" - rfs_version: "v9.0.6" + current_version: "5.3.0" + current_ruby_version: "5.3.0" + docs_version: "5.3" + rfs_version: "v10.0.0" github_org: "https://github.com/twbs" repo: "https://github.com/twbs/bootstrap" twitter: "getbootstrap" @@ -66,22 +66,23 @@ params: swag: "https://cottonbureau.com/people/bootstrap" download: - source: "https://github.com/twbs/bootstrap/archive/v5.2.3.zip" - dist: "https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-dist.zip" - dist_examples: "https://github.com/twbs/bootstrap/releases/download/v5.2.3/bootstrap-5.2.3-examples.zip" + source: "https://github.com/twbs/bootstrap/archive/v5.3.0.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.3.0/bootstrap-5.3.0-examples.zip" cdn: # 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_hash: "sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" - css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.rtl.min.css" - css_rtl_hash: "sha384-DOXMLfHhQkvFFp+rWTZwVlPVqdIhpDVYT9csOnHSgWQWPX0v5MCGtjCJbY6ERspU" - js: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" - js_hash: "sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" - js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" - js_bundle_hash: "sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" - popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" - popper_hash: "sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" + css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" + css_hash: "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" + css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css" + css_rtl_hash: "sha384-PJsj/BTMqILvmcej7ulplguok8ag4xFTPryRq8xevL7eBYSmpXKcbNVuy+P0RMgq" + js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js" + js_hash: "sha384-fbbOQedDUMZZ5KreZpsbe1LCZPVmfTnH7ois6mU1QK+m14rQ1l2bGBq41eYeM/fS" + js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" + js_bundle_hash: "sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" + popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" + 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: min: 2 diff --git a/js/index.esm.js b/js/index.esm.js index b837649..155d9fb 100644 --- a/js/index.esm.js +++ b/js/index.esm.js @@ -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) * -------------------------------------------------------------------------- */ -export { default as Alert } from './src/alert' -export { default as Button } from './src/button' -export { default as Carousel } from './src/carousel' -export { default as Collapse } from './src/collapse' -export { default as Dropdown } from './src/dropdown' -export { default as Modal } from './src/modal' -export { default as Offcanvas } from './src/offcanvas' -export { default as Popover } from './src/popover' -export { default as ScrollSpy } from './src/scrollspy' -export { default as Tab } from './src/tab' -export { default as Toast } from './src/toast' -export { default as Tooltip } from './src/tooltip' +export { default as Alert } from './src/alert.js' +export { default as Button } from './src/button.js' +export { default as Carousel } from './src/carousel.js' +export { default as Collapse } from './src/collapse.js' +export { default as Dropdown } from './src/dropdown.js' +export { default as Modal } from './src/modal.js' +export { default as Offcanvas } from './src/offcanvas.js' +export { default as Popover } from './src/popover.js' +export { default as ScrollSpy } from './src/scrollspy.js' +export { default as Tab } from './src/tab.js' +export { default as Toast } from './src/toast.js' +export { default as Tooltip } from './src/tooltip.js' diff --git a/js/index.umd.js b/js/index.umd.js index 5abe8db..a33df74 100644 --- a/js/index.umd.js +++ b/js/index.umd.js @@ -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) * -------------------------------------------------------------------------- */ -import Alert from './src/alert' -import Button from './src/button' -import Carousel from './src/carousel' -import Collapse from './src/collapse' -import Dropdown from './src/dropdown' -import Modal from './src/modal' -import Offcanvas from './src/offcanvas' -import Popover from './src/popover' -import ScrollSpy from './src/scrollspy' -import Tab from './src/tab' -import Toast from './src/toast' -import Tooltip from './src/tooltip' +import Alert from './src/alert.js' +import Button from './src/button.js' +import Carousel from './src/carousel.js' +import Collapse from './src/collapse.js' +import Dropdown from './src/dropdown.js' +import Modal from './src/modal.js' +import Offcanvas from './src/offcanvas.js' +import Popover from './src/popover.js' +import ScrollSpy from './src/scrollspy.js' +import Tab from './src/tab.js' +import Toast from './src/toast.js' +import Tooltip from './src/tooltip.js' export default { Alert, diff --git a/js/src/alert.js b/js/src/alert.js index 59de828..88232bc 100644 --- a/js/src/alert.js +++ b/js/src/alert.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): alert.js + * Bootstrap alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' -import { enableDismissTrigger } from './util/component-functions' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { enableDismissTrigger } from './util/component-functions.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/base-component.js b/js/src/base-component.js index 0c1a259..d13c7ab 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -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) * -------------------------------------------------------------------------- */ -import Data from './dom/data' -import { executeAfterTransition, getElement } from './util/index' -import EventHandler from './dom/event-handler' -import Config from './util/config' +import Data from './dom/data.js' +import EventHandler from './dom/event-handler.js' +import Config from './util/config.js' +import { executeAfterTransition, getElement } from './util/index.js' /** * Constants */ -const VERSION = '5.2.3' +const VERSION = '5.3.0' /** * Class definition diff --git a/js/src/button.js b/js/src/button.js index 03e7604..a797f50 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): button.js + * Bootstrap button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/carousel.js b/js/src/carousel.js index 24bbe39..68d11a3 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -1,24 +1,23 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): carousel.js + * Bootstrap carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' import { defineJQueryPlugin, - getElementFromSelector, getNextActiveElement, isRTL, isVisible, reflow, triggerTransitionEnd -} from './util/index' -import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' -import Swipe from './util/swipe' -import BaseComponent from './base-component' +} from './util/index.js' +import Swipe from './util/swipe.js' /** * Constants @@ -330,7 +329,7 @@ class Carousel extends BaseComponent { if (!activeElement || !nextElement) { // 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 } @@ -431,7 +430,7 @@ class Carousel extends BaseComponent { */ 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)) { return diff --git a/js/src/collapse.js b/js/src/collapse.js index 204d180..9f0c60c 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -1,20 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): collapse.js + * Bootstrap collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' import { defineJQueryPlugin, getElement, - getElementFromSelector, - getSelectorFromElement, reflow -} from './util/index' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' -import BaseComponent from './base-component' +} from './util/index.js' /** * Constants @@ -68,7 +66,7 @@ class Collapse extends BaseComponent { const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (const elem of toggleList) { - const selector = getSelectorFromElement(elem) + const selector = SelectorEngine.getSelectorFromElement(elem) const filterElement = SelectorEngine.find(selector) .filter(foundElement => foundElement === this._element) @@ -185,7 +183,7 @@ class Collapse extends BaseComponent { this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) for (const trigger of this._triggerArray) { - const element = getElementFromSelector(trigger) + const element = SelectorEngine.getElementFromSelector(trigger) if (element && !this._isShown(element)) { this._addAriaAndCollapsedClass([trigger], false) @@ -229,7 +227,7 @@ class Collapse extends BaseComponent { const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE) for (const element of children) { - const selected = getElementFromSelector(element) + const selected = SelectorEngine.getElementFromSelector(element) if (selected) { this._addAriaAndCollapsedClass([element], this._isShown(selected)) @@ -285,10 +283,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( event.preventDefault() } - const selector = getSelectorFromElement(this) - const selectorElements = SelectorEngine.find(selector) - - for (const element of selectorElements) { + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { Collapse.getOrCreateInstance(element, { toggle: false }).toggle() } }) diff --git a/js/src/dom/data.js b/js/src/dom/data.js index 2c6a46e..407f67e 100644 --- a/js/src/dom/data.js +++ b/js/src/dom/data.js @@ -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) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 9876d77..561d875 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -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) * -------------------------------------------------------------------------- */ -import { getjQuery } from '../util/index' +import { getjQuery } from '../util/index.js' /** * Constants @@ -128,7 +128,7 @@ function findHandler(events, callable, delegationSelector = null) { function normalizeParameters(originalTypeEvent, handler, delegationFunction) { 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) let typeEvent = getTypeEvent(originalTypeEvent) @@ -198,9 +198,8 @@ function removeHandler(element, events, typeEvent, handler, delegationSelector) function removeNamespacedHandlers(element, events, typeEvent, namespace) { const storeElementEvent = events[typeEvent] || {} - for (const handlerKey of Object.keys(storeElementEvent)) { + for (const [handlerKey, event] of Object.entries(storeElementEvent)) { if (handlerKey.includes(namespace)) { - const event = storeElementEvent[handlerKey] 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, '') if (!inNamespace || originalTypeEvent.includes(handlerKey)) { - const event = storeElementEvent[keyHandlers] removeHandler(element, events, typeEvent, event.callable, event.delegationSelector) } } @@ -281,8 +279,7 @@ const EventHandler = { defaultPrevented = jQueryEvent.isDefaultPrevented() } - let evt = new Event(event, { bubbles, cancelable: true }) - evt = hydrateObj(evt, args) + const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args) if (defaultPrevented) { evt.preventDefault() @@ -300,8 +297,8 @@ const EventHandler = { } } -function hydrateObj(obj, meta) { - for (const [key, value] of Object.entries(meta || {})) { +function hydrateObj(obj, meta = {}) { + for (const [key, value] of Object.entries(meta)) { try { obj[key] = value } catch { diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 38ecfe4..dd86a9f 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -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) * -------------------------------------------------------------------------- */ diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 1ba104f..3cecf6f 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -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) * -------------------------------------------------------------------------- */ -import { isDisabled, isVisible } from '../util/index' +import { isDisabled, isVisible, parseSelector } from '../util/index.js' -/** - * Constants - */ +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 parseSelector(selector) +} const SelectorEngine = { find(selector, element = document.documentElement) { @@ -77,6 +98,28 @@ const SelectorEngine = { ].map(selector => `${selector}:not([tabindex^="-"])`).join(',') return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) + }, + + getSelectorFromElement(element) { + const selector = getSelector(element) + + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null + } + + return null + }, + + getElementFromSelector(element) { + const selector = getSelector(element) + + return selector ? SelectorEngine.findOne(selector) : null + }, + + getMultipleElementsFromSelector(element) { + const selector = getSelector(element) + + return selector ? SelectorEngine.find(selector) : [] } } diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 9596baa..af5fd16 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -1,13 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dropdown.js + * Bootstrap dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ import * as Popper from '@popperjs/core' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import SelectorEngine from './dom/selector-engine.js' import { defineJQueryPlugin, + execute, getElement, getNextActiveElement, isDisabled, @@ -15,11 +20,7 @@ import { isRTL, isVisible, noop -} from './util/index' -import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' -import BaseComponent from './base-component' +} from './util/index.js' /** * Constants @@ -95,7 +96,7 @@ class Dropdown extends BaseComponent { this._popper = null 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] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || 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 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 = [{ name: 'applyStyles', enabled: false @@ -319,7 +320,7 @@ class Dropdown extends BaseComponent { return { ...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() - // 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) ? this : (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || diff --git a/js/src/modal.js b/js/src/modal.js index 26c7e8c..b44cbb9 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -1,18 +1,18 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): modal.js + * Bootstrap modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' -import ScrollBarHelper from './util/scrollbar' -import BaseComponent from './base-component' -import Backdrop from './util/backdrop' -import FocusTrap from './util/focustrap' -import { enableDismissTrigger } from './util/component-functions' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' +import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index.js' +import ScrollBarHelper from './util/scrollbar.js' /** * Constants @@ -139,12 +139,12 @@ class Modal extends BaseComponent { } dispose() { - for (const htmlElement of [window, this._dialog]) { - EventHandler.off(htmlElement, EVENT_KEY) - } + EventHandler.off(window, EVENT_KEY) + EventHandler.off(this._dialog, EVENT_KEY) this._backdrop.dispose() this._focustrap.deactivate() + super.dispose() } @@ -208,7 +208,6 @@ class Modal extends BaseComponent { } if (this._config.keyboard) { - event.preventDefault() this.hide() return } @@ -336,7 +335,7 @@ class Modal extends BaseComponent { */ 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)) { event.preventDefault() diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 7dd06fd..8d1feb1 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -1,23 +1,22 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): offcanvas.js + * Bootstrap offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import Backdrop from './util/backdrop.js' +import { enableDismissTrigger } from './util/component-functions.js' +import FocusTrap from './util/focustrap.js' import { defineJQueryPlugin, - getElementFromSelector, isDisabled, isVisible -} from './util/index' -import ScrollBarHelper from './util/scrollbar' -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' +} from './util/index.js' +import ScrollBarHelper from './util/scrollbar.js' /** * Constants @@ -199,12 +198,12 @@ class Offcanvas extends BaseComponent { return } - if (!this._config.keyboard) { - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + if (this._config.keyboard) { + this.hide() 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) { - const target = getElementFromSelector(this) + const target = SelectorEngine.getElementFromSelector(this) if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() diff --git a/js/src/popover.js b/js/src/popover.js index 1b09dd4..612c521 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -1,12 +1,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): popover.js + * Bootstrap popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin } from './util/index' -import Tooltip from './tooltip' +import Tooltip from './tooltip.js' +import { defineJQueryPlugin } from './util/index.js' /** * Constants diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 01aba99..69de715 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): scrollspy.js + * Bootstrap scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' -import BaseComponent from './base-component' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index.js' /** * Constants @@ -208,11 +208,11 @@ class ScrollSpy extends BaseComponent { 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 if (isVisible(observableSection)) { - this._targetLinks.set(anchor.hash, anchor) + this._targetLinks.set(decodeURI(anchor.hash), anchor) this._observableSections.set(anchor.hash, observableSection) } } diff --git a/js/src/tab.js b/js/src/tab.js index 8dc4644..d9993d5 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): tab.js + * Bootstrap tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' -import BaseComponent from './base-component' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import SelectorEngine from './dom/selector-engine.js' +import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js' /** * Constants @@ -43,7 +43,7 @@ const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)' const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]' const SELECTOR_OUTER = '.nav-item, .list-group-item' const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}` -const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // todo: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_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) { 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}`) } @@ -106,7 +106,7 @@ class Tab extends BaseComponent { 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 = () => { if (element.getAttribute('role') !== 'tab') { @@ -133,7 +133,7 @@ class Tab extends BaseComponent { element.classList.remove(CLASS_NAME_ACTIVE) 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 = () => { if (element.getAttribute('role') !== 'tab') { @@ -203,7 +203,7 @@ class Tab extends BaseComponent { } _setInitialAttributesOnTargetPanel(child) { - const target = getElementFromSelector(child) + const target = SelectorEngine.getElementFromSelector(child) if (!target) { return @@ -212,7 +212,7 @@ class Tab extends BaseComponent { this._setAttributeIfNotExists(target, 'role', 'tabpanel') if (child.id) { - this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`) + this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`) } } diff --git a/js/src/toast.js b/js/src/toast.js index a7fe775..d5d9c0e 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): toast.js + * Bootstrap toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, reflow } from './util/index' -import EventHandler from './dom/event-handler' -import BaseComponent from './base-component' -import { enableDismissTrigger } from './util/component-functions' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import { enableDismissTrigger } from './util/component-functions.js' +import { defineJQueryPlugin, reflow } from './util/index.js' /** * Constants diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 748a0e1..1252811 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,17 +1,17 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): tooltip.js + * Bootstrap tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ import * as Popper from '@popperjs/core' -import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index' -import { DefaultAllowlist } from './util/sanitizer' -import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' -import BaseComponent from './base-component' -import TemplateFactory from './util/template-factory' +import BaseComponent from './base-component.js' +import EventHandler from './dom/event-handler.js' +import Manipulator from './dom/manipulator.js' +import { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index.js' +import { DefaultAllowlist } from './util/sanitizer.js' +import TemplateFactory from './util/template-factory.js' /** * Constants @@ -62,7 +62,7 @@ const Default = { delay: 0, fallbackPlacements: ['top', 'right', 'bottom', 'left'], html: false, - offset: [0, 0], + offset: [0, 6], placement: 'top', popperConfig: null, sanitize: true, @@ -197,7 +197,7 @@ class Tooltip extends BaseComponent { return } - // todo v6 remove this OR make it optional + // TODO: v6 remove this or make it optional this._disposePopper() const tip = this._getTipElement() @@ -302,13 +302,13 @@ class Tooltip extends BaseComponent { _createTipElement(content) { const tip = this._getTemplateFactory(content).toHtml() - // todo: remove this check on v6 + // TODO: remove this check in v6 if (!tip) { return null } 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`) const tipId = getUID(this.constructor.NAME).toString() @@ -370,9 +370,7 @@ class Tooltip extends BaseComponent { } _createPopper(tip) { - const placement = typeof this._config.placement === 'function' ? - this._config.placement.call(this, tip, this._element) : - this._config.placement + const placement = execute(this._config.placement, [this, tip, this._element]) const attachment = AttachmentMap[placement.toUpperCase()] return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) } @@ -392,7 +390,7 @@ class Tooltip extends BaseComponent { } _resolvePossibleFunction(arg) { - return typeof arg === 'function' ? arg.call(this._element) : arg + return execute(arg, [this._element]) } _getPopperConfig(attachment) { @@ -438,7 +436,7 @@ class Tooltip extends BaseComponent { return { ...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() { const config = {} - for (const key in this._config) { - if (this.constructor.Default[key] !== this._config[key]) { - config[key] = this._config[key] + for (const [key, value] of Object.entries(this._config)) { + if (this.constructor.Default[key] !== value) { + config[key] = value } } diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 78279e0..0d478e9 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -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) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import { execute, executeAfterTransition, getElement, reflow } from './index' -import Config from './config' +import EventHandler from '../dom/event-handler.js' +import Config from './config.js' +import { execute, executeAfterTransition, getElement, reflow } from './index.js' /** * Constants diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index c2f99cc..4be828f 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -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) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import { getElementFromSelector, isDisabled } from './index' +import EventHandler from '../dom/event-handler.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isDisabled } from './index.js' const enableDismissTrigger = (component, method = 'hide') => { const clickEvent = `click.dismiss${component.EVENT_KEY}` @@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => { return } - const target = getElementFromSelector(this) || this.closest(`.${name}`) + const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`) const instance = component.getOrCreateInstance(target) // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method diff --git a/js/src/util/config.js b/js/src/util/config.js index 1205905..a2b4bfb 100644 --- a/js/src/util/config.js +++ b/js/src/util/config.js @@ -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) * -------------------------------------------------------------------------- */ -import { isElement, toType } from './index' -import Manipulator from '../dom/manipulator' +import Manipulator from '../dom/manipulator.js' +import { isElement, toType } from './index.js' /** * Class definition @@ -49,8 +49,7 @@ class Config { } _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { - for (const property of Object.keys(configTypes)) { - const expectedTypes = configTypes[property] + for (const [property, expectedTypes] of Object.entries(configTypes)) { const value = config[property] const valueType = isElement(value) ? 'element' : toType(value) diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index ef69166..158f3d1 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -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) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' -import Config from './config' +import EventHandler from '../dom/event-handler.js' +import SelectorEngine from '../dom/selector-engine.js' +import Config from './config.js' /** * Constants diff --git a/js/src/util/index.js b/js/src/util/index.js index 297e571..68b8d89 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -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) * -------------------------------------------------------------------------- */ @@ -9,6 +9,20 @@ const MAX_UID = 1_000_000 const MILLISECONDS_MULTIPLIER = 1000 const TRANSITION_END = 'transitionend' +/** + * Properly escape IDs selectors to handle weird IDs + * @param {string} selector + * @returns {string} + */ +const parseSelector = selector => { + if (selector && window.CSS && window.CSS.escape) { + // document.querySelector needs escaping to handle IDs (html5+) containing for instance / + selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`) + } + + return selector +} + // Shout-out Angus Croll (https://goo.gl/pxwQGp) const toType = object => { if (object === null || object === undefined) { @@ -30,47 +44,6 @@ const getUID = 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 => { if (!element) { return 0 @@ -117,7 +90,7 @@ const getElement = object => { } if (typeof object === 'string' && object.length > 0) { - return document.querySelector(object) + return document.querySelector(parseSelector(object)) } return null @@ -249,10 +222,8 @@ const defineJQueryPlugin = plugin => { }) } -const execute = callback => { - if (typeof callback === 'function') { - callback() - } +const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { + return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue } const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { @@ -318,10 +289,8 @@ export { executeAfterTransition, findShadowRoot, getElement, - getElementFromSelector, getjQuery, getNextActiveElement, - getSelectorFromElement, getTransitionDurationFromElement, getUID, isDisabled, @@ -330,6 +299,7 @@ export { isVisible, noop, onDOMContentLoaded, + parseSelector, reflow, triggerTransitionEnd, toType diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 5328691..d2b0808 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -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) * -------------------------------------------------------------------------- */ -const uriAttributes = new Set([ - 'background', - 'cite', - 'href', - 'itemtype', - 'longdesc', - 'poster', - 'src', - 'xlink:href' -]) - +// js-docs-start allow-list 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 = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], @@ -81,6 +41,43 @@ export const DefaultAllowlist = { u: [], ul: [] } +// js-docs-end allow-list + +const uriAttributes = new Set([ + 'background', + 'cite', + 'href', + 'itemtype', + 'longdesc', + 'poster', + 'src', + 'xlink:href' +]) + +/** + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. + * + * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 + */ +// eslint-disable-next-line unicorn/better-regex +const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i + +const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase() + + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)) + } + + return true + } + + // Check if a regular expression validates the attribute. + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) + .some(regex => regex.test(attributeName)) +} export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!unsafeHtml.length) { @@ -100,7 +97,6 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!Object.keys(allowList).includes(elementName)) { element.remove() - continue } diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 5cac7b6..413f178 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -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) * -------------------------------------------------------------------------- */ -import SelectorEngine from '../dom/selector-engine' -import Manipulator from '../dom/manipulator' -import { isElement } from './index' +import Manipulator from '../dom/manipulator.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isElement } from './index.js' /** * Constants diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js index 7126360..d2f7087 100644 --- a/js/src/util/swipe.js +++ b/js/src/util/swipe.js @@ -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) * -------------------------------------------------------------------------- */ -import Config from './config' -import EventHandler from '../dom/event-handler' -import { execute } from './index' +import EventHandler from '../dom/event-handler.js' +import Config from './config.js' +import { execute } from './index.js' /** * Constants diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index cf402fa..f73589b 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -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) * -------------------------------------------------------------------------- */ -import { DefaultAllowlist, sanitizeHtml } from './sanitizer' -import { getElement, isElement } from '../util/index' -import SelectorEngine from '../dom/selector-engine' -import Config from './config' +import SelectorEngine from '../dom/selector-engine.js' +import Config from './config.js' +import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js' +import { execute, getElement, isElement } from './index.js' /** * Constants @@ -143,7 +143,7 @@ class TemplateFactory extends Config { } _resolvePossibleFunction(arg) { - return typeof arg === 'function' ? arg(this) : arg + return execute(arg, [this]) } _putElementInTemplate(element, templateElement) { diff --git a/js/tests/browsers.js b/js/tests/browsers.js index 8adedc6..c515e64 100644 --- a/js/tests/browsers.js +++ b/js/tests/browsers.js @@ -1,6 +1,7 @@ -/* eslint-env node */ /* eslint-disable camelcase */ +'use strict' + const browsers = { safariMac: { base: 'BrowserStack', diff --git a/js/tests/integration/bundle-modularity.js b/js/tests/integration/bundle-modularity.js index 8546141..3c1eec9 100644 --- a/js/tests/integration/bundle-modularity.js +++ b/js/tests/integration/bundle-modularity.js @@ -1,3 +1,5 @@ +/* eslint-disable import/extensions, import/no-unassigned-import */ + import Tooltip from '../../dist/tooltip' import '../../dist/carousel' diff --git a/js/tests/integration/rollup.bundle-modularity.js b/js/tests/integration/rollup.bundle-modularity.js index a8670ca..63d6515 100644 --- a/js/tests/integration/rollup.bundle-modularity.js +++ b/js/tests/integration/rollup.bundle-modularity.js @@ -1,7 +1,7 @@ -/* eslint-env node */ +'use strict' const commonjs = require('@rollup/plugin-commonjs') -const configRollup = require('./rollup.bundle') +const configRollup = require('./rollup.bundle.js') const config = { ...configRollup, diff --git a/js/tests/integration/rollup.bundle.js b/js/tests/integration/rollup.bundle.js index caddcab..8b3c578 100644 --- a/js/tests/integration/rollup.bundle.js +++ b/js/tests/integration/rollup.bundle.js @@ -1,4 +1,4 @@ -/* eslint-env node */ +'use strict' const { babel } = require('@rollup/plugin-babel') const { nodeResolve } = require('@rollup/plugin-node-resolve') diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js index 6636ff1..36bf7f2 100644 --- a/js/tests/karma.conf.js +++ b/js/tests/karma.conf.js @@ -1,5 +1,3 @@ -/* eslint-env node */ - 'use strict' const path = require('node:path') @@ -8,7 +6,7 @@ const { babel } = require('@rollup/plugin-babel') const istanbul = require('rollup-plugin-istanbul') const { nodeResolve } = require('@rollup/plugin-node-resolve') const replace = require('@rollup/plugin-replace') -const { browsers } = require('./browsers') +const { browsers } = require('./browsers.js') const ENV = process.env const BROWSERSTACK = Boolean(ENV.BROWSERSTACK) @@ -105,7 +103,7 @@ if (BROWSERSTACK) { config.browserStack = { username: ENV.BROWSER_STACK_USERNAME, 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', retryLimit: 2 } diff --git a/js/tests/unit/.eslintrc.json b/js/tests/unit/.eslintrc.json deleted file mode 100644 index 6362a1a..0000000 --- a/js/tests/unit/.eslintrc.json +++ /dev/null @@ -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" - } -} diff --git a/js/tests/unit/alert.spec.js b/js/tests/unit/alert.spec.js index d3740c9..97cc3cc 100644 --- a/js/tests/unit/alert.spec.js +++ b/js/tests/unit/alert.spec.js @@ -1,6 +1,6 @@ -import Alert from '../../src/alert' -import { getTransitionDurationFromElement } from '../../src/util/index' -import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture' +import Alert from '../../src/alert.js' +import { getTransitionDurationFromElement } from '../../src/util/index.js' +import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Alert', () => { let fixtureEl diff --git a/js/tests/unit/base-component.spec.js b/js/tests/unit/base-component.spec.js index b2352d6..5b7d52e 100644 --- a/js/tests/unit/base-component.spec.js +++ b/js/tests/unit/base-component.spec.js @@ -1,7 +1,7 @@ -import BaseComponent from '../../src/base-component' -import { clearFixture, getFixture } from '../helpers/fixture' -import EventHandler from '../../src/dom/event-handler' -import { noop } from '../../src/util' +import BaseComponent from '../../src/base-component.js' +import EventHandler from '../../src/dom/event-handler.js' +import { noop } from '../../src/util/index.js' +import { clearFixture, getFixture } from '../helpers/fixture.js' class DummyClass extends BaseComponent { constructor(element) { diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js index 09ed17e..6624fee 100644 --- a/js/tests/unit/button.spec.js +++ b/js/tests/unit/button.spec.js @@ -1,5 +1,5 @@ -import Button from '../../src/button' -import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture' +import Button from '../../src/button.js' +import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Button', () => { let fixtureEl diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js index d951bd5..c468b5c 100644 --- a/js/tests/unit/carousel.spec.js +++ b/js/tests/unit/carousel.spec.js @@ -1,8 +1,8 @@ -import Carousel from '../../src/carousel' -import EventHandler from '../../src/dom/event-handler' -import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture' -import { isRTL, noop } from '../../src/util/index' -import Swipe from '../../src/util/swipe' +import Carousel from '../../src/carousel.js' +import EventHandler from '../../src/dom/event-handler.js' +import { isRTL, noop } from '../../src/util/index.js' +import Swipe from '../../src/util/swipe.js' +import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Carousel', () => { const { Simulator, PointerEvent } = window diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js index 9c86719..58c5367 100644 --- a/js/tests/unit/collapse.spec.js +++ b/js/tests/unit/collapse.spec.js @@ -1,6 +1,6 @@ -import Collapse from '../../src/collapse' -import EventHandler from '../../src/dom/event-handler' -import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture' +import Collapse from '../../src/collapse.js' +import EventHandler from '../../src/dom/event-handler.js' +import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js' describe('Collapse', () => { let fixtureEl @@ -277,25 +277,25 @@ describe('Collapse', () => { return new Promise(resolve => { fixtureEl.innerHTML = [ '

', - '
', - ' ', + '
', + ' ', '
', - '
', + '
', '
', '
', '
', - '
', - ' ', + '
', + ' ', '
', - '
', + '
', '
content
', '
', '
', '
', - '
', - ' ', + '
', + ' ', '
', - '
', + '
', '
content
', '
', '
', @@ -338,12 +338,12 @@ describe('Collapse', () => { fixtureEl.innerHTML = [ '
', '
', - '

', + '

', ' ', '

', - '
', + '
', '
', '
@@ -186,7 +186,7 @@ title: Album example
- 9 mins + 9 mins
@@ -197,7 +197,7 @@ title: Album example -