From 04390e303be8d64904198559712036bb85b1b9d6 Mon Sep 17 00:00:00 2001
From: Mike Alhayek
Date: Mon, 15 Jun 2026 10:31:26 -0700
Subject: [PATCH] Add Tags and List Style select menu
---
.github/workflows/deploy-docs.yml | 6 +
.github/workflows/publish_npm_release.yml | 151 ++++-
CHANGELOG.md | 11 +
Gruntfile.js | 25 +-
README.md | 6 +-
docs/docs/examples.mdx | 405 +++++++------
docs/docs/index.md | 21 +-
docs/docs/options.mdx | 153 +++--
docs/docusaurus.config.js | 3 +
docs/src/components/LiveExample.js | 103 +++-
docs/src/css/custom.css | 2 +-
docs/static/examples/tags-editor.html | 38 ++
docs/static/js/chunk-recovery.js | 92 +++
.../static/vendor/fontawesome/css/all.min.css | 9 +
.../fontawesome/webfonts/fa-brands-400.woff2 | Bin 0 -> 110088 bytes
.../fontawesome/webfonts/fa-regular-400.woff2 | Bin 0 -> 18924 bytes
.../fontawesome/webfonts/fa-solid-900.woff2 | Bin 0 -> 114740 bytes
.../webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4032 bytes
js/bootstrap-select.js | 545 ++++++++++++++----
less/bootstrap-select.less | 278 ++++++++-
less/variables.less | 2 +-
nuget/bootstrap-select.nuspec | 2 +-
package-lock.json | 21 +-
package.json | 3 +-
sass/bootstrap-select.scss | 246 +++++++-
tests/e2e/single-select.spec.js | 35 ++
tests/e2e/tags-editor.spec.js | 286 +++++++++
tests/index.html | 1 +
28 files changed, 2052 insertions(+), 392 deletions(-)
create mode 100644 docs/static/examples/tags-editor.html
create mode 100644 docs/static/js/chunk-recovery.js
create mode 100644 docs/static/vendor/fontawesome/css/all.min.css
create mode 100644 docs/static/vendor/fontawesome/webfonts/fa-brands-400.woff2
create mode 100644 docs/static/vendor/fontawesome/webfonts/fa-regular-400.woff2
create mode 100644 docs/static/vendor/fontawesome/webfonts/fa-solid-900.woff2
create mode 100644 docs/static/vendor/fontawesome/webfonts/fa-v4compatibility.woff2
create mode 100644 tests/e2e/tags-editor.spec.js
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index f682eea1..bd891d15 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -6,6 +6,12 @@ on:
- main
paths:
- 'docs/**'
+ - 'js/**'
+ - 'less/**'
+ - 'sass/**'
+ - 'Gruntfile.js'
+ - 'package.json'
+ - 'package-lock.json'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch:
diff --git a/.github/workflows/publish_npm_release.yml b/.github/workflows/publish_npm_release.yml
index 502cd3bf..23ad88e1 100644
--- a/.github/workflows/publish_npm_release.yml
+++ b/.github/workflows/publish_npm_release.yml
@@ -6,21 +6,42 @@ on:
- 'v*.*.*'
permissions:
- contents: read
+ contents: write
+ pages: write
+ id-token: write
jobs:
publish:
runs-on: ubuntu-latest
name: Publish crestapps-bootstrap-select
+ outputs:
+ version: ${{ steps.release_meta.outputs.version }}
+ docs_version: ${{ steps.release_meta.outputs.docs_version }}
+ should_snapshot_docs: ${{ steps.release_meta.outputs.should_snapshot_docs }}
steps:
- - name: Get the version
- id: get_version
+ - name: Parse release metadata
+ id: release_meta
run: |
VERSION="${GITHUB_REF_NAME#v}"
- echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
+ if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
+ echo "::error title=Invalid version tag::Expected a semantic version tag like v1.2.3, received '$GITHUB_REF_NAME'."
+ exit 1
+ fi
+
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "docs_version=${MAJOR}.${MINOR}" >> "$GITHUB_OUTPUT"
+
+ if [[ "$PATCH" == "0" ]]; then
+ echo "should_snapshot_docs=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "should_snapshot_docs=false" >> "$GITHUB_OUTPUT"
+ fi
shell: bash
- uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
- uses: actions/setup-node@v6
with:
@@ -50,5 +71,125 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
- npm version "${{ steps.get_version.outputs.VERSION }}" --no-git-tag-version --allow-same-version
+ npm version "${{ steps.release_meta.outputs.version }}" --no-git-tag-version --allow-same-version
npm publish --access public --tag latest
+
+ snapshot_docs:
+ runs-on: ubuntu-latest
+ name: Snapshot release docs
+ needs: publish
+ if: needs.publish.outputs.should_snapshot_docs == 'true'
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ ref: ${{ github.sha }}
+
+ - name: Setup node
+ uses: actions/setup-node@v6
+ with:
+ node-version: '20.19'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Skip existing docs snapshot
+ id: snapshot_state
+ env:
+ DOCS_VERSION: ${{ needs.publish.outputs.docs_version }}
+ run: |
+ git fetch origin main --depth=1
+
+ if git cat-file -e "origin/main:docs/versioned_sidebars/version-${DOCS_VERSION}-sidebars.json" 2>/dev/null; then
+ echo "should_commit=false" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+
+ echo "should_commit=true" >> "$GITHUB_OUTPUT"
+
+ - name: Create docs snapshot
+ if: steps.snapshot_state.outputs.should_commit == 'true'
+ env:
+ DOCS_VERSION: ${{ needs.publish.outputs.docs_version }}
+ run: |
+ pushd docs
+ npx docusaurus docs:version "$DOCS_VERSION"
+ popd
+
+ - name: Push docs snapshot to main
+ if: steps.snapshot_state.outputs.should_commit == 'true'
+ env:
+ DOCS_VERSION: ${{ needs.publish.outputs.docs_version }}
+ run: |
+ if git diff --quiet -- docs/versions.json docs/versioned_docs docs/versioned_sidebars; then
+ echo "No docs snapshot changes to commit."
+ exit 0
+ fi
+
+ SNAPSHOT_DIR="$RUNNER_TEMP/docs-version-$DOCS_VERSION"
+ mkdir -p "$SNAPSHOT_DIR"
+ cp docs/versions.json "$SNAPSHOT_DIR/versions.json"
+ cp -R docs/versioned_docs "$SNAPSHOT_DIR/versioned_docs"
+ cp -R docs/versioned_sidebars "$SNAPSHOT_DIR/versioned_sidebars"
+
+ git switch -C docs-version-sync origin/main
+ rm -rf docs/versioned_docs docs/versioned_sidebars
+ mkdir -p docs/versioned_docs docs/versioned_sidebars
+ cp "$SNAPSHOT_DIR/versions.json" docs/versions.json
+ cp -R "$SNAPSHOT_DIR/versioned_docs/." docs/versioned_docs/
+ cp -R "$SNAPSHOT_DIR/versioned_sidebars/." docs/versioned_sidebars/
+
+ if git diff --quiet -- docs/versions.json docs/versioned_docs docs/versioned_sidebars; then
+ echo "Main already contains the ${DOCS_VERSION} docs snapshot."
+ exit 0
+ fi
+
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add docs/versions.json docs/versioned_docs docs/versioned_sidebars
+ git commit -m "docs: snapshot ${DOCS_VERSION} release docs"
+ git push origin HEAD:main
+
+ build_docs:
+ runs-on: ubuntu-latest
+ name: Build release docs
+ needs:
+ - publish
+ - snapshot_docs
+ if: always() && needs.publish.result == 'success' && (needs.snapshot_docs.result == 'success' || needs.snapshot_docs.result == 'skipped')
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ ref: main
+
+ - name: Setup node
+ uses: actions/setup-node@v6
+ with:
+ node-version: '20.19'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build docs site
+ run: npm run docs:build
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs/build
+
+ deploy_docs:
+ runs-on: ubuntu-latest
+ name: Deploy release docs
+ needs: build_docs
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d5b38e2..e0f0b3cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# v1.1.0 (CrestApps fork)
+
+### Highlights
+
+- Added release-time docs versioning for `major.minor` releases so published docs
+ snapshots remain selectable after future updates.
+- Fixed Font Awesome loading in the Docusaurus site by bundling the CSS and
+ webfonts with the docs build.
+- Expanded the docs examples page with a dedicated list-style selected-items
+ example.
+
# v1.0.1 (CrestApps fork)
This is the first release of the CrestApps fork of
diff --git a/Gruntfile.js b/Gruntfile.js
index 5990a37e..4977b95c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -140,12 +140,25 @@ module.exports = function (grunt) {
copy: {
docs: {
- expand: true,
- cwd: 'dist/',
- src: [
- '**/*'
- ],
- dest: 'docs/static/dist/'
+ files: [
+ {
+ expand: true,
+ cwd: 'dist/',
+ src: [
+ '**/*'
+ ],
+ dest: 'docs/static/dist/'
+ },
+ {
+ expand: true,
+ cwd: 'node_modules/@fortawesome/fontawesome-free/',
+ src: [
+ 'css/all.min.css',
+ 'webfonts/*'
+ ],
+ dest: 'docs/static/vendor/fontawesome/'
+ }
+ ]
}
},
diff --git a/README.md b/README.md
index c239f91b..d8689f67 100644
--- a/README.md
+++ b/README.md
@@ -74,11 +74,11 @@ jsDelivr. Prefer pinning an explicit package version in production:
-
-
+
+
```
-You can replace `@1.0.0` with the version you want to consume. During
+You can replace `@1.1.0` with the version you want to consume. During
development, `@latest` also works, but a fixed version is safer for production
deployments.
diff --git a/docs/docs/examples.mdx b/docs/docs/examples.mdx
index 2a6a0ea2..ec093668 100644
--- a/docs/docs/examples.mdx
+++ b/docs/docs/examples.mdx
@@ -12,42 +12,30 @@ import LiveExample from '@site/src/components/LiveExample';
The examples use the vanilla JavaScript / Bootstrap 5+ API: `new Selectpicker(el)` or the `selectpicker` class, which auto-initializes. jQuery is not required.
:::
-
-
-
Standalone basic
-
Open the local plugin build on a plain HTML page.
-
Open basic example
-
-
-
Live search
-
Test search filtering against hosted example markup.
-
Open live search
-
-
-
Multiple select
-
Verify multiselect and action-box behavior.
-
Open multiselect
-
-
+The main examples now live directly on this docs page so they inherit the docs theme, including light and dark mode. The standalone HTML files are still kept under `docs/static/examples/` for quick smoke-testing outside Docusaurus.
## Standard select boxes
Make this:
+ html={String.raw`
+
Native select:
-
- Mustard
- Ketchup
- Relish
-
+
+ Mustard
+ Ketchup
+ Relish
+
+
- Become this:
+
+
Enhanced with bootstrap-select:
-
- Mustard
- Ketchup
- Relish
- `}
+
+ Mustard
+ Ketchup
+ Relish
+
+
`}
/>
```html
@@ -110,16 +98,12 @@ The examples use the vanilla JavaScript / Bootstrap 5+ API: `new Selectpicker(el
```
-# Live search
-
----
-
## Live search
You can add a search input by passing `data-live-search="true"` attribute:
+ html={String.raw`
Hot Dog, Fries and a Soda
Burger, Shake and a Smile
Sugar, Spice and all things nice
@@ -132,7 +116,7 @@ You can add a search input by passing `data-live-search="true"` attribute:
Add key words to options to improve their searchability using `data-tokens`.
+ html={String.raw`
Hot Dog, Fries and a Soda
Burger, Shake and a Smile
Sugar, Spice and all things nice
@@ -147,6 +131,189 @@ Add key words to options to improve their searchability using `data-tokens`.
```
+## Tags-style live search with open options
+
+Use `showSelectedTags` to keep selections visible as removable tags above the search box, while the button switches to a compact summary instead of repeating the same values.
+
+
+ Orchard Core
+ Bootstrap 5
+ Vue
+ Taxonomy
+ Open option
+ Editor UX
+ `}
+/>
+
+```html
+
+ Orchard Core
+ Bootstrap 5
+ Vue
+ Taxonomy
+ Open option
+ Editor UX
+
+```
+
+If you prefer a Bootstrap-style checkbox instead of the floating checkmark, set `selectionIndicator` to `checkbox`:
+
+
+ Bootstrap 5
+ Vue
+ React
+ Svelte
+ `}
+/>
+
+```html
+
+ Bootstrap 5
+ Vue
+ React
+ Svelte
+
+```
+
+## List-style menu
+
+Set `selectedItemsStyle` to `list` to render the removable selections as a stacked Bootstrap list group:
+
+
+ Orchard Core
+ Bootstrap 5
+ Vue
+ Taxonomy
+ Editor UX
+ `}
+/>
+
+```html
+
+ Orchard Core
+ Bootstrap 5
+ Vue
+
+```
+
+## Floating labels with visible tags
+
+When a tags-style picker is placed inside a Bootstrap 5 `form-floating` wrapper, the selected tags stay visible inside the control after the menu closes, with balanced top and bottom spacing around the tags.
+
+
+
+ 2026
+ 2023
+ 2021
+ 2019
+ 2018
+
+ Years
+ `}
+/>
+
+```html
+
+
+ 2026
+ 2023
+ 2021
+
+ Years
+
+```
+
+For remote-backed pickers, initialize with JavaScript and provide `source.create(callback, searchValue)` to save the new item before selecting it:
+
+```js
+new Selectpicker('#tag-editor', {
+ liveSearch: true,
+ showSelectedTags: true,
+ openOptions: true,
+ source: {
+ data: function (callback) {
+ callback(existingTags);
+ },
+ search: function (callback, page, searchValue) {
+ callback(filterTags(searchValue));
+ },
+ create: function (callback, searchValue) {
+ saveTag(searchValue).then(function (tag) {
+ callback({
+ text: tag.displayText,
+ value: tag.id
+ });
+ });
+ }
+ }
+});
+```
+
# Limit the number of selections
Limit the number of options that can be selected via the `data-max-options` attribute. It also works for option groups. Customize the message displayed when the limit is reached with `maxOptionsText`.
@@ -204,7 +371,7 @@ Use the `placeholder` attribute to set the default placeholder text when nothing
Multiple
-
+
Mustard
Ketchup
Relish
@@ -213,7 +380,7 @@ Use the `placeholder` attribute to set the default placeholder text when nothing
Standard
-
+
Mustard
Ketchup
Relish
@@ -313,7 +480,7 @@ You can set the button classes via the `data-style` attribute:
-
+
Mustard
Ketchup
Relish
@@ -327,7 +494,14 @@ You can set the button classes via the `data-style` attribute:
-
+
+ Mustard
+ Ketchup
+ Relish
+
+
+
+
Mustard
Ketchup
Relish
@@ -347,7 +521,7 @@ You can set the button classes via the `data-style` attribute:
...
-
+
...
@@ -355,7 +529,11 @@ You can set the button classes via the `data-style` attribute:
...
-
+
+ ...
+
+
+
...
@@ -516,105 +694,33 @@ Wrap selects in grid columns, or any custom parent element, to easily enforce de
```
-
-
-Alternatively, use the `data-width` attribute to set the width of the select. Set `data-width` to `'auto'` to automatically adjust the width of the select to its widest option. `'fit'` automatically adjusts the width of the select to the width of its currently selected option. An exact value can also be specified, e.g., `300px` or `50%`.
-
-
-
-
- width: 'auto'
-
- Mustard
- Ketchup
- Relish
- All of the above (and much, much more!)
-
-
-
-
-
-
-
- width: 'fit'
-
- Mustard
- Ketchup
- Relish
- All of the above (and much, much more!)
-
-
-
-
-
-
-
- width: '150px'
-
- Mustard
- Ketchup
- Relish
- All of the above (and much, much more!)
-
-
-
-
-
-
-
- width: '75%'
-
- Mustard
- Ketchup
- Relish
- All of the above (and much, much more!)
-
-
-
-
`}
-/>
-
-```html
-
- ...
-
-
- ...
-
-
- ...
-
-
- ...
-
-```
-
# Customize options
---
-## Icons
+## Font Awesome icons
-Add an icon to an option or optgroup with the `data-icon` attribute:
+This example uses Font Awesome. Add an icon to an option or optgroup with the `data-icon` attribute:
:::info Bootstrap 5 icons
Bootstrap 5 does not include an icon font. To use Font Awesome or another icon library, set `iconBase` and `tickIcon` to match that library.
:::
+The selected option renders its icon in the button, and the menu shows the icons for the remaining options as well.
+
- Mustard
- Ketchup
- Relish
- Mayonnaise
- Barbecue Sauce
+ html={String.raw`
+ Ketchup
+ Barbecue Sauce
+ Mustard
+ Relish
+ Mayonnaise
`}
/>
```html
-
- Ketchup
+
+ Ketchup
```
@@ -812,55 +918,6 @@ Add a header to the dropdown menu, e.g. `header: 'Select a condiment'` or `data-
```
-## Container
-
-Append the select menu to a specific element, e.g. `container: 'body'` or `data-container=".main-content"`. This is useful if the select element is inside an element with `overflow: hidden`.
-
-
-
- container: false
-
- Mustard
- Ketchup
- Relish
- Mayonnaise
-
- Barbecue Sauce
- Salad Dressing
- Tabasco
- Salsa
-
-
-
- container: 'body'
-
- Mustard
- Ketchup
- Relish
- Mayonnaise
-
- Barbecue Sauce
- Salad Dressing
- Tabasco
- Salsa
-
-
- `}
- style={{ overflow: 'hidden' }}
-/>
-
-```html
-
-
- ...
-
-
- ...
-
-
-```
-
## Dropup menu
`dropupAuto` is set to true by default, which automatically determines whether or not the menu should display above or below the select box. If `dropupAuto` is set to false, manually make the select a dropup menu by adding the `.dropup` class to the select.
diff --git a/docs/docs/index.md b/docs/docs/index.md
index e75fbf3d..88d3323e 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -47,11 +47,11 @@ Prefer pinning an explicit package version in production:
-
-
+
+
```
-You can replace `@1.0.1` with the version you want to consume. During development,
+You can replace `@1.1.0` with the version you want to consume. During development,
`@latest` also works, but a fixed version is safer for production deployments.
When loaded via a `
+
+