From 94220f5879be41c387841cb487035f2992b31823 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:43:21 +0000 Subject: [PATCH 01/26] chore(deps-dev): bump @rollup/plugin-terser from 0.4.4 to 1.0.0 Bumps [@rollup/plugin-terser](https://github.com/rollup/plugins/tree/HEAD/packages/terser) from 0.4.4 to 1.0.0. - [Changelog](https://github.com/rollup/plugins/blob/master/packages/terser/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/beep-v1.0.0/packages/terser) --- updated-dependencies: - dependency-name: "@rollup/plugin-terser" dependency-version: 1.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ac2ee..edb8a69 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "devDependencies": { "@commitlint/cli": "^20.3.1", "@commitlint/config-conventional": "^20.3.1", - "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-terser": "^1.0.0", "@types/bootstrap": "^5.2.10", "@types/jest": "^30.0.0", "@types/jquery": "^3.5.33", From e993a5a45326aa3e76268c4707a2f4c3076a9a4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 06:44:54 +0000 Subject: [PATCH 02/26] chore(deps-dev): bump cypress-plugin-tab from 1.0.5 to 2.0.0 Bumps [cypress-plugin-tab](https://github.com/kuceb/cypress-plugin-tab) from 1.0.5 to 2.0.0. - [Release notes](https://github.com/kuceb/cypress-plugin-tab/releases) - [Changelog](https://github.com/kuceb/cypress-plugin-tab/blob/master/RELEASE_NOTES.md) - [Commits](https://github.com/kuceb/cypress-plugin-tab/compare/v1.0.5...v2.0.0) --- updated-dependencies: - dependency-name: cypress-plugin-tab dependency-version: 2.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ac2ee..ed9798c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "commitlint": "^20.3.1", "eslint": "^10.0.0", "cypress": "^15.8.1", - "cypress-plugin-tab": "^1.0.5", + "cypress-plugin-tab": "^2.0.0", "doctoc": "^2.2.0", "dompurify": "^3.0.0", "globals": "^17.0.0", From dcc736a030ded931b42fa2b682d3fcfb73fc729d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 06:45:07 +0000 Subject: [PATCH 03/26] ci(deps): bump the minor-and-patch-updates group across 1 directory with 2 updates Bumps the minor-and-patch-updates group with 2 updates in the / directory: [cypress-io/github-action](https://github.com/cypress-io/github-action) and [SonarSource/sonarqube-scan-action](https://github.com/sonarsource/sonarqube-scan-action). Updates `cypress-io/github-action` from 7.1.2 to 7.1.9 - [Release notes](https://github.com/cypress-io/github-action/releases) - [Changelog](https://github.com/cypress-io/github-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/cypress-io/github-action/compare/84d178e4bbce871e23f2ffa3085898cde0e4f0ec...783cb3f07983868532cabaedaa1e6c00ff4786a8) Updates `SonarSource/sonarqube-scan-action` from 7.0.0 to 7.1.0 - [Release notes](https://github.com/sonarsource/sonarqube-scan-action/releases) - [Commits](https://github.com/sonarsource/sonarqube-scan-action/compare/a31c9398be7ace6bbfaf30c0bd5d415f843d45e9...299e4b793aaa83bf2aba7c9c14bedbb485688ec4) --- updated-dependencies: - dependency-name: cypress-io/github-action dependency-version: 7.1.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch-updates - dependency-name: SonarSource/sonarqube-scan-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch-updates ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64d7320..892bc01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: run: npm run build - name: Cypress run #v6.10.8 - uses: cypress-io/github-action@84d178e4bbce871e23f2ffa3085898cde0e4f0ec + uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 with: browser: chrome @@ -119,6 +119,6 @@ jobs: - name: SonarQube Scan # v7.0.0 - uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 + uses: SonarSource/sonarqube-scan-action@299e4b793aaa83bf2aba7c9c14bedbb485688ec4 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file From 926682793baa589c20a691a9457ecbab25d105b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 4 Apr 2026 15:53:01 +0200 Subject: [PATCH 04/26] chore: update readme --- README.md | 159 ++--------------------------------------- README.template.md | 171 +-------------------------------------------- 2 files changed, 7 insertions(+), 323 deletions(-) diff --git a/README.md b/README.md index 618be51..4d7d8bb 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/bootstrap5-toggle/security/policy). -# Demos +# Demo and documentation -**Demos and API Docs:** https://palcarazm.github.io/bootstrap5-toggle/ +**Please read our documentation page for a completed usage explanation:** https://palcarazm.github.io/bootstrap5-toggle/ --- @@ -50,14 +50,7 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/ - [Usage](#usage) - [Initialize With HTML](#initialize-with-html) - [Initialize With Code](#initialize-with-code) -- [API](#api) - - [Options](#options) - - [Methods](#methods) -- [Events](#events) - - [Event Propagation](#event-propagation) - - [Stopping Event Propagation](#stopping-event-propagation) - - [API vs Input](#api-vs-input) -- [Collaborators welcom!](#collaborators-welcom) +- [Collaborators welcome!](#collaborators-welcome) @@ -128,151 +121,7 @@ EX: Initialize id `chkToggle` with a single line of JavaScript. ``` -# API - -## Options - -- Options can be passed via data attributes or JavaScript -- For data attributes, append the option name to `data-` (ex: `data-on="Enabled"`) - -```html - - - -``` - -| Name | Type | Default | Description | -| ---------- | ----------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `onlabel` | string/html | "On" | Text of the on toggle | -| `offlabel` | string/html | "Off" | Text of the off toggle | -| `size` | string | "normal" | Size of the toggle. Possible values are: `large`, `normal`, `small`, `mini`. | -| `onstyle` | string | "primary" | Style of the on toggle. Possible values are: `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark` and with `outline-` prefix | -| `offstyle` | string | "secondary" | Style of the off toggle. Possible values are: `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark` and with `outline-` prefix | -| `onvalue` | string | _null_ | Sets on state value | -| `offvalue` | string | _null_ | Sets off state value | -| `ontitle` | string | _null_ | Title of the on toggle | -| `offtitle` | string | _null_ | Title of the off toggle | -| `style` | string | | Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference. | -| `width` | integer | _null_ | Sets the width of the toggle. if set to _null_, width will be auto-calculated. | -| `height` | integer | _null_ | Sets the height of the toggle. if set to _null_, height will be auto-calculated. | -| `tabindex` | integer | 0 | Sets the tabindex of the toggle. | -| `tristate` | boolean | false | Sets tristate support | - -## Methods - -Methods can be used to control toggles directly. - -```html - - -``` - -| Method | Example | Description | -| ------------- | --------------------------------------------- | ------------------------------------------------------------------------------------- | -| initialize | `toggleDemo.bootstrapToggle()` | Initializes the toggle plugin with options | -| destroy | `toggleDemo.bootstrapToggle('destroy')` | Destroys the toggle | -| rerender | `toggleDemo.bootstrapToggle('rerender')` | Rerender toggle with the appropriated size. Useful when parent is collapsed at first. | -| on | `toggleDemo.bootstrapToggle('on')` | Sets the toggle to 'On' state | -| off | `toggleDemo.bootstrapToggle('off')` | Sets the toggle to 'Off' state | -| toggle | `toggleDemo.bootstrapToggle('toggle')` | Toggles the state of the toggle on/off | -| enable | `toggleDemo.bootstrapToggle('enable')` | Enables the toggle | -| disable | `toggleDemo.bootstrapToggle('disable')` | Disables the toggle | -| readonly | `toggleDemo.bootstrapToggle('readonly')` | Disables the toggle but preserve checkbox enabled | -| indeterminate | `toggleDemo.bootstrapToggle('indeterminate')` | Sets the toggle to 'indeterminate' state | -| determinate | `toggleDemo.bootstrapToggle('determinate')` | Sets the toggle to 'determinate' state | - -# Events - -## Event Propagation - -Note All events are propagated to and from input element to the toggle. - -You should listen to events from the `` directly rather than look for custom events. - -```html - -
- -``` - -## Stopping Event Propagation - -Passing `true` to the on, off, toggle, determinate and indeterminate methods will enable the silent option to prevent the control from propagating the change event in cases where you want to update the controls on/off state, but do not want to fire the onChange event. - -```html - - - - - - -``` - -## API vs Input - -This also means that using the API or Input to trigger events will work both ways. - -```html - - - - - - -``` - -# Collaborators welcom! +# Collaborators welcome! - :sos: Do you need some help? Open a thread in [GitHub Discussions Q&A](https://github.com/palcarazm/bootstrap5-toggle/discussions/new?category=q-a) - :bug: Do you find a bug? Open an issue in [GitHub bug report](https://github.com/palcarazm/bootstrap5-toggle/issues/new?template=01-BUG_REPORT.yml) diff --git a/README.template.md b/README.template.md index 6d4de38..e4b95ad 100644 --- a/README.template.md +++ b/README.template.md @@ -29,9 +29,9 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/bootstrap5-toggle/security/policy). -# Demos +# Demo and documentation -**Demos and API Docs:** https://palcarazm.github.io/bootstrap5-toggle/ +**Please read our documentation page for a completed usage explanation:** https://palcarazm.github.io/bootstrap5-toggle/ --- @@ -39,27 +39,6 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/ -- [Bootstrap 5 Toggle](#bootstrap-5-toggle) - [Library Distributions](#library-distributions) -- [Demos](#demos) -- [Related Bootstrap Plugins](#related-bootstrap-plugins) -- [Installation](#installation) - - [CDN](#cdn) - - [ECMAS Interface](#ecmas-interface) - - [jQuery Interface](#jquery-interface) - - [Download](#download) - - [NPM](#npm) - - [Yarn](#yarn) -- [Usage](#usage) - - [Initialize With HTML](#initialize-with-html) - - [Initialize With Code](#initialize-with-code) -- [API](#api) - - [Options](#options) - - [Methods](#methods) -- [Events](#events) - - [Event Propagation](#event-propagation) - - [Stopping Event Propagation](#stopping-event-propagation) - - [API vs Input](#api-vs-input) -- [Collaborators welcom!](#collaborators-welcom) @@ -130,151 +109,7 @@ EX: Initialize id `chkToggle` with a single line of JavaScript. ``` -# API - -## Options - -- Options can be passed via data attributes or JavaScript -- For data attributes, append the option name to `data-` (ex: `data-on="Enabled"`) - -```html - - - -``` - -| Name | Type | Default | Description | -| ---------- | ----------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `onlabel` | string/html | "On" | Text of the on toggle | -| `offlabel` | string/html | "Off" | Text of the off toggle | -| `size` | string | "normal" | Size of the toggle. Possible values are: `large`, `normal`, `small`, `mini`. | -| `onstyle` | string | "primary" | Style of the on toggle. Possible values are: `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark` and with `outline-` prefix | -| `offstyle` | string | "secondary" | Style of the off toggle. Possible values are: `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark` and with `outline-` prefix | -| `onvalue` | string | _null_ | Sets on state value | -| `offvalue` | string | _null_ | Sets off state value | -| `ontitle` | string | _null_ | Title of the on toggle | -| `offtitle` | string | _null_ | Title of the off toggle | -| `style` | string | | Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference. | -| `width` | integer | _null_ | Sets the width of the toggle. if set to _null_, width will be auto-calculated. | -| `height` | integer | _null_ | Sets the height of the toggle. if set to _null_, height will be auto-calculated. | -| `tabindex` | integer | 0 | Sets the tabindex of the toggle. | -| `tristate` | boolean | false | Sets tristate support | - -## Methods - -Methods can be used to control toggles directly. - -```html - - -``` - -| Method | Example | Description | -| ------------- | --------------------------------------------- | ------------------------------------------------------------------------------------- | -| initialize | `toggleDemo.bootstrapToggle()` | Initializes the toggle plugin with options | -| destroy | `toggleDemo.bootstrapToggle('destroy')` | Destroys the toggle | -| rerender | `toggleDemo.bootstrapToggle('rerender')` | Rerender toggle with the appropriated size. Useful when parent is collapsed at first. | -| on | `toggleDemo.bootstrapToggle('on')` | Sets the toggle to 'On' state | -| off | `toggleDemo.bootstrapToggle('off')` | Sets the toggle to 'Off' state | -| toggle | `toggleDemo.bootstrapToggle('toggle')` | Toggles the state of the toggle on/off | -| enable | `toggleDemo.bootstrapToggle('enable')` | Enables the toggle | -| disable | `toggleDemo.bootstrapToggle('disable')` | Disables the toggle | -| readonly | `toggleDemo.bootstrapToggle('readonly')` | Disables the toggle but preserve checkbox enabled | -| indeterminate | `toggleDemo.bootstrapToggle('indeterminate')` | Sets the toggle to 'indeterminate' state | -| determinate | `toggleDemo.bootstrapToggle('determinate')` | Sets the toggle to 'determinate' state | - -# Events - -## Event Propagation - -Note All events are propagated to and from input element to the toggle. - -You should listen to events from the `` directly rather than look for custom events. - -```html - -
- -``` - -## Stopping Event Propagation - -Passing `true` to the on, off, toggle, determinate and indeterminate methods will enable the silent option to prevent the control from propagating the change event in cases where you want to update the controls on/off state, but do not want to fire the onChange event. - -```html - - - - - - -``` - -## API vs Input - -This also means that using the API or Input to trigger events will work both ways. - -```html - - - - - - -``` - -# Collaborators welcom! +# Collaborators welcome! - :sos: Do you need some help? Open a thread in [GitHub Discussions Q&A](https://github.com/palcarazm/bootstrap5-toggle/discussions/new?category=q-a) - :bug: Do you find a bug? Open an issue in [GitHub bug report](https://github.com/palcarazm/bootstrap5-toggle/issues/new?template=01-BUG_REPORT.yml) From 46759e0642a95204b7570db606bd14d4da194abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 4 Apr 2026 16:54:43 +0200 Subject: [PATCH 05/26] docs: move docs to main branch --- .github/workflows/deploy-docs.yml | 9 +- .gitignore | 34 ++- .npmignore | 32 +- docs/.gitignore | 6 + docs/Gruntfile.js | 104 +++++++ docs/LICENSE | 23 ++ docs/package.json | 51 ++++ docs/rollup.config.js | 15 + docs/src/CNAME | 1 + docs/src/api/eol/v3 | 6 + docs/src/api/eol/v4 | 6 + docs/src/api/eol/v5 | 6 + docs/src/assets/img/card.png | Bin 0 -> 20824 bytes docs/src/assets/img/favicon.ico | Bin 0 -> 8686 bytes docs/src/assets/img/fork-me.webp | Bin 0 -> 3010 bytes docs/src/assets/img/logo_off.png | Bin 0 -> 6032 bytes docs/src/assets/img/logo_on.png | Bin 0 -> 5801 bytes docs/src/assets/img/logo_settings.json | 37 +++ docs/src/css/styles.css | 75 +++++ docs/src/index.html | 113 +++++++ docs/src/js/components/Aside.js | 122 ++++++++ docs/src/js/components/Badge.js | 23 ++ docs/src/js/components/CodeBlock.js | 18 ++ docs/src/js/components/CodePanel.js | 73 +++++ docs/src/js/components/Console.js | 80 +++++ docs/src/js/components/Documentation.js | 30 ++ docs/src/js/components/Example.js | 10 + docs/src/js/components/Footer.js | 31 ++ docs/src/js/components/Header.js | 67 ++++ docs/src/js/components/Main.js | 21 ++ docs/src/js/components/Notice.js | 89 ++++++ docs/src/js/components/VersionPill.js | 24 ++ .../components/documentation/Accessibility.js | 266 ++++++++++++++++ docs/src/js/components/documentation/Api.js | 20 ++ .../components/documentation/Distribution.js | 105 +++++++ .../js/components/documentation/DocAlert.js | 54 ++++ .../js/components/documentation/DocArticle.js | 57 ++++ .../js/components/documentation/DocSection.js | 16 + .../src/js/components/documentation/Events.js | 16 + .../js/components/documentation/Features.js | 38 +++ .../components/documentation/Installation.js | 73 +++++ docs/src/js/components/documentation/Usage.js | 42 +++ .../documentation/api/InputInteraction.js | 167 ++++++++++ .../components/documentation/api/Methods.js | 151 +++++++++ .../components/documentation/api/Options.js | 286 ++++++++++++++++++ .../components/documentation/api/Rerender.js | 61 ++++ .../js/components/documentation/api/State.js | 47 +++ .../documentation/events/CustomEvents.js | 136 +++++++++ .../documentation/events/EventPropagation.js | 49 +++ .../documentation/events/SilencedActions.js | 125 ++++++++ .../documentation/features/AnimationSpeed.js | 58 ++++ .../documentation/features/Colors.js | 49 +++ .../documentation/features/CustomFormValue.js | 50 +++ .../documentation/features/CustomSize.js | 40 +++ .../documentation/features/CustomStyle.js | 63 ++++ .../documentation/features/CustomText.js | 51 ++++ .../documentation/features/CustomTitle.js | 44 +++ .../documentation/features/FormLayout.js | 102 +++++++ .../features/KeyboardInteraction.js | 36 +++ .../documentation/features/OutlineColors.js | 49 +++ .../components/documentation/features/Size.js | 36 +++ .../documentation/features/StateStatus.js | 65 ++++ .../documentation/features/Tooltip.js | 61 ++++ .../documentation/features/Tristate.js | 32 ++ docs/src/js/main.js | 16 + docs/src/js/tasks/BootsrapDarkmodeToggler.js | 9 + docs/src/js/tasks/BootstrapToggler.js | 9 + docs/src/js/tasks/CodeHighlighter.js | 8 + docs/src/js/tasks/DOMBuilder.js | 17 ++ docs/src/js/tasks/LogoSwitcher.js | 16 + docs/src/js/tasks/TOCBuilder.js | 13 + sonar-project.properties | 2 +- 72 files changed, 3620 insertions(+), 21 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/Gruntfile.js create mode 100644 docs/LICENSE create mode 100644 docs/package.json create mode 100644 docs/rollup.config.js create mode 100644 docs/src/CNAME create mode 100644 docs/src/api/eol/v3 create mode 100644 docs/src/api/eol/v4 create mode 100644 docs/src/api/eol/v5 create mode 100644 docs/src/assets/img/card.png create mode 100644 docs/src/assets/img/favicon.ico create mode 100644 docs/src/assets/img/fork-me.webp create mode 100644 docs/src/assets/img/logo_off.png create mode 100644 docs/src/assets/img/logo_on.png create mode 100644 docs/src/assets/img/logo_settings.json create mode 100644 docs/src/css/styles.css create mode 100644 docs/src/index.html create mode 100644 docs/src/js/components/Aside.js create mode 100644 docs/src/js/components/Badge.js create mode 100644 docs/src/js/components/CodeBlock.js create mode 100644 docs/src/js/components/CodePanel.js create mode 100644 docs/src/js/components/Console.js create mode 100644 docs/src/js/components/Documentation.js create mode 100644 docs/src/js/components/Example.js create mode 100644 docs/src/js/components/Footer.js create mode 100644 docs/src/js/components/Header.js create mode 100644 docs/src/js/components/Main.js create mode 100644 docs/src/js/components/Notice.js create mode 100644 docs/src/js/components/VersionPill.js create mode 100644 docs/src/js/components/documentation/Accessibility.js create mode 100644 docs/src/js/components/documentation/Api.js create mode 100644 docs/src/js/components/documentation/Distribution.js create mode 100644 docs/src/js/components/documentation/DocAlert.js create mode 100644 docs/src/js/components/documentation/DocArticle.js create mode 100644 docs/src/js/components/documentation/DocSection.js create mode 100644 docs/src/js/components/documentation/Events.js create mode 100644 docs/src/js/components/documentation/Features.js create mode 100644 docs/src/js/components/documentation/Installation.js create mode 100644 docs/src/js/components/documentation/Usage.js create mode 100644 docs/src/js/components/documentation/api/InputInteraction.js create mode 100644 docs/src/js/components/documentation/api/Methods.js create mode 100644 docs/src/js/components/documentation/api/Options.js create mode 100644 docs/src/js/components/documentation/api/Rerender.js create mode 100644 docs/src/js/components/documentation/api/State.js create mode 100644 docs/src/js/components/documentation/events/CustomEvents.js create mode 100644 docs/src/js/components/documentation/events/EventPropagation.js create mode 100644 docs/src/js/components/documentation/events/SilencedActions.js create mode 100644 docs/src/js/components/documentation/features/AnimationSpeed.js create mode 100644 docs/src/js/components/documentation/features/Colors.js create mode 100644 docs/src/js/components/documentation/features/CustomFormValue.js create mode 100644 docs/src/js/components/documentation/features/CustomSize.js create mode 100644 docs/src/js/components/documentation/features/CustomStyle.js create mode 100644 docs/src/js/components/documentation/features/CustomText.js create mode 100644 docs/src/js/components/documentation/features/CustomTitle.js create mode 100644 docs/src/js/components/documentation/features/FormLayout.js create mode 100644 docs/src/js/components/documentation/features/KeyboardInteraction.js create mode 100644 docs/src/js/components/documentation/features/OutlineColors.js create mode 100644 docs/src/js/components/documentation/features/Size.js create mode 100644 docs/src/js/components/documentation/features/StateStatus.js create mode 100644 docs/src/js/components/documentation/features/Tooltip.js create mode 100644 docs/src/js/components/documentation/features/Tristate.js create mode 100644 docs/src/js/main.js create mode 100644 docs/src/js/tasks/BootsrapDarkmodeToggler.js create mode 100644 docs/src/js/tasks/BootstrapToggler.js create mode 100644 docs/src/js/tasks/CodeHighlighter.js create mode 100644 docs/src/js/tasks/DOMBuilder.js create mode 100644 docs/src/js/tasks/LogoSwitcher.js create mode 100644 docs/src/js/tasks/TOCBuilder.js diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 7ab8d2d..73ec8ab 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,9 +2,9 @@ name: Deploy documentation on: push: - branches: [ "gh-pages" ] + branches: [ "v5" ] pull_request: - branches: [ "gh-pages" ] + branches: [ "v5" ] workflow_dispatch: permissions: @@ -19,6 +19,9 @@ concurrency: jobs: build: runs-on: ubuntu-latest + defaults: + run: + working-directory: docs steps: - name: Checkout uses: actions/checkout@v6 @@ -35,7 +38,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: - path: './dist' + path: ./docs/dist - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 53e439d..1136f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,30 @@ -.DS_Store +# Node modules node_modules package-lock.json + +# Packaging +bootstrap5-toggle-*.tgz + +# Debug +debug + +# Dist +js +!src/main/js +!docs/src/js +css +!docs/src/css + +# Logs *.log -bootstrap*.tgz -cypress/videos -cypress/screenshots -cypress/downloads + +# Cache and config dirs and files .vscode .dccache -js -css -debug -coverage \ No newline at end of file +.DS_Store + +# Test +coverage +cypress/videos +cypress/screenshots +cypress/downloads \ No newline at end of file diff --git a/.npmignore b/.npmignore index 9406777..95284e5 100644 --- a/.npmignore +++ b/.npmignore @@ -1,14 +1,30 @@ -.DS_Store +# Node modules node_modules -*.log + +# Packaging +bootstrap5-toggle-*.tgz + +# Debug debug -img + +# Docs +docs + +# Logs +*.log + +# Cache and config dirs and files .github .vscode -.prettierrc .dccache -test -bootstrap*.tgz -cypress +.DS_Store +.prettierrc + +# Cypress and testing +test/* +cypress/* cypress.config.js -README.template.md +coverage + +# Templates +*.template* \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..956b179 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +.dccache +node_modules +package-lock.json +*.log +dist \ No newline at end of file diff --git a/docs/Gruntfile.js b/docs/Gruntfile.js new file mode 100644 index 0000000..9491fea --- /dev/null +++ b/docs/Gruntfile.js @@ -0,0 +1,104 @@ +module.exports = function (grunt) { + const pkg = grunt.file.readJSON("package.json"); + + grunt.initConfig({ + pkg, + clean: { + dist: ["dist"], + }, + copy: { + assets: { + expand: true, + cwd: "src/assets/", + src: "**/*", + dest: "dist/assets/", + }, + api: { + expand: true, + cwd: "src/api/", + src: "**/*", + dest: "dist/api/", + }, + cname: { + src: "CNAME", + dest: "dist/CNAME", + }, + html: { + src: "src/index.html", + dest: "dist/index.html", + options: { + process: function (content, _srcpath) { + return content + .replace(/\{\{bootstrapVersion\}\}/g, pkg.dependencies.bootstrap) + .replace(/\{\{jqueryVersion\}\}/g, pkg.dependencies.jquery) + .replace( + /\{\{fontawesomeVersion\}\}/g, + pkg.dependencies["@fortawesome/fontawesome-free"], + ) + .replace( + /\{\{highlightjsVersion\}\}/g, + pkg.dependencies["@highlightjs/cdn-assets"], + ) + .replace( + /\{\{highlightjsBadgeVersion\}\}/g, + pkg.dependencies["highlightjs-badge"], + ) + .replace( + /\{\{bootstrapTocVersion\}\}/g, + pkg.config.externalDependencies["afeld/bootstrap-toc"], + ) + .replace( + /\{\{bsDarkmodeToggleVersion\}\}/g, + pkg.dependencies["bs-darkmode-toggle"], + ) + .replace( + /\{\{bootstrap5ToggleVersion\}\}/g, + pkg.dependencies["bootstrap5-toggle"], + ); + }, + }, + }, + }, + exec: { + rollup: "npx rollup -c", + postcss: + "npx postcss src/css/styles.css -o dist/css/styles.min.css --map --env production", + }, + watch: { + assets: { + files: ["src/assets/**/*"], + tasks: ["copy:assets"], + options: { spawn: false }, + }, + api: { + files: ["src/api/**/*"], + tasks: ["copy:api"], + options: { spawn: false }, + }, + css: { + files: ["src/css/styles.css"], + tasks: ["exec:postcss"], + options: { spawn: false }, + }, + js: { + files: ["src/js/**/*.js"], + tasks: ["exec:rollup"], + options: { spawn: false }, + }, + html: { + files: ["src/index.html"], + tasks: ["copy:html"], + options: { spawn: false }, + }, + }, + }); + + grunt.loadNpmTasks("grunt-contrib-clean"); + grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-exec"); + grunt.loadNpmTasks("grunt-contrib-watch"); + + grunt.registerTask("build", ["clean", "copy", "exec:rollup", "exec:postcss"]); + + grunt.registerTask("dev", ["build", "watch"]); +}; diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..8687b23 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) + +- Copyright (c) 2011-2014 Min Hur, The New York Times Company +- Copyright (c) 2018-2019 Brent Ely +- Copyright (c) 2022 Pablo Alcaraz Martínez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..4669ea8 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,51 @@ +{ + "name": "bootstrap5-toggle-doc", + "version": "1.0.0", + "description": "Documentation of bootstrap5-toggle", + "main": "src/js/main.js", + "repository": { + "type": "git", + "url": "git+https://github.com/palcarazm/bootstrap5-toggle.git" + }, + "keywords": [ + "documentation" + ], + "author": { + "name": "Pablo Alcaraz Martínez", + "url": "https://github.com/palcarazm/" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/palcarazm/bootstrap5-toggle/issues" + }, + "homepage": "https://palcarazm.github.io/bootstrap5-toggle/", + "scripts": { + "build": "grunt build", + "dev": "grunt dev" + }, + "config": { + "externalDependencies": { + "afeld/bootstrap-toc": "1.0.1" + } + }, + "dependencies": { + "@fortawesome/fontawesome-free": "7.2.0", + "@highlightjs/cdn-assets": "11.11.1", + "bootstrap": "5.3.8", + "bootstrap5-toggle": "5.3.0", + "bs-darkmode-toggle": "1.0.0-rc1", + "highlightjs-badge": "0.1.9", + "jquery": "4.0.0" + }, + "devDependencies": { + "@rollup/plugin-terser": "^1.0.0", + "grunt": "^1.6.1", + "grunt-contrib-clean": "^2.0.1", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-watch": "^1.1.0", + "grunt-exec": "^3.0.0", + "postcss": "^8.5.6", + "postcss-cli": "^11.0.1", + "rollup": "^4.59.0" + } +} diff --git a/docs/rollup.config.js b/docs/rollup.config.js new file mode 100644 index 0000000..dc45157 --- /dev/null +++ b/docs/rollup.config.js @@ -0,0 +1,15 @@ +const terser = require("@rollup/plugin-terser"); + +module.exports = [ + { + input: "src/js/main.js", + output: [ + { + file: "dist/js/bundle.min.js", + format: "umd", + sourcemap: true, + plugins: [terser()] + } + ] + } +]; \ No newline at end of file diff --git a/docs/src/CNAME b/docs/src/CNAME new file mode 100644 index 0000000..e52f285 --- /dev/null +++ b/docs/src/CNAME @@ -0,0 +1 @@ +https://palcarazm.github.io/bootstrap5-toggle/ diff --git a/docs/src/api/eol/v3 b/docs/src/api/eol/v3 new file mode 100644 index 0000000..efe16d8 --- /dev/null +++ b/docs/src/api/eol/v3 @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "EOL", + "message": "ended", + "color": "critical" +} diff --git a/docs/src/api/eol/v4 b/docs/src/api/eol/v4 new file mode 100644 index 0000000..a92a108 --- /dev/null +++ b/docs/src/api/eol/v4 @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "EOL", + "message": "critical support since 30 Jun 2026", + "color": "important" +} diff --git a/docs/src/api/eol/v5 b/docs/src/api/eol/v5 new file mode 100644 index 0000000..51ee0a9 --- /dev/null +++ b/docs/src/api/eol/v5 @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "EOL", + "message": "active support", + "color": "success" +} diff --git a/docs/src/assets/img/card.png b/docs/src/assets/img/card.png new file mode 100644 index 0000000000000000000000000000000000000000..da0084cc068914fc6aec0c69a4b54cc997a3e2cb GIT binary patch literal 20824 zcmeFYcTkg2_dke;0-_=yAR-`06#{}t3q_G8y+{pJx)6|FLJ<*=B8c=75RfJqf{XqOdCM${5n;TKmyO7Z3BSqTJ!^PRmN7j1u%YK#Z= zaYDU%n5qqO$~#P9a_5l$l~qb@``w$LuwP$UK9i^VLVZpCaZGngslLGHpU^ZPLwS{G zgBe5llB61bY{>-jy&*sM1#TnjM14qdt`O2#XpcELDe+gG@v&>v%J#q?UP)hMt~D=n z5!LoxBh+YPyXnUUfrZ%8>dVu=^Q|Pr_hNOmc%Dz*xLS>Q9Py85 zc~RU$JuqlxhY=;h?_MKi?%Wh76C;Y4WPfRC zZFCXdMqF7aDKZM@+a>e&h--}m6hRX5)#`|ul$ogG*1g9t;nJYjmPD?C~LEDGYZIDu^nED-jU;{_NlAA(iigU-OVgOlaY|5Kl!f& z;~U2y3&B??W?2ATrs{d!gZwEcgh&c{u)KzFr4cQk z;i5-EQWQrV9KesjV;|N>vH`g2&cawIlPtpf{b?;f8xsP{b1ZINJ_>Iph4{u+g(@(K zq?dPT*vuJR-E5(8X!N>9@`IHq18#ZT;3Fd;eb$wh(|a(K#S(<=j1x77kHTX}L4Hs} zsn9yI&!5Mwp_gVUaKkI}G3;#;k_kFritT<2A4}gk2!LW+tY8|(N3H|(4FjEMU86ner z0@AwEDpyt!p^PBR?rT;hzxmhn1YwTEO@?vH8&B;G3cafPb=*`Rh_*0sJdrch^Q-N? zNNR^qfR|5iepD=M!p0hU0zU@?B|z;Ki) zQ}$?aw?{^qIhgz1(F4*Zx9~MGXnS}2B;6EsgtxUr6N|U?IW&E69@J7A_}BIA$ftIA zyWE{PpZ9mYym=%M*DQ4!P78Mk^VV)=QxAOe9z85SAZH>asNn*AeK*sp4-7|`0TCt<5LikC77no}dfLn$<#9KTo~2qU z?rO)_8VVpKC=j92{(C$KEJJU!ic4d}Ujy&g^O)OnRqfe`BlFjeE@MT;K7-v(?D}fU zA?#U)^K3K9zniZsq8h+ga<>UXd0x_5iN;R(FHZAIA%NfaP_#fFj3)*F=oL#aoK2dX>7t^XGt##;`t6z0<{ppinE98vtCVeqf`s{duCVr<{$?u4Ab&FLIa^Dd4}8VjZ_m6ZwDFOmrY@l8$c z1QZPK-3^;;T8LaA1RYr}4ZSaweQhb;_FHYdHFr<-7c$lh9U3eXckd?j_1Z1FV5$%5 zu5^9Ld4eG_h(RhB{DIpS^%A%RnIF{3pByU_lT_B=Ev06i9A_dq&Eo#PX z#QMnq$5DM%TH}8;Z#rb5$Lidz!f#+AJm#1s;qHH$qqgIOZ-@r`V23fktVmp|;dQ}y zR~vjAYLfNRghG<5w!DHT7t;hb8BiUvd|Ob{jl&uJEb{$Vme)}mL>nxMAKFu^D?|8a ztx?}Yp-7oXA%hM&W_5Eoar0LbX!4BNSkpBFCOh6MVSZ!X-lYwq~Z& zV1?zikUN&IsYZ!LV#xV08`J)jA)fKyUP5Id>~EsY=r_i7QVwfPJ8gRjQrK=mUX&`xgUo zdU$kghL{FrNKz>$b>-Y|>NnS_lkrs^xe<3jhw}fpO%N~km^)AQfCM+hdW5i$iM5IT z$6BH6S!W)o5!ljwyrKWByF%Xk_S>_unV)1udA2 zyusBF0*>Z}CH1HYJ8JmUY?d}c?`>Hoi+)kmOZcO286hv2I*_j0-;jGJ2!3*jOVYfu zZzOJK6p4Aj^NP!|R&kbOj(GGO0>`Gf66r0EZQ)rzoc~!9&R=#txMZ;I=>a2ZVflO( z({CU}5G48@RGm(ke5dj}KsqC;(_=haKUSx8t3`iGW=p}GDMAn!yt_Ygge8cZ#4xf1 z-@ULcd3NLtDBU_su{xmG)-nz9J%xfMTf~u)kX1swL(|4moPki0?(yZ->6{8`YX-b1 z{O383`4_VJ!g++9OxC5KPSM*hs@A@Lf@^~-z7*em%NRF%GP}D?bru$I*}xbAYG#Ha4euZ_go%ePK7ZW4ZguM+?hHkWAK<_2_pHqx4851>H( zoWkZW-Wg4$&~F?xURoxutgn`qKHPMbx>7B8r)vRV~?MzN)Qz zDE|!hoU7ShckqwiSZ?L_{iA_a@nrDsrP|ra%~r6acR4>xvWNFSzbas8)aJG~ee@&c zMQ+;iEiOXWp7#CowMKV$3-3#S+=HlbpBp<+z=5*{N1cPq@osvMSdkraxO%^wkY%{Y zr|Dy`>L2%BB%5nJ@7^!(tDdNyP*=yrC8?CNp6*&nT_3`xUx@AVe8;Uo_o=wqi`KL8 zh1<+J#btPr{jOY#`P;1(docDq#=`&FEqQ8}=`xN*F^BAxdvG0&yuOxG96 z21PzdRfKhsg5GV%3bDkAl^2snO8J?T%<$=2@giCM5#Otz{;SUS)Ax47G2k-83lK+@ z1FDA&Tkm?>c3Bu9gpeS(K+|vY4k&V*YRMfta2Aaf1yw7!Cy|2WFT>@eX*IHb{T=Bn z%yhl)4klu?QhOwB;r*|%*e|+5i&2+z9h(qOVo&KEod+}_Kr)9>^tTj)TP0Y}l%IpuU*vi&tSVtGd z=A|;XD>^+ynSLJ#x*Fl?_BXW>iip-CC3HWbyK9nO{(HC2UNXXBr&@xuE8|Cl z*47mQhYWwV>(bvkxC`Ul$<&_0=!1UDae$H8^kJ#I+d0nDoZASuiIGuOJx1F0Tg;LH zf0?${qLKs`Lt0nsrgGs$>3I=Z%&ezlbe)VtnkCCi^AOo;YzZwY-67y$1{qPPrH_7< z6}TC;@D&ov5I1kZ8Dh2)+UPiTy>bIDpi1=En}p`PkroG%m(67G4g+1uaV-a@Eq7(+ ztTF6iuq=!!o_G1Ne;L~?5;L`T_`1t18l@@>_V(pOsjGz2w59(m%ag@TS8+8GzPmua zjIQEEucorgm5?Vf3OOtYF9zh^yzG`to(H~J&<8Mhr{W-4`^VL6krsH|=`awi!4f6Y zq=KDuvEa$^jB61GZo6DhTLze7>?V(OYm!XjWhByq8bvY!XjpYaDyvtJ4o$K zNV@3<)=cr!3@gcLPp19f;s+vPS++DMACD}qrI9R@Z>8?wW=%8%?i^|h*V z;Ykq)?Rlg7Au`Z_;IuLy?dTU7r2QTV}vpjBY4 zE3@*^!yq~Rf|0E)gR8>m6eopnZC%r&rYd>b0jEUqIU4Q{3LUc*qMaUxf5my2qJXiB zP2#aW&FGzr&u7rekHESU5jotVr-Ys;(---t`#k(Q4>X!iGTLh>JdSC;YHaNHBnVnR zX4$SZOBq1E-k?Qovc0*AUWyh<19bglj@uL9le6zy2OZ&}Rq9(wN}`EysMdK|zYI`4oy-0`LL+w4Zo))y?1bCUgyv6j7L z$B^K&A7i0Txd(lqry~vcTAsY^M^%_?Y2kof^pJ12f?U&O$1eWr@U$Z;m2$?5> zIWI;g=g2lzS(ErUi>6lMXIOH`Jxw?-B)W-BxC?p?Ba$sp?7>0d2Mj2CYl`Iz3=x;&h^ za;JAhWQ+R~7cEL^HG~Myrt>JoLH&sr#_C`P4LNLY$kB=h`Aidt|&GAWOAc ze*3QC!ehl|m@Fx1W8<);RGpi)u&>8`0<<$ElgurkC~rk{S^EmUPZVqd(@{163jN7@ z-A!vpce-u(!{dUG zu&bi;vrw+c_yacsREOrjsp;(scX-HFSM;UUE@=#pyuS8bVA#HZ7nhPN9}gn32-jy02t z8Kc__=Ea`p&z1;0vF+m(ay=zb7xb{!o@Mkua%Jei&l@8>cpg#9nlEFhlT|F8+S3^b z4HA3n8}H1!hcPE5>j$YYb{^@JdpW(dG4^M&8A!VeXPh`}RlhL%Et81wtFOT(;P zkWnKqDZ1(a<8D$ItV}QLt`U#vnHp=>{X4-9TM5%Ofk}YVH~z37!;&TKP6;L4{U&Cz z9?>Z+!-J{*`O@^GItzA63_@$u+Fvf^N0!DpVOMPq3Ccb$=qqY_$*h^i0jFd94ezTb zZfuez-wKUm0ixHkN}D$EHDz*)Wht3A^7FgBK7rRvG|Fq7b_I}tsLdzVk7QV5TUrYx zoXQd&*vdQ%GcShL*gZKPObW5KdLQ4b3*#gO6;IwT|0~ZN0Ky;=3#!5)s}2{p4!=2?yW2J62J=3Q_^_E;g&&Xrc>Bv zkOf{@h39;@2hP+hV8api=?29qqpVN-J1d`&LPW&ZMKXL0zIJooqHDF}Xq2$1gU_*VHz8~LG+kBUFyt3h?5Z<>!#IxVi!r-UF0;1QFD4-I`5*xdI1Oxi9 zgiMLjlIN#<^WFxx5%RXApvlKWk>39hSa>8jR3bflwW%T9(86yksL&UTioSraJ zS^!LH{KUN;u~hu3;xjjWH>gPL-Ev#9q~cV*w=QI2Zbueup=4R2asQJwT0l9?a-X3Y zZ#2nF5|$mX$|Bk1K$O&h>+Fy)&$oVCNvl7VW0TSIx8Uh9iVO~0alfn7@VO#&>ZVrhTKlnj;D-KWWc|^m^paTL zF=KzK-d0OWI)y->Lw57T+~8k2gta^DFDYoRA3i%ut7Rf>r0)>wXc2TpQ}Rs;yq6k0 zLWAJI;RVoTP3qBSrmP+*ZQc_(pL4kBY#FZ_1p2MbBiankYlnJXdF3jWry0Myhe~=# z3JmqbE*I5r?V?&mT5^<*7qe?V>Z87Qr2G=hj)X+7JC0WFnLIG{=}Gsj*N&gs@S@y~ znWKn!)g0I&jZJ^{CrA)@1c8ya>L^MUqAicd!;RI$Qj+eYyyhW#uCZe6i-(@S(4}uo zh?*19=gq4T$%X=@9bB8dP5n=vRvd#ZB*fTN=pR~AO-m5N)@jc(>Nh8nT+v)Gvz$9o zG!^ed9A*-ay5UdWv5=xCZXPJ`-F^OA=YKS(+@>+0N#@%v=(4fjP16ya!Ka!B4+D!} zjA);+qZE2gUHYeL&FIsHCcwh=#MPNBQ4YX%J-^U~h#rMa2O zVntpt>9>UV`Cui7e?k3e?^~rh?N$|QBK4cDhLz@;I%7WFU_?c&)%tw_W0rrZTk*un zIh>r{MTl(deHDQiF(Cd_u>>S5Mn7$}v3N^#ai=cI=laVvAP6H4@tQb3p?a|158}9U zo0K1h9>_<7X;25x-@&S5frcU20nH^-i5E#Swn`&!t70^1q=t-UK69`hiyqzDrb&Uy z`TC(M%I`{7Vb;GY-M4N9dx56M9Vq)bVD^r!s)L4N3O`pNLM-X|HDO*^uB=A#vzcHP zf%-OY%37)J95Ic`6%S4O+26EHQ5S$FN42a5g%&l-rcLdQ!^C&6Sdm7BKo(MvE4?Yu zZScC^5ZC_m*Ixm%0I0$ON!tl`AeVQEUff5Z4*=w#Uym zndE()<@ZAx{NRj4LF5pkAlB`f8^_~LV=)ijsx`ZnRTPKWx^Dzfi2yc#MUMU9VBw&> zZ)7B?6#{)cR5R0z-n3wi5j*_dZ1K9OYJlN@h(*c^g`Yb|H}tSmo4C>~(}Q=sTT)Hrkc++NIs{&&ruTX=4ct5! z<9EdFS0^mJ6GlQGIWeM=rwRt&X#2-6N$)QWR{eHkHLvS7U0&Bpd{Y1X)NrApqS}V_ z&COR8*nEv-LrWnf2eJ{@;t5aqb1zf|8uZLs2KkA~*=J_~M}37X^l+z65w+iJ(eCO6 zy$>fXx;?2FiyXQw*Rb^gaAe{cpQlSTHT57mS0a_QsO9jJ z1g8c>2mArit@@cO-SfxrcZ^uvz}+L=_f<>u5X|Q`ID*B^7hTw0PPnE|uPBnDjunxN z5a6k{5i}+X0n=jrr#4#o1dt;e1UM>rmWW$km8Of3#TE$OH+mSBuxL`<_A^Udmfa;f zb_F~(+G8194{`84w<3J%l-gJCH!VJKHWoMKTf9G+>lv({0^k>_SF;3eY14%Z8D9byA3bUYfth&FJEw z>`GMMs93G*w|E8)Ma-~3dYfkb_BkkMSR_*5E!>tCrRVzd{cS+Hdt%~X=p3+^g>j2F z&-OK6h4_bP&3Kllp~RJG?3Zo!$%!FBqWJ>9Gzp^y4OBhP&lZUdsLlFncOP%r$px$4 z)Mg%DKX}@oTH9pHLJH(hTB_4^q-U-sp3OGet^CRmn;m=>Xq7}kB5p+qvbme)2~dxh@GL!Sea5}A-12w*>Ip)8)VR;XUhk=9}>6$ z2VR;O{b*vY`1XDv?UqX=Q&wITV~e?k@URL){6=DE8s-U!*yxul+@!s*Pa-FZBH+AdL)e``u6eqWRYn zkNQyx>Crnn)SP##g>vfe@d8;jpt~-AIkG z%2LK`H;Z2z1CIl1N6iBRYi*;OSAu1wFAM+d(;x8)5{(wbG}_lY<&9pCVi8UMbu>*9i2e>AQ7cxjrAvmUg%gfR#7zqc%4c#MOKr17!P2&xm$~ zH}^o~_8eA)6MH)tOgKDESz5Q>HI-o8uXfijyr~gylG~E`F8)U56_&N?t3=3KiW>9k zDFG6tebIV@!{0tPl6jAZX9v!viwM6DT{DkaoU%9GEfSlQpAv0A|LkXYW9gcE$DV^f z9bY0@AOp-wW?D*P0TYhv<@AXqul@r-d89ffu}J%Zj=X(E>k*BSk(x}a@p5tgIG4R1)8tt%Ug1a zfWgoL!4QC60~=xr`zmhcZ#d5_`I5cV= zs6_Cenv5u_9Na_cY=5x-;O4u1G~RHl$K&o$WSoas;Gb8JU1Ff8VE${6oLjRl1|@NB zTelL9FsH^W<}+vIeGB2cCYJY2W5rUCMT$NX4KbF`p38cQt{fq&sE0w1&*`JPYd@O~ z)I4@&B?bL_9h_Sg!J$Fd+JV${=&+gmrrmZNzbS3GGKg8vbR)Nk4jzOdsiP zO@26V%Qs4EQVAftLKqJ=u6EH*_;DcZe>%Qf5`3uQQvXTBg14p%{%k~%7)DWaTa6Xq zqf`a@Zmq?w_*YN#%xL!{7Yr*xiB|Yu_A=Yd2l;WUp0>;6bbaYh^3+wxg_gZbcMy@~ z;qC`hYzXs?c0OSH9sTXhUmO`F=wG5{xfMd|x29x%x)i(55cH=MS5f2N%B=3n+=>|8 zwD=X!CyN|8Sq`c0D#}XwBD8gusA5WFYuGnT=2OE$!~jjiqq48;h8uW`&~-4x$KcZ0p$p%z%Mgg#-aCug8vBj z1P%kc93#o#+kJn(d9>$6%HLT2lZf2!iDUiRAs~BkT%rXJ8cCmDOA^gE#Zbpr^)~z*55Ei0j*@ZsswySw!9(!q(nQ zrN;=_@$H3M(N511;bl}#=UG=TSVliuEC(=z1S%mZuOO~(#zMI-c5Bj3LU}Yet6sY~ z1GNNF<#M;azf|dp45FiijT{AaRVtnsYiQdAD$2a?Y-dG!u~93I;rROdd0Q|l=Ns&T zd$f?}tq*ELS7r=mLd6FkE{Z~rp9!XHYAn9FsF+|o-4QHWGH%}96Na?Dh?1#IF=k!e z1-^PFL&^Ur*ZPlDc^xOV9Wdh6>%nH8a=GP!8&b23z8wz$QHAZVs9HHB183Z2@8^%$ zkC6&(%e9Cry9PeJkHUkLuFT`yJACl2x($bOD&Mxw^GuI@q{?w$Iutj5p`{Io+m!NB zW&aBU=+`YD>rCda@p>ogT}GpuSKjfI?_f5hpaNJV4@+g2e$F=Z(k+Y%-hG0!sjuqSP!Vq2 zxI$LJZ7XkENp>(qy2;~^L`Z@`D5ovCmuhRq)W%?0CfTssc`}mM{9G?CX3V*BUSoIkd*58c*$H;l~hBwF!4 zs*-0J>~)*u4qE=pcFZvOyrv!s0(gWO=mReYVu`G&r6zh+8gs%#{+327Rj`Gil2o>f z(^ZLq$wkZaakGUFRBR&h#$q$2mgcK5Mg)$FdYfka%|7{fy_*#pW{cmb@|=&b!;ZO; zZ~q=2+oj{v@tJOb!wbkhq4B>-k^Hc3#k{U*`voiN7|A^B5JekG6 zUGV%EC2YW}VO}3YJwo;uIHa^7QQn)>Ay~zr^IkXatE=;c5-XmA>i$Iddo{z=M3JHj zKIwuFS7z!VGI}%higlw4xAgA;YAE}ky*?X?H793O?vM@YHccvgQ5lh>q*d{z^GdF6 z=Z*R!bTJqn*gfY)d3ok6D=Mwbq@9RE)1<$vu7zpL<0q#uXRp1IR9$HnZ{L~ z^z#UsIf8EW80%Q4k$Kngz+dWe)rcgj_W|%3hRioVRD6fh8Ks0NMF0;6R2SAlrJ=N8 z`mSI>;o?J|>d!IjGxxIr?M2S?aUkgv+Ul9REiqGO=GNUoXx-tnT<>J05%PP3$gkgo zomy>SeTE5(X;Fw&wn7s>)fb^#YPzM~#l2Ixx16m&dx$sNtIg8+wc~4tzpgq8Vy;I& z(87@Qc037#bOBeen$G!S^AlhkA!YyDAZxB}tj(VN;jYUG#yVMxdh;KdGZAVnsrzO% zPc%DT*?V~!12(CXIi-%8IGVc^SVG$Z)^4RBAm0XK#7^zK<_HK}y3mX2oS~1PD9(`pV?k6TT4#4no<7jGozP!&xVtMgXbI80h9Pg}h^yAu> z&m?0zf=d%V`Z^JspE1;~WCuAv0Y;V^(tXV3yE|4Of?;4)Cay+TnHr@$!2WUuPuz0-pbS z%yoqejurFUOxuLVq!hc<80jtCi=dXKqjdE9khGw1RQSNdP@vXn%Qtcci12LwzMEmJ zkq}uCyu4l?7l6ys0rGxcx!j${H*t$AdVB(c<8Q!IxxUVdqyl0qRiA$8*KjTnwILx9 zyoWmALVYHMRHt_siWbO3`NUGMAE$JZKl+ zAnaq09aZH^IubE9itRJn$FP*1`(jqnE5~H%WG{g~(Wtk{#=&>UK1^2Rqd%0}h=V zWAh9*6+z*14}uE|4up<16G>p_k6~+S zAKS{iYaDcQ>!P|7RP%ifzsZOyNS|Wuxfz)7=?oc|-3aAe7D0PJ-k^|BBcLTqJD}P< zMSM>1>-0Xw_N{$!>oDhJ4G9vGfLE47DI_6g#g5mq;WGw3_j7JU(zcnL8rr#8B&q?Q z#8nUGjk_tvm4qd~@n#H5l_ol)IuxpD&*(HrYE*`k{1h--#0(y)5#uy1Lg^e|>-EB* zc&-E-nBn3|g8Cm-#}jiknO-?NIJx`zRS@Q!E6W6U`#$8{u{C8~P^k~k<~#8k$y}5| zRE46nWk1;7Im7EKKNt7XIrCy6x)R`(>R~0ZB-mS_24R4VDNVWA=k5Nogwq`$|A)z1%8Rsh!3 z5>CX-P$KsWyhGze|6Y2)Le5N9eJBXwEZ7dHtEM$tF<(@7v9J0T&EC}EIn`ymK;jkHN@O#Y=UwT{cWvBGw> zR?EFGXkujDv9RB4>%pHRhr({-&$ZHv*|T2td;S(GM20zVq3Xc(tlg#LMDtyBHJ~*6 zvS`8Y%eA(5AKVD=R!*nJM;S!QqYNWtCIh~zTqY!Ef9Hm7Uu~x&BZ7C;h*%@3z!@5r zJiI|#!rc_uHwiEdk$K$s6mCOK@^OVoq8GQL9SlB4L`3{M;@Z%T*^0#!k1dB8GJMM@ zzuSM>iwqavk$h;Y>ey(iW}l&N$>T7RRrU2VBgB3wY3`e1LX0Nv`rPED;X>t264cE9 zV#znPi4p0%R~FPl$A1>W<{!npyGVEah~NI-ng5C6%^QtG4Z0uyPoV(3*8zQWJ(!Ro`qaQy$)-w*8u7Jw7Bf z(%H0E_4$ZKQ~FrU>@JZNH_Cj{ZuYOGJ|_bqUjH}&{0jhS`gp|BUqy|S>kJv%B^RHv zk~C7R_3qtOAv~cO-^B7kVr*U>3coP-%Fn(NS+OF~ndZn|IGSX|F2}1Ig=bG^W z%L}nL+vxnE;(G1;xv{S#1AdeXefnW~pFzbEXTX-JJfHCF_ro9Q_fSg+O^3c&qeMiu z-AFetX_G&P{r9Z)=Mtl%oL;f_(56M%3PY;gn$^nr}9TBu31Ddy&nci2g_YhL+6c+h*ms z^`u|sKt(q+_EI0=R=wAe8ofBLAUt30``ipS2o7aq3lwkMDLHga!a0j9L*c3p_7r!n_Vj;fv*E+3isZkQH`?zqDv}JJotJA`e2J*NZRBc1X?D$`D<+%p>YX4 z%;*ZhE#&$a4xCEp^wh*w2FP8M#$-jaUI1LEoPvdb<^q6`KX%O`n$0e@?{2o-@D8^Z zLW~&pcex;;ho6t?7K8!RsV##CKf8h#SLy;)jP4c0EKVU7 zTi5$S{T$Fhp18qz84SAGOaI%Q@HQn4oRK)>2Vh$BJVx$H*b$iLEqsoTNEbK;@sWhL zcvgN7xi}vDn>h)5gWZ zCc~QCUtt?#Es2ZnT}pc=nl-C2Rc+n;l_&q$yEdj4v7%?jJ^zOmwRHPQ@m^y}kY=y02Fn&EZf!QIxFbQ9hg+Oui%zt}*ePPC4l zs1JG!OtT6OxXp?%#1gdUj-35*W)~Vmd6|)owE<5}q0?`{>QZ(audV zK%S1s@GLK_$DJ0;CwX#Sh+m$RQ&#*Azc&j!I)SN6?VP$=O@GgDIp2+9v)u64WpS|7 z9857SDUi`r`zvtBdIrmzV7t0$n&I@h^>}2I*Y%_%byT=puhbjFY zq@r#X52i$RkWz8%hurwU007-dwROz1?pnVqjhI@7cH^1=B9+VS5+-CMgX%>w!fR^- zl3%>sSc>hpp9R79zyg|j7bDwOb}Z?^T74dwth5rc9LN|a;mZXXwz?#HEQTND^fh`TFHh} z(YpZk!L-qvlY1lMEOzs3^N_Kj?nrxEQy-l)(@zp#ZZu2YL|R)NU#zG85PVTiu4&HI znA#jK;D{HySSIYAhh`p2Ftl8*1{!{fZb$+C)mrRs4nkQp((q2Xpt(AdP6wbmwezWB{- zUR3-_BzoD@SQu4)>2fzW)(KQyTjCdok`9*ja_KxAAeNOC?{(}(X_#3wmTpAi>MsNq zM_&xPs#g`__SQYrAgKxVTdv=&3uZ8xe^)6(ODo$}_L#23cs}=;a4QG$eCj<#=qVJ+ z&VvaMjIZR2cs?W9jJ8<9z^^R{I=Y9*E}Pe%wWQ)rgT?#pndcXu_1AhrQ#ppEHFf36 z_rJ=C2ieck=~)f7z-;1w)N7Kl9eQISQD3zL8dp2Ht=>eSo#BYFT4c z1Vf-qt)|Z&{XS|-Jsk{xJZ*j1@-zgC){1HOs5m=O4}tD&@|Vb%o!)X@yWX*=|G>hq z=lVp^R0~r!_NhC-FVrU84VZruWau{lnaWhKIUh8sdjmCot?zun&U)QrC!yHw32q_v zsUD&&Yb10DVv)Lh1W6&6WKbVG;FPa%YMxf>speQw%a)^8wPxA+dYR~#^)vgYwBMHT z39+P+3d}K^DZrg&hCUg1p;hj|IWK%;tw+WxTbhC0$<*^Jzwv^UF)8{;8uln}w)*#; zdX}M#{ny&11EWbe{RA-f&Buc}1N#o8FG#PfDY2tc(Vh`Hz4`es9I5r7lqP-i$JIRlFU8_Do1H*`k4$;Mi}%2zz=)zuZn=iQ9n;DimxXP%l0F zEp~KmM#U$Q{(7471i#qBhJB3lPx6FiS7NYp50sJ_wD@aDAOva{D@Eq5$(H)dHDg^f zVTB&R4+}&GnQ9Jsi`<-YGmf5R+i3LarflTqULwpGr6$5{T0+jy9G@On;2ykN_<$kh zS`(Ir-Y!0{JH7!zr&jJ5dnGs1YI1YgW{TO#J+syb*G?t$>;qmOcyegTdBvZTBb`1##FJ4YH=gNL2|}tPBRjj zd2J;;^NO(rmMq91M&I4S?yJ*+Hc!9|`~VX2}&&IuDRA7m(@s^tCzqI2L$BkK&lSrs`Q= z=_4j-;DUef1f=jw;Bt+1uKu9{@*Vm({b>p7EkBqCw*>rjHmP4D;YiOxu)`WG< zCxhqR8FS{Q_I_^_pECRh@_>N>#hP^xdEg7z)17R)*IKRla;!4N*p^SKhiR=s-I7Ov zs;}nRs1YdM{Bx2{7BPDeH=JtRFTVF-@@KEnkM)OJ(YLm3M&EW#fAgYFU+hl4*0YXI zTj_r{tEHZ^Jgt?xKcr^~sxq}@nXj{0k%?Z_AG-0#Drp01Oy(_L^-UyAUW zx_nyh^c>LFWQ(4jw7*;4mpTa`-3+(9-&E^rl}LJZaWSLOWav6 zUa}8A{i!FqD$MF2Vt&#fA6J?SsQYW-o#miHZVxq0Z890 zW(8REI}a?+<@UbNp4ohYTcvWZnzdP}(kKUyQ98#Nq4#8T^VuWbNtYVDXa!iEBcljNV}W#2!V9MrSS z*lNi9-`Y9Tf3~(Yj=N5emg+qLpgnaEK~qDV)=)!DMN+D2N|YFD zERL~ao&`0fRf!sEN+l(ROQ?pLqTwdz{r!CIA8_967kfYZS!+Fef1YQp-`?xnUW8VG zn-rQrsX1m~vHe#&OlPc>Enf4p!bd(IR!>l4DH@`xJ5KP=?~jkkwj^eSo_jb>8yru| z0z6vm%nTb+VXOT){(4}Vx=4Z}w)AnBS{cGJ?&W636DV=gW=eccwDHcpQ%<2NK^|Ak zbuuCYWC$e8zTe~^4q(x6nfL4tS zEP4WFZjMgI0oKsc>>t@Wc``Mgm*xnL0EGTLGi1rm5my`PHp>IW6?XlGl(M=CtIyA@ z%@|BSmuvp+Ri%$bW+TPyOyMvvPE0$!oaCDfj@Tv%WGU@YFhH@_`dNLp8)8I{4DkHF zhs4q<>rEkRCN|T-ybq&%3*P{-41VVR)ndRN(Xc^hBRMx%4SVwHXL40`b ztM`5U`Pf0^ip^~X9~|6Q^&ean*S=mn5(UG((YrQh7Kk0P(uEIrM_l|mW z6{`)wy&DGm6u`Ow{q!JArW|K6o$K5v!!F0+c3B^$S`jXQY^^eUj73gyps#G&{nmM7 z)#%rWjZGP2PG;DoJEpMNGUf+W^46+K9f5*wOBN!iX__Y{I8QtAizEtu&aJQiX;u$?>Sg_xD zU`p{+>UWx=^FBBxX~oYBFReQYJx|1V=b@a-3nWj|wUqWA-e}u0H;5&QeR5Ke3|>l?}RhU`vC%BVC0OZCz*ClW5-nbr zU!DS0vx=0*@?I9$=upidi^A5GkP35GwE&Pj-QX03DDAC-sfNEaCFR`XeESpMA`8dN zWJV`q;gpM~^?uJ?&QyDx2QdzL1SY*mC`zCq%C}PU0_658{z8EygA|7A7(4^r7+Iwf z&y6~YNaoY)qn@x%Q~M&y<pUVqwxx3FTu#u|v@wjT?sc_n0C z>w$V9!n}q`F!uhY!-8oqa$;1p<_5y9X73n>qXqg7vKS-=rb%s>W}RB<7}eWL9V%cg{tj`JXH^Um?#iNvE*!Fj!{VjJ?M;U_?q=fiG4_lke)@0@A@+^YIH z792Z`1+~wNL8F%2_?gxRC(PW@W;UOPs~2!<(Om{B@vdDYn)a=%uK$Rq9ZWxtF_2k|7Pb7`kWbP?Qt`8iwiF=x6uI7Bm z8A6?Qg2@iwK@Drf6rEm0afjB<0+q| zb5rVO1l>V{t@Hy=_0jqP|?3&|NhFI@4F$ zKkC=LFpaP8Q+y$S5GbZunZ+{BBV&H$IDuueR|4@K3IBQPE^9RV!;O3l+|?btLq65= zGW4cE4?9Bvc2eP?5^9H-wL!{F!qO63QfG(I0@J%Ud|hMDp0FUN9|L?RpN9Q(K81=q zJmYgu(L1{MW5WQl>;1NLt{Df_B#<%b~($#@RIw5ORli!zTNef3X_5@w<7@DMW zF9|qJqzx`8M%<%9t-aL#ZX^CX1HKyWiw>i^FhkQM2CU#OV_HI@^IvnNUr99k?L~*^ zQNF7%FUS3>Yjr^Yp0XY7YOHTb|?scH*p>u9`DR_7cp%$RJ-t-FCXKXXm)wS04=T77MMu~{} znjWpdposD{@);rR2ST$S5ISXWOrZZuRO8cqEX@-l#pw(DteXdywz${qj3?4>B@Y8~ zY4>v{e-BXEmZaAcUCzVse{4o7zA1mBO5dUFgN&65wl^#zQ?IcNuE zS#Zugv+h^qfz{99tGoSXZbh6>!#@fn zE$lw|*y5fSwucu=d<>~2b}y{Vv5(1SM*LJx&#GqVK`VuOD{5mfNf zs~17Mi3j7Y+TBeWkrwn2i#@eqFGVS#@qJ0wY&Y(Y*=%pK$>iJ2eDl6HZ+*6)i zPo#Q+R70I_j%GeDWkjaV3Ol;x6;|IG7~5s(VjMA76L5`cUj^7hbvk~f{LBxUeYPgv zZUbj;n`)MBJ|T_LqHhgM90BK#De&Gv`@aKcv1K;`FsIYtKYz~LXX*S+Kn$;DVeb{k z5ZC;C=s)EHyW+#IAwSpc_-T!Anpa`mg374gGn(6BV}Q!Z56K(nm#uRntX^1IIh#KU zyfRV{x2XKseNTOWIMcpZGyc8`Y=fQ zK6{yzyJeX7r2@8`sh&5crHQ+;IA2G`_k;U8FOJ0fYAG{$UN$EAQ$57_w|{{9Q(2r@ z{lQ^iEJ*P6j+X_hFB_Bm{c$ZrypYB5V@S3kHSRQSW!FmQQtps#h%b_yUG0}j`s68D zy~v;HA?A`BH;L|zH8=Sgpk`5rUCNBA7e6P3Taht}j42J9bVWhAJIIr4R(rs9t%km2@ zUYI36#98*nO1ek5ajb0qtRK^hALD(+(fO^Qy9MP_IdE3CU}`Q6= zY5l=jkx!7mwEi}A<`d2Q4!qd?71btMUvW3Pw{Szr%piCIA2c literal 0 HcmV?d00001 diff --git a/docs/src/assets/img/fork-me.webp b/docs/src/assets/img/fork-me.webp new file mode 100644 index 0000000000000000000000000000000000000000..21c460ded9f3caaea4ba962df85e589079497252 GIT binary patch literal 3010 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 literal 0 HcmV?d00001 diff --git a/docs/src/assets/img/logo_off.png b/docs/src/assets/img/logo_off.png new file mode 100644 index 0000000000000000000000000000000000000000..db7bf4877b8eef2a7f2ac222bbb20d996ff2e5a1 GIT binary patch literal 6032 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 literal 0 HcmV?d00001 diff --git a/docs/src/assets/img/logo_on.png b/docs/src/assets/img/logo_on.png new file mode 100644 index 0000000000000000000000000000000000000000..b5fcc7c3ad6b5532399977d32a661af71874a61a GIT binary patch literal 5801 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 literal 0 HcmV?d00001 diff --git a/docs/src/assets/img/logo_settings.json b/docs/src/assets/img/logo_settings.json new file mode 100644 index 0000000..7ee7aa1 --- /dev/null +++ b/docs/src/assets/img/logo_settings.json @@ -0,0 +1,37 @@ +{ + "version": "v1.0.0", + "components": { + "icon": { + "name": "icon", + "text": "", + "color": "#7337cd", + "fontFamily": "\"Font Awesome 5 Free\"", + "fontWeight": "900", + "fontSize": 30, + "letterSpacing": 0 + }, + "main": { + "name": "main", + "text": "Bootstrap 5", + "color": "#7337cd", + "fontFamily": "Quicksand", + "fontWeight": "normal", + "fontSize": 20, + "letterSpacing": 0 + }, + "accent": { + "name": "accent", + "text": "Toggle", + "color": "#ffffff", + "fontFamily": "Quicksand", + "fontWeight": "bold", + "fontSize": 20, + "letterSpacing": 0 + } + }, + "global": { + "offset": { "size": 0, "color": "#e2e2e2" }, + "layout": "HORIZONTAL", + "shapes": true + } +} diff --git a/docs/src/css/styles.css b/docs/src/css/styles.css new file mode 100644 index 0000000..f47a6d2 --- /dev/null +++ b/docs/src/css/styles.css @@ -0,0 +1,75 @@ +a { + text-decoration: none; +} + +code.hljs { + border: 1px solid #ccc; + padding: 1em; + white-space: pre; + margin-bottom: 10px; +} + +code.hljs .hljs-tag { + display: inline-flex; +} + +code.hljs .hljs-tag .hljs-attr { + margin-left: 0.3rem; +} + +.example { + position: relative; + border: 1px solid #ccc; + padding: 1em 1em 0.5em 1em; + border-radius: 4px 4px 0 0; +} + +.example:after { + content: "Example"; + position: absolute; + top: 0px; + right: 0px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #ccc; + color: #9da0a4; + border-radius: 0px 4px 0px 4px; + border-width: 0px 0px 1px 1px; +} + +.example + code.hljs { + border-top: 0; + border-radius: 0px 0px 4px 4px; +} + +.example > * { + margin-bottom: 10px; +} + +.example > div.toggle { + margin-right: 10px; +} +nav#toc .navbar-nav { + flex-direction: column; +} + +nav[data-toggle="toc"] .nav > li > a { + color: var(--gray); +} +nav[data-toggle="toc"] .nav-link.active, +nav[data-toggle="toc"] .nav-link.active:focus, +nav[data-toggle="toc"] .nav-link.active:hover { + color: var(--purple); + border-color: var(--purple); +} +.bs-darkmode-dark nav[data-toggle="toc"] .nav-link.active, +.bs-darkmode-dark nav[data-toggle="toc"] .nav-link.active:focus, +.bs-darkmode-dark nav[data-toggle="toc"] .nav-link.active:hover { + filter: brightness(1.4); +} + +.bs-darkmode-dark .img-toggle img { + filter: brightness(1.7); +} diff --git a/docs/src/index.html b/docs/src/index.html new file mode 100644 index 0000000..51add85 --- /dev/null +++ b/docs/src/index.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + + Bootstrap 5 Toggle | v5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/js/components/Aside.js b/docs/src/js/components/Aside.js new file mode 100644 index 0000000..31630c0 --- /dev/null +++ b/docs/src/js/components/Aside.js @@ -0,0 +1,122 @@ +import Badge from "./Badge"; + +class Aside { + static #badges = [ + { + name: "sponsor", + href: "https://github.com/sponsors/palcarazm", + imgSrc: + "https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#white", + }, + { + name: "GitHub repository", + href: "https://github.com/palcarazm/bootstrap5-toggle", + imgSrc: + "https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white", + }, + { + name: "License", + href: "https://github.com/palcarazm/bootstrap5-toggle/blob/v5/LICENSE", + imgSrc: + "https://img.shields.io/github/license/palcarazm/bootstrap5-toggle.svg?style=for-the-badge", + }, + ]; + + static build() { + const aside = document.createElement("aside"); + aside.className = "col-md-3 col-lg-2"; + + const container = document.createElement("div"); + container.className = "position-sticky"; + container.style.top = "0rem"; + + container.append( + Aside.#logoContainer(), + Aside.#navBar(), + Aside.#badgeBar(), + ); + + aside.appendChild(container); + return aside; + } + + static #logoContainer() { + const container = document.createElement("div"); + container.className = + "d-none d-md-block position-relative pb-5 mb-3 img-toggle"; + + const logoOn = document.createElement("img"); + logoOn.className = + "d-block mx-auto position-absolute top-50 start-50 translate-middle w-100"; + logoOn.src = "assets/img/logo_on.png"; + logoOn.alt = "Bootstrap 5 Toggle"; + container.appendChild(logoOn); + + const logoOff = document.createElement("img"); + logoOff.className = + "d-block mx-auto position-absolute top-50 start-50 translate-middle w-100 invisible"; + logoOff.src = "assets/img/logo_off.png"; + logoOff.alt = "Bootstrap 5 Toggle"; + container.appendChild(logoOff); + + return container; + } + + static #navBar() { + const navBar = document.createElement("nav"); + navBar.className = + "navbar navbar-expand-md navbar-light flex-md-column p-0"; + navBar.setAttribute("aria-label", "Site menu"); + + const button = document.createElement("button"); + button.className = "navbar-toggler"; + button.type = "button"; + button.setAttribute("data-bs-toggle", "collapse"); + button.setAttribute("data-bs-target", "#navbarSupportedContent"); + button.setAttribute("aria-controls", "navbarSupportedContent"); + button.setAttribute("aria-expanded", "false"); + button.setAttribute("aria-label", "Toggle navigation"); + + const span = document.createElement("span"); + span.className = "navbar-toggler-icon"; + + button.appendChild(span); + navBar.appendChild(button); + + const darkModeToggle = document.createElement("div"); + darkModeToggle.dataset.plugin = "bs-darkmode-toggle"; + darkModeToggle.dataset.allowCookie = ""; + + navBar.appendChild(darkModeToggle); + + const collapseDiv = document.createElement("div"); + collapseDiv.className = "collapse navbar-collapse"; + collapseDiv.id = "navbarSupportedContent"; + + const navMenu = document.createElement("nav"); + navMenu.id = "toc"; + navMenu.setAttribute("data-toggle", "toc"); + navMenu.setAttribute("aria-label", "Table of contents"); + + collapseDiv.appendChild(navMenu); + navBar.appendChild(collapseDiv); + + return navBar; + } + + static #badgeBar() { + const badges = document.createElement("div"); + badges.className = "d-none d-md-flex flex-column align-items-center mt-4"; + + Aside.#badges.forEach((badge, index) => { + badges.appendChild( + Badge.build({ + ...badge, + className: index === 0 ? "mt-0" : "mt-1", + }), + ); + }); + return badges; + } +} +export default Aside; diff --git a/docs/src/js/components/Badge.js b/docs/src/js/components/Badge.js new file mode 100644 index 0000000..a925bf4 --- /dev/null +++ b/docs/src/js/components/Badge.js @@ -0,0 +1,23 @@ +class Badge { + static build({ name, href, imgSrc, className = "" }) { + const container = document.createElement("div"); + container.className = className; + + const badge = { name, href, imgSrc }; + const badgeLink = document.createElement("a"); + badgeLink.href = badge.href; + badgeLink.title = badge.name; + badgeLink.target = "_blank"; + badgeLink.rel = "noopener noreferrer"; + + const badgeImg = document.createElement("img"); + badgeImg.src = badge.imgSrc; + badgeImg.alt = badge.name; + + badgeLink.appendChild(badgeImg); + container.appendChild(badgeLink); + return container; + } +} + +export default Badge; diff --git a/docs/src/js/components/CodeBlock.js b/docs/src/js/components/CodeBlock.js new file mode 100644 index 0000000..f097664 --- /dev/null +++ b/docs/src/js/components/CodeBlock.js @@ -0,0 +1,18 @@ +import Example from "./Example"; + +class CodeBlock { + static build({ language, code, example = [] }) { + const codeBlock = document.createElement("div"); + if (example.length > 0) codeBlock.appendChild(Example.build(example)); + const pre = document.createElement("pre"); + const codeElement = document.createElement("code"); + codeElement.className = `highlight language-${language}`; + codeElement.textContent = code; + + pre.appendChild(codeElement); + codeBlock.appendChild(pre); + return codeBlock; + } +} + +export default CodeBlock; diff --git a/docs/src/js/components/CodePanel.js b/docs/src/js/components/CodePanel.js new file mode 100644 index 0000000..cafeadf --- /dev/null +++ b/docs/src/js/components/CodePanel.js @@ -0,0 +1,73 @@ +import CodeBlock from "./CodeBlock"; +import Example from "./Example"; + +class CodePanel { + static build({ + name, + language, + tabs = ["ECMAScript", "jQuery"], + contents, + example = [], + }) { + const codePanel = document.createElement("div"); + + if (tabs.length !== contents.length) + throw new Error("Tabs and contents arrays must have the same length."); + + if (tabs.length === 0 || contents.length === 0) + throw new Error("Tabs and contents arrays cannot be empty."); + + codePanel.appendChild(CodePanel.#navTabs(name, tabs)); + if (example.length > 0) codePanel.appendChild(Example.build(example)); + codePanel.appendChild(CodePanel.#navPanes(name, language, contents)); + return codePanel; + } + + static #navTabs(name, labels) { + const navTabs = document.createElement("ul"); + navTabs.className = "nav nav-tabs"; + navTabs.role = "tablist"; + + labels.forEach((tabName, index) => { + const navItem = document.createElement("li"); + navItem.className = "nav-item"; + navItem.role = "presentation"; + + const navLink = document.createElement("button"); + navLink.className = `nav-link ${index === 0 ? "active" : ""}`; + navLink.id = `${name}-${index}-tab`; + navLink.role = "tab"; + navLink.type = "button"; + navLink.setAttribute("data-bs-toggle", "tab"); + navLink.setAttribute("data-bs-target", `#${name}-${index}`); + navLink.setAttribute("aria-controls", `${name}-${index}`); + navLink.setAttribute("aria-selected", index === 0 ? "true" : "false"); + navLink.textContent = tabName; + + navItem.append(navLink); + navTabs.append(navItem); + }); + + return navTabs; + } + + static #navPanes(name, language, contents) { + const navPanes = document.createElement("div"); + navPanes.className = "tab-content"; + + contents.forEach((code, index) => { + const tabPane = document.createElement("div"); + tabPane.className = `tab-pane fade ${index === 0 ? "show active" : ""}`; + tabPane.id = `${name}-${index}`; + tabPane.role = "tabpanel"; + tabPane.tabindex = index === 0 ? 0 : -1; + tabPane.setAttribute("aria-labelledby", `${name}-${index}-tab`); + + tabPane.append(CodeBlock.build({ language, code })); + navPanes.append(tabPane); + }); + return navPanes; + } +} + +export default CodePanel; diff --git a/docs/src/js/components/Console.js b/docs/src/js/components/Console.js new file mode 100644 index 0000000..796b377 --- /dev/null +++ b/docs/src/js/components/Console.js @@ -0,0 +1,80 @@ +class Console { + #element; + #pre; + + constructor() { + this.#element = document.createElement("div"); + this.#element.classList.add("d-flex", "mt-2", "align-items-start"); + + const aside = document.createElement("div"); + aside.className = "d-flex flex-column me-2"; + + const legend = document.createElement("div"); + legend.className = "font-monospace text-muted"; + legend.textContent = "Console"; + aside.append(legend); + + const btnGroup = document.createElement("div"); + btnGroup.className = "btn-group btn-group-sm btn-group-vertical"; + btnGroup.setAttribute("role", "group"); + aside.append(btnGroup); + + const clearBtn = document.createElement("button"); + clearBtn.className = "btn btn-outline-secondary"; + clearBtn.title = "Clear"; + clearBtn.innerHTML = ``; + clearBtn.addEventListener("click", () => this.clear()); + btnGroup.append(clearBtn); + + const console = document.createElement("div"); + console.className = "flex-grow-1 me-2"; + this.#pre = document.createElement("pre"); + console.append(this.#pre); + + this.#element.append(aside, console); + } + + get htmlElement() { + return this.#element; + } + + #handleMode(mode) { + switch (mode) { + case "replace": + this.clear(); + return; + case "append": + return; + default: + throw new Error("Unsupported mode"); + } + } + + clear() { + this.#pre.innerHTML = ""; + } + + json({ mode, data }) { + this.#handleMode(mode); + + const code = document.createElement("code"); + code.className = "highlight language-json"; + code.textContent = JSON.stringify(data, null, 2); + + this.#pre.append(code); + hljs.highlightElement(code); + } + + log({ level = "info", mode, data }) { + this.#handleMode(mode); + + const code = document.createElement("code"); + code.className = "highlight language-shell"; + code.textContent = `> ${new Date().toISOString()} - ${data}`; + + this.#pre.append(code); + hljs.highlightElement(code); + } +} + +export default Console; diff --git a/docs/src/js/components/Documentation.js b/docs/src/js/components/Documentation.js new file mode 100644 index 0000000..2aa0dde --- /dev/null +++ b/docs/src/js/components/Documentation.js @@ -0,0 +1,30 @@ +import Distribution from "./documentation/Distribution"; +import Installation from "./documentation/Installation"; +import Usage from "./documentation/Usage"; +import Features from "./documentation/Features"; +import Api from "./documentation/Api"; +import Events from "./documentation/Events"; +import Accessibility from "./documentation/Accessibility"; + +class Documentation { + static #sections = [ + Distribution.build(), + Installation.build(), + Usage.build(), + Features.build(), + Api.build(), + Events.build(), + Accessibility.build(), + ]; + + static build() { + const documentation = document.createElement("div"); + documentation.className = "col-md-9 col-lg-10"; + + documentation.append(...Documentation.#sections); + + return documentation; + } +} + +export default Documentation; diff --git a/docs/src/js/components/Example.js b/docs/src/js/components/Example.js new file mode 100644 index 0000000..0ef9eab --- /dev/null +++ b/docs/src/js/components/Example.js @@ -0,0 +1,10 @@ +class Example { + static build(examples) { + const example = document.createElement("div"); + example.className = "example"; + example.append(...examples); + return example; + } +} + +export default Example; diff --git a/docs/src/js/components/Footer.js b/docs/src/js/components/Footer.js new file mode 100644 index 0000000..b23566e --- /dev/null +++ b/docs/src/js/components/Footer.js @@ -0,0 +1,31 @@ +class Footer { + static #userAttributions = [ + `Original by Min Hur`, + `Bootstrap 4 by Brent Ely`, + `Bootstrap 5 by Pablo Alcaraz Martínez`, + ]; + + static build() { + const footer = document.createElement("footer"); + footer.className = "bg-light border-top p-4"; + + const container = document.createElement("div"); + container.className = "container"; + + const content = document.createElement("div"); + content.className = + "d-flex font-weight-light flex-column flex-md-row justify-content-around align-items-center mb-4"; + + Footer.#userAttributions.forEach((attribution) => { + const span = document.createElement("span"); + span.innerHTML = attribution; + content.appendChild(span); + }); + + container.appendChild(content); + footer.appendChild(container); + return footer; + } +} + +export default Footer; diff --git a/docs/src/js/components/Header.js b/docs/src/js/components/Header.js new file mode 100644 index 0000000..bb0f31b --- /dev/null +++ b/docs/src/js/components/Header.js @@ -0,0 +1,67 @@ +class Header { + static build() { + const header = document.createElement("header"); + header.appendChild(Header.#forkMeLink()); + header.appendChild(Header.#logoContainer()); + return header; + } + + static #forkMeLink() { + const forkMeLink = document.createElement("a"); + forkMeLink.className = "position-absolute top-0 end-0 d-none d-md-inline"; + forkMeLink.href = "https://github.com/palcarazm/bootstrap5-toggle"; + forkMeLink.target = "_blank"; + forkMeLink.rel = "noopener noreferrer"; + + const img = document.createElement("img"); + img.loading = "lazy"; + img.width = "149"; + img.height = "149"; + img.src = "assets/img/fork-me.webp"; + img.className = "attachment-full size-full"; + img.alt = "Fork me on GitHub"; + img.setAttribute("data-recalc-dims", "1"); + + forkMeLink.appendChild(img); + return forkMeLink; + } + + static #logoContainer() { + const container = document.createElement("div"); + container.className = "px-4 py-5 my-5 text-center"; + + const logoWrapper = document.createElement("div"); + logoWrapper.className = "position-relative mx-auto mb-4 img-toggle"; + + const logoOn = document.createElement("img"); + logoOn.className = + "d-block mx-auto position-absolute top-50 start-50 translate-middle"; + logoOn.src = "assets/img/logo_on.png"; + logoOn.alt = "Bootstrap 5 Toggle"; + logoWrapper.appendChild(logoOn); + + const logoOff = document.createElement("img"); + logoOff.className = + "d-block mx-auto position-absolute top-50 start-50 translate-middle invisible"; + logoOff.src = "assets/img/logo_off.png"; + logoOff.alt = "Bootstrap 5 Toggle"; + logoWrapper.appendChild(logoOff); + + container.appendChild(logoWrapper); + + const heading = document.createElement("div"); + heading.className = "col-lg-6 mx-auto"; + + const paragraph = document.createElement("p"); + paragraph.className = "lead mb-4"; + paragraph.textContent = + "Bootstrap 5 Toggle is a bootstrap plugin that converts checkboxes into responsive toggles."; + + heading.appendChild(paragraph); + container.appendChild(heading); + + return container; + } +} + +export default Header; diff --git a/docs/src/js/components/Main.js b/docs/src/js/components/Main.js new file mode 100644 index 0000000..4d54c81 --- /dev/null +++ b/docs/src/js/components/Main.js @@ -0,0 +1,21 @@ +import Aside from "./Aside"; +import Documentation from "./Documentation"; + +class Main { + static #sections = [Aside.build(), Documentation.build()]; + + static build() { + const main = document.createElement("main"); + main.className = "container mb-3"; + + const row = document.createElement("div"); + row.className = "row g5"; + + row.append(...Main.#sections); + main.appendChild(row); + + return main; + } +} + +export default Main; diff --git a/docs/src/js/components/Notice.js b/docs/src/js/components/Notice.js new file mode 100644 index 0000000..4fa980a --- /dev/null +++ b/docs/src/js/components/Notice.js @@ -0,0 +1,89 @@ +class Notice { + static #HTMLTextCols = [ + `Dark mode integration (check out Notice plugin)`, + `Includes a mini (-xs) size`, + `With jQuery and vanilla JavaScript (ECMAS) interfaces`, + `Supports three states toggle`, + ]; + + static build() { + const section = document.createElement("section"); + section.className = "container my-5"; + section.id = "notice"; + section.appendChild(Notice.#noticeAlert()); + return section; + } + + static #noticeAlert() { + const notice = document.createElement("div"); + notice.className = "alert alert-success"; + notice.setAttribute("role", "alert"); + + const h1 = document.createElement("h1"); + h1.className = "alert-heading fs-2"; + h1.textContent = "Made for Bootstrap 5!"; + notice.appendChild(h1); + + const p = document.createElement("p"); + p.className = "font-weight-light fs-5"; + p.textContent = `This page and all of the switch buttons shown are running on Bootstrap v${versions.bootstrap} and Bootstrap5-toggle v${versions.bootstrap5Toggle}.`; + notice.appendChild(p); + + const hr = document.createElement("hr"); + notice.appendChild(hr); + + notice.appendChild(Notice.#noticePanel()); + + return notice; + } + + static #noticePanel() { + const panel = document.createElement("div"); + panel.className = "row"; + Notice.#HTMLTextCols.forEach((HTMLTextCol) => { + panel.appendChild(Notice.#noticePanelItem(HTMLTextCol)); + }); + + return panel; + } + + static #noticePanelItem(HTMLTextCol) { + const panelItem = document.createElement("div"); + panelItem.className = "col-12 col-md-3 mb-1 mb-md-0"; + + const row = document.createElement("div"); + row.className = "row align-items-center h-100"; + + const textCol = document.createElement("div"); + textCol.className = "col"; + textCol.innerHTML = HTMLTextCol; + + row.append(Notice.#noticeIconCol(), textCol); + panelItem.appendChild(row); + + return panelItem; + } + + static #noticeIconCol() { + const iconCol = document.createElement("div"); + iconCol.className = "col-auto p-0 p-md-2 text-right"; + + const iconLarge = document.createElement("i"); + iconLarge.className = + "fa-solid fa-circle-check text-success d-none d-sm-none d-md-none d-lg-block"; + iconLarge.setAttribute("style", "font-size:48px"); + iconLarge.setAttribute("aria-hidden", "true"); + iconCol.appendChild(iconLarge); + + const iconSmall = document.createElement("i"); + iconSmall.className = + "fa-solid fa-circle-check text-success d-block d-sm-block d-md-block d-lg-none "; + iconSmall.setAttribute("style", "font-size:24px"); + iconSmall.setAttribute("aria-hidden", "true"); + iconCol.appendChild(iconSmall); + + return iconCol; + } +} + +export default Notice; diff --git a/docs/src/js/components/VersionPill.js b/docs/src/js/components/VersionPill.js new file mode 100644 index 0000000..0a90de6 --- /dev/null +++ b/docs/src/js/components/VersionPill.js @@ -0,0 +1,24 @@ +class VersionPill { + static build({ version, action }) { + let color; + switch (action) { + case "SINCE": + color = "success"; + break; + case "DEPRECATED": + color = "warning"; + break; + case "REMOVED": + color = "danger"; + break; + default: + throw new Error("Unsupported action"); + } + const pill = document.createElement("span"); + pill.className = `badge rounded-pill bg-${color} px-2`; + pill.textContent = `${action} v${version}`; + return pill; + } +} + +export default VersionPill; diff --git a/docs/src/js/components/documentation/Accessibility.js b/docs/src/js/components/documentation/Accessibility.js new file mode 100644 index 0000000..83d4318 --- /dev/null +++ b/docs/src/js/components/documentation/Accessibility.js @@ -0,0 +1,266 @@ +import DocSection from "./DocSection"; +import DocArticle from "./DocArticle"; + +class Accessibility extends DocSection { + static build() { + return super.build("accessibility", "ARIA Accessibility", [ + DocArticle.build({ + title: "Key Accessibility Features", + description: Accessibility.#featuresCards(), + versionPill: { action: "SINCE", version: "5.3.0" }, + }), + DocArticle.build({ + title: "WCAG 2.2 Compliance", + description: Accessibility.#wcagCompliance(), + className: "mt-3", + versionPill: { action: "SINCE", version: "5.3.0" }, + }), + ]); + } + + static #features = [ + { + title: "Semantic Role Implementation", + description: `Each toggle switch is assigned the role="switch" attribute, allowing screen readers to identify it as a toggleable control.`, + }, + { + title: "State Representation", + description: `The aria-checked attribute dynamically reflects the toggle's state (on/off), providing real-time feedback to assistive technologies.`, + }, + { + title: "Labeling", + description: `Toggles can be associated with descriptive labels using the aria-labelledby or aria-label attributes, enhancing clarity for screen reader users.`, + }, + + { + title: "Keyboard Navigation", + description: `The toggle supports keyboard navigation with TAB and ENTER keys, ensuring full accessibility for users who rely on keyboard input.`, + }, + { + title: "Indeterminate State Accessibility", + description: `When a toggle is in an indeterminate state, it uses the aria-checked="mixed" attribute to inform assistive technologies of its unique status.`, + }, + { + title: "Focus Management", + description: `The toggle is fully focusable and provides visual focus indicators, aiding users in tracking their navigation through the interface.`, + }, + ]; + + static #featuresCards() { + const fragment = document.createDocumentFragment(); + + const description = document.createElement("p"); + description.innerHTML = `Bootstrap Toggle is built with accessibility in mind. The implementation ensures seamless compatibility with screen readers, keyboard navigation, and other assistive technologies.`; + fragment.appendChild(description); + + const container = document.createElement("div"); + container.classList.add( + "row", + "row-cols-1", + "row-cols-md-2", + "row-cols-xl-3", + "g-4", + ); + Accessibility.#features.forEach((feature) => { + const col = document.createElement("div"); + col.classList.add("col"); + + const card = document.createElement("div"); + card.classList.add("card"); + + const cardHeader = document.createElement("div"); + cardHeader.classList.add("card-header"); + cardHeader.textContent = feature.title; + card.appendChild(cardHeader); + + const cardBody = document.createElement("div"); + cardBody.classList.add("card-body"); + const description = document.createElement("p"); + description.classList.add("card-text"); + description.innerHTML = feature.description; + cardBody.appendChild(description); + card.appendChild(cardBody); + + col.appendChild(card); + container.appendChild(col); + }); + + fragment.appendChild(container); + return fragment; + } + + static #wcagChecklist = [ + { + criterion: "1.3.1 Info and Relationships", + url: "https://www.w3.org/TR/WCAG22/#info-and-relationships", + compliance: "Compliant", + evidence: + "role='switch', proper ARIA attributes, semantic structure", + }, + { + criterion: "1.4.4 Resize Text", + url: "https://www.w3.org/TR/WCAG22/#resize-text", + compliance: "Compliant", + evidence: "Uses relative units, responsive design", + }, + { + criterion: "2.1.1 Keyboard", + url: "https://www.w3.org/TR/WCAG22/#keyboard", + compliance: "Compliant", + evidence: + "Responds to Space and Enter keys, all functionality keyboard accessible", + }, + { + criterion: "2.1.2 No Keyboard Trap", + url: "https://www.w3.org/TR/WCAG22/#no-keyboard-trap", + compliance: "Compliant", + evidence: + "tabindex=0 with proper focus management, users can navigate away", + }, + { + criterion: "2.1.4 Character Key Shortcuts", + url: "https://www.w3.org/TR/WCAG22/#character-key-shortcuts", + compliance: "Compliant", + evidence: "No single-character shortcuts used", + }, + { + criterion: "2.4.3 Focus Order", + url: "https://www.w3.org/TR/WCAG22/#focus-order", + compliance: "Compliant", + evidence: "tabindex respects DOM order", + notice: + "Component follows logical DOM order; integration depends on page structure", + }, + { + criterion: "2.4.7 Focus Visible", + url: "https://www.w3.org/TR/WCAG22/#focus-visible", + compliance: "Partially", + evidence: "Programmatic focus management implemented", + notice: + "Visual focus indicators require CSS styling from consuming application, component defaults style meet criterion", + }, + { + criterion: "2.4.11 Focus Not Obscured (Minimum)", + url: "https://www.w3.org/TR/WCAG22/#focus-not-obscured-minimum", + compliance: "Partially", + evidence: "Programmatic focus management implemented", + notice: + "Component doesn't obscure its own focus, but page-level layout/styling may affect visibility", + }, + { + criterion: "2.4.12 Focus Not Obscured (Enhanced)", + url: "https://www.w3.org/TR/WCAG22/#focus-not-obscured-enhanced", + compliance: "Partially", + evidence: "Programmatic focus management implemented", + notice: + "Same as 2.4.11; component-level focus is clear but dependent on page context", + }, + { + criterion: "2.5.3 Label in Name", + url: "https://www.w3.org/TR/WCAG22/#label-in-name", + compliance: "Compliant", + evidence: "aria-label matches visual label when provided", + }, + { + criterion: "3.2.1 On Focus", + url: "https://www.w3.org/TR/WCAG22/#on-focus", + compliance: "Compliant", + evidence: "Focus does not trigger unexpected context changes", + }, + { + criterion: "3.3.2 Labels or Instructions", + url: "https://www.w3.org/TR/WCAG22/#labels-or-instructions", + compliance: "Compliant", + evidence: + "Supports aria-label and aria-labelledby with fallback to default label", + }, + { + criterion: "4.1.1 Parsing", + url: "https://www.w3.org/TR/WCAG22/#parsing", + compliance: "Compliant", + evidence: "Valid HTML, no duplicate IDs, proper nesting", + }, + { + criterion: "4.1.2 Name, Role, Value", + url: "https://www.w3.org/TR/WCAG22/#name-role-value", + compliance: "Compliant", + evidence: + "role='switch', aria-label | aria-labelledby for name, aria-checked for value", + }, + { + criterion: "4.1.3 Status Messages", + url: "https://www.w3.org/TR/WCAG22/#status-messages", + compliance: "Not Compliant", + evidence: + "There is not aria-live, aria-atomic, or aria-relevant for dynamic state announcements", + notice: + "State changes (ON/OFF/INDETERMINATE) are not announced via aria-live regions. Screen readers detect changes through aria-checked updates but not proactively announced.", + }, + ]; + + static #wcagCompliance() { + const fragment = document.createDocumentFragment(); + + const description = document.createElement("p"); + description.innerHTML = `Bootstrap Toggle adheres to WCAG 2.2 AA standards by implementing proper ARIA roles and attributes, ensuring that all users, including those with disabilities, can effectively interact with toggle switches.`; + + const list = document.createElement("ul"); + list.classList.add("list-group", "list-group-flush"); + Accessibility.#wcagChecklist.forEach((item) => { + const listItem = document.createElement("li"); + listItem.classList.add("list-group-item"); + + const header = document.createElement("div"); + header.classList.add( + "d-flex", + "justify-content-start", + "align-items-center", + "gap-2", + ); + + const url = document.createElement("a"); + url.classList.add("text-decoration-none"); + url.href = item.url; + url.innerHTML = ``; + url.target = "_blank"; + header.appendChild(url); + + const criterionText = document.createElement("strong"); + criterionText.innerHTML = item.criterion; + header.appendChild(criterionText); + + const badge = document.createElement("span"); + badge.classList.add("badge"); + if (item.compliance === "Compliant") { + badge.classList.add("bg-success"); + } else if (item.compliance === "Partially") { + badge.classList.add("bg-warning"); + } else { + badge.classList.add("bg-danger"); + } + badge.textContent = item.compliance; + header.appendChild(badge); + listItem.appendChild(header); + + const evidence = document.createElement("p"); + evidence.classList.add("m-0"); + evidence.innerHTML = item.evidence; + listItem.appendChild(evidence); + + if (item.notice) { + const notice = document.createElement("p"); + notice.classList.add("m-0"); + notice.innerHTML = `${item.notice}`; + listItem.appendChild(notice); + } + + list.appendChild(listItem); + }); + + fragment.appendChild(description); + fragment.appendChild(list); + return fragment; + } +} + +export default Accessibility; diff --git a/docs/src/js/components/documentation/Api.js b/docs/src/js/components/documentation/Api.js new file mode 100644 index 0000000..f39757f --- /dev/null +++ b/docs/src/js/components/documentation/Api.js @@ -0,0 +1,20 @@ +import InputInteraction from "./api/InputInteraction"; +import Methods from "./api/Methods"; +import Options from "./api/Options"; +import Rerender from "./api/Rerender"; +import State from "./api/State"; +import DocSection from "./DocSection"; + +class Api extends DocSection { + static build() { + return super.build("api", "API", [ + Options.build(), + Methods.build(), + InputInteraction.build(), + State.build(), + Rerender.build(), + ]); + } +} + +export default Api; diff --git a/docs/src/js/components/documentation/Distribution.js b/docs/src/js/components/documentation/Distribution.js new file mode 100644 index 0000000..70be81d --- /dev/null +++ b/docs/src/js/components/documentation/Distribution.js @@ -0,0 +1,105 @@ +import DocArticle from "./DocArticle"; +import DocSection from "./DocSection"; + +class Distribution extends DocSection { + static headers = ["Bootstrap Support", "Plugin Last Release", "End Of Life"]; + static entries = [ + { bootstrap: "5", plugin: "5", eol: "5" }, + { bootstrap: "4", plugin: "4", eol: "4" }, + { bootstrap: "3", plugin: "3", eol: "3" }, + ]; + + static build() { + return super.build( + "distribution", + "Library Distributions", + [ + DocArticle.build({ + description: Distribution.#table(), + }), + ], + "mb-3" + ); + } + + static #table() { + const container = document.createElement("div"); + container.className = "table-responsive"; + + const table = document.createElement("table"); + table.className = "table table-striped table-condensed mb-0"; + + const caption = document.createElement("caption"); + caption.textContent = "Library Distributions"; + + table.append( + caption, + Distribution.#tableHeader(), + Distribution.#tableBody() + ); + container.appendChild(table); + return container; + } + + static #tableHeader() { + const thead = document.createElement("thead"); + const row = document.createElement("tr"); + + Distribution.headers.forEach((headerText) => { + const th = document.createElement("th"); + th.textContent = headerText; + row.appendChild(th); + }); + + thead.appendChild(row); + return thead; + } + + static #tableBody() { + const tbody = document.createElement("tbody"); + Distribution.entries.forEach((entry) => { + const row = document.createElement("tr"); + const tdBootstrap = document.createElement("td"); + const aBootstrap = document.createElement("a"); + aBootstrap.href = `https://getbootstrap.com/docs/${entry.bootstrap}.0`; + aBootstrap.target = "_blank"; + aBootstrap.rel = "noopener noreferrer"; + aBootstrap.title = `Bootstrap ${entry.bootstrap}`; + const imgBootstrap = document.createElement("img"); + imgBootstrap.src = `https://img.shields.io/static/v1?label=bootstrap&message=%5Ev${entry.bootstrap}.0.0&color=informational&logo=bootstrap&logoColor=white`; + imgBootstrap.alt = `Bootstrap ${entry.bootstrap}`; + aBootstrap.appendChild(imgBootstrap); + tdBootstrap.appendChild(aBootstrap); + + const tdPlugin = document.createElement("td"); + const aPlugin = document.createElement("a"); + aPlugin.href = `https://github.com/palcarazm/bootstrap5-toggle/releases`; + aPlugin.target = "_blank"; + aPlugin.rel = "noopener noreferrer"; + aPlugin.title = `bs-toggle v${entry.plugin}`; + const imgPlugin = document.createElement("img"); + imgPlugin.src = `https://img.shields.io/github/package-json/v/palcarazm/bootstrap5-toggle/v${entry.plugin}?logo=github`; + imgPlugin.alt = `bs-toggle v${entry.plugin}`; + aPlugin.appendChild(imgPlugin); + tdPlugin.appendChild(aPlugin); + + const tdEOL = document.createElement("td"); + const aEOL = document.createElement("a"); + aEOL.href = `https://github.com/palcarazm/bootstrap5-toggle/security/policy`; + aEOL.target = "_blank"; + aEOL.rel = "noopener noreferrer"; + aEOL.title = `End of Life`; + const imgEOL = document.createElement("img"); + imgEOL.src = `https://img.shields.io/endpoint?url=https%3A%2F%2Fpalcarazm.github.io%2Fbootstrap5-toggle%2Fapi%2Feol%2Fv${entry.eol}`; + imgEOL.alt = `EOL v${entry.eol}`; + aEOL.appendChild(imgEOL); + tdEOL.appendChild(aEOL); + + row.append(tdBootstrap, tdPlugin, tdEOL); + tbody.appendChild(row); + }); + return tbody; + } +} + +export default Distribution; diff --git a/docs/src/js/components/documentation/DocAlert.js b/docs/src/js/components/documentation/DocAlert.js new file mode 100644 index 0000000..c91d2ec --- /dev/null +++ b/docs/src/js/components/documentation/DocAlert.js @@ -0,0 +1,54 @@ +import VersionPill from "../VersionPill"; + +class DocAlert { + static build({ type, title, content, versionPill = null }) { + const alert = document.createElement("div"); + alert.className = `alert alert-${type}`; + + const alertBody = document.createElement("div"); + alertBody.innerHTML = content; + + alert.append(DocAlert.#alertHeader(type, title, versionPill), alertBody); + return alert; + } + + static #alertHeader(type, title, versionPill) { + const header = document.createElement("div"); + header.className = + "d-flex justify-content-between align-items-center alert-heading"; + + let icon = document.createElement("i"); + switch (type) { + case "info": + icon.className = "fa-solid fa-info-circle"; + break; + case "success": + icon.className = "fa-solid fa-circle-check"; + break; + case "warning": + icon.className = "fa-solid fa-exclamation-circle"; + break; + case "danger": + icon.className = "fa-solid fa-exclamation-triangle"; + break; + default: + icon = null; + break; + } + + const col = document.createElement("div"); + if (icon) col.appendChild(icon); + const titleContainer = document.createElement("span"); + titleContainer.className = "text-uppercase fw-bold ms-1"; + titleContainer.textContent = title; + col.appendChild(titleContainer); + + header.appendChild(col); + + if (versionPill) header.appendChild(VersionPill.build(versionPill)); + + return header; + } +} + +export default DocAlert; diff --git a/docs/src/js/components/documentation/DocArticle.js b/docs/src/js/components/documentation/DocArticle.js new file mode 100644 index 0000000..be1a405 --- /dev/null +++ b/docs/src/js/components/documentation/DocArticle.js @@ -0,0 +1,57 @@ +import Badge from "../Badge"; +import CodeBlock from "../CodeBlock"; +import CodePanel from "../CodePanel"; +import VersionPill from "../VersionPill"; +import DocAlert from "./DocAlert"; + +class DocArticle { + static build({ + title = null, + className = "", + badge = null, + versionPill = null, + description = null, + example = [], + codePanel = null, + codeBlock = null, + alert = null, + }) { + const docArticle = document.createElement("article"); + docArticle.className = `ps-2 px-2 border-start border-2 ${className}`; + + if (title) + docArticle.appendChild( + DocArticle.#articleHeader(title, badge, versionPill) + ); + + if (description) docArticle.appendChild(description); + + if (codePanel && codeBlock) + throw new Error("CodePanel and CodeBlock are mutually exclusive."); + if (codePanel) + docArticle.appendChild(CodePanel.build({ example, ...codePanel })); + if (codeBlock) + docArticle.appendChild(CodeBlock.build({ example, ...codeBlock })); + + if (alert) docArticle.appendChild(DocAlert.build(alert)); + + return docArticle; + } + + static #articleHeader(title, badge, versionPill) { + const header = document.createElement("div"); + header.className = "d-flex justify-content-between align-items-center"; + const titleContainer = document.createElement("h3"); + titleContainer.className = "text-secondary"; + titleContainer.textContent = title; + header.appendChild(titleContainer); + + if (badge) header.appendChild(Badge.build(badge)); + + if (versionPill) header.appendChild(VersionPill.build(versionPill)); + + return header; + } +} + +export default DocArticle; diff --git a/docs/src/js/components/documentation/DocSection.js b/docs/src/js/components/documentation/DocSection.js new file mode 100644 index 0000000..3bd08ee --- /dev/null +++ b/docs/src/js/components/documentation/DocSection.js @@ -0,0 +1,16 @@ +class DocSection { + static build(name, title, HTMLElements, className = "") { + const section = document.createElement("section"); + section.id = name; + section.className = `container ${className}`; + section.append(DocSection.#header(title), ...HTMLElements); + return section; + } + static #header(title) { + const header = document.createElement("h2"); + header.textContent = title; + return header; + } +} + +export default DocSection; diff --git a/docs/src/js/components/documentation/Events.js b/docs/src/js/components/documentation/Events.js new file mode 100644 index 0000000..36e1646 --- /dev/null +++ b/docs/src/js/components/documentation/Events.js @@ -0,0 +1,16 @@ +import DocSection from "./DocSection"; +import CustomEvents from "./events/CustomEvents"; +import EventPropagation from "./events/EventPropagation"; +import SilencedActions from "./events/SilencedActions"; + +class Events extends DocSection { + static build() { + return super.build("events", "Events", [ + EventPropagation.build(), + CustomEvents.build(), + SilencedActions.build(), + ]); + } +} + +export default Events; diff --git a/docs/src/js/components/documentation/Features.js b/docs/src/js/components/documentation/Features.js new file mode 100644 index 0000000..a9be466 --- /dev/null +++ b/docs/src/js/components/documentation/Features.js @@ -0,0 +1,38 @@ +import DocSection from "./DocSection"; +import AnimationSpeed from "./features/AnimationSpeed"; +import Colors from "./features/Colors"; +import CustomFormValue from "./features/CustomFormValue"; +import CustomSize from "./features/CustomSize"; +import CustomStyle from "./features/CustomStyle"; +import CustomText from "./features/CustomText"; +import CustomTitle from "./features/CustomTitle"; +import FormLayout from "./features/FormLayout"; +import KeyboardInteraction from "./features/KeyboardInteraction"; +import OutlineColors from "./features/OutlineColors"; +import Size from "./features/Size"; +import StateStatus from "./features/StateStatus"; +import Tooltip from "./features/Tooltip"; +import Tristate from "./features/Tristate"; + +class Features extends DocSection { + static build() { + return super.build("features", "Features", [ + Size.build(), + CustomSize.build(), + Tristate.build(), + Colors.build(), + OutlineColors.build(), + CustomText.build(), + CustomTitle.build(), + Tooltip.build(), + CustomStyle.build(), + AnimationSpeed.build(), + KeyboardInteraction.build(), + StateStatus.build(), + CustomFormValue.build(), + FormLayout.build(), + ]); + } +} + +export default Features; diff --git a/docs/src/js/components/documentation/Installation.js b/docs/src/js/components/documentation/Installation.js new file mode 100644 index 0000000..70e07e6 --- /dev/null +++ b/docs/src/js/components/documentation/Installation.js @@ -0,0 +1,73 @@ +import DocArticle from "./DocArticle"; +import DocSection from "./DocSection"; + +class Installation extends DocSection { + static build() { + return super.build("installation", "Installation", [ + Installation.#cdn(), + Installation.#github(), + Installation.#npm(), + Installation.#yarn(), + ]); + } + + static #cdn() { + const ecmas = ` +`; + const jquery = ` +`; + return DocArticle.build({ + title: "CDN", + badge: { + name: "jsDelivr", + href: "https://www.jsdelivr.com/package/npm/bootstrap5-toggle", + imgSrc: + "https://img.shields.io/jsdelivr/npm/hm/bootstrap5-toggle?label=hits&logo=jsdelivr&logoColor=white", + }, + codePanel: { + name: "cdn", + language: "html", + contents: [ecmas, jquery], + }, + }); + } + + static #github() { + return DocArticle.build({ + title: "Download from GitHub", + badge: { + name: "Latest release", + href: "https://github.com/palcarazm/bootstrap5-toggle/releases", + imgSrc: + "https://img.shields.io/github/package-json/v/palcarazm/bootstrap5-toggle/v5?logo=github", + }, + }); + } + + static #npm() { + return DocArticle.build({ + title: "NPM", + badge: { + name: "NPM", + href: "https://www.npmjs.com/package/bootstrap5-toggle", + imgSrc: "https://img.shields.io/npm/dm/bootstrap5-toggle?logo=npm", + }, + codeBlock: { + language: "shell", + code: `npm install bootstrap5-toggle@${versions.bootstrap5Toggle}`, + }, + }); + } + + static #yarn() { + return DocArticle.build({ + title: "Yarn", + codeBlock: { + language: "shell", + code: `yarn add bootstrap5-toggle@${versions.bootstrap5Toggle}`, + }, + }); + } +} + +export default Installation; diff --git a/docs/src/js/components/documentation/Usage.js b/docs/src/js/components/documentation/Usage.js new file mode 100644 index 0000000..a507740 --- /dev/null +++ b/docs/src/js/components/documentation/Usage.js @@ -0,0 +1,42 @@ +import DocSection from "./DocSection"; +import DocArticle from "./DocArticle"; + +class Usage extends DocSection { + static build() { + return super.build("usage", "Usage", [Usage.#html(), Usage.#js()]); + } + + static #html() { + const description = document.createElement("p"); + description.innerHTML = `Simply add data-toggle="toggle" to convert checkboxes into toggles.`; + + return DocArticle.build({ + title: "Initialize with HTML", + description, + codeBlock: { + language: "html", + code: ``, + }, + }); + } + + static #js() { + const description = document.createElement("p"); + description.innerHTML = `Simply call the bootstrapToggle method to convert checkboxes into toggles. See Options for additional colors, etc.

`; + + const ecmas = `document.querySelector(mySelector).bootstrapToggle();`; + const jquery = `$(mySelector).bootstrapToggle();`; + + return DocArticle.build({ + title: "Initialize with JavaScript", + description, + codePanel: { + name: "usage-js", + language: "javascript", + contents: [ecmas, jquery], + }, + }); + } +} + +export default Usage; diff --git a/docs/src/js/components/documentation/api/InputInteraction.js b/docs/src/js/components/documentation/api/InputInteraction.js new file mode 100644 index 0000000..f33d072 --- /dev/null +++ b/docs/src/js/components/documentation/api/InputInteraction.js @@ -0,0 +1,167 @@ +import DocArticle from "../DocArticle"; + +class InputInteraction extends DocArticle { + static #interactions = [ + { + action: "checked", + function: "myInput.checked = true", + description: "Sets the toggle to 'On' state", + demo: (input) => { + input.checked = true; + }, + }, + { + action: "unchecked", + function: "myInput.checked = false", + description: "Sets the toggle to 'Off' state", + demo: (input) => { + input.checked = false; + }, + }, + { + action: "indeterminate", + function: "myInput.indeterminate = true", + description: "Sets the toggle to 'indeterminate' state", + demo: (input) => { + input.indeterminate = true; + }, + }, + { + action: "determinate", + function: "myInput.indeterminate = false", + description: + "Sets the toggle to the previous 'determinate' state ('on' or 'off')", + demo: (input) => { + input.indeterminate = false; + }, + }, + { + action: "enable", + function: "myInput.disabled = false", + description: "Enables the toggle", + demo: (input) => { + input.disabled = false; + }, + }, + { + action: "disable", + function: "myInput.disabled = true", + description: "Disables the toggle", + demo: (input) => { + input.disabled = true; + }, + }, + { + action: "read-only", + function: "myInput.readOnly = true", + description: + "Disables the toggle but the checkbox stay readable in form data.", + demo: (input) => { + input.readOnly = true; + }, + }, + { + action: "read-write", + function: "myInput.readOnly = false", + description: + "Removes the readonly state, enabling user interaction with the toggle.", + demo: (input) => { + input.readOnly = false; + }, + }, + ]; + + static build() { + const input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.setAttribute("id", "api-input-interaction"); + input.setAttribute("checked", ""); + input.setAttribute("tristate", ""); + input.dataset.toggle = "toggle"; + + return super.build({ + title: "Input Interaction", + description: InputInteraction.#description(input), + codeBlock: { + language: "html", + code: ` +`, + }, + example: [input], + }); + } + + static #description(input) { + const description = document.createElement("div"); + + const paragraph = document.createElement("p"); + paragraph.innerHTML = + "Interact with the input elements dispatch changes to the toggle:"; + description.append(paragraph, InputInteraction.#table(input)); + + return description; + } + + static #table(input) { + const table = document.createElement("table"); + table.className = "table table-striped table-condensed"; + const caption = document.createElement("caption"); + caption.textContent = "Input interaction demo"; + table.append( + caption, + InputInteraction.#thead(), + InputInteraction.#tbody(input), + ); + + return table; + } + + static #thead() { + const thead = document.createElement("thead"); + const tr = document.createElement("tr"); + tr.append( + ...["Action", "Example", "Description", "Demo"].map((label) => { + const th = document.createElement("th"); + th.textContent = label; + return th; + }), + ); + thead.append(tr); + return thead; + } + + static #tbody(input) { + const tbody = document.createElement("tbody"); + + const trs = InputInteraction.#interactions.map( + ({ action, function: func, description, demo }) => { + const td1 = document.createElement("td"); + td1.innerHTML = `${action}`; + + const td2 = document.createElement("td"); + td2.innerHTML = `${func}`; + + const td3 = document.createElement("td"); + td3.textContent = description; + + const td4 = document.createElement("td"); + const button = document.createElement("button"); + button.className = "btn btn-outline-dark btn-sm w-100"; + button.textContent = action; + button.onclick = () => { + demo(input); + }; + td4.append(button); + + const tr = document.createElement("tr"); + tr.append(td1, td2, td3, td4); + return tr; + }, + ); + + tbody.append(...trs); + return tbody; + } +} + +export default InputInteraction; diff --git a/docs/src/js/components/documentation/api/Methods.js b/docs/src/js/components/documentation/api/Methods.js new file mode 100644 index 0000000..cf7f008 --- /dev/null +++ b/docs/src/js/components/documentation/api/Methods.js @@ -0,0 +1,151 @@ +import DocArticle from "../DocArticle"; + +class Methods extends DocArticle { + static #methods = [ + { + method: "initialize", + params: null, + description: "Initializes the toggle plugin with options", + }, + { + method: "destroy", + params: "destroy", + description: "Destroys the toggle", + }, + { + method: "rerender", + params: "rerender", + description: "Rerenders the toggle", + }, + { + method: "on", + params: "on", + description: "Sets the toggle to 'On' state", + }, + { + method: "off", + params: "off", + description: "Sets the toggle to 'Off' state", + }, + { + method: "toggle", + params: "toggle", + description: "Toggles the state of the toggle", + }, + { + method: "indeterminate", + params: "indeterminate", + description: "Sets the toggle to 'indeterminate' state", + }, + { + method: "determinate", + params: "determinate", + description: + "Sets the toggle to the previous 'determinate' state ('on' or 'off')", + }, + { + method: "enable", + params: "enable", + description: "Enables the toggle", + }, + { + method: "disable", + params: "disable", + description: "Disables the toggle", + }, + { + method: "readonly", + params: "readonly", + description: + "Disables the toggle but the checkbox stay readable in form data.", + }, + ]; + + static build() { + const input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.setAttribute("id", "api-methods-toggle"); + input.setAttribute("checked", ""); + + return super.build({ + title: "Methods", + description: Methods.#description(input), + codeBlock: { + language: "html", + code: ` +`, + }, + example: [input], + }); + } + + static #description(input) { + const description = document.createElement("div"); + + const paragraph = document.createElement("p"); + paragraph.innerHTML = + "Methods can be used to control toggles directly. The following methods are available in the following way:"; + description.append(paragraph, Methods.#table(input)); + + return description; + } + + static #table(input) { + const table = document.createElement("table"); + table.className = "table table-striped table-condensed"; + const caption = document.createElement("caption"); + caption.textContent = "API methods demo"; + table.append(caption, Methods.#thead(), Methods.#tbody(input)); + + return table; + } + + static #thead() { + const thead = document.createElement("thead"); + const tr = document.createElement("tr"); + tr.append( + ...["Method", "Example", "Description", "Demo"].map((label) => { + const th = document.createElement("th"); + th.textContent = label; + return th; + }) + ); + thead.append(tr); + return thead; + } + + static #tbody(input) { + const tbody = document.createElement("tbody"); + + const trs = Methods.#methods.map(({ method, params, description }) => { + const td1 = document.createElement("td"); + td1.innerHTML = `${method}`; + + const td2 = document.createElement("td"); + td2.innerHTML = params + ? `myToggle.bootstrapToggle("${params}")` + : `myToggle.bootstrapToggle()`; + + const td3 = document.createElement("td"); + td3.textContent = description; + + const td4 = document.createElement("td"); + const button = document.createElement("button"); + button.className = "btn btn-outline-dark btn-sm w-100"; + button.textContent = method; + button.onclick = () => { + input.bootstrapToggle(params); + }; + td4.append(button); + + const tr = document.createElement("tr"); + tr.append(td1, td2, td3, td4); + return tr; + }); + + tbody.append(...trs); + return tbody; + } +} + +export default Methods; diff --git a/docs/src/js/components/documentation/api/Options.js b/docs/src/js/components/documentation/api/Options.js new file mode 100644 index 0000000..1e56a46 --- /dev/null +++ b/docs/src/js/components/documentation/api/Options.js @@ -0,0 +1,286 @@ +import DocArticle from "../DocArticle"; + +class Options extends DocArticle { + static build() { + return super.build({ + title: "Options", + description: Options.#description(), + example: Options.#example(), + codePanel: Options.#codePanel(), + alert: { + type: "warning", + title: "Deprecation notice", + versionPill: { + version: "5.0.0", + action: "DEPRECATED", + }, + content: `Using on and off user options is deprecated and will throw a console warning. Use onlabel and offlabel user options instead.`, + }, + }); + } + + static #codePanel() { + const ecmas = `
+ + +
+`; + const jquery = `
+ + +
+`; + return { + name: "api-options", + language: "javascript", + tabs: ["ECMAScript", "jQuery"], + contents: [ecmas, jquery], + }; + } + + static #example() { + const form = document.createElement("form"); + form.id = "form_state_status"; + form.className = "d-flex justify-content-between align-items-center"; + + const input = document.createElement("input"); + input.id = "api-option-toggle"; + input.type = "checkbox"; + input.checked = true; + + const button = document.createElement("button"); + button.type = "submit"; + button.className = "btn btn-outline-secondary"; + button.innerHTML = "Launch Bootstrap Toggle"; + + form.append(input, button); + + form.onsubmit = (e) => { + e.preventDefault(); + input.bootstrapToggle({ + onlable: "Enabled", + offlabel: "Disabled", + onstyle: "success", + offstyle: "danger", + onvalue: "1", + offvalue: "0", + size: "lg", + style: null, + width: null, + height: null, + tabindex: -1, + tristate: true, + tooltip: { + placement: "top", + title: { on: "Enabled", off: "Disabled", mixed: "Mixed" }, + }, + }); + }; + + return [form]; + } + + static #description() { + const description = document.createElement("div"); + description.innerHTML = `

Options can be passed via data attributes or JavaScript. For data attributes, append the option name to data-, as in data-on="Enabled". Data attributes will take precedence over JavaScript options.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
API constructor options
NameTypeDefaultDescription
onlabelstring | html"On"Text of the on toggle label.
offlabelstring | html"Off"Text of the off toggle label.
onstylestring"primary" + Style of the on toggle.
Possible values are: + primary, secondary, success, danger, + warning, info, light, dark
+
offstylestring"secondary" + Style of the off toggle.
Possible values are: + primary, secondary, success, danger, + warning, info, light, dark
+
onvaluestringnullSets on state value.
offvaluestringnullSets off state value.
sizestringnull + Size of the toggle. If set to null, button is default/normal size.
+ Possible values are: + lg, sm, xs
+
stylestringnull + Appends the provided value to the toggle's class attribute. + Use this to apply custom styles to the toggle. +
widthintegernull + Sets the width of the toggle.
+ If set to null, width will be calculated. +
heightintegernull + Sets the height of the toggle.
+ If set to null, height will be calculated. +
tabindexinteger0Sets the tabindex.
tristatebooleanfalseSets tristate support.
tooltipobjectundefined + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tooltip options
NameTypeDefaultDescription
placementstring"top"Placement of the tooltip. Possible values are: top, bottom, left, and right.
title.onstring | htmlundefinedTitle of the tooltip when the toggle is on.
title.offstring | htmlundefinedTitle of the tooltip when the toggle is off.
title.mixedstring | htmlundefinedTitle of the tooltip when the toggle is in mixed state (if tristate is enabled).
+
+
`; + return description; + } +} + +export default Options; diff --git a/docs/src/js/components/documentation/api/Rerender.js b/docs/src/js/components/documentation/api/Rerender.js new file mode 100644 index 0000000..6f6960b --- /dev/null +++ b/docs/src/js/components/documentation/api/Rerender.js @@ -0,0 +1,61 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class Rerender extends DocArticle { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle can be re-rendered with the rerender method. This can be used to update toggle when a process updates input data attributes.`; + + const code = `
+ + +
+`; + + return super.build({ + title: "Re-render", + description, + example: Rerender.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const form = document.createElement("form"); + form.id = "api-state-form"; + form.className = "d-flex justify-content-between align-items-center"; + form.innerHTML = ` + `; + + const console = new Console(); + + form.onsubmit = (e) => { + e.preventDefault(); + const myToggle = e.target.elements["api-rerender-toggle"]; + const onLabel = crypto.getRandomValues(new Uint32Array(1))[0]; + myToggle.dataset.onlabel = onLabel; + console.log({ + mode: "append", + data: `On label set to: ${onLabel}`, + }); + + myToggle.bootstrapToggle("rerender"); + }; + + return [form, console.htmlElement]; + } +} + +export default Rerender; diff --git a/docs/src/js/components/documentation/api/State.js b/docs/src/js/components/documentation/api/State.js new file mode 100644 index 0000000..31ad0c5 --- /dev/null +++ b/docs/src/js/components/documentation/api/State.js @@ -0,0 +1,47 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class State extends DocArticle { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Checked state of a toggle can be set by reading checked property. Use the element property for checked state myToggle.checked (returns true or false).`; + + const code = `
+ + +
+`; + + return super.build({ + title: "Toggle State", + description, + example: State.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const form = document.createElement("form"); + form.id = "api-state-form"; + form.className = "d-flex justify-content-between align-items-center"; + form.innerHTML = ` + `; + + const console = new Console(); + + form.onsubmit = (e) => { + e.preventDefault(); + console.log({ + mode: "append", + data: `Checked: ${e.target.elements["api-state-toggle"].checked}`, + }); + }; + + return [form, console.htmlElement]; + } +} + +export default State; diff --git a/docs/src/js/components/documentation/events/CustomEvents.js b/docs/src/js/components/documentation/events/CustomEvents.js new file mode 100644 index 0000000..373361b --- /dev/null +++ b/docs/src/js/components/documentation/events/CustomEvents.js @@ -0,0 +1,136 @@ +import { version } from "grunt"; +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class CustomEvents extends DocArticle { + static #events = [ + { + name: "toggle:on", + action: "on", + }, + { + name: "toggle:off", + action: "off", + }, + { + name: "toggle:mixed", + action: "indeterminate", + }, + { + name: "toggle:enabled", + action: "enable", + }, + { + name: "toggle:disabled", + action: "disable", + }, + { + name: "toggle:readonly", + action: "readonly", + }, + ]; + + static build() { + const input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.setAttribute("id", "custom-events-toggle"); + input.setAttribute("checked", ""); + input.setAttribute("tristate", ""); + input.setAttribute("data-toggle", "toggle"); + const console = new Console(); + + CustomEvents.#events.forEach((event) => { + input.addEventListener(event.name, (e) => { + console.log({ + mode: "append", + data: `Event ${event.name} fired. Toggle state: ${JSON.stringify(e.detail.state, null, 2)}`, + }); + }); + }); + + return super.build({ + title: "Custom Events", + description: CustomEvents.#description(input), + codeBlock: { + language: "html", + code: ` +`, + }, + example: [input, console.htmlElement], + versionPill: { action: "SINCE", version: "5.3.0" }, + }); + } + + static #description(input) { + const description = document.createElement("div"); + + const paragraph = document.createElement("p"); + paragraph.innerHTML = + "Bootstrap Toggle emit custom events when its state or status is changed. The events details contain the current state of the toggle. The following custom events are available:"; + description.append(paragraph, CustomEvents.#table(input)); + + return description; + } + + static #table(input) { + const table = document.createElement("table"); + table.className = "table table-striped table-condensed"; + const caption = document.createElement("caption"); + caption.textContent = "Custom events demo"; + table.append(caption, CustomEvents.#thead(), CustomEvents.#tbody(input)); + + return table; + } + + static #thead() { + const thead = document.createElement("thead"); + const tr = document.createElement("tr"); + tr.append( + ...["Event", "Example", "Launch"].map((label) => { + const th = document.createElement("th"); + th.textContent = label; + return th; + }), + ); + thead.append(tr); + return thead; + } + + static #tbody(input) { + const tbody = document.createElement("tbody"); + + const trs = CustomEvents.#events.map(({ name, action }) => { + const td1 = document.createElement("td"); + td1.innerHTML = `${name}`; + + const td2 = document.createElement("td"); + td2.innerHTML = `myToggle.addEventListener("${name}", (e)=>{...})`; + + const td3 = document.createElement("td"); + const defaultBtn = document.createElement("button"); + defaultBtn.className = "btn btn-outline-dark btn-sm w-100"; + defaultBtn.textContent = action; + defaultBtn.onclick = () => { + input.bootstrapToggle(action, false); + }; + td3.append(defaultBtn); + + const tr = document.createElement("tr"); + tr.append(td1, td2, td3); + return tr; + }); + + tbody.append(...trs); + return tbody; + } +} + +export default CustomEvents; diff --git a/docs/src/js/components/documentation/events/EventPropagation.js b/docs/src/js/components/documentation/events/EventPropagation.js new file mode 100644 index 0000000..8f1ead1 --- /dev/null +++ b/docs/src/js/components/documentation/events/EventPropagation.js @@ -0,0 +1,49 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class EventPropagation extends DocArticle { + static build() { + const description = document.createElement("p"); + description.innerHTML = `All events are propagated to and from input element to the toggle. Listen for events on the input directly as the toggle stays synced with the + input.`; + + const code = ` +`; + + return super.build({ + title: "Event Propagation", + description, + example: EventPropagation.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const input = document.createElement("input"); + input.type = "checkbox"; + input.dataset.toggle = "toggle"; + input.checked = true; + input.id = "events-propagation-toggle"; + + const console = new Console(); + + input.addEventListener("change", (e) => { + console.log({ + mode: "append", + data: `Input change fired. Checked state: ${input.checked}`, + }); + }); + + return [input, console.htmlElement]; + } +} + +export default EventPropagation; diff --git a/docs/src/js/components/documentation/events/SilencedActions.js b/docs/src/js/components/documentation/events/SilencedActions.js new file mode 100644 index 0000000..c1f382f --- /dev/null +++ b/docs/src/js/components/documentation/events/SilencedActions.js @@ -0,0 +1,125 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class SilencedActions extends DocArticle { + static #methods = [ + "on", + "off", + "toggle", + "indeterminate", + "determinate", + "enable", + "disable", + "readonly", + ]; + + static build() { + const input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.setAttribute("id", "events-silence-toggle"); + input.setAttribute("checked", ""); + input.setAttribute("data-toggle", "toggle"); + const console = new Console(); + + input.addEventListener("change", (e) => { + console.log({ + mode: "append", + data: `Input change fired. Checked state: ${input.checked}`, + }); + }); + + return super.build({ + title: "Silenced Actions", + description: SilencedActions.#description(input), + codeBlock: { + language: "html", + code: ` +`, + }, + example: [input, console.htmlElement], + }); + } + + static #description(input) { + const description = document.createElement("div"); + + const paragraph = document.createElement("p"); + paragraph.innerHTML = + "Methods can be used in silence mode just specifying the second argument to true on the bootstrapToggle call. This is useful to prevent the control from propagating the change event in cases where you want to update the state, but do not want to fire the onChange event. The following methods are available in silence mode in the following way:"; + description.append(paragraph, SilencedActions.#table(input)); + + return description; + } + + static #table(input) { + const table = document.createElement("table"); + table.className = "table table-striped table-condensed"; + const caption = document.createElement("caption"); + caption.textContent = "Silenced methods demo"; + table.append( + caption, + SilencedActions.#thead(), + SilencedActions.#tbody(input), + ); + + return table; + } + + static #thead() { + const thead = document.createElement("thead"); + const tr = document.createElement("tr"); + tr.append( + ...["Method", "Example", "Normal", "Silenced"].map((label) => { + const th = document.createElement("th"); + th.textContent = label; + return th; + }), + ); + thead.append(tr); + return thead; + } + + static #tbody(input) { + const tbody = document.createElement("tbody"); + + const trs = SilencedActions.#methods.map((method) => { + const td1 = document.createElement("td"); + td1.innerHTML = `${method}`; + + const td2 = document.createElement("td"); + td2.innerHTML = `myToggle.bootstrapToggle("${method}", true)`; + + const td3 = document.createElement("td"); + const defaultBtn = document.createElement("button"); + defaultBtn.className = "btn btn-outline-dark btn-sm w-100"; + defaultBtn.textContent = method; + defaultBtn.onclick = () => { + input.bootstrapToggle(method, false); + }; + td3.append(defaultBtn); + + const td4 = document.createElement("td"); + const silenceBtn = document.createElement("button"); + silenceBtn.className = "btn btn-outline-dark btn-sm w-100"; + silenceBtn.textContent = method; + silenceBtn.onclick = () => { + input.bootstrapToggle(method, true); + }; + td4.append(silenceBtn); + + const tr = document.createElement("tr"); + tr.append(td1, td2, td3, td4); + return tr; + }); + + tbody.append(...trs); + return tbody; + } +} + +export default SilencedActions; diff --git a/docs/src/js/components/documentation/features/AnimationSpeed.js b/docs/src/js/components/documentation/features/AnimationSpeed.js new file mode 100644 index 0000000..fe2c1e7 --- /dev/null +++ b/docs/src/js/components/documentation/features/AnimationSpeed.js @@ -0,0 +1,58 @@ +import DocArticle from "../DocArticle"; + +class AnimationSpeed { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Transition speed can be easily controlled with css transition property on .toggle-group. You can also turn animation off completely.`; + + const code = ` + + +`; + + return DocArticle.build({ + title: "Animation Speed", + description, + example: AnimationSpeed.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const style = document.createElement("style"); + style.textContent = ` + .slow .toggle-group { transition: left 0.7s; -webkit-transition: left 0.7s; } + .fast .toggle-group { transition: left 0.1s; -webkit-transition: left 0.1s; } + .none .toggle-group { transition: none; -webkit-transition: none; }`; + + const inputs = ["slow", "fast", "none"].map((style) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + input.dataset.style = style; + return input; + }); + + return [style, ...inputs]; + } +} +export default AnimationSpeed; diff --git a/docs/src/js/components/documentation/features/Colors.js b/docs/src/js/components/documentation/features/Colors.js new file mode 100644 index 0000000..3fd0645 --- /dev/null +++ b/docs/src/js/components/documentation/features/Colors.js @@ -0,0 +1,49 @@ +import DocArticle from "../DocArticle"; + +class Colors { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle implements all Bootstrap colors. Just add the data-onstyle or data-offstyle attribute with one of the following values: primary, secondary, success, danger, warning, info, light or dark.`; + + const code = ` + + + + + + +`; + + return DocArticle.build({ + title: "Colors", + description, + example: Colors.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const example = [ + "primary", + "secondary", + "success", + "danger", + "warning", + "info", + "light", + "dark", + ].map((color) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + input.dataset.onstyle = color; + return input; + }); + return example; + } +} +export default Colors; diff --git a/docs/src/js/components/documentation/features/CustomFormValue.js b/docs/src/js/components/documentation/features/CustomFormValue.js new file mode 100644 index 0000000..1bb5ada --- /dev/null +++ b/docs/src/js/components/documentation/features/CustomFormValue.js @@ -0,0 +1,50 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class CustomFormValue { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Form submit values can be customized with the data-onvalue and data-offvalue attributes.If original checkbox have a value set, data-onvalue will be ignored preserving the original value.`; + + const code = `
+ + + + +
`; + + return DocArticle.build({ + title: "Custom form submit values", + description, + example: CustomFormValue.#example(), + codeBlock: { + language: "html", + code, + }, + versionPill: { version: "4.3.0", action: "SINCE" }, + }); + } + + static #example() { + const form = document.createElement("form"); + form.id = "form_state_status"; + form.className = "d-flex justify-content-between align-items-center"; + form.innerHTML = ` + + + `; + + const console = new Console(); + + form.onsubmit = (e) => { + e.preventDefault(); + console.json({ + mode: "replace", + data: Object.fromEntries(new FormData(e.target)), + }); + }; + + return [form, console.htmlElement]; + } +} +export default CustomFormValue; diff --git a/docs/src/js/components/documentation/features/CustomSize.js b/docs/src/js/components/documentation/features/CustomSize.js new file mode 100644 index 0000000..9688a52 --- /dev/null +++ b/docs/src/js/components/documentation/features/CustomSize.js @@ -0,0 +1,40 @@ +import DocArticle from "../DocArticle"; + +class CustomSize { + static build() { + const description = document.createElement("p"); + description.innerHTML = ` Bootstrap toggle can handle custom sizes by data-width and data-height options.`; + + const code = ` + +`; + + return DocArticle.build({ + title: "Custom Sizes", + description, + example: CustomSize.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const example = [ + { width: "100", height: "75" }, + { height: "5rem" }, + { width: "6em" }, + ].map(({ width, height }) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + if (width) input.dataset.width = width; + if (height) input.dataset.height = height; + return input; + }); + return example; + } +} +export default CustomSize; diff --git a/docs/src/js/components/documentation/features/CustomStyle.js b/docs/src/js/components/documentation/features/CustomStyle.js new file mode 100644 index 0000000..f34d3f5 --- /dev/null +++ b/docs/src/js/components/documentation/features/CustomStyle.js @@ -0,0 +1,63 @@ +import DocArticle from "../DocArticle"; + +class CustomStyle { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle allows to style the buttons to fit an existing UX. Just add the data-style attribute to set a custom css class.`; + + const code = ` + + + + + +`; + + return DocArticle.build({ + title: "Custom Style", + description, + example: CustomStyle.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const iosStyle = document.createElement("style"); + iosStyle.textContent = `.toggle.ios, .toggle.ios .toggle-on, .toggle.ios .toggle-off, .toggle.ios .toggle-handle { border-radius: 20rem; }`; + + const iosInput = document.createElement("input"); + iosInput.type = "checkbox"; + iosInput.checked = true; + iosInput.dataset.toggle = "toggle"; + iosInput.dataset.style = "ios"; + + const androidStyle = document.createElement("style"); + androidStyle.textContent = `.toggle.android , .toggle.android .toggle-on, .toggle.android .toggle-off, .toggle.android .toggle-handle{ border-radius: 0px; }`; + + const androidInput = document.createElement("input"); + androidInput.type = "checkbox"; + androidInput.checked = true; + androidInput.dataset.toggle = "toggle"; + androidInput.dataset.style = "android"; + + return [iosStyle, iosInput, androidStyle, androidInput]; + } +} +export default CustomStyle; diff --git a/docs/src/js/components/documentation/features/CustomText.js b/docs/src/js/components/documentation/features/CustomText.js new file mode 100644 index 0000000..5ea4f21 --- /dev/null +++ b/docs/src/js/components/documentation/features/CustomText.js @@ -0,0 +1,51 @@ +import DocArticle from "../DocArticle"; + +class CustomText { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle allows to set custom labels. Just add the data-onlabel or data-offlabel attribute to set a custom label. Plain text and HTML are supported.`; + + const code = ` +`; + + return DocArticle.build({ + title: "Custom Text", + description, + example: CustomText.#example(), + codeBlock: { + language: "html", + code, + }, + alert: { + type: "warning", + title: "Deprecation notice", + versionPill: { + version: "5.0.0", + action: "DEPRECATED", + }, + content: `Using data-on and data-off data attributes is deprecated and will throw a console warning. Use data-onlabel and data-offlabel data attributes instead.`, + }, + }); + } + + static #example() { + const input1 = document.createElement("input"); + input1.type = "checkbox"; + input1.checked = true; + input1.dataset.toggle = "toggle"; + input1.dataset.onlabel = "Ready"; + input1.dataset.offlabel = "Not Ready"; + input1.dataset.onstyle = "success"; + input1.dataset.offstyle = "danger"; + + const input2 = document.createElement("input"); + input2.type = "checkbox"; + input2.checked = true; + input2.dataset.toggle = "toggle"; + input2.dataset.onlabel = " Play"; + input2.dataset.offlabel = " Pause"; + + return [input1, input2]; + } +} +export default CustomText; diff --git a/docs/src/js/components/documentation/features/CustomTitle.js b/docs/src/js/components/documentation/features/CustomTitle.js new file mode 100644 index 0000000..9728616 --- /dev/null +++ b/docs/src/js/components/documentation/features/CustomTitle.js @@ -0,0 +1,44 @@ +import DocArticle from "../DocArticle"; + +class CustomTitle { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle allows to set custom title. Just add the data-ontitle or data-offtitle attribute to set a custom label. To set the same title in + both parts just use HTML standard title attribute.`; + + const code = ` +`; + + return DocArticle.build({ + title: "Custom Title", + description, + versionPill: { + version: "5.0.0", + action: "SINCE", + }, + example: CustomTitle.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const input1 = document.createElement("input"); + input1.type = "checkbox"; + input1.checked = true; + input1.dataset.toggle = "toggle"; + input1.dataset.ontitle = "ON"; + input1.dataset.offtitle = "OFF"; + + const input2 = document.createElement("input"); + input2.type = "checkbox"; + input2.checked = true; + input2.dataset.toggle = "toggle"; + input2.title = "TOGGLE"; + + return [input1, input2]; + } +} +export default CustomTitle; diff --git a/docs/src/js/components/documentation/features/FormLayout.js b/docs/src/js/components/documentation/features/FormLayout.js new file mode 100644 index 0000000..0633c84 --- /dev/null +++ b/docs/src/js/components/documentation/features/FormLayout.js @@ -0,0 +1,102 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class FormLayout { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle can be used in all bootstrap form layout.`; + + const code = ` +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+ + + +
+
+ + + +
`; + + return DocArticle.build({ + title: "Form Layout", + description, + example: FormLayout.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const formStacked = document.createElement("form"); + formStacked.className = "border p-1 mb-1"; + formStacked.innerHTML = `
+ + +
+
+ + +
`; + + const fromInline = document.createElement("form"); + fromInline.className = "border p-1 mb-1"; + fromInline.innerHTML = `
+ + +
+
+ + +
`; + + const formsInputGroup = [ + { text: "Small", size: "sm" }, + { text: "Default", size: "md" }, + { text: "Large", size: "lg" }, + ].map(({ text, size }) => { + const form = document.createElement("form"); + form.className = `border p-1 ${size !== "lg" ? "mb-1" : ""}`; + form.classList.add("input-group", `input-group-${size}`); + form.innerHTML = ` + + + `; + return form; + }); + + return [formStacked, fromInline, ...formsInputGroup]; + } +} +export default FormLayout; diff --git a/docs/src/js/components/documentation/features/KeyboardInteraction.js b/docs/src/js/components/documentation/features/KeyboardInteraction.js new file mode 100644 index 0000000..28c4813 --- /dev/null +++ b/docs/src/js/components/documentation/features/KeyboardInteraction.js @@ -0,0 +1,36 @@ +import DocArticle from "../DocArticle"; + +class KeyboardInteraction { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle supports keyboard interaction. Toggle can be switched pressing SPACEBAR or ENTER while it's focused and support TAB navigation. +A custom tabindex can be set by adding the tabindex attribute, default value is 0.`; + + const code = ` + +`; + + return DocArticle.build({ + title: "Keyboard Interaction", + versionPill: { version: "5.0.0", action: "SINCE" }, + description, + example: KeyboardInteraction.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + return ["-1", "", "1"].map((tabindex) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + if (tabindex !== "") input.setAttribute("tabindex", tabindex); + return input; + }); + } +} +export default KeyboardInteraction; diff --git a/docs/src/js/components/documentation/features/OutlineColors.js b/docs/src/js/components/documentation/features/OutlineColors.js new file mode 100644 index 0000000..56b2ae3 --- /dev/null +++ b/docs/src/js/components/documentation/features/OutlineColors.js @@ -0,0 +1,49 @@ +import DocArticle from "../DocArticle"; + +class OutlineColors { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle implements also all Bootstrap outline colors. Just add the data-onstyle or data-offstyle attribute with one of the following values: outline-primary, outline-secondary, outline-success, outline-danger, outline-warning, outline-info, outline-light or outline-dark.`; + + const code = ` + + + + + + +`; + + return DocArticle.build({ + title: "Outline Colors", + description, + example: OutlineColors.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const example = [ + "outline-primary", + "outline-secondary", + "outline-success", + "outline-danger", + "outline-warning", + "outline-info", + "outline-light", + "outline-dark", + ].map((color) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + input.dataset.onstyle = color; + return input; + }); + return example; + } +} +export default OutlineColors; diff --git a/docs/src/js/components/documentation/features/Size.js b/docs/src/js/components/documentation/features/Size.js new file mode 100644 index 0000000..e72b452 --- /dev/null +++ b/docs/src/js/components/documentation/features/Size.js @@ -0,0 +1,36 @@ +import DocArticle from "../DocArticle"; + +class Size { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle is available in different sizes. Just add the data-size attribute with one of the following values: lg, sm or xs.`; + + const code = ` + + +`; + + return DocArticle.build({ + title: "Size", + description, + example: Size.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const example = ["lg", "default", "sm", "xs"].map((size) => { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + if (size != "default") input.dataset.size = size; + return input; + }); + return example; + } +} +export default Size; diff --git a/docs/src/js/components/documentation/features/StateStatus.js b/docs/src/js/components/documentation/features/StateStatus.js new file mode 100644 index 0000000..ba1cc0d --- /dev/null +++ b/docs/src/js/components/documentation/features/StateStatus.js @@ -0,0 +1,65 @@ +import Console from "../../Console"; +import DocArticle from "../DocArticle"; + +class StateStatus { + static build() { + const code = `
+ + + + +
`; + + return DocArticle.build({ + title: "Toggle State and Status", + description: StateStatus.#description(), + example: StateStatus.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #description() { + const description = document.createElement("div"); + + const paragraph = document.createElement("p"); + paragraph.innerHTML = `Toggle state and status is simply set adding vanilla html attributes to the checkbox:`; + description.append(paragraph); + + const ul = document.createElement("ul"); + const li1 = document.createElement("li"); + li1.innerHTML = `Simply add checked to convert checkboxes into on toggles.`; + const li2 = document.createElement("li"); + li2.innerHTML = `Simply add disabled to convert checkboxes into disabled toggles. Checkboxes will not be presents in the payload of a submitted form.`; + const li3 = document.createElement("li"); + li3.innerHTML = `Simply add readonly to convert checkboxes into disabled toggles but presents in the payload of a submitted form.`; + ul.append(li1, li2, li3); + description.append(ul); + + return description; + } + + static #example() { + const form = document.createElement("form"); + form.className = "d-flex justify-content-between align-items-center"; + form.innerHTML = ` + + + `; + + const console = new Console(); + + form.onsubmit = (e) => { + e.preventDefault(); + console.json({ + mode: "replace", + data: Object.fromEntries(new FormData(e.target)), + }); + }; + + return [form, console.htmlElement]; + } +} +export default StateStatus; diff --git a/docs/src/js/components/documentation/features/Tooltip.js b/docs/src/js/components/documentation/features/Tooltip.js new file mode 100644 index 0000000..3a14349 --- /dev/null +++ b/docs/src/js/components/documentation/features/Tooltip.js @@ -0,0 +1,61 @@ +import DocArticle from "../DocArticle"; + +class Tooltip { + static build() { + const description = document.createDocumentFragment(); + + const p1 = document.createElement("p"); + p1.innerHTML = `Bootstrap toggle allows to set tooltip. Just add the data-tooltip-title-on or data-tooltip-title-off or data-tooltip-title-mixed attribute to set a custom label. Plain text and HTML are supported. The attribute data-tooltip-title is used as fallback when a title for the specific state is not provided.`; + description.appendChild(p1); + + const p2 = document.createElement("p"); + p2.innerHTML = `Tooltips placement can be set using the data-tooltip-placement attribute. Possible values are: top, bottom, left, and right. The default value is top.`; + description.appendChild(p2); + + const code = ` + + Checked" data-tooltip-title-off=" Unchecked" />`; + + return DocArticle.build({ + title: "Tooltip", + description, + example: Tooltip.#example(), + codeBlock: { + language: "html", + code, + }, + versionPill: { action: "SINCE", version: "5.3.0" }, + }); + } + + static #example() { + const input1 = document.createElement("input"); + input1.type = "checkbox"; + input1.checked = true; + input1.dataset.toggle = "toggle"; + input1.dataset.tooltipTitle = "Fallback"; + input1.dataset.tooltipTitleOn = "Checked"; + input1.dataset.tooltipTitleOff = "Unchecked"; + input1.setAttribute("tristate", ""); + + const input2 = document.createElement("input"); + input2.type = "checkbox"; + input2.checked = true; + input2.dataset.toggle = "toggle"; + input2.dataset.tooltipTitleOn = "Checked"; + input2.dataset.tooltipTitleOff = "Unchecked"; + input2.dataset.tooltipPlacement = "bottom"; + + const input3 = document.createElement("input"); + input3.type = "checkbox"; + input3.checked = true; + input3.dataset.toggle = "toggle"; + input3.dataset.tooltipTitleOn = + ' Checked'; + input3.dataset.tooltipTitleOff = + ' Unchecked'; + + return [input1, input2, input3]; + } +} +export default Tooltip; diff --git a/docs/src/js/components/documentation/features/Tristate.js b/docs/src/js/components/documentation/features/Tristate.js new file mode 100644 index 0000000..ccb5851 --- /dev/null +++ b/docs/src/js/components/documentation/features/Tristate.js @@ -0,0 +1,32 @@ +import DocArticle from "../DocArticle"; + +class Tristate { + static build() { + const description = document.createElement("p"); + description.innerHTML = `Bootstrap toggle is available in tristate mode. Just add the tristate attribute to make a tristate toggle.`; + + const code = ``; + + return DocArticle.build({ + title: "Tristate Toggle", + versionPill: { version: "4.3.0", action: "SINCE" }, + description, + example: Tristate.#example(), + codeBlock: { + language: "html", + code, + }, + }); + } + + static #example() { + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + input.dataset.toggle = "toggle"; + input.setAttribute("tristate", ""); + + return [input]; + } +} +export default Tristate; diff --git a/docs/src/js/main.js b/docs/src/js/main.js new file mode 100644 index 0000000..f5504b8 --- /dev/null +++ b/docs/src/js/main.js @@ -0,0 +1,16 @@ +import BootstrapToggler from "./tasks/BootstrapToggler"; +import BootsrapDarkmodeToggler from "./tasks/BootsrapDarkmodeToggler"; +import CodeHighlighter from "./tasks/CodeHighlighter"; +import DOMBuilder from "./tasks/DOMBuilder"; +import LogoSwitcher from "./tasks/LogoSwitcher"; +import TOCBuilder from "./tasks/TOCBuilder"; + +DOMBuilder.run(); +LogoSwitcher.run({ toggleDelay: 3000 }); + +window.onload = () => { + BootstrapToggler.run(); + BootsrapDarkmodeToggler.run(); + CodeHighlighter.run(); + TOCBuilder.run(); +}; diff --git a/docs/src/js/tasks/BootsrapDarkmodeToggler.js b/docs/src/js/tasks/BootsrapDarkmodeToggler.js new file mode 100644 index 0000000..8634b73 --- /dev/null +++ b/docs/src/js/tasks/BootsrapDarkmodeToggler.js @@ -0,0 +1,9 @@ +class BootsrapDarkmodeToggler { + static run() { + document.querySelectorAll('[data-plugin="bs-darkmode-toggle"]').forEach((element) => { + element.bsDarkmodeToggle(); + }); + } +} + +export default BootsrapDarkmodeToggler; diff --git a/docs/src/js/tasks/BootstrapToggler.js b/docs/src/js/tasks/BootstrapToggler.js new file mode 100644 index 0000000..fae8f10 --- /dev/null +++ b/docs/src/js/tasks/BootstrapToggler.js @@ -0,0 +1,9 @@ +class BootstrapToggler { + static run() { + document.querySelectorAll('[data-toggle="toggle"]').forEach((element) => { + element.bootstrapToggle(); + }); + } +} + +export default BootstrapToggler; diff --git a/docs/src/js/tasks/CodeHighlighter.js b/docs/src/js/tasks/CodeHighlighter.js new file mode 100644 index 0000000..659136f --- /dev/null +++ b/docs/src/js/tasks/CodeHighlighter.js @@ -0,0 +1,8 @@ +class CodeHighlighter { + static run() { + hljs.highlightAll(); + window.highlightJsBadge(); + } +} + +export default CodeHighlighter; diff --git a/docs/src/js/tasks/DOMBuilder.js b/docs/src/js/tasks/DOMBuilder.js new file mode 100644 index 0000000..a1d9472 --- /dev/null +++ b/docs/src/js/tasks/DOMBuilder.js @@ -0,0 +1,17 @@ +import Footer from "../components/Footer"; +import Header from "../components/Header"; +import Main from "../components/Main"; +import Notice from "../components/Notice"; + +class DOMBuilder { + static #sections = [ + Header.build(), + Notice.build(), + Main.build(), + Footer.build(), + ]; + static run() { + document.body.append(...this.#sections); + } +} +export default DOMBuilder; diff --git a/docs/src/js/tasks/LogoSwitcher.js b/docs/src/js/tasks/LogoSwitcher.js new file mode 100644 index 0000000..3896a1b --- /dev/null +++ b/docs/src/js/tasks/LogoSwitcher.js @@ -0,0 +1,16 @@ +class LogoSwitcher { + /** + * Run the logo switcher. + * @param {Object} [options] - Options. + * @param {number} [options.toggleDelay=2000] - Delay between toggle in milliseconds. + */ + static run({ toggleDelay = 2000 } = {}) { + document.querySelectorAll(".img-toggle img").forEach((element) => { + setInterval(function () { + element.classList.toggle("invisible"); + }, toggleDelay); + }); + } +} + +export default LogoSwitcher; diff --git a/docs/src/js/tasks/TOCBuilder.js b/docs/src/js/tasks/TOCBuilder.js new file mode 100644 index 0000000..bd17ad0 --- /dev/null +++ b/docs/src/js/tasks/TOCBuilder.js @@ -0,0 +1,13 @@ +class TOCBuilder { + static run() { + $("#toc").html(""); + Toc.init({ + $nav: $("#toc"), + }); + const _scrollSpy = new bootstrap.ScrollSpy(document.body, { + target: "#toc", + }); + } +} + +export default TOCBuilder; diff --git a/sonar-project.properties b/sonar-project.properties index e1ed5ec..ef1db6f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,5 +3,5 @@ sonar.organization=palcarazm sonar.javascript.lcov.reportPaths=coverage/lcov.info sonar.typescript.tsconfigPath=tsconfig.json -sonar.coverage.exclusions=test/**,cypress/**,src/test/**,src/main/js/**,src/main/ts/**/index.*.ts,src/main/ts/types/** +sonar.coverage.exclusions=docs/**,test/**,cypress/**,src/test/**,src/main/js/**,src/main/ts/**/index.*.ts,src/main/ts/types/** sonar.cpd.exclusions=test/**,cypress/**,src/test/** \ No newline at end of file From 01ea9716c8172ec123affc3844df93a499603369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 4 Apr 2026 17:01:32 +0200 Subject: [PATCH 06/26] ci: add dependabot to docs package --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fa374b0..5d3e138 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,19 @@ updates: prefix: "chore(deps): " prefix-development: "chore(deps-dev): " + # ========== DOCS DEPENDENCIES ========== + - package-ecosystem: "npm" + directory: "/docs" + schedule: + interval: "weekly" + day: "friday" + groups: + version-updates: + applies-to: "version-updates" + commit-message: + prefix: "chore(docs): " + prefix-development: "chore(docs): " + # ========== GITHUB ACTIONS ========== - package-ecosystem: "github-actions" directory: "/" From a1272c95c689c3ad0717e930cffeaa501ee01192 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:04:39 +0000 Subject: [PATCH 07/26] chore(deps-dev): bump @types/jquery from 3.5.34 to 4.0.0 Bumps [@types/jquery](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jquery) from 3.5.34 to 4.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jquery) --- updated-dependencies: - dependency-name: "@types/jquery" dependency-version: 4.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1ac2ee..0091c0a 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@rollup/plugin-terser": "^0.4.4", "@types/bootstrap": "^5.2.10", "@types/jest": "^30.0.0", - "@types/jquery": "^3.5.33", + "@types/jquery": "^4.0.0", "@typescript-eslint/eslint-plugin": "^8.20.0", "@typescript-eslint/parser": "^8.20.0", "commitlint": "^20.3.1", From 95479813f120f31154c962588c554815a05b2690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 4 Apr 2026 17:08:26 +0200 Subject: [PATCH 08/26] ci: update dependabot config --- .github/dependabot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5d3e138..0ad8d9f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,6 +28,7 @@ updates: groups: version-updates: applies-to: "version-updates" + update-types: ["major", "minor", "patch"] commit-message: prefix: "chore(docs): " prefix-development: "chore(docs): " From 223a0a653aded7b1319832cc9df80f4d600518ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:08:44 +0000 Subject: [PATCH 09/26] ci(deps): bump actions/download-artifact from 7 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 892bc01..8076a11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,7 @@ jobs: fetch-depth: 0 - name: Download coverage artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: coverage-report-22.x path: coverage From 9cccfae2b5242f34dbb1148c18e67b133afd2b9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:08:50 +0000 Subject: [PATCH 10/26] ci(deps): bump actions/deploy-pages from 4 to 5 Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 73ec8ab..65e2871 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -41,4 +41,4 @@ jobs: path: ./docs/dist - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From aa004a6db8d974764f5cb118d060ef5e2f2e23fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:08:53 +0000 Subject: [PATCH 11/26] ci(deps): bump actions/upload-artifact from 6 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 892bc01..b57b009 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: run: npm run test:unit - name: Upload coverage report to Github if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: coverage-report-${{ matrix.node-version }} path: coverage From 0a588692c5cfd66f38897a2e6e14033d8ca8289d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:08:56 +0000 Subject: [PATCH 12/26] ci(deps): bump actions/configure-pages from 5 to 6 Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 5 to 6. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 73ec8ab..5f3d0cb 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -34,7 +34,7 @@ jobs: - name: Build run: npm run build - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: From 1480b7b8e189af29723a0dbb40f0d8b69e440808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Tue, 7 Apr 2026 13:14:49 +0200 Subject: [PATCH 13/26] chore(docs): upgrade bs-darkmode-toggle to stable and switch to local storage - Updated bs-darkmode-toggle from 1.0.0-rc1 to 1.0.0 - Replaced dataset.allowCookie with dataset.storage = "local" to use local storage instead of cookies - Updated footer background from bg-light to bg-body-secondary for better theme consistency This aligns the dark mode plugin with its stable API and improves user preference persistence. --- docs/package.json | 2 +- docs/src/js/components/Aside.js | 2 +- docs/src/js/components/Footer.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/package.json b/docs/package.json index 4669ea8..013fb0c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -33,7 +33,7 @@ "@highlightjs/cdn-assets": "11.11.1", "bootstrap": "5.3.8", "bootstrap5-toggle": "5.3.0", - "bs-darkmode-toggle": "1.0.0-rc1", + "bs-darkmode-toggle": "1.0.0", "highlightjs-badge": "0.1.9", "jquery": "4.0.0" }, diff --git a/docs/src/js/components/Aside.js b/docs/src/js/components/Aside.js index 31630c0..373e395 100644 --- a/docs/src/js/components/Aside.js +++ b/docs/src/js/components/Aside.js @@ -85,7 +85,7 @@ class Aside { const darkModeToggle = document.createElement("div"); darkModeToggle.dataset.plugin = "bs-darkmode-toggle"; - darkModeToggle.dataset.allowCookie = ""; + darkModeToggle.dataset.storage = "local"; navBar.appendChild(darkModeToggle); diff --git a/docs/src/js/components/Footer.js b/docs/src/js/components/Footer.js index b23566e..e7ba84e 100644 --- a/docs/src/js/components/Footer.js +++ b/docs/src/js/components/Footer.js @@ -7,7 +7,7 @@ class Footer { static build() { const footer = document.createElement("footer"); - footer.className = "bg-light border-top p-4"; + footer.className = "bg-body-secondary border-top p-4"; const container = document.createElement("div"); container.className = "container"; From d9b6318d383064f3ae794696cefe3a0c6824d3d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:03:01 +0200 Subject: [PATCH 14/26] chore(deps-dev): bump typescript from 5.9.3 to 6.0.2 (#306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps-dev): bump typescript from 5.9.3 to 6.0.2 Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 6.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps-dev): bump typescript from 5.9.3 to 6.0.2 Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 6.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * refactor: upgrade TypeScript target to ES2016 and improve config consistency - Change target from ES5 to ES2016 across tsconfig.json and tsconfig.test.json - Add 'types' field with 'node' in tsconfig.json, 'jest' and 'node' in tsconfig.test.json - Add 'noEmit': true to tsconfig.test.json to prevent accidental output - Switch tsconfig.test.json module from ESNext to commonjs (required for Jest) - Add allowCypressEnv: false to cypress.config.js - Upgrade @typescript-eslint/eslint-plugin and parser from 8.20.0 to 8.58.0 - Normalize indentation and add trailing commas in cypress.config.js Why: ES2016 provides modern language features (Promise, Map, Set, classes) without sacrificing broad compatibility. Explicit type roots and noEmit prevent type resolution errors and accidental build artifacts. CommonJS is required for Jest's module resolution. Disabling Cypress env vars prevents CI environment injection from overriding test configs. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pablo Alcaraz Martínez --- cypress.config.js | 13 +++++++------ package.json | 6 +++--- tsconfig.json | 5 +++-- tsconfig.test.json | 8 +++++--- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index 4036afe..460e0b4 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,11 +1,12 @@ const { defineConfig } = require("cypress"); module.exports = defineConfig({ - e2e: { - setupNodeEvents(on, config) { - // implement node event listeners here + allowCypressEnv: false, + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, }, - }, - video: false, - videoCompression: false + video: false, + videoCompression: false, }); diff --git a/package.json b/package.json index 9f3ff27..3d0efa2 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "@types/bootstrap": "^5.2.10", "@types/jest": "^30.0.0", "@types/jquery": "^4.0.0", - "@typescript-eslint/eslint-plugin": "^8.20.0", - "@typescript-eslint/parser": "^8.20.0", + "@typescript-eslint/eslint-plugin": "^8.58.0", + "@typescript-eslint/parser": "^8.58.0", "commitlint": "^20.3.1", "eslint": "^10.0.0", "cypress": "^15.8.1", @@ -82,7 +82,7 @@ "postcss-cli": "^11.0.1", "rollup": "^4.54.0", "ts-jest": "^29.4.6", - "typescript": "^5.9.3" + "typescript": "^6.0.2" }, "peerDependencies": { "bootstrap": ">=5 <6" diff --git a/tsconfig.json b/tsconfig.json index 7d76549..e63ca5c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES5", + "target": "ES2016", "module": "ESNext", "strict": true, "declaration": false, @@ -8,7 +8,8 @@ "skipLibCheck": true, "rootDir": "src/main/ts", "outDir": "src/main/js", - "typeRoots": [ + "types": ["node"], + "typeRoots": [ "src/main/ts/types", "node_modules/@types" ] diff --git a/tsconfig.test.json b/tsconfig.test.json index d0a7d50..27a2156 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,14 +1,16 @@ { "compilerOptions": { - "target": "ES5", - "module": "ESNext", + "target": "ES2016", + "module": "commonjs", // CommonJS used cause of jest need "strict": true, "declaration": false, "esModuleInterop": true, "skipLibCheck": true, "rootDir": "src/main/ts", "outDir": "src/main/js", - "typeRoots": [ + "noEmit": true, + "types": ["jest", "node"], + "typeRoots": [ "src/main/ts/types", "node_modules/@types" ] From 0b1424edf0a663d2edb6cbec85814fcbbb5dde70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:46:44 +0000 Subject: [PATCH 15/26] chore(docs): bump bootstrap5-toggle Bumps the version-updates group in /docs with 1 update: [bootstrap5-toggle](https://github.com/palcarazm/bootstrap5-toggle). Updates `bootstrap5-toggle` from 5.3.0 to 5.3.3 - [Release notes](https://github.com/palcarazm/bootstrap5-toggle/releases) - [Commits](https://github.com/palcarazm/bootstrap5-toggle/compare/v5.3.0...v5.3.3) --- updated-dependencies: - dependency-name: bootstrap5-toggle dependency-version: 5.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: version-updates ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 013fb0c..d9abee7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -32,7 +32,7 @@ "@fortawesome/fontawesome-free": "7.2.0", "@highlightjs/cdn-assets": "11.11.1", "bootstrap": "5.3.8", - "bootstrap5-toggle": "5.3.0", + "bootstrap5-toggle": "5.3.3", "bs-darkmode-toggle": "1.0.0", "highlightjs-badge": "0.1.9", "jquery": "4.0.0" From 1256068856c0842f9c52da703b8d733ee1e8e67f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 06:45:31 +0000 Subject: [PATCH 16/26] ci(deps): bump actions/upload-pages-artifact from 4 to 5 Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 1136314..528d426 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -36,7 +36,7 @@ jobs: - name: Setup Pages uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: ./docs/dist - name: Deploy to GitHub Pages From 438594e2a0dbf15653c0751b307339a1a78464fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 07:39:05 +0000 Subject: [PATCH 17/26] ci(deps): bump SonarSource/sonarqube-scan-action from 7.1.0 to 8.0.0 Bumps [SonarSource/sonarqube-scan-action](https://github.com/sonarsource/sonarqube-scan-action) from 7.1.0 to 8.0.0. - [Release notes](https://github.com/sonarsource/sonarqube-scan-action/releases) - [Commits](https://github.com/sonarsource/sonarqube-scan-action/compare/299e4b793aaa83bf2aba7c9c14bedbb485688ec4...59db25f34e16620e48ab4bb9e4a5dce155cb5432) --- updated-dependencies: - dependency-name: SonarSource/sonarqube-scan-action dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de1caa2..76ce940 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,6 @@ jobs: - name: SonarQube Scan # v7.0.0 - uses: SonarSource/sonarqube-scan-action@299e4b793aaa83bf2aba7c9c14bedbb485688ec4 + uses: SonarSource/sonarqube-scan-action@59db25f34e16620e48ab4bb9e4a5dce155cb5432 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file From a9b23e0b4d6d4c13b97c7fa00c99f71a24649701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 06:45:03 +0000 Subject: [PATCH 18/26] chore(deps-dev): bump @commitlint/cli from 20.5.3 to 21.0.2 Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 20.5.3 to 21.0.2. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.0.2/@commitlint/cli) --- updated-dependencies: - dependency-name: "@commitlint/cli" dependency-version: 21.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d37298e..5843680 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "npm": ">=10" }, "devDependencies": { - "@commitlint/cli": "^20.3.1", + "@commitlint/cli": "^21.0.2", "@commitlint/config-conventional": "^20.3.1", "@rollup/plugin-terser": "^1.0.0", "@types/bootstrap": "^5.2.10", From 242c7f46f209d31b11c310c6c808848e7e3f4e34 Mon Sep 17 00:00:00 2001 From: xianzuyang9-blip Date: Tue, 9 Jun 2026 12:50:05 +0800 Subject: [PATCH 19/26] fix: export published css and js assets --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d37298e..65514ad 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "types": "./dist/bootstrap5-toggle.d.ts", "import": "./dist/bootstrap5-toggle.mjs", "require": "./dist/bootstrap5-toggle.cjs" - } + }, + "./css/*": "./css/*", + "./js/*": "./js/*" }, "scripts": { "build": "grunt build", From 3ddcaf0c972fd39461d999ca2dc74a7964da51f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:43:45 +0000 Subject: [PATCH 20/26] ci(deps): bump cypress-io/github-action Bumps the minor-and-patch-updates group with 1 update in the / directory: [cypress-io/github-action](https://github.com/cypress-io/github-action). Updates `cypress-io/github-action` from 7.1.9 to 7.4.0 - [Release notes](https://github.com/cypress-io/github-action/releases) - [Changelog](https://github.com/cypress-io/github-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/cypress-io/github-action/compare/783cb3f07983868532cabaedaa1e6c00ff4786a8...948d67d3074f1bbb6379c8bdbb04e95d2f8e593f) --- updated-dependencies: - dependency-name: cypress-io/github-action dependency-version: 7.1.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch-updates ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de1caa2..2bb8a3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: run: npm run build - name: Cypress run #v6.10.8 - uses: cypress-io/github-action@783cb3f07983868532cabaedaa1e6c00ff4786a8 + uses: cypress-io/github-action@948d67d3074f1bbb6379c8bdbb04e95d2f8e593f with: browser: chrome From 099fb26ed6c1b972d7bf5fce810872887f960e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 20 Jun 2026 11:31:44 +0200 Subject: [PATCH 21/26] refactor(build): replace Grunt with npm scripts and modern tooling Remove Grunt and its plugins (grunt, grunt-banner, grunt-contrib-clean, grunt-contrib-copy, grunt-exec) as the build orchestration layer. Replace with npm scripts using tsc, rollup, postcss, and rimraf. Key changes: - Add clean, build, watch scripts with rimraf, concurrently - Centralize copyright banner in scripts/package-banner.js - Move TypeScript output to dist/tmp/ (isolated from source) - Add postcss.config.js for banner injection in CSS - Update rollup.config.js to use banner from shared utility - Add version:update script to sync README and docs/package.json - Add preversion hook running lint + tests before version bump Benefits: - Reduced dependencies (remove 4 Grunt packages) - Simpler, more transparent build pipeline - Build artifacts isolated from source (dist/tmp/) - Dynamic copyright banners from package.json - Parallel watch mode with concurrently BREAKING CHANGE: TypeScript declaration output moved from src/main/@types to dist/tmp/@types. This only affects developers building from source, not package consumers. Closes #330 --- .npmignore | 1 + Gruntfile.cjs | 60 --------------- docs/package.json | 1 + docs/postcss.config.js | 5 ++ package.json | 29 ++++--- postcss.config.js | 8 ++ rollup.config.js | 117 ++++++++++++++++------------- scripts/package-banner.js | 14 ++++ scripts/update-version.js | 24 ++++++ src/main/css/bootstrap5-toggle.css | 13 ---- tsconfig.json | 2 +- 11 files changed, 137 insertions(+), 137 deletions(-) delete mode 100644 Gruntfile.cjs create mode 100644 docs/postcss.config.js create mode 100644 postcss.config.js create mode 100644 scripts/package-banner.js create mode 100644 scripts/update-version.js diff --git a/.npmignore b/.npmignore index 95284e5..1beb4cb 100644 --- a/.npmignore +++ b/.npmignore @@ -3,6 +3,7 @@ node_modules # Packaging bootstrap5-toggle-*.tgz +dist/tmp # Debug debug diff --git a/Gruntfile.cjs b/Gruntfile.cjs deleted file mode 100644 index 680ef7c..0000000 --- a/Gruntfile.cjs +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = function setup(grunt) { - "use strict"; - - grunt.initConfig({ - pkg: grunt.file.readJSON("package.json"), - banner: - "/* Copyright Notice\n" + - " * <%= pkg.name %> v<%= pkg.version %>\n" + - " * <%= pkg.homepage %>\n" + - " * @author 2011-2014 Min Hur (https://github.com/minhur)\n" + - " * @author 2018-2019 Brent Ely (https://github.com/gitbrent)\n" + - " * @author 2022 Pablo Alcaraz Martínez (https://github.com/palcarazm)\n" + - " * @funding <%= pkg.funding.type %>\n" + - " * @see <%= pkg.funding.url %>\n" + - " * @license <%= pkg.license %>\n" + - " * @see https://github.com/palcarazm/bootstrap5-toggle/blob/master/LICENSE\n" + - " */\n", - clean: ["js","css", "dist"], - exec: { - ts: "npx tsc", - rollup: "npx rollup -c", - postcss: "npx postcss src/main/css/bootstrap5-toggle.css -o css/bootstrap5-toggle.css --map", - postcssMin: "npx postcss src/main/css/bootstrap5-toggle.css -o css/bootstrap5-toggle.min.css --map --env production" - }, - usebanner: { - taskName: { - options: { - position: "top", - banner: "<%= banner %>", - linebreak: true, - replace: true, - }, - files: { - src: ["css/*.css", "js/*.js", "dist/**/*"], - }, - }, - }, - copy: { - main: { - src: "README.template.md", - dest: "README.md", - options: { - process: function (content, _srcpath) { - let pkg = grunt.file.readJSON("package.json"); - return content.replaceAll("#version#", pkg.version); - }, - }, - }, - }, - }); - - grunt.loadNpmTasks("grunt-banner"); - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks("grunt-contrib-copy"); - grunt.loadNpmTasks("grunt-exec"); - - grunt.registerTask("default", ["clean", "exec:ts", "exec:rollup", "exec:postcss", "exec:postcssMin", "usebanner"]); - grunt.registerTask("build", ["clean", "exec:ts", "exec:rollup", "exec:postcss", "exec:postcssMin", "usebanner"]); - grunt.registerTask("readme", ["copy"]); -}; diff --git a/docs/package.json b/docs/package.json index d9abee7..269cf04 100644 --- a/docs/package.json +++ b/docs/package.json @@ -39,6 +39,7 @@ }, "devDependencies": { "@rollup/plugin-terser": "^1.0.0", + "cssnano": "^8.0.2", "grunt": "^1.6.1", "grunt-contrib-clean": "^2.0.1", "grunt-contrib-copy": "^1.0.0", diff --git a/docs/postcss.config.js b/docs/postcss.config.js new file mode 100644 index 0000000..61752fc --- /dev/null +++ b/docs/postcss.config.js @@ -0,0 +1,5 @@ +const cssnano = require("cssnano"); + +module.exports = [{ + plugins: [cssnano()], +}]; \ No newline at end of file diff --git a/package.json b/package.json index 65514ad..8f31de0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "files": [ "css/*", "js/*", - "dist" + "dist/*" ], "exports": { ".": { @@ -35,10 +35,19 @@ "./js/*": "./js/*" }, "scripts": { - "build": "grunt build", - "postversion": "grunt build && grunt readme && doctoc README.md --github", - "prepack": "grunt build && grunt readme && doctoc README.md --github", - "prepublishOnly": "git add -A && git commit", + "clean": "rimraf js css dist", + "clean:tmp": "rimraf dist/tmp", + "prebuild": "npm run clean", + "build": "tsc && npm run build:js && npm run build:css", + "build:js": "rollup -c", + "build:css": "postcss src/main/css/bootstrap5-toggle.css -o css/bootstrap5-toggle.css --map && postcss src/main/css/bootstrap5-toggle.css -o css/bootstrap5-toggle.min.css --map --use cssnano && postcss src/main/css/bootstrap5-toggle.css -o dist/bootstrap5-toggle.css --map --use cssnano", + "postbuild": "npm run clean:tmp", + "watch": "concurrently \"tsc --watch\" \"rollup --watch\" \"postcss src/main/css/bootstrap5-toggle.css -o css/bootstrap5-toggle.css --map --watch\" \"postcss src/main/css/bootstrap5-toggle.css -o dist/bootstrap5-toggle.css --map --watch\"", + "preversion": "npm run lint && npm run test", + "version": "npm run build && npm run version:update && doctoc README.md --github && git add -A", + "version:update": "node scripts/update-version.js", + "postversion": "git push --follow-tags", + "prepack": "npm run build && npm run version:update && doctoc README.md --github", "test": "npm run test:unit && npm run test:e2e", "test:e2e": "cypress run --headless", "test:unit": "jest --coverage", @@ -76,25 +85,25 @@ "@types/bootstrap": "^5.2.10", "@types/jest": "^30.0.0", "@types/jquery": "^4.0.0", + "@types/node": "^26.0.0", "@typescript-eslint/eslint-plugin": "^8.58.0", "@typescript-eslint/parser": "^8.58.0", "commitlint": "^20.3.1", + "concurrently": "^10.0.3", + "cssnano": "^8.0.2", "cypress": "^15.8.1", "cypress-plugin-tab": "^2.0.0", "doctoc": "^2.2.0", "dompurify": "^3.0.0", "eslint": "^10.0.0", "globals": "^17.0.0", - "grunt": "^1.5.3", - "grunt-banner": "^0.6.0", - "grunt-contrib-clean": "^2.0.1", - "grunt-contrib-copy": "^1.0.0", - "grunt-exec": "^3.0.0", "husky": "^9.0.11", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "postcss": "^8.5.6", + "postcss-banner": "^4.0.1", "postcss-cli": "^11.0.1", + "rimraf": "^6.1.3", "rollup": "^4.54.0", "rollup-plugin-dts": "^6.4.1", "ts-jest": "^29.4.6", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..68c5325 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +import banner from "postcss-banner"; +import { bannerContent } from "./scripts/package-banner.js"; + +export default { + plugins: [ + banner({ banner: bannerContent }), + ] +}; \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index e51032d..27bb668 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,5 +1,55 @@ import terser from "@rollup/plugin-terser"; import dts from "rollup-plugin-dts"; +import { bannerContent } from "./scripts/package-banner.js"; + +const banner = `/* + * ${bannerContent.replaceAll("\n", "\n * ")} + */`; + +const umdECMAS = { + input: "src/main/js/index.ecmas.js", + output: [ + { + file: "js/bootstrap5-toggle.ecmas.js", + format: "umd", + sourcemap: true, + banner, + }, + { + file: "js/bootstrap5-toggle.ecmas.min.js", + format: "umd", + sourcemap: true, + plugins: [terser()], + banner, + }, + ], +}; + +const umdJquery = { + input: "src/main/js/index.jquery.js", + external: ["jquery"], + output: [ + { + file: "js/bootstrap5-toggle.jquery.js", + format: "umd", + sourcemap: true, + globals: { + jquery: "jQuery", + }, + banner, + }, + { + file: "js/bootstrap5-toggle.jquery.min.js", + format: "umd", + sourcemap: true, + globals: { + jquery: "jQuery", + }, + plugins: [terser()], + banner, + }, + ], +}; const mainConfig = { input: "src/main/js/index.js", @@ -7,71 +57,32 @@ const mainConfig = { { file: "dist/bootstrap5-toggle.cjs", format: "cjs", - sourcemap: false, - plugins: [terser()] + sourcemap: true, + plugins: [terser()], + banner, }, { file: "dist/bootstrap5-toggle.mjs", format: "es", - sourcemap: false, - plugins: [terser()] - } - ] + sourcemap: true, + plugins: [terser()], + banner, + }, + ], }; const typesConfig = { - input: "src/main/@types/index.d.ts", + input: "dist/tmp/@types/index.d.ts", output: { file: "dist/bootstrap5-toggle.d.ts", - format: "es" + format: "es", + banner, }, plugins: [ dts({ respectExternal: true, - }) - ] + }), + ], }; -export default [ - { - input: "src/main/js/index.ecmas.js", - output: [ - { - file: "js/bootstrap5-toggle.ecmas.js", - format: "umd", - sourcemap: true - }, - { - file: "js/bootstrap5-toggle.ecmas.min.js", - format: "umd", - sourcemap: true, - plugins: [terser()] - } - ] - }, - { - input: "src/main/js/index.jquery.js", - external: ["jquery"], - output: [ - { - file: "js/bootstrap5-toggle.jquery.js", - format: "umd", - sourcemap: true, - globals: { - jquery: "jQuery" - } - }, - { - file: "js/bootstrap5-toggle.jquery.min.js", - format: "umd", - sourcemap: true, - globals: { - jquery: "jQuery" - }, - plugins: [terser()] - } - ] - }, - mainConfig, - typesConfig, -]; +export default [umdECMAS, umdJquery, mainConfig, typesConfig]; diff --git a/scripts/package-banner.js b/scripts/package-banner.js new file mode 100644 index 0000000..f56ad8f --- /dev/null +++ b/scripts/package-banner.js @@ -0,0 +1,14 @@ +import fs from "node:fs"; + +const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); + +export const bannerContent = `Copyright Notice +${pkg.name} v${pkg.version} +${pkg.homepage} +@author 2011-2014 Min Hur (https://github.com/minhur) +@author 2018-2019 Brent Ely (https://github.com/gitbrent) +@author 2022 Pablo Alcaraz Martínez (https://github.com/palcarazm) +@funding ${pkg.funding.type} +@see ${pkg.funding.url} +@license ${pkg.license} +@see https://github.com/palcarazm/bootstrap5-toggle/blob/master/LICENSE`; \ No newline at end of file diff --git a/scripts/update-version.js b/scripts/update-version.js new file mode 100644 index 0000000..cb9cf86 --- /dev/null +++ b/scripts/update-version.js @@ -0,0 +1,24 @@ +// scripts/update-package-version.js +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, ".."); + +const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")); +const version = pkg.version; + +// Update README +const template = fs.readFileSync(path.join(rootDir, "README.template.md"), "utf8"); +const updatedReadme = template.replaceAll("#version#", version); +fs.writeFileSync(path.join(rootDir, "README.md"), updatedReadme); +console.log(`README.md updated to version ${version}`); + +// Update docs/package.json +const docsPkgPath = path.join(rootDir, "docs", "package.json"); +const docsPkg = JSON.parse(fs.readFileSync(docsPkgPath, "utf8")); +docsPkg.dependencies = docsPkg.dependencies || {}; +docsPkg.dependencies["bootstrap5-toggle"] = version; +fs.writeFileSync(docsPkgPath, JSON.stringify(docsPkg, null, 2) + "\n"); +console.log(`docs/package.json dependency updated to ${version}`); \ No newline at end of file diff --git a/src/main/css/bootstrap5-toggle.css b/src/main/css/bootstrap5-toggle.css index d8d5f3f..b8bde77 100644 --- a/src/main/css/bootstrap5-toggle.css +++ b/src/main/css/bootstrap5-toggle.css @@ -1,16 +1,3 @@ -/* Copyright Notice - * bootstrap5-toggle v5.1.3 - * https://palcarazm.github.io/bootstrap5-toggle/ - * @author 2011-2014 Min Hur (https://github.com/minhur) - * @author 2018-2019 Brent Ely (https://github.com/gitbrent) - * @author 2022 Pablo Alcaraz Martínez (https://github.com/palcarazm) - * @funding GitHub Sponsors - * @see https://github.com/sponsors/palcarazm - * @license MIT - * @see https://github.com/palcarazm/bootstrap5-toggle/blob/master/LICENSE - */ - - /* * @added 3.0.0: Return support for "*-xs" removed in Bootstrap-4 * @see: [Comment](https://github.com/twbs/bootstrap/issues/21881#issuecomment-341972830) diff --git a/tsconfig.json b/tsconfig.json index b2430f2..9d3dfe7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "strict": true, "declaration": true, "declarationMap": false, - "declarationDir": "./src/main/@types", + "declarationDir": "./dist/tmp/@types", "esModuleInterop": true, "skipLibCheck": true, "rootDir": "src/main/ts", From f428911c817a53f7ecefbf6b479b01952d6e954a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 20 Jun 2026 11:38:14 +0200 Subject: [PATCH 22/26] fix: rename Cypress configuration file as cjs --- cypress.config.js => cypress.config.cjs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cypress.config.js => cypress.config.cjs (100%) diff --git a/cypress.config.js b/cypress.config.cjs similarity index 100% rename from cypress.config.js rename to cypress.config.cjs From 60322f4a8b32f19f765568dc2d8b3e1043f31bb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 09:43:37 +0000 Subject: [PATCH 23/26] chore(deps-dev): bump commitlint from 20.5.3 to 21.0.2 Bumps [commitlint](https://github.com/conventional-changelog/commitlint/tree/HEAD/@alias/commitlint) from 20.5.3 to 21.0.2. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@alias/commitlint/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.0.2/@alias/commitlint) --- updated-dependencies: - dependency-name: commitlint dependency-version: 21.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f31de0..7b536cb 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@types/node": "^26.0.0", "@typescript-eslint/eslint-plugin": "^8.58.0", "@typescript-eslint/parser": "^8.58.0", - "commitlint": "^20.3.1", + "commitlint": "^21.0.2", "concurrently": "^10.0.3", "cssnano": "^8.0.2", "cypress": "^15.8.1", From d54009d8b77ef28d0043c0b179f6e482bd75bf0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 09:49:38 +0000 Subject: [PATCH 24/26] chore(deps-dev): bump @commitlint/config-conventional Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 20.5.3 to 21.0.2. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.0.2/@commitlint/config-conventional) --- updated-dependencies: - dependency-name: "@commitlint/config-conventional" dependency-version: 21.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40c444e..21e1ef1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ }, "devDependencies": { "@commitlint/cli": "^21.0.2", - "@commitlint/config-conventional": "^20.3.1", + "@commitlint/config-conventional": "^21.0.2", "@rollup/plugin-terser": "^1.0.0", "@types/bootstrap": "^5.2.10", "@types/jest": "^30.0.0", From c02fcfc8788cba340baf6555cd1f928d9feb8a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 20 Jun 2026 12:47:36 +0200 Subject: [PATCH 25/26] refactor: preserve instance identity in Toggle.rerender() Replaces the previous destroy+recreate implementation with an in-place rebuild that preserves the same object reference. The method now follows a precise sequence: 1. Restore original input properties 2. Unbind all event listeners 3. Destroy the DOM builder 4. Re-resolve options from element and user options 5. Create a fresh StateReducer 6. Build new toggle DOM structure 7. Re-bind all event listeners 8. Re-intercept input properties This ensures that this.element.bsToggle remains unchanged, making external references to the instance safe. Implements #318 --- src/main/ts/BootstrapToggle.ts | 48 +++++++++++++++++++++++------ src/test/ts/BootstrapToggle.test.ts | 29 ++++++++++++++--- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/main/ts/BootstrapToggle.ts b/src/main/ts/BootstrapToggle.ts index 607585b..4aaf8fc 100644 --- a/src/main/ts/BootstrapToggle.ts +++ b/src/main/ts/BootstrapToggle.ts @@ -8,9 +8,10 @@ import ToggleEvents, { ToggleEventDetail } from "./types/ToggleEvents"; export class Toggle { private readonly element: HTMLInputElement & { bsToggle?: Toggle }; private readonly userOptions: UserOptions; - private readonly options: ToggleOptions; - private readonly stateReducer: StateReducer; - private readonly domBuilder: DOMBuilder; + + private options: ToggleOptions; + private stateReducer: StateReducer; + private domBuilder: DOMBuilder; private pointer: { x: number; y: number } | null = null; private readonly SCROLL_THRESHOLD = 10; @@ -532,11 +533,40 @@ export class Toggle { } /** - * Destroys the toggle element and reinitializes it with the same options. - *This method is useful when you need to reinitialize the toggle element with the same options. - */ - rerender() { - this.destroy(); - const _ = new Toggle(this.element, this.userOptions); + * Rebuilds the toggle component in-place without destroying the current instance. + * + * This method is useful when you need to reinitialize the toggle element with the same options, + * for example after HTML attribute changes or when the DOM structure has been modified externally. + * + * The method follows this sequence: + * 1. Restores original input properties (checked, disabled, readOnly, indeterminate) + * 2. Unbinds all event listeners (pointer, keyboard, label, form reset) + * 3. Destroys the DOM builder (removes toggle DOM, restores original checkbox) + * 4. Re-resolves options from the element and user options + * 5. Creates a fresh StateReducer with the updated tristate value + * 6. Builds new toggle DOM structure + * 7. Re-binds all event listeners + * 8. Re-intercepts input properties for external change detection + * + * Important: The instance identity is preserved (this.element.bsToggle remains unchanged), + * making it safe for external code that holds references to this instance. + * + * @returns void + */ + rerender() { + this.restoreInputProperties(); + this.unbindEventListeners(); + this.domBuilder.destroy(); + + this.options = OptionResolver.resolve(this.element, this.userOptions); + this.stateReducer = new StateReducer(this.element, this.options.tristate); + this.domBuilder = new DOMBuilder( + this.element, + this.options, + this.stateReducer.get() + ); + + this.bindEventListeners(); + this.interceptInputProperties(); } } diff --git a/src/test/ts/BootstrapToggle.test.ts b/src/test/ts/BootstrapToggle.test.ts index 91f4f27..c46d59f 100644 --- a/src/test/ts/BootstrapToggle.test.ts +++ b/src/test/ts/BootstrapToggle.test.ts @@ -1,7 +1,9 @@ +/// /* eslint-disable @typescript-eslint/no-explicit-any */ import { Toggle } from "../../main/ts/BootstrapToggle"; import { DOMBuilder } from "../../main/ts/core/DOMBuilder"; +import { OptionResolver } from "../../main/ts/core/OptionResolver"; import { ToggleActionType, ToggleStateValue } from "../../main/ts/core/StateReducer.types"; import ToggleEvents from "../../main/ts/types/ToggleEvents"; @@ -709,14 +711,33 @@ describe("Toggle", () => { }); describe("rerender()", () =>{ - it("rerender destroys and reinitializes toggle", () => { - (input as any).bootstrapToggle = jest.fn(); - + it("rebuilds toggle in-place while preserving instance identity", () => { const toggle = new Toggle(input, {}); + const instanceRef = toggle; + + const originalBsToggle = (input as any).bsToggle; + toggle.rerender(); - + + expect(toggle).toBe(instanceRef); + expect((input as any).bsToggle).toBe(originalBsToggle); + expect((input as any).bsToggle).toBe(toggle); + expect(destroyMock).toHaveBeenCalled(); expect(DOMBuilder).toHaveBeenCalledTimes(2); }); + + it("re-resolves options from HTML attributes after change", () => { + + const toggle = new Toggle(input, { onlabel: "Old" }); + + const resolveSpy = jest.spyOn(OptionResolver, "resolve"); + input.dataset.onlabel = "New"; + + toggle.rerender(); + + expect(resolveSpy).toHaveBeenCalledWith(input, toggle["userOptions"]); + resolveSpy.mockRestore(); + }); }); }); From 5b3f1a2f311dad06ccad81b8223767539d1bfa89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Alcaraz=20Mart=C3=ADnez?= Date: Sat, 20 Jun 2026 12:55:20 +0200 Subject: [PATCH 26/26] 5.4.0 --- README.md | 12 ++++++------ docs/package.json | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 999e3f8..d6af625 100644 --- a/README.md +++ b/README.md @@ -66,18 +66,18 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/ ```html - + ``` ### jQuery Interface ```html - + ``` ## Download @@ -89,13 +89,13 @@ See EOL for each version in [Security Policy Page](https://github.com/palcarazm/ [![NPM Badge](https://img.shields.io/npm/dm/bootstrap5-toggle?logo=npm)](https://www.npmjs.com/package/bootstrap5-toggle) ```ksh -npm install bootstrap5-toggle@5.3.1 +npm install bootstrap5-toggle@5.4.0 ``` ## Yarn ```ksh -yarn add bootstrap5-toggle@5.3.1 +yarn add bootstrap5-toggle@5.4.0 ``` # Usage diff --git a/docs/package.json b/docs/package.json index 269cf04..ee5e73e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -32,7 +32,7 @@ "@fortawesome/fontawesome-free": "7.2.0", "@highlightjs/cdn-assets": "11.11.1", "bootstrap": "5.3.8", - "bootstrap5-toggle": "5.3.3", + "bootstrap5-toggle": "5.4.0", "bs-darkmode-toggle": "1.0.0", "highlightjs-badge": "0.1.9", "jquery": "4.0.0" diff --git a/package.json b/package.json index c17fd6f..4964390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap5-toggle", - "version": "5.3.1", + "version": "5.4.0", "type": "module", "author": { "name": "Pablo Alcaraz Martínez",