You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2022/12/02 16:06:05 UTC

[myfaces-tobago] branch tobago-5.x updated (157b0aa615 -> 3e92a66c3b)

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a change to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


    from 157b0aa615 fix(mvn): docker tomcat profile
     new eb2cca850e feat: selectManyList component
     new 3e92a66c3b build(theme): rebuild after implement tc:selectManyList

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../myfaces/tobago/component/RendererTypes.java    |   2 +
 .../{SupportsHelp.java => SupportsFilter.java}     |   4 +-
 .../org/apache/myfaces/tobago/component/Tags.java  |   2 +
 ...tUIInput.java => AbstractUISelectManyList.java} |  40 +-
 .../renderkit/renderer/SelectManyListRenderer.java | 209 ++++++++++
 ...tion.java => SelectManyListTagDeclaration.java} |  66 ++--
 .../declaration/{HasHelp.java => HasFilter.java}   |   8 +-
 .../tobago/renderkit/css/BootstrapClass.java       |   3 +
 .../myfaces/tobago/renderkit/css/TobagoClass.java  |   5 +
 .../tobago/renderkit/html/HtmlElements.java        |   1 +
 .../example/demo/SelectManyListController.java     | 138 +++++++
 .../apache/myfaces/tobago/example/demo/names.txt   |  61 ++-
 .../80-selectManyList/SelectManyList.xhtml         |  87 +++++
 tobago-theme/jest.config.js                        |   5 +-
 tobago-theme/src/main/scss/_tobago.scss            | 161 ++++++++
 .../src/main/css/tobago.css                        | 121 ++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../src/main/css/tobago.css                        | 121 ++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../src/main/css/tobago.css                        | 121 ++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-speyside/src/main/css/tobago.css  | 121 ++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-standard/src/main/css/tobago.css  | 121 ++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-standard/src/main/js/tobago.js    |   4 +-
 .../src/main/js/tobago.js.map                      |   2 +-
 .../src/main/resources/META-INF/tobago-config.xml  |   3 +
 .../src/main/ts/tobago-all.ts                      |   1 +
 .../src/main/ts/tobago-filter-registry.test.ts     | 124 ++++++
 .../src/main/ts/tobago-filter-registry.ts          | 102 +++++
 .../src/main/ts/tobago-select-many-list.ts         | 421 +++++++++++++++++++++
 42 files changed, 2008 insertions(+), 76 deletions(-)
 copy tobago-core/src/main/java/org/apache/myfaces/tobago/component/{SupportsHelp.java => SupportsFilter.java} (93%)
 copy tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/{AbstractUIInput.java => AbstractUISelectManyList.java} (71%)
 create mode 100644 tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListRenderer.java
 copy tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/{SelectManyListboxTagDeclaration.java => SelectManyListTagDeclaration.java} (68%)
 copy tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/{HasHelp.java => HasFilter.java} (90%)
 create mode 100644 tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
 copy tobago-core/src/main/resources/org/apache/myfaces/tobago/context/TobagoMessageBundle_en.properties => tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/names.txt (63%)
 create mode 100644 tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
 create mode 100644 tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.test.ts
 create mode 100644 tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.ts
 create mode 100644 tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts


[myfaces-tobago] 02/02: build(theme): rebuild after implement tc:selectManyList

Posted by hn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit 3e92a66c3b345510131f10f2205cd4bec33ed154
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Fri Dec 2 16:52:40 2022 +0100

    build(theme): rebuild after implement tc:selectManyList
---
 .../src/main/css/tobago.css                        | 121 +++++++++++++++++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../src/main/css/tobago.css                        | 121 +++++++++++++++++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../src/main/css/tobago.css                        | 121 +++++++++++++++++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-speyside/src/main/css/tobago.css  | 121 +++++++++++++++++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-standard/src/main/css/tobago.css  | 121 +++++++++++++++++++++
 .../src/main/css/tobago.css.map                    |   2 +-
 .../src/main/css/tobago.min.css                    |   2 +-
 .../src/main/css/tobago.min.css.map                |   2 +-
 .../tobago-theme-standard/src/main/js/tobago.js    |   4 +-
 .../src/main/js/tobago.js.map                      |   2 +-
 22 files changed, 623 insertions(+), 18 deletions(-)

diff --git a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css
index 71458fa1c5..28ecffff09 100644
--- a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css
+++ b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css
@@ -10869,6 +10869,7 @@ textarea.form-control-lg {
  */
 /* used bootstrap icons ---------------------------------------------------- */
 /* non-bootstrap variables --------------------------------------- */
+/* bootstrap variables --------------------------------------- */
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -11808,6 +11809,126 @@ tobago-select-one-radio input[type=radio].border-info:focus {
   box-shadow: 0 0 0 0.25rem rgba(56, 156, 48, 0.25);
 }
 
+tobago-select-many-list {
+  display: block;
+}
+tobago-select-many-list.tobago-label-container .dropdown, tobago-select-many-list.tobago-label-container .list-group {
+  flex: 1 0 0;
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field {
+  color: #a0a0a0;
+  background-color: #fffbe8;
+  border-color: #d5cfb0;
+  outline: 0;
+  box-shadow: 0 0 0 0.25rem rgba(82, 150, 150, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(255, 0, 190, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 0, 190, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(56, 156, 48, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group {
+  --bs-list-group-color: #a0a0a0;
+  --bs-list-group-bg: #fffbe8;
+  --bs-list-group-border-color: #d5cfb0;
+  box-shadow: 0 0 0 0.25rem rgba(82, 150, 150, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(255, 0, 190, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 0, 190, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(56, 156, 48, 0.25);
+}
+tobago-select-many-list.tobago-disabled .tobago-select-field {
+  color: rgba(160, 160, 160, 0.5);
+  background-color: #d0d0d0;
+}
+tobago-select-many-list .list-group {
+  height: inherit;
+  max-height: inherit;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  tobago-select-many-list .list-group {
+    transition: none;
+  }
+}
+tobago-select-many-list .list-group.border-danger .list-group-item, tobago-select-many-list .list-group.border-warning .list-group-item, tobago-select-many-list .list-group.border-info .list-group-item {
+  border-color: inherit;
+}
+tobago-select-many-list .list-group .list-group-item.tobago-select-field {
+  border-bottom-color: rgba(0, 0, 0, 0.125);
+}
+tobago-select-many-list .tobago-select-field {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+tobago-select-many-list .tobago-select-field.dropdown-toggle::after {
+  content: none;
+}
+tobago-select-many-list .tobago-select-field.list-group-item.form-control {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group + .tobago-filter {
+  margin-left: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group {
+  margin-right: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter {
+  margin: 0;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+  flex: 1 0 0;
+  min-width: 8rem;
+  background-color: transparent;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:focus {
+  box-shadow: none;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:-moz-read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-options.list-group-item {
+  padding: 0;
+  overflow-y: auto;
+}
+tobago-select-many-list .tobago-options .table {
+  margin-bottom: 0;
+}
+tobago-select-many-list .tobago-options .table tr {
+  cursor: pointer;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-disabled {
+  color: rgba(160, 160, 160, 0.5);
+  background-color: #d0d0d0;
+  cursor: initial;
+  pointer-events: none;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-mark {
+  --bs-table-accent-bg: var(--bs-table-hover-bg);
+  color: var(--bs-table-hover-color);
+}
+tobago-select-many-list .tobago-options .table tr:last-of-type td {
+  border-bottom-width: 0;
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css.map b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css.map
index 7a478f4dc8..b7145e2ae6 100644
--- a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css.map
+++ b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.s [...]
\ No newline at end of file
+{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.s [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css
index 37cd5e9662..e171ae7752 100644
--- a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css
+++ b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css
@@ -1,2 +1,2 @@
-@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#ff00be;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000000;--bs-white:#ffffff;--bs-gray:#777777;--bs-gray-dark:#323232;--bs-gray-100:#f8f9fa;--bs-gray-200:#d0d0d0;--bs-gray-300:#dee2e6;--bs-gray-400:#a0a0a0;--bs-gray-500:#adb5bd;--bs-gray-600:#777777;--bs-gray-700:#495057;--bs-gray-800:#323232;--bs-gray-900:#212529;--bs [...]
+@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#ff00be;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000000;--bs-white:#ffffff;--bs-gray:#777777;--bs-gray-dark:#323232;--bs-gray-100:#f8f9fa;--bs-gray-200:#d0d0d0;--bs-gray-300:#dee2e6;--bs-gray-400:#a0a0a0;--bs-gray-500:#adb5bd;--bs-gray-600:#777777;--bs-gray-700:#495057;--bs-gray-800:#323232;--bs-gray-900:#212529;--bs [...]
 /*# sourceMappingURL=tobago.min.css.map */
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css.map b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css.map
index bf087b8e40..e23b8d5666 100644
--- a/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css.map
+++ b/tobago-theme/tobago-theme-charlotteville/src/main/css/tobago.min.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago-theme-charlotteville/src/main/css/tobago.css"],"names":[],"mappings":"iBAuCA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,QACZ,WAAY,QACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9 [...]
\ No newline at end of file
+{"version":3,"sources":["tobago-theme-charlotteville/src/main/css/tobago.css"],"names":[],"mappings":"iBAuCA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,QACZ,WAAY,QACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9 [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css
index a71184e669..006d9e470a 100644
--- a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css
+++ b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css
@@ -10896,6 +10896,7 @@ textarea.form-control-lg {
  */
 /* used bootstrap icons ---------------------------------------------------- */
 /* non-bootstrap variables --------------------------------------- */
+/* bootstrap variables --------------------------------------- */
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -11835,6 +11836,126 @@ tobago-select-one-radio input[type=radio].border-info:focus {
   box-shadow: 0 0 0 0.25rem rgba(19, 14, 143, 0.25);
 }
 
+tobago-select-many-list {
+  display: block;
+}
+tobago-select-many-list.tobago-label-container .dropdown, tobago-select-many-list.tobago-label-container .list-group {
+  flex: 1 0 0;
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field {
+  color: #a0a0a0;
+  background-color: #000000;
+  border-color: #ffbd57;
+  outline: 0;
+  box-shadow: 0 0 0 0.25rem rgba(214, 130, 0, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(255, 178, 67, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(178, 104, 18, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(19, 14, 143, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group {
+  --bs-list-group-color: #a0a0a0;
+  --bs-list-group-bg: #000000;
+  --bs-list-group-border-color: #ffbd57;
+  box-shadow: 0 0 0 0.25rem rgba(214, 130, 0, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(255, 178, 67, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(178, 104, 18, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(19, 14, 143, 0.25);
+}
+tobago-select-many-list.tobago-disabled .tobago-select-field {
+  color: rgba(160, 160, 160, 0.5);
+  background-color: #e9ecef;
+}
+tobago-select-many-list .list-group {
+  height: inherit;
+  max-height: inherit;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  tobago-select-many-list .list-group {
+    transition: none;
+  }
+}
+tobago-select-many-list .list-group.border-danger .list-group-item, tobago-select-many-list .list-group.border-warning .list-group-item, tobago-select-many-list .list-group.border-info .list-group-item {
+  border-color: inherit;
+}
+tobago-select-many-list .list-group .list-group-item.tobago-select-field {
+  border-bottom-color: rgba(0, 0, 0, 0.125);
+}
+tobago-select-many-list .tobago-select-field {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+tobago-select-many-list .tobago-select-field.dropdown-toggle::after {
+  content: none;
+}
+tobago-select-many-list .tobago-select-field.list-group-item.form-control {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group + .tobago-filter {
+  margin-left: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group {
+  margin-right: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter {
+  margin: 0;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+  flex: 1 0 0;
+  min-width: 8rem;
+  background-color: transparent;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:focus {
+  box-shadow: none;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:-moz-read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-options.list-group-item {
+  padding: 0;
+  overflow-y: auto;
+}
+tobago-select-many-list .tobago-options .table {
+  margin-bottom: 0;
+}
+tobago-select-many-list .tobago-options .table tr {
+  cursor: pointer;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-disabled {
+  color: rgba(160, 160, 160, 0.5);
+  background-color: #e9ecef;
+  cursor: initial;
+  pointer-events: none;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-mark {
+  --bs-table-accent-bg: var(--bs-table-hover-bg);
+  color: var(--bs-table-hover-color);
+}
+tobago-select-many-list .tobago-options .table tr:last-of-type td {
+  border-bottom-width: 0;
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css.map b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css.map
index 12b3ad4f93..8bbb2ebfe5 100644
--- a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css.map
+++ b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.s [...]
\ No newline at end of file
+{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.s [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css
index 26f8b1a8f3..f55fe27891 100644
--- a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css
+++ b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css
@@ -1,2 +1,2 @@
-@charset "UTF-8";@font-face{font-family:Amaranth;font-style:normal;font-weight:400;src:url("../fonts/Amaranth-Regular.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:normal;font-weight:700;src:url("../fonts/Amaranth-Bold.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:italic;src:url("../fonts/Amaranth-Italic.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:italic;font-weight:700;src:url("../fonts/Amaranth-BoldItalic.otf") format( [...]
+@charset "UTF-8";@font-face{font-family:Amaranth;font-style:normal;font-weight:400;src:url("../fonts/Amaranth-Regular.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:normal;font-weight:700;src:url("../fonts/Amaranth-Bold.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:italic;src:url("../fonts/Amaranth-Italic.otf") format("opentype")}@font-face{font-family:Amaranth;font-style:italic;font-weight:700;src:url("../fonts/Amaranth-BoldItalic.otf") format( [...]
 /*# sourceMappingURL=tobago.min.css.map */
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css.map b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css.map
index 612a3da763..af7a9e83da 100644
--- a/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css.map
+++ b/tobago-theme/tobago-theme-roxborough/src/main/css/tobago.min.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago-theme-roxborough/src/main/css/tobago.css"],"names":[],"mappings":"iBAiCA,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,qCAAqC,mBAE5C,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,kCAAkC,mBAEzC,WACE,YAAa,SACb,WAAY,OACZ,IAAK,oCAAoC,mBAE3C,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,wCAAwC,mBAE/C,mBACE,YAAa,QAAQ,CAAE,KAAK,CAAE,MAShC,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,QACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAC [...]
\ No newline at end of file
+{"version":3,"sources":["tobago-theme-roxborough/src/main/css/tobago.css"],"names":[],"mappings":"iBAiCA,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,qCAAqC,mBAE5C,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,kCAAkC,mBAEzC,WACE,YAAa,SACb,WAAY,OACZ,IAAK,oCAAoC,mBAE3C,WACE,YAAa,SACb,WAAY,OACZ,YAAa,IACb,IAAK,wCAAwC,mBAE/C,mBACE,YAAa,QAAQ,CAAE,KAAK,CAAE,MAShC,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,QACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAC [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css
index 9f4a0a99df..032e111502 100644
--- a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css
+++ b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css
@@ -10870,6 +10870,7 @@ textarea.form-control-lg {
  */
 /* used bootstrap icons ---------------------------------------------------- */
 /* non-bootstrap variables --------------------------------------- */
+/* bootstrap variables --------------------------------------- */
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -11809,6 +11810,126 @@ tobago-select-one-radio input[type=radio].border-info:focus {
   box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
 }
 
+tobago-select-many-list {
+  display: block;
+}
+tobago-select-many-list.tobago-label-container .dropdown, tobago-select-many-list.tobago-label-container .list-group {
+  flex: 1 0 0;
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field {
+  color: #212529;
+  background-color: #fff;
+  border-color: #86b7fe;
+  outline: 0;
+  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group {
+  --bs-list-group-color: #212529;
+  --bs-list-group-bg: #fff;
+  --bs-list-group-border-color: #86b7fe;
+  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
+}
+tobago-select-many-list.tobago-disabled .tobago-select-field {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e9ecef;
+}
+tobago-select-many-list .list-group {
+  height: inherit;
+  max-height: inherit;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  tobago-select-many-list .list-group {
+    transition: none;
+  }
+}
+tobago-select-many-list .list-group.border-danger .list-group-item, tobago-select-many-list .list-group.border-warning .list-group-item, tobago-select-many-list .list-group.border-info .list-group-item {
+  border-color: inherit;
+}
+tobago-select-many-list .list-group .list-group-item.tobago-select-field {
+  border-bottom-color: rgba(0, 0, 0, 0.125);
+}
+tobago-select-many-list .tobago-select-field {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+tobago-select-many-list .tobago-select-field.dropdown-toggle::after {
+  content: none;
+}
+tobago-select-many-list .tobago-select-field.list-group-item.form-control {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group + .tobago-filter {
+  margin-left: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group {
+  margin-right: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter {
+  margin: 0;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+  flex: 1 0 0;
+  min-width: 8rem;
+  background-color: transparent;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:focus {
+  box-shadow: none;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:-moz-read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-options.list-group-item {
+  padding: 0;
+  overflow-y: auto;
+}
+tobago-select-many-list .tobago-options .table {
+  margin-bottom: 0;
+}
+tobago-select-many-list .tobago-options .table tr {
+  cursor: pointer;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-disabled {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e9ecef;
+  cursor: initial;
+  pointer-events: none;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-mark {
+  --bs-table-accent-bg: var(--bs-table-hover-bg);
+  color: var(--bs-table-hover-color);
+}
+tobago-select-many-list .tobago-options .table tr:last-of-type td {
+  border-bottom-width: 0;
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css.map b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css.map
index 37494696a2..fc80abd8dc 100644
--- a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css.map
+++ b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_ [...]
\ No newline at end of file
+{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_ [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css
index 3f2bd013c0..1fe3fc2958 100644
--- a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css
+++ b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css
@@ -1,2 +1,2 @@
-@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-prima [...]
+@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-prima [...]
 /*# sourceMappingURL=tobago.min.css.map */
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css.map b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css.map
index 165209c996..4412c85ead 100644
--- a/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css.map
+++ b/tobago-theme/tobago-theme-scarborough/src/main/css/tobago.min.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago-theme-scarborough/src/main/css/tobago.css"],"names":[],"mappings":"iBAuBA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9B,i [...]
\ No newline at end of file
+{"version":3,"sources":["tobago-theme-scarborough/src/main/css/tobago.css"],"names":[],"mappings":"iBAuBA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9B,i [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css
index 4a07c5da15..411764e46a 100644
--- a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css
+++ b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css
@@ -10596,6 +10596,7 @@ textarea.form-control-lg {
  */
 /* used bootstrap icons ---------------------------------------------------- */
 /* non-bootstrap variables --------------------------------------- */
+/* bootstrap variables --------------------------------------- */
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -11534,6 +11535,126 @@ tobago-select-one-radio input[type=radio].border-info:focus {
   box-shadow: 0 0 0 0.25rem rgba(91, 192, 222, 0.25);
 }
 
+tobago-select-many-list {
+  display: block;
+}
+tobago-select-many-list.tobago-label-container .dropdown, tobago-select-many-list.tobago-label-container .list-group {
+  flex: 1 0 0;
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field {
+  color: #212529;
+  background-color: white;
+  border-color: #8cab91;
+  outline: 0;
+  box-shadow: 0 0 0 0.25rem rgba(24, 87, 34, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(211, 0, 64, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(240, 173, 78, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(91, 192, 222, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group {
+  --bs-list-group-color: #212529;
+  --bs-list-group-bg: white;
+  --bs-list-group-border-color: #8cab91;
+  box-shadow: 0 0 0 0.25rem rgba(24, 87, 34, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(211, 0, 64, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(240, 173, 78, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(91, 192, 222, 0.25);
+}
+tobago-select-many-list.tobago-disabled .tobago-select-field {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e3e4e5;
+}
+tobago-select-many-list .list-group {
+  height: inherit;
+  max-height: inherit;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  tobago-select-many-list .list-group {
+    transition: none;
+  }
+}
+tobago-select-many-list .list-group.border-danger .list-group-item, tobago-select-many-list .list-group.border-warning .list-group-item, tobago-select-many-list .list-group.border-info .list-group-item {
+  border-color: inherit;
+}
+tobago-select-many-list .list-group .list-group-item.tobago-select-field {
+  border-bottom-color: rgba(0, 0, 0, 0.125);
+}
+tobago-select-many-list .tobago-select-field {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+tobago-select-many-list .tobago-select-field.dropdown-toggle::after {
+  content: none;
+}
+tobago-select-many-list .tobago-select-field.list-group-item.form-control {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group + .tobago-filter {
+  margin-left: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group {
+  margin-right: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter {
+  margin: 0;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+  flex: 1 0 0;
+  min-width: 8rem;
+  background-color: transparent;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:focus {
+  box-shadow: none;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:-moz-read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-options.list-group-item {
+  padding: 0;
+  overflow-y: auto;
+}
+tobago-select-many-list .tobago-options .table {
+  margin-bottom: 0;
+}
+tobago-select-many-list .tobago-options .table tr {
+  cursor: pointer;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-disabled {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e3e4e5;
+  cursor: initial;
+  pointer-events: none;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-mark {
+  --bs-table-accent-bg: var(--bs-table-hover-bg);
+  color: var(--bs-table-hover-color);
+}
+tobago-select-many-list .tobago-options .table tr:last-of-type td {
+  border-bottom-width: 0;
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css.map b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css.map
index 04c470149a..d61875ffc7 100644
--- a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css.map
+++ b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_modules/bootstrap/scss/mixins/_lists.scss",".. [...]
\ No newline at end of file
+{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../scss/_custom.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_modules/bootstrap/scss/mixins/_lists.scss",".. [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css
index 789bb58e16..580c93323d 100644
--- a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css
+++ b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css
@@ -1,2 +1,2 @@
-@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:rgb(58, 37, 100);--bs-pink:#d63384;--bs-red:rgb(211, 0, 64);--bs-orange:#d90;--bs-yellow:#ffc107;--bs-green:rgb(29, 163, 50);--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:rgb(120, 140, 148);--bs-gray-dark:#323232;--bs-gray-100:#f7f7f7;--bs-gray-200:#e3e4e5;--bs-gray-300:#d7d7d7;--bs-gray-400:#ced4da;--bs-gray-500:#acacac;--bs-gray-600:rgb(120, 140, 148);--bs-gray-700:#55595c;--bs-gray [...]
+@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:rgb(58, 37, 100);--bs-pink:#d63384;--bs-red:rgb(211, 0, 64);--bs-orange:#d90;--bs-yellow:#ffc107;--bs-green:rgb(29, 163, 50);--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:rgb(120, 140, 148);--bs-gray-dark:#323232;--bs-gray-100:#f7f7f7;--bs-gray-200:#e3e4e5;--bs-gray-300:#d7d7d7;--bs-gray-400:#ced4da;--bs-gray-500:#acacac;--bs-gray-600:rgb(120, 140, 148);--bs-gray-700:#55595c;--bs-gray [...]
 /*# sourceMappingURL=tobago.min.css.map */
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css.map b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css.map
index 23b5820532..cd76408c6e 100644
--- a/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css.map
+++ b/tobago-theme/tobago-theme-speyside/src/main/css/tobago.min.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago-theme-speyside/src/main/css/tobago.css"],"names":[],"mappings":"iBAuCA,MACE,UAAW,QACX,YAAa,QACb,YAAa,iBACb,UAAW,QACX,SAAU,gBACV,YAAa,KACb,YAAa,QACb,WAAY,iBACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,mBACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,mBACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,gBACd,eAAgB,QAChB,aAAc,iBACd,UAAW,QACX,aAAc,QACd,YAAa,gBACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,EAAE,CAAE,GAC1B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IA [...]
\ No newline at end of file
+{"version":3,"sources":["tobago-theme-speyside/src/main/css/tobago.css"],"names":[],"mappings":"iBAuCA,MACE,UAAW,QACX,YAAa,QACb,YAAa,iBACb,UAAW,QACX,SAAU,gBACV,YAAa,KACb,YAAa,QACb,WAAY,iBACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,mBACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,mBACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,gBACd,eAAgB,QAChB,aAAc,iBACd,UAAW,QACX,aAAc,QACd,YAAa,gBACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,EAAE,CAAE,GAC1B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IA [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/css/tobago.css b/tobago-theme/tobago-theme-standard/src/main/css/tobago.css
index c90a0be6c2..2a41688acc 100644
--- a/tobago-theme/tobago-theme-standard/src/main/css/tobago.css
+++ b/tobago-theme/tobago-theme-standard/src/main/css/tobago.css
@@ -10853,6 +10853,7 @@ textarea.form-control-lg {
  */
 /* used bootstrap icons ---------------------------------------------------- */
 /* non-bootstrap variables --------------------------------------- */
+/* bootstrap variables --------------------------------------- */
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -11792,6 +11793,126 @@ tobago-select-one-radio input[type=radio].border-info:focus {
   box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
 }
 
+tobago-select-many-list {
+  display: block;
+}
+tobago-select-many-list.tobago-label-container .dropdown, tobago-select-many-list.tobago-label-container .list-group {
+  flex: 1 0 0;
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field {
+  color: #212529;
+  background-color: #fff;
+  border-color: #86b7fe;
+  outline: 0;
+  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.25);
+}
+tobago-select-many-list.tobago-focus .dropdown .tobago-select-field.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group {
+  --bs-list-group-color: #212529;
+  --bs-list-group-bg: #fff;
+  --bs-list-group-border-color: #86b7fe;
+  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-danger {
+  box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-warning {
+  box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.25);
+}
+tobago-select-many-list.tobago-focus .list-group.border-info {
+  box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
+}
+tobago-select-many-list.tobago-disabled .tobago-select-field {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e9ecef;
+}
+tobago-select-many-list .list-group {
+  height: inherit;
+  max-height: inherit;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  tobago-select-many-list .list-group {
+    transition: none;
+  }
+}
+tobago-select-many-list .list-group.border-danger .list-group-item, tobago-select-many-list .list-group.border-warning .list-group-item, tobago-select-many-list .list-group.border-info .list-group-item {
+  border-color: inherit;
+}
+tobago-select-many-list .list-group .list-group-item.tobago-select-field {
+  border-bottom-color: rgba(0, 0, 0, 0.125);
+}
+tobago-select-many-list .tobago-select-field {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+tobago-select-many-list .tobago-select-field.dropdown-toggle::after {
+  content: none;
+}
+tobago-select-many-list .tobago-select-field.list-group-item.form-control {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group + .tobago-filter {
+  margin-left: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .btn-group {
+  margin-right: 0.25rem;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter {
+  margin: 0;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+  flex: 1 0 0;
+  min-width: 8rem;
+  background-color: transparent;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:focus {
+  box-shadow: none;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:-moz-read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-select-field .tobago-filter:read-only {
+  width: 0;
+  min-width: 0;
+}
+tobago-select-many-list .tobago-options.list-group-item {
+  padding: 0;
+  overflow-y: auto;
+}
+tobago-select-many-list .tobago-options .table {
+  margin-bottom: 0;
+}
+tobago-select-many-list .tobago-options .table tr {
+  cursor: pointer;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-disabled {
+  color: rgba(33, 37, 41, 0.5);
+  background-color: #e9ecef;
+  cursor: initial;
+  pointer-events: none;
+}
+tobago-select-many-list .tobago-options .table tr.tobago-mark {
+  --bs-table-accent-bg: var(--bs-table-hover-bg);
+  color: var(--bs-table-hover-color);
+}
+tobago-select-many-list .tobago-options .table tr:last-of-type td {
+  border-bottom-width: 0;
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-standard/src/main/css/tobago.css.map b/tobago-theme/tobago-theme-standard/src/main/css/tobago.css.map
index 10ddf6b36b..9d560803d9 100644
--- a/tobago-theme/tobago-theme-standard/src/main/css/tobago.css.map
+++ b/tobago-theme/tobago-theme-standard/src/main/css/tobago.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_ [...]
\ No newline at end of file
+{"version":3,"sources":["tobago.css","../scss/tobago-theme.scss","../../../../node_modules/bootstrap/scss/mixins/_banner.scss","../../../../node_modules/bootstrap/scss/_root.scss","../../../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../../../node_modules/bootstrap/scss/_reboot.scss","../../../../node_modules/bootstrap/scss/_variables.scss","../../../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../../../node_modules/bootstrap/scss/_type.scss","../../../../node_ [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css b/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css
index 58298127f0..a82e742782 100644
--- a/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css
+++ b/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css
@@ -1,2 +1,2 @@
-@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-prima [...]
+@charset "UTF-8";:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-prima [...]
 /*# sourceMappingURL=tobago.min.css.map */
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css.map b/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css.map
index c47376a95c..fd6242268f 100644
--- a/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css.map
+++ b/tobago-theme/tobago-theme-standard/src/main/css/tobago.min.css.map
@@ -1 +1 @@
-{"version":3,"sources":["tobago-theme-standard/src/main/css/tobago.css"],"names":[],"mappings":"iBAuBA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9B,iBAA [...]
\ No newline at end of file
+{"version":3,"sources":["tobago-theme-standard/src/main/css/tobago.css"],"names":[],"mappings":"iBAuBA,MACE,UAAW,QACX,YAAa,QACb,YAAa,QACb,UAAW,QACX,SAAU,QACV,YAAa,QACb,YAAa,QACb,WAAY,QACZ,UAAW,QACX,UAAW,QACX,WAAY,KACZ,WAAY,KACZ,UAAW,QACX,eAAgB,QAChB,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,cAAe,QACf,aAAc,QACd,eAAgB,QAChB,aAAc,QACd,UAAW,QACX,aAAc,QACd,YAAa,QACb,WAAY,QACZ,UAAW,QACX,iBAAkB,EAAE,CAAE,GAAG,CAAE,IAC3B,mBAAoB,GAAG,CAAE,GAAG,CAAE,IAC9B,iBAA [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
index d5d1c728b6..ae97249f68 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -1,8 +1,8 @@
-!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";class e extends HTMLElement{constructor(){super(),this.CssClass={SHOW:"show",COLLAPSE:"collapse",COLLAPSING:"collapsing"},this.toggleButton.addEventListener("click",this.toggleCollapse.bind(this))}connectedCallback(){this.expanded="true"===this.toggleButton.ariaExpanded}toggleCollapse(e){window.clearTimeout(this.timeout),this.expanded?(this.expanded=!1,this.navbarContent.style.height=`${this.navbar [...]
+!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";class e extends HTMLElement{constructor(){super(),this.CssClass={SHOW:"show",COLLAPSE:"collapse",COLLAPSING:"collapsing"},this.toggleButton.addEventListener("click",this.toggleCollapse.bind(this))}connectedCallback(){this.expanded="true"===this.toggleButton.ariaExpanded}toggleCollapse(e){window.clearTimeout(this.timeout),this.expanded?(this.expanded=!1,this.navbarContent.style.height=`${this.navbar [...]
 /*!
     * Bootstrap v5.2.3 (https://getbootstrap.com/)
     * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
     * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
     */
-const Me="transitionend",Re=e=>{let t=e.getAttribute("data-bs-target");if(!t||"#"===t){let s=e.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s=`#${s.split("#")[1]}`),t=s&&"#"!==s?s.trim():null}return t},je=e=>{const t=Re(e);return t&&document.querySelector(t)?t:null},He=e=>{const t=Re(e);return t?document.querySelector(t):null},Pe=e=>{e.dispatchEvent(new Event(Me))},Be=e=>!(!e||"object"!=typeof e)&&(void 0!==e.jquery&&( [...]
+const Me="transitionend",De=e=>{let t=e.getAttribute("data-bs-target");if(!t||"#"===t){let s=e.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s=`#${s.split("#")[1]}`),t=s&&"#"!==s?s.trim():null}return t},je=e=>{const t=De(e);return t&&document.querySelector(t)?t:null},Be=e=>{const t=De(e);return t?document.querySelector(t):null},Pe=e=>{e.dispatchEvent(new Event(Me))},qe=e=>!(!e||"object"!=typeof e)&&(void 0!==e.jquery&&( [...]
 //# sourceMappingURL=tobago.js.map
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
index 7685b3e20a..f4a8ff96e0 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
@@ -1 +1 @@
-{"version":3,"file":"tobago.js","sources":["../ts/tobago-bar.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../../../node_m [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../ts/tobago-bar.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../../../node_m [...]
\ No newline at end of file


[myfaces-tobago] 01/02: feat: selectManyList component

Posted by hn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit eb2cca850e1f692304ff7173bfd1f878b9b792a5
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Wed Oct 26 22:01:10 2022 +0200

    feat: selectManyList component
    
    * add new tc:selectManyList component
    
    Issue: TOBAGO-2159
---
 .../myfaces/tobago/component/RendererTypes.java    |   2 +
 .../myfaces/tobago/component/SupportsFilter.java   |  25 ++
 .../org/apache/myfaces/tobago/component/Tags.java  |   2 +
 .../component/AbstractUISelectManyList.java        |  84 ++++
 .../renderkit/renderer/SelectManyListRenderer.java | 209 ++++++++++
 .../component/SelectManyListTagDeclaration.java    |  94 +++++
 .../internal/taglib/declaration/HasFilter.java     |  29 ++
 .../tobago/renderkit/css/BootstrapClass.java       |   3 +
 .../myfaces/tobago/renderkit/css/TobagoClass.java  |   5 +
 .../tobago/renderkit/html/HtmlElements.java        |   1 +
 .../example/demo/SelectManyListController.java     | 138 +++++++
 .../apache/myfaces/tobago/example/demo/names.txt   |  75 ++++
 .../80-selectManyList/SelectManyList.xhtml         |  87 +++++
 tobago-theme/jest.config.js                        |   5 +-
 tobago-theme/src/main/scss/_tobago.scss            | 161 ++++++++
 .../src/main/resources/META-INF/tobago-config.xml  |   3 +
 .../src/main/ts/tobago-all.ts                      |   1 +
 .../src/main/ts/tobago-filter-registry.test.ts     | 124 ++++++
 .../src/main/ts/tobago-filter-registry.ts          | 102 +++++
 .../src/main/ts/tobago-select-many-list.ts         | 421 +++++++++++++++++++++
 20 files changed, 1570 insertions(+), 1 deletion(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/RendererTypes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/RendererTypes.java
index 27e90ae5fe..d77b7b1c31 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/RendererTypes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/RendererTypes.java
@@ -68,6 +68,7 @@ public enum RendererTypes {
   SelectBooleanCheckbox,
   SelectBooleanToggle,
   SelectManyCheckbox,
+  SelectManyList,
   SelectManyListbox,
   SelectManyShuttle,
   SelectOneChoice,
@@ -141,6 +142,7 @@ public enum RendererTypes {
   public static final String SELECT_BOOLEAN_CHECKBOX = "SelectBooleanCheckbox";
   public static final String SELECT_BOOLEAN_TOGGLE = "SelectBooleanToggle";
   public static final String SELECT_MANY_CHECKBOX = "SelectManyCheckbox";
+  public static final String SELECT_MANY_LIST = "SelectManyList";
   public static final String SELECT_MANY_LISTBOX = "SelectManyListbox";
   public static final String SELECT_MANY_SHUTTLE = "SelectManyShuttle";
   public static final String SELECT_ONE_CHOICE = "SelectOneChoice";
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsFilter.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsFilter.java
new file mode 100644
index 0000000000..a61bdbf953
--- /dev/null
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/SupportsFilter.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.component;
+
+public interface SupportsFilter {
+
+  String getFilter();
+}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Tags.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Tags.java
index 6620e764e2..9b6050a801 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Tags.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/component/Tags.java
@@ -75,6 +75,7 @@ public enum Tags {
   selectItem,
   selectItems,
   selectManyCheckbox,
+  selectManyList,
   selectManyListbox,
   selectManyShuttle,
   selectOneChoice,
@@ -147,6 +148,7 @@ public enum Tags {
   public static final String SELECT_ITEM = "selectItem";
   public static final String SELECT_ITEMS = "selectItems";
   public static final String SELECT_MANY_CHECKBOX = "selectManyCheckbox";
+  public static final String SELECT_MANY_LIST = "selectManyList";
   public static final String SELECT_MANY_LISTBOX = "selectManyListbox";
   public static final String SELECT_MANY_SHUTTLE = "selectManyShuttle";
   public static final String SELECT_ONE_CHOICE = "selectOneChoice";
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyList.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyList.java
new file mode 100644
index 0000000000..952c1ddb9f
--- /dev/null
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISelectManyList.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.internal.component;
+
+import org.apache.myfaces.tobago.component.SupportFieldId;
+import org.apache.myfaces.tobago.component.SupportsAutoSpacing;
+import org.apache.myfaces.tobago.component.SupportsFilter;
+import org.apache.myfaces.tobago.component.SupportsHelp;
+import org.apache.myfaces.tobago.component.SupportsLabelLayout;
+import org.apache.myfaces.tobago.component.Visual;
+import org.apache.myfaces.tobago.internal.taglib.component.SelectManyListTagDeclaration;
+import org.apache.myfaces.tobago.util.ComponentUtils;
+
+import javax.faces.component.behavior.ClientBehaviorHolder;
+import javax.faces.context.FacesContext;
+import java.util.Collection;
+
+/**
+ * {@link SelectManyListTagDeclaration}
+ */
+public abstract class AbstractUISelectManyList extends AbstractUISelectManyBase
+  implements SupportsAutoSpacing, Visual, SupportsLabelLayout, ClientBehaviorHolder, SupportsHelp, SupportFieldId,
+  SupportsFilter {
+
+  private transient boolean nextToRenderIsLabel;
+
+  @Override
+  public Object[] getSelectedValues() {
+    final Object value = getValue();
+    if (value instanceof Collection) {
+      return ((Collection) value).toArray();
+    } else {
+      return (Object[]) value;
+    }
+  }
+
+  @Override
+  public String getFieldId(final FacesContext facesContext) {
+    return getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "field";
+  }
+
+  public abstract Integer getTabIndex();
+
+  public abstract boolean isDisabled();
+
+  public abstract boolean isInline();
+
+  public boolean isError() {
+    final FacesContext facesContext = FacesContext.getCurrentInstance();
+    return !isValid()
+      || !facesContext.getMessageList(getClientId(facesContext)).isEmpty();
+  }
+
+  public abstract boolean isFocus();
+
+  public abstract String getFilter();
+
+  @Override
+  public boolean isNextToRenderIsLabel() {
+    return nextToRenderIsLabel;
+  }
+
+  @Override
+  public void setNextToRenderIsLabel(final boolean nextToRenderIsLabel) {
+    this.nextToRenderIsLabel = nextToRenderIsLabel;
+  }
+}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListRenderer.java
new file mode 100644
index 0000000000..d6ebee277d
--- /dev/null
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectManyListRenderer.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.internal.renderkit.renderer;
+
+import org.apache.myfaces.tobago.context.Markup;
+import org.apache.myfaces.tobago.internal.component.AbstractUISelectManyList;
+import org.apache.myfaces.tobago.internal.util.ArrayUtils;
+import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
+import org.apache.myfaces.tobago.internal.util.SelectItemUtils;
+import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
+import org.apache.myfaces.tobago.renderkit.css.CssItem;
+import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
+import org.apache.myfaces.tobago.renderkit.html.Arias;
+import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
+import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
+import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
+import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
+import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
+import org.apache.myfaces.tobago.util.ComponentUtils;
+import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
+
+import javax.faces.context.FacesContext;
+import javax.faces.model.SelectItem;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectManyListRenderer<T extends AbstractUISelectManyList> extends SelectManyRendererBase<T> {
+  @Override
+  public HtmlElements getComponentTag() {
+    return HtmlElements.TOBAGO_SELECT_MANY_LIST;
+  }
+
+  @Override
+  protected CssItem[] getComponentCss(final FacesContext facesContext, final T component) {
+    final List<SelectItem> items = SelectItemUtils.getItemList(facesContext, component);
+    final boolean disabled = !items.iterator().hasNext() || component.isDisabled() || component.isReadonly();
+
+    List<CssItem> cssItems = new ArrayList<>();
+    if (disabled) {
+      cssItems.add(TobagoClass.DISABLED);
+    }
+    return cssItems.toArray(new CssItem[0]);
+  }
+
+  @Override
+  protected String getFieldId(FacesContext facesContext, T component) {
+    return component.getFieldId(facesContext);
+  }
+
+  @Override
+  protected void encodeBeginField(FacesContext facesContext, T component) throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
+    final String clientId = component.getClientId(facesContext);
+    final String fieldId = component.getFieldId(facesContext);
+    final String filterId = clientId + ComponentUtils.SUB_SEPARATOR + "filter";
+    final String selectedId = clientId + ComponentUtils.SUB_SEPARATOR + "selected";
+    final List<SelectItem> items = SelectItemUtils.getItemList(facesContext, component);
+    final boolean disabled = !items.iterator().hasNext() || component.isDisabled() || component.isReadonly();
+    final String filter = component.getFilter();
+    final boolean inline = component.isInline();
+    final Markup markup = component.getMarkup();
+    final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, component);
+    final Integer tabIndex = component.getTabIndex();
+
+    encodeHiddenSelect(facesContext, component, items, clientId, selectedId, disabled);
+
+    writer.startElement(HtmlElements.DIV);
+    writer.writeClassAttribute(
+        inline ? BootstrapClass.LIST_GROUP : BootstrapClass.DROPDOWN,
+        inline ? BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)) : null);
+
+    encodeSelectField(facesContext, component, clientId, fieldId, filterId, filter, disabled, inline, title, tabIndex);
+    encodeOptions(facesContext, component, items, clientId, inline, disabled);
+
+    writer.endElement(HtmlElements.DIV);
+  }
+
+  @Override
+  protected void writeAdditionalAttributes(FacesContext facesContext, TobagoResponseWriter writer, T input)
+      throws IOException {
+    super.writeAdditionalAttributes(facesContext, writer, input);
+    writer.writeAttribute(CustomAttributes.FILTER, input.getFilter(), true);
+  }
+
+  private void encodeHiddenSelect(final FacesContext facesContext, final T component, final List<SelectItem> items,
+      final String clientId, final String selectedId, final boolean disabled) throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
+    writer.startElement(HtmlElements.SELECT);
+    writer.writeIdAttribute(selectedId);
+    writer.writeNameAttribute(clientId);
+    writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
+    writer.writeAttribute(HtmlAttributes.REQUIRED, component.isRequired());
+    writer.writeClassAttribute(BootstrapClass.D_NONE);
+    writer.writeAttribute(HtmlAttributes.MULTIPLE, true);
+
+    final Object[] values = component.getSelectedValues();
+    final String[] submittedValues = getSubmittedValues(component);
+    renderSelectItems(component, null, items, values, submittedValues, writer, facesContext);
+    writer.endElement(HtmlElements.SELECT);
+  }
+
+  private void encodeSelectField(final FacesContext facesContext, final T component,
+      final String clientId, final String fieldId, final String filterId, final String filter, final boolean disabled,
+      final boolean inline, final String title, final Integer tabIndex) throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
+    writer.startElement(HtmlElements.DIV);
+    writer.writeIdAttribute(fieldId);
+    writer.writeNameAttribute(clientId);
+    HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
+    writer.writeClassAttribute(
+        inline ? BootstrapClass.FORM_CONTROL : BootstrapClass.FORM_SELECT,
+        TobagoClass.SELECT__FIELD,
+        inline ? BootstrapClass.LIST_GROUP_ITEM : BootstrapClass.DROPDOWN_TOGGLE,
+        inline ? null : BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)),
+        component.getCustomClass());
+    writer.writeAttribute(HtmlAttributes.TITLE, title, true);
+    writer.writeAttribute(Arias.EXPANDED, Boolean.FALSE.toString(), false);
+    writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
+
+    writer.startElement(HtmlElements.INPUT);
+    writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.TEXT);
+    writer.writeIdAttribute(filterId);
+    writer.writeClassAttribute(TobagoClass.FILTER, BootstrapClass.FORM_CONTROL);
+    writer.writeAttribute(HtmlAttributes.AUTOCOMPLETE, "off", false);
+    writer.writeAttribute(HtmlAttributes.READONLY, filter == null || filter.isEmpty());
+    writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
+    writer.writeAttribute(HtmlAttributes.TABINDEX, tabIndex);
+    renderFocus(clientId, component.isFocus(), component.isError(), facesContext, writer);
+
+    writer.endElement(HtmlElements.INPUT);
+
+    writer.endElement(HtmlElements.DIV);
+  }
+
+  private void encodeOptions(final FacesContext facesContext, final T component, final List<SelectItem> items,
+      final String clientId, final boolean inline, final boolean disabled) throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
+    writer.startElement(HtmlElements.DIV);
+    writer.writeClassAttribute(
+        TobagoClass.OPTIONS,
+        inline ? BootstrapClass.LIST_GROUP_ITEM : BootstrapClass.DROPDOWN_MENU);
+    writer.writeNameAttribute(clientId);
+
+    writer.startElement(HtmlElements.TABLE);
+    writer.writeClassAttribute(BootstrapClass.TABLE, BootstrapClass.TABLE_HOVER, BootstrapClass.TABLE_SM);
+    writer.startElement(HtmlElements.TBODY);
+
+    final Object[] values = component.getSelectedValues();
+    final String[] submittedValues = getSubmittedValues(component);
+    for (SelectItem item : items) {
+      Object itemValue = item.getValue();
+      // when using selectItem tag with a literal value: use the converted value
+      if (itemValue instanceof String && values != null && values.length > 0 && !(values[0] instanceof String)) {
+        itemValue = ComponentUtils.getConvertedValue(facesContext, component, (String) itemValue);
+      }
+      final String formattedValue = getFormattedValue(facesContext, (T) component, itemValue);
+      final boolean contains;
+      if (submittedValues == null) {
+        contains = ArrayUtils.contains(values, itemValue);
+      } else {
+        contains = ArrayUtils.contains(submittedValues, formattedValue);
+      }
+      writer.startElement(HtmlElements.TR);
+      writer.writeAttribute(DataAttributes.VALUE, formattedValue, true);
+      writer.writeClassAttribute(
+          contains ? BootstrapClass.TABLE_PRIMARY : null,
+          disabled || item.isDisabled() ? TobagoClass.DISABLED : null);
+
+      writer.startElement(HtmlElements.TD);
+      writer.writeAttribute(HtmlAttributes.VALUE, formattedValue, true);
+      writer.writeText(item.getLabel());
+      writer.endElement(HtmlElements.TD);
+      writer.endElement(HtmlElements.TR);
+    }
+
+    writer.endElement(HtmlElements.TBODY);
+    writer.endElement(HtmlElements.TABLE);
+    writer.endElement(HtmlElements.DIV);
+  }
+
+  @Override
+  protected void encodeEndField(FacesContext facesContext, T component) throws IOException {
+    final TobagoResponseWriter writer = getResponseWriter(facesContext);
+
+    encodeBehavior(writer, facesContext, component);
+  }
+}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListTagDeclaration.java
new file mode 100644
index 0000000000..e7572d504b
--- /dev/null
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/SelectManyListTagDeclaration.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.internal.taglib.component;
+
+import org.apache.myfaces.tobago.apt.annotation.Behavior;
+import org.apache.myfaces.tobago.apt.annotation.Preliminary;
+import org.apache.myfaces.tobago.apt.annotation.Tag;
+import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
+import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
+import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
+import org.apache.myfaces.tobago.component.ClientBehaviors;
+import org.apache.myfaces.tobago.component.RendererTypes;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasAutoSpacing;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasBinding;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverter;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasConverterMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasFilter;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasHelp;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasId;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabel;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasLabelLayout;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasRequiredMessageForSelect;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasTabIndex;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasTip;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasValidator;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasValidatorMessage;
+import org.apache.myfaces.tobago.internal.taglib.declaration.HasValueChangeListener;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsDisabled;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsFocus;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsInline;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsReadonly;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsRendered;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsRequiredForSelect;
+import org.apache.myfaces.tobago.internal.taglib.declaration.IsVisual;
+
+import javax.faces.component.UISelectMany;
+
+/**
+ * Render a multi selection option listbox.
+ */
+@Preliminary
+@Tag(name = "selectManyList")
+@UIComponentTag(
+  uiComponent = "org.apache.myfaces.tobago.component.UISelectManyList",
+  uiComponentFacesClass = "javax.faces.component.UISelectMany",
+  componentFamily = UISelectMany.COMPONENT_FAMILY,
+  rendererType = RendererTypes.SELECT_MANY_LIST,
+  allowedChildComponenents = {"javax.faces.SelectItem", "javax.faces.SelectItems"},
+  behaviors = {
+    @Behavior(
+      name = ClientBehaviors.CHANGE,
+      isDefault = true),
+    @Behavior(
+      name = ClientBehaviors.INPUT),
+    @Behavior(
+      name = ClientBehaviors.CLICK),
+    @Behavior(
+      name = ClientBehaviors.DBLCLICK),
+    @Behavior(
+      name = ClientBehaviors.FOCUS),
+    @Behavior(
+      name = ClientBehaviors.BLUR)
+  })
+
+public interface SelectManyListTagDeclaration
+  extends HasId, IsDisabled, IsRendered, HasBinding, HasTip, HasHelp,
+  IsReadonly, HasConverter, IsRequiredForSelect, HasLabel, HasValidator, HasValueChangeListener, HasLabelLayout,
+  HasValidatorMessage, HasConverterMessage, HasRequiredMessageForSelect, HasTabIndex, IsFocus, IsVisual,
+  HasAutoSpacing, HasFilter, IsInline {
+
+  /**
+   * The value of the multi select.
+   */
+  @TagAttribute
+  @UIComponentTagAttribute(type = {"java.lang.Object[]", "java.util.List"})
+  void setValue(String value);
+}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasFilter.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasFilter.java
new file mode 100644
index 0000000000..b11db54a21
--- /dev/null
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/declaration/HasFilter.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.internal.taglib.declaration;
+
+import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
+import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
+
+public interface HasFilter {
+  @TagAttribute
+  @UIComponentTagAttribute
+  void setFilter(String filter);
+}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
index 538a92ae08..f41e2921db 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/BootstrapClass.java
@@ -410,6 +410,8 @@ public enum BootstrapClass implements CssItem {
   JUSTIFY_CONTENT_CENTER("justify-content-center"),
   JUSTIFY_CONTENT_END("justify-content-end"),
   JUSTIFY_CONTENT_START("justify-content-start"),
+  LIST_GROUP("list-group"),
+  LIST_GROUP_ITEM("list-group-item"),
   MB_0("mb-0"),
   MB_1("mb-1"),
   MB_2("mb-2"),
@@ -614,6 +616,7 @@ public enum BootstrapClass implements CssItem {
   TABLE_DARK("table-dark"),
   TABLE_HOVER("table-hover"),
   TABLE_INFO("table-info"),
+  TABLE_PRIMARY("table-primary"),
   TABLE_SM("table-sm"),
   TABLE_STRIPED("table-striped"),
   TOOLTIP_ARROW("tooltip-arrow"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
index 6e2212b726..91af83d7ed 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/css/TobagoClass.java
@@ -49,12 +49,15 @@ public enum TobagoClass implements CssItem {
 //  DATE__PICKER("tobago-date-picker"),
   DELETED("tobago-deleted"),
   DESCENDING("tobago-descending"),
+  DISABLED("tobago-disabled"),
   DISPLAY__INLINE__BLOCK("tobago-display-inline-block"),
   DROPDOWN__SUBMENU("tobago-dropdown-submenu"),
   EXPANDED("tobago-expanded"),
   //  FILE("tobago-file"),
 //  FIGURE("tobago-figure"),
+  FOCUS("tobago-focus"),
   FOLDER("tobago-folder"),
+  FILTER("tobago-filter"),
   HEADER("tobago-header"),
   //  IMAGE("tobago-image"),
   // tbd: can be removed?
@@ -69,6 +72,7 @@ public enum TobagoClass implements CssItem {
   NOW("tobago-now"),
   NUMBER("tobago-number"),
   OBJECT("tobago-object"),
+  OPTIONS("tobago-options"),
   OUT("tobago-out"),
   //  PAGE("tobago-page"),
   PAGE__MENU_STORE("tobago-page-menuStore"),
@@ -80,6 +84,7 @@ public enum TobagoClass implements CssItem {
   REQUIRED("tobago-required"),
   RESIZE("tobago-resize"),
   SECTION__CONTENT("tobago-section-content"),
+  SELECT__FIELD("tobago-select-field"),
   SELECT_MANY_LISTBOX__OPTION("tobago-selectManyListbox-option"),
   //  SELECT_MANY_SHUTTLE("tobago-selectManyShuttle"),
 //  SELECT_MANY_SHUTTLE__ADD("tobago-selectManyShuttle-add"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
index 5e98ec05ea..727dc64f3d 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
@@ -172,6 +172,7 @@ public enum HtmlElements {
   TOBAGO_SELECT_BOOLEAN_CHECKBOX("tobago-select-boolean-checkbox"),
   TOBAGO_SELECT_BOOLEAN_TOGGLE("tobago-select-boolean-toggle"),
   TOBAGO_SELECT_MANY_CHECKBOX("tobago-select-many-checkbox"),
+  TOBAGO_SELECT_MANY_LIST("tobago-select-many-list"),
   TOBAGO_SELECT_MANY_LISTBOX("tobago-select-many-listbox"),
   TOBAGO_SELECT_MANY_SHUTTLE("tobago-select-many-shuttle"),
   TOBAGO_SELECT_ONE_CHOICE("tobago-select-one-choice"),
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
new file mode 100644
index 0000000000..023b3c826e
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.example.demo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.SessionScoped;
+import javax.faces.application.FacesMessage;
+import javax.faces.context.FacesContext;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+@SessionScoped
+@Named
+public class SelectManyListController implements Serializable {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Inject
+  private AstroData astroData;
+
+  private List<SolarObject> planets;
+  private SolarObject[] selected1 = new SolarObject[0];
+  private SolarObject[] selected2 = new SolarObject[0];
+  private SolarObject[] selected3 = new SolarObject[0];
+  private SolarObject[] selected4 = new SolarObject[0];
+  private List<String> names;
+  private String[] selected5 = new String[0];
+
+  private String filterType;
+
+  @PostConstruct
+  public void init() {
+    planets = astroData.getSatellites("Sun");
+
+    names = new ArrayList<>();
+    try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+        Thread.currentThread().getContextClassLoader().getResourceAsStream(
+            "org/apache/myfaces/tobago/example/demo/names.txt"), StandardCharsets.UTF_8))) {
+      String line;
+      while ((line = reader.readLine()) != null) {
+        line = line.trim();
+        if (!line.startsWith("#") && line.length() > 0) {
+          names.add(line);
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Can't load names", e);
+    }
+  }
+
+  public List<SolarObject> getPlanets() {
+    return planets;
+  }
+
+  public SolarObject[] getSelected1() {
+    return selected1;
+  }
+
+  public void setSelected1(SolarObject[] selected1) {
+    this.selected1 = selected1;
+  }
+
+  public SolarObject[] getSelected2() {
+    return selected2;
+  }
+
+  public void setSelected2(SolarObject[] selected2) {
+    this.selected2 = selected2;
+  }
+
+  public SolarObject[] getSelected3() {
+    return selected3;
+  }
+
+  public void setSelected3(SolarObject[] selected3) {
+    this.selected3 = selected3;
+  }
+
+  public SolarObject[] getSelected4() {
+    return selected4;
+  }
+
+  public void setSelected4(SolarObject[] selected4) {
+    this.selected4 = selected4;
+  }
+
+  public List<String> getNames() {
+    if (names.size() < 1) {
+      FacesContext.getCurrentInstance().addMessage(null,
+          new FacesMessage(FacesMessage.SEVERITY_ERROR, "Names not loaded!", null));
+      return new ArrayList<>();
+    }
+    return names;
+  }
+
+  public String[] getSelected5() {
+    return selected5;
+  }
+
+  public void setSelected5(String[] selected5) {
+    this.selected5 = selected5;
+  }
+
+  public String getFilterType() {
+    return filterType;
+  }
+
+  public void setFilterType(String filterType) {
+    this.filterType = filterType;
+  }
+}
diff --git a/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/names.txt b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/names.txt
new file mode 100644
index 0000000000..cff719831c
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/resources/org/apache/myfaces/tobago/example/demo/names.txt
@@ -0,0 +1,75 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Adrián
+Agnès
+Amélie
+André
+Asbjörn
+Asbjørn
+Behlül
+Björn
+Börje
+Çağlar
+Celâl
+Chloé
+Chloë
+François
+Gökay
+Gökhan
+Göran
+Gösta
+Gustav
+Güvençe
+Héloise
+Inès
+János
+Jokūbas
+Jöran
+KŠthe
+Léa
+Léo
+Maél
+Maël
+Maël
+Mathéo
+Mátyás
+Miraç
+Mjølnir
+Mónica
+Noël
+Nurdoğan
+Örjan
+Özer
+Özgür
+Öztürk
+Raphaël
+Renée
+Ruairí
+Rüzgar
+Rüştü
+Seán
+Siân
+Sölve
+Sönke
+Sørina
+Timéo
+Tunçay
+Ümit
+Uğur
+Yağmur
+Yiğitcan
+Zoë
+Zülfikar
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
new file mode 100644
index 0000000000..69294f9ec2
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<ui:composition template="/main.xhtml"
+                xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:tc="http://myfaces.apache.org/tobago/component"
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+                xmlns:f="http://xmlns.jcp.org/jsf/core">
+
+  <!-- todo -->
+  <tc:badge markup="warning"
+            value="Preliminary feature, may be subject to change!"/><br/>
+
+  <tc:section label="not inline / no filter">
+    <tc:selectManyList id="selected1" value="#{selectManyListController.selected1}">
+      <tc:selectItems value="#{selectManyListController.planets}"
+                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+    </tc:selectManyList>
+  </tc:section>
+
+  <tc:section label="inline / no filter">
+    <tc:selectManyList id="selected2" value="#{selectManyListController.selected2}" inline="true">
+      <tc:selectItems value="#{selectManyListController.planets}"
+                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+    </tc:selectManyList>
+    <tc:button label="Submit"/>
+  </tc:section>
+
+  <tc:section label="not inline / with filter">
+    <tc:selectManyList id="selected3" value="#{selectManyListController.selected3}" filter="contains">
+      <tc:selectItems value="#{selectManyListController.planets}"
+                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+    </tc:selectManyList>
+    <tc:button label="Submit"/>
+  </tc:section>
+
+  <tc:section label="inline / with filter:">
+    <tc:selectManyList id="selected4" value="#{selectManyListController.selected4}" filter="contains" inline="true">
+      <tc:selectItems value="#{selectManyListController.planets}"
+                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+    </tc:selectManyList>
+    <tc:button label="Submit"/>
+  </tc:section>
+
+  <tc:section id="filter" label="Filter types: contains, startsWith, containsExact, startsWithExact">
+    <tc:selectOneRadio label="Filter type" value="#{selectManyListController.filterType}">
+      <tc:selectItem itemValue="#{null}" itemLabel="please select"/>
+      <tc:selectItem itemValue="contains"/>
+      <tc:selectItem itemValue="startsWith"/>
+      <tc:selectItem itemValue="containsExact"/>
+      <tc:selectItem itemValue="startsWithExact"/>
+      <f:ajax render="filter" execute="filter"/>
+    </tc:selectOneRadio>
+    <tc:selectManyList id="selected5" value="#{selectManyListController.selected5}"
+                       filter="#{selectManyListController.filterType}" inline="true">
+      <tc:selectItems value="#{selectManyListController.names}"
+                      var="name" itemLabel="#{name}" itemValue="#{name}"/>
+      <tc:style maxHeight="300px"/>
+    </tc:selectManyList>
+
+    Dependent of the type of the filter:
+    Ignores case.
+    Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
+    dieresis (ü or ï), and cedilla (ç).
+  </tc:section>
+
+  <tc:section label="Filter types: custom">
+    todo
+  </tc:section>
+
+</ui:composition>
diff --git a/tobago-theme/jest.config.js b/tobago-theme/jest.config.js
index 80e4883ab1..58aebcbf99 100644
--- a/tobago-theme/jest.config.js
+++ b/tobago-theme/jest.config.js
@@ -18,5 +18,8 @@
 module.exports = {
   preset: "ts-jest",
   testEnvironment: "node",
-  rootDir: "tobago-theme-standard/"
+  rootDir: "tobago-theme-standard/",
+  globals: {
+    "window": {}
+  }
 };
diff --git a/tobago-theme/src/main/scss/_tobago.scss b/tobago-theme/src/main/scss/_tobago.scss
index e75b2f42ea..ecb0d9346c 100644
--- a/tobago-theme/src/main/scss/_tobago.scss
+++ b/tobago-theme/src/main/scss/_tobago.scss
@@ -50,6 +50,9 @@ $tobago-page-padding-top: 1rem;
 $tobago-header-margin-bottom: $tobago-page-padding-top;
 $tobago-flex-layout-spacing: 0.5rem;
 
+/* bootstrap variables --------------------------------------- */
+$form-select-disabled-color: rgba($form-select-color, $tobago-form-disabled-alpha) !default;
+
 /* utilities ----------------------------------------------------- */
 .tobago-display-inline-block {
   display: inline-block;
@@ -111,6 +114,25 @@ $tobago-flex-layout-spacing: 0.5rem;
   }
 }
 
+@mixin formControlFocusShadows() {
+  //_form-control:focus from bootstrap
+  @if $enable-shadows {
+    @include box-shadow($input-box-shadow, $input-focus-box-shadow);
+  } @else {
+    // Avoid using mixin so we can pass custom focus shadow properly
+    box-shadow: $input-focus-box-shadow;
+  }
+}
+
+@mixin formControlFocus() {
+  //_form-control:focus from bootstrap
+  color: $input-focus-color;
+  background-color: $input-focus-bg;
+  border-color: $input-focus-border-color;
+  outline: 0;
+  @include formControlFocusShadows();
+}
+
 @mixin formControlSelectListDisabled() {
   &:disabled option, option:disabled {
     color: rgba($input-color, $tobago-form-disabled-alpha);
@@ -1162,6 +1184,145 @@ tobago-select-one-radio {
   }
 }
 
+tobago-select-many-list {
+  display: block;
+
+  &.tobago-label-container {
+    .dropdown, .list-group {
+      flex: 1 0 0;
+    }
+  }
+
+  &.tobago-focus {
+    @mixin borderColorShadow($name, $color) {
+      &.border-#{$name} {
+        box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity);
+      }
+    }
+
+    .dropdown .tobago-select-field {
+      @include formControlFocus();
+      @include borderColorShadow("danger", $danger);
+      @include borderColorShadow("warning", $warning);
+      @include borderColorShadow("info", $info);
+    }
+
+    .list-group {
+      --bs-list-group-color: #{$input-focus-color};
+      --bs-list-group-bg: #{$input-focus-bg};
+      --bs-list-group-border-color: #{$input-focus-border-color};
+
+      @include formControlFocusShadows();
+      @include borderColorShadow("danger", $danger);
+      @include borderColorShadow("warning", $warning);
+      @include borderColorShadow("info", $info);
+    }
+  }
+
+  &.tobago-disabled {
+    .tobago-select-field {
+      color: $form-select-disabled-color;
+      background-color: $form-select-disabled-bg;
+      border-color: $form-select-disabled-border-color;
+    }
+  }
+
+  .list-group {
+    height: inherit;
+    max-height: inherit;
+    @include box-shadow($form-select-box-shadow);
+    @include transition($form-select-transition);
+
+    &.border-danger, &.border-warning, &.border-info {
+      .list-group-item {
+        border-color: inherit;
+      }
+    }
+
+    .list-group-item.tobago-select-field {
+      border-bottom-color: $list-group-border-color;
+    }
+  }
+
+  .tobago-select-field {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+
+    &.dropdown-toggle::after {
+      content: none;
+    }
+
+    &.list-group-item.form-control {
+      border-bottom-left-radius: 0;
+      border-bottom-right-radius: 0;
+
+      padding: $form-select-padding-y $form-select-indicator-padding $form-select-padding-y $form-select-padding-x;
+    }
+
+    .btn-group + .tobago-filter {
+      margin-left: 0.25rem;
+    }
+
+    .btn-group {
+      margin-right: 0.25rem;
+    }
+
+    .tobago-filter {
+      margin: 0;
+      border: 0;
+      border-radius: 0;
+      padding: 0;
+      flex: 1 0 0;
+      min-width: 8rem;
+      background-color: transparent;
+
+      &:focus {
+        box-shadow: none;
+      }
+
+      &:read-only {
+        width: 0;
+        min-width: 0;
+      }
+    }
+  }
+
+  .tobago-options {
+    &.list-group-item {
+      padding: 0;
+      overflow-y: auto;
+    }
+
+    .table {
+      margin-bottom: 0;
+
+      tr {
+        cursor: pointer;
+
+        &.tobago-disabled {
+          color: $form-select-disabled-color;
+          background-color: $form-select-disabled-bg;
+          border-color: $form-select-disabled-border-color;
+          cursor: initial;
+          pointer-events: none;
+        }
+
+        &.tobago-mark {
+          --bs-table-accent-bg: var(--bs-table-hover-bg);
+          color: var(--bs-table-hover-color);
+        }
+
+        &:last-of-type {
+          td {
+            border-bottom-width: 0;
+          }
+        }
+      }
+    }
+  }
+}
+
 /* selectManyCheckbox ----------------------------------------------------- */
 tobago-select-many-checkbox {
   display: block;
diff --git a/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml b/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml
index e07725496b..c9faad6aed 100644
--- a/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml
+++ b/tobago-theme/tobago-theme-standard/src/main/resources/META-INF/tobago-config.xml
@@ -77,6 +77,9 @@
         <tag name="selectManyCheckbox">
           <attribute name="labelLayout" default="flexLeft"/>
         </tag>
+        <tag name="selectManyList">
+          <attribute name="labelLayout" default="flexLeft"/>
+        </tag>
         <tag name="selectManyListbox">
           <attribute name="labelLayout" default="flexLeft"/>
         </tag>
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
index 15bd6e250a..d9f7acd722 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
@@ -36,6 +36,7 @@ import "./tobago-reload";
 import "./tobago-scroll";
 import "./tobago-select-boolean-checkbox";
 import "./tobago-select-boolean-toggle";
+import "./tobago-select-many-list";
 import "./tobago-select-many-checkbox";
 import "./tobago-select-many-listbox";
 import "./tobago-select-many-shuttle";
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.test.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.test.ts
new file mode 100644
index 0000000000..c63ece9144
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.test.ts
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TobagoFilterRegistry} from "./tobago-filter-registry";
+
+beforeEach(() => {
+  Object.defineProperty(window, "navigator", {value: {}, configurable: true});
+  // "es" is only one example
+  Object.defineProperty(window.navigator, "language", {value: "es", configurable: true});
+});
+
+test("a startsWith a", () => {
+  const p = TobagoFilterRegistry.get("startsWith");
+  expect(p("a", "a")).toBeTruthy();
+});
+
+test("a startsWith b", () => {
+  const p = TobagoFilterRegistry.get("startsWith");
+  expect(p("a", "b")).toBeFalsy();
+});
+
+test("a startsWith A", () => {
+  const p = TobagoFilterRegistry.get("startsWith");
+  expect(p("a", "A")).toBeTruthy();
+});
+
+test("AB startsWith á", () => {
+  const p = TobagoFilterRegistry.get("startsWith");
+  expect(p("AB", "á")).toBeTruthy();
+});
+
+test("AB contains á", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("aB", "á")).toBeTruthy();
+});
+
+test("BAB contains á", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("BAB", "á")).toBeTruthy();
+});
+
+test("El niño startsWith \u00F1", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("El niño", "\u00F1")).toBeFalsy();
+});
+
+test("El niño contains \u00F1", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("El niño", "\u00F1")).toBeTruthy();
+});
+
+test("El niño startsWith \u006E\u0303", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("El niño", "\u006E\u0303")).toBeFalsy();
+});
+
+test("El niño contains \u006E\u0303", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("El niño", "\u006E\u0303")).toBeTruthy();
+});
+
+test("El niño startsWith Ñ", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("El niño", "Ñ")).toBeFalsy();
+});
+
+test("El niño contains Ñ", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("El niño", "Ñ")).toBeTruthy();
+});
+
+test("Am\u00e9lie startsWith Am\u0065\u0301lie", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("Am\u00e9lie", "Am\u0065\u0301lie")).toBeTruthy();
+});
+
+test("Am\u00e9lie contains Am\u0065\u0301lie", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("Am\u00e9lie", "Am\u0065\u0301lie")).toBeTruthy();
+});
+
+test("Barış startsWith Baris", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+// XXX skip  expect(c("Barış", "Baris")).toBeTruthy();
+});
+
+test("Barış contains Baris", () => {
+  const c = TobagoFilterRegistry.get("contains");
+// XXX skip  expect(c("Barış", "Baris")).toBeTruthy();
+});
+
+test("Uğur startsWith Ugur", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("Uğur", "Ugur")).toBeTruthy();
+});
+
+test("Uğur contains Ugur", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("Uğur", "Ugur")).toBeTruthy();
+});
+
+test("Mjølnir startsWith Mjolnir", () => {
+  const c = TobagoFilterRegistry.get("startsWith");
+  expect(c("Mjølnir", "Mjolnir")).toBeTruthy();
+});
+
+test("Mjølnir contains Mjolnir", () => {
+  const c = TobagoFilterRegistry.get("contains");
+  expect(c("Mjølnir", "Mjolnir")).toBeTruthy();
+});
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.ts
new file mode 100644
index 0000000000..27d3bab0ed
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-filter-registry.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export class TobagoFilterRegistry {
+
+  // todo: use "function(string, string): boolean" instead of "any"
+  private static map: Map<string, any> = new Map<string, any>();
+
+  static {
+    /**
+     * Filter for "contains" search.
+     * Ignores case.
+     * Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
+     * dieresis (ü or ï), and cedilla (ç).
+     */
+    TobagoFilterRegistry.set("contains",
+        (item: string, search: string): boolean =>
+            TobagoFilterRegistry.localeContains(item, search, false)
+    );
+
+    /**
+     * Filter for "startsWith" search.
+     * Ignores case.
+     * Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
+     * dieresis (ü or ï), and cedilla (ç).
+     */
+    TobagoFilterRegistry.set("startsWith",
+        (item: string, search: string): boolean =>
+            TobagoFilterRegistry.localeContains(item, search, true)
+    );
+
+    /**
+     * Filter for "containsExact" search.
+     */
+    TobagoFilterRegistry.set("containsExact",
+        (item: string, search: string): boolean =>
+            item.indexOf(search) >= 0
+    );
+
+    /**
+     * Filter for "startsWithExact" search.
+     */
+    TobagoFilterRegistry.set("startsWithExact",
+        (item: string, search: string): boolean =>
+            item.indexOf(search) == 0
+    );
+
+  }
+
+  static set(key: string, value: any): void {
+    this.map.set(key, value);
+  }
+
+  static get(key: string): any {
+    const value = this.map.get(key);
+    if (value) {
+      return value;
+    } else {
+      console.warn("TobagoFilterRegistry.get(" + key + ") = undefined");
+      return null;
+    }
+  }
+
+  private static localeContains(item: string, search: string, startOnly: boolean) {
+    item = item.normalize();
+    search = search.normalize();
+
+    const searchLength = search.length;
+    const diffLength = startOnly ? 0 : item.length - searchLength;
+
+    // console.log("a", item);
+    // console.log("b", search);
+    // console.log("diffLength", diffLength);
+
+    for (let i = 0; i <= diffLength; i++) {
+      // console.log("i", i);
+      const s = item.substring(i, i + searchLength);
+      // console.log("s", s);
+      if (s
+          .localeCompare(search, window.navigator.language, {sensitivity: "base"}) === 0) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+}
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts
new file mode 100644
index 0000000000..fdc7a4cae5
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-many-list.ts
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {TobagoFilterRegistry} from "./tobago-filter-registry";
+import {createPopper, Instance} from "@popperjs/core";
+
+class SelectManyList extends HTMLElement {
+  private popper: Instance;
+
+  private readonly CssClass = {
+    DROPDOWN_MENU: "dropdown-menu",
+    SHOW: "show",
+    TABLE_ACTIVE: "table-active",
+    TABLE_PRIMARY: "table-primary",
+    TOBAGO_DISABLED: "tobago-disabled",
+    TOBAGO_FOCUS: "tobago-focus",
+    TOBAGO_MARK: "tobago-mark",
+    TOBAGO_OPTIONS: "tobago-options"
+  };
+
+  private readonly Key = {
+    ARROW_DOWN: "ArrowDown",
+    ARROW_UP: "ArrowUp",
+    ENTER: "Enter",
+    ESCAPE: "Escape",
+    SPACE: " "
+  };
+
+  constructor() {
+    super();
+  }
+
+  get hiddenSelect(): HTMLSelectElement {
+    return this.querySelector("select");
+  }
+
+  get selectField(): HTMLDivElement {
+    return this.querySelector(".tobago-select-field");
+  }
+
+  get badgeCloseButtons(): NodeListOf<HTMLButtonElement> {
+    return this.selectField.querySelectorAll("button.btn.badge");
+  }
+
+  get filter(): string {
+    return this.getAttribute("filter");
+  }
+
+  get filterInput(): HTMLInputElement {
+    return this.querySelector(".tobago-filter");
+  }
+
+  get dropdownMenu(): HTMLDivElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(`.dropdown-menu[name='${this.id}']`);
+  }
+
+  get menuStore(): HTMLDivElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(".tobago-page-menuStore");
+  }
+
+  get tbody(): HTMLElement {
+    const root = this.getRootNode() as ShadowRoot | Document;
+    return root.querySelector(`.tobago-options[name='${this.id}'] tbody`);
+  }
+
+  get enabledRows(): NodeListOf<HTMLTableRowElement> {
+    return this.tbody.querySelectorAll<HTMLTableRowElement>("tr:not(.tobago-disabled)");
+  }
+
+  get markedRow(): HTMLTableRowElement {
+    return this.tbody.querySelector<HTMLTableRowElement>("." + this.CssClass.TOBAGO_MARK);
+  }
+
+  connectedCallback(): void {
+    if (this.dropdownMenu) {
+      this.popper = createPopper(this.selectField, this.dropdownMenu, {});
+      window.addEventListener("resize", this.resizeEvent.bind(this));
+    }
+    document.addEventListener("click", this.clickEvent.bind(this));
+    this.filterInput.addEventListener("focus", this.focusEvent.bind(this));
+    this.filterInput.addEventListener("blur", this.blurEvent.bind(this));
+    this.selectField.addEventListener("keydown", this.keydownEvent.bind(this));
+
+    // init badges
+    this.querySelectorAll("option:checked").forEach(
+      option => this.sync(<HTMLOptionElement>option)
+    );
+
+    this.initList();
+
+    // init filter
+    if (this.filter) {
+      const input = this.filterInput;
+      input.addEventListener("keyup", this.filterEvent.bind(this));
+    }
+
+    // handle autofocus; trigger focus event
+    if (document.activeElement.id === this.filterInput.id) {
+      this.focusEvent();
+    }
+  }
+
+  select(event: MouseEvent): void {
+    const target = <HTMLElement>event.target;
+    const row = target.closest("tr");
+    this.selectRow(row);
+  }
+
+  selectRow(row: HTMLTableRowElement): void {
+    const itemValue = row.dataset.tobagoValue;
+    console.info("itemValue", itemValue);
+    const select = this.hiddenSelect;
+    const option: HTMLOptionElement = select.querySelector(`[value="${itemValue}"]`);
+    option.selected = !option.selected;
+    this.sync(option);
+  }
+
+  removeBadge(event: MouseEvent): void {
+    const target = <HTMLElement>event.target;
+    const group: HTMLElement = target.closest(".btn-group");
+    const itemValue = group.dataset.tobagoValue;
+    const select = this.hiddenSelect;
+    const option: HTMLOptionElement = select.querySelector(`[value="${itemValue}"]`);
+    option.selected = false;
+    this.sync(option);
+  }
+
+  sync(option: HTMLOptionElement) {
+    const itemValue = option.value;
+    const row: HTMLTableRowElement = this.tbody.querySelector(`[data-tobago-value="${itemValue}"]`);
+    if (option.selected) {
+      // create badge
+      const tabIndex: number = this.filterInput.tabIndex;
+      this.filterInput.insertAdjacentHTML("beforebegin",
+        this.getRowTemplate(itemValue, row.innerText, option.disabled || this.hiddenSelect.disabled, tabIndex));
+
+      // todo: nicer adding the @click with lit-html
+      const closeButton = this.selectField
+        .querySelector(".btn-group[data-tobago-value='" + itemValue + "'] button.btn.badge");
+      closeButton?.addEventListener("click", this.removeBadge.bind(this));
+      closeButton?.addEventListener("focus", this.focusEvent.bind(this));
+      closeButton?.addEventListener("blur", this.blurEvent.bind(this));
+
+      // highlight list row
+      row.classList.add(this.CssClass.TABLE_PRIMARY);
+    } else {
+      // remove badge
+      const badge = this.selectField.querySelector(`[data-tobago-value="${itemValue}"]`);
+      const previousBadge = badge.previousElementSibling;
+      const nextBadge = badge.nextElementSibling.tagName === "SPAN" ? badge.nextElementSibling : null;
+      badge.remove();
+      if (previousBadge) {
+        previousBadge.querySelector<HTMLButtonElement>("button.btn.badge").focus();
+      } else if (nextBadge) {
+        nextBadge.querySelector<HTMLButtonElement>("button.btn.badge").focus();
+      } else {
+        this.filterInput.disabled = false;
+        this.filterInput.focus();
+      }
+
+      // remove highlight list row
+      row.classList.remove(this.CssClass.TABLE_PRIMARY);
+    }
+
+    if (!this.classList.contains(this.CssClass.TOBAGO_DISABLED) && !this.filter) {
+      // disable input field to prevent focus.
+      if (this.badgeCloseButtons.length > 0 && this.filterInput.id === document.activeElement.id) {
+        this.badgeCloseButtons.item(this.badgeCloseButtons.length - 1).focus();
+      }
+      this.filterInput.disabled = this.badgeCloseButtons.length > 0;
+    }
+  }
+
+  getRowTemplate(value: string, text: string, disabled: boolean, tabIndex: number): string {
+    return disabled ? `
+<span class="btn-group" role="group" data-tobago-value="${value}">
+  <tobago-badge class="badge text-bg-primary btn disabled">${text}</tobago-badge>
+</span>` : `
+<span class="btn-group" role="group" data-tobago-value="${value}">
+  <tobago-badge class="badge text-bg-primary btn">${text}</tobago-badge>
+  <button type='button' class='tobago-button btn btn-secondary badge'
+  ${tabIndex > 0 ? " tabindex='" + String(tabIndex) + "'" : ""}><i class='bi-x-lg'></i></button>
+</span>`;
+  }
+
+  filterEvent(event: Event): void {
+    const input = event.currentTarget as HTMLInputElement;
+    const searchString = input.value;
+    console.info("searchString", searchString);
+    const filterFunction = TobagoFilterRegistry.get(this.filter);
+    // XXX todo: if filterFunction not found?
+    if (filterFunction != null) {
+      this.querySelectorAll("tr").forEach(row => {
+        const itemValue = row.dataset.tobagoValue;
+        if (filterFunction(itemValue, searchString)) {
+          row.classList.remove("d-none");
+        } else {
+          row.classList.add("d-none");
+        }
+      });
+    }
+  }
+
+  private clickEvent(event: MouseEvent): void {
+    if (this.isDeleted(event.target as Element)) {
+      // do nothing, this is probably a removed badge
+    } else if (this.isPartOfSelectField(event.target as Element)
+      || this.isPartOfTobagoOptions(event.target as Element)) {
+
+      if (!this.filterInput.disabled) {
+        this.filterInput.focus();
+      } else if (this.badgeCloseButtons.length > 0) {
+        this.badgeCloseButtons[0].focus();
+      }
+
+    } else {
+      this.hideDropdown();
+      this.setFocus(false);
+    }
+  }
+
+  private keydownEvent(event: KeyboardEvent) {
+    switch (event.key) {
+      case this.Key.ESCAPE:
+        this.hideDropdown();
+        this.removeTableRowMark();
+        break;
+      case this.Key.ARROW_DOWN:
+        event.preventDefault();
+        this.showDropdown();
+        this.markNextTableRow();
+        break;
+      case this.Key.ARROW_UP:
+        event.preventDefault();
+        this.showDropdown();
+        this.markPreviousTableRow();
+        break;
+      case this.Key.ENTER:
+      case this.Key.SPACE:
+        if (this.markedRow) {
+          event.preventDefault();
+          this.selectMarkedOption();
+        } else if (document.activeElement.id === this.filterInput.id) {
+          this.showDropdown();
+        }
+        break;
+    }
+  }
+
+  private markNextTableRow(): void {
+    const rows = this.enabledRows;
+    const indexOfMark = this.indexOfTobagoMark(rows);
+    if (indexOfMark >= 0) {
+      if (indexOfMark + 1 < rows.length) {
+        rows.item(indexOfMark).classList.remove(this.CssClass.TOBAGO_MARK);
+        this.addTableRowMark(rows.item(indexOfMark + 1));
+      } else {
+        rows.item(rows.length - 1).classList.remove(this.CssClass.TOBAGO_MARK);
+        this.addTableRowMark(rows.item(0));
+      }
+    } else if (rows.length > 0) {
+      this.addTableRowMark(rows.item(0));
+    }
+  }
+
+  private markPreviousTableRow(): void {
+    const rows = this.enabledRows;
+    const indexOfMark = this.indexOfTobagoMark(rows);
+    if (indexOfMark >= 0) {
+      if ((indexOfMark - 1) >= 0) {
+        rows.item(indexOfMark).classList.remove(this.CssClass.TOBAGO_MARK);
+        this.addTableRowMark(rows.item(indexOfMark - 1));
+      } else {
+        rows.item(0).classList.remove(this.CssClass.TOBAGO_MARK);
+        this.addTableRowMark(rows.item(rows.length - 1));
+      }
+    } else if (rows.length > 0) {
+      this.addTableRowMark(rows.item(rows.length - 1));
+    }
+  }
+
+  private addTableRowMark(row: HTMLTableRowElement): void {
+    row.classList.add(this.CssClass.TOBAGO_MARK);
+    if (!this.dropdownMenu) {
+      row.scrollIntoView({block: "center"});
+    }
+  }
+
+  private removeTableRowMark(): void {
+    this.markedRow?.classList.remove(this.CssClass.TOBAGO_MARK);
+  }
+
+  private selectMarkedOption(): void {
+    const row = this.tbody.querySelector<HTMLTableRowElement>("." + this.CssClass.TOBAGO_MARK);
+    this.selectRow(row);
+  }
+
+  private indexOfTobagoMark(rows: NodeListOf<HTMLTableRowElement>): number {
+    for (let i = 0; i < rows.length; i++) {
+      if (rows.item(i).classList.contains(this.CssClass.TOBAGO_MARK)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  private isPartOfSelectField(element: Element): boolean {
+    if (element) {
+      if (this.selectField.id === element.id) {
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfSelectField(element.parentElement) : false;
+      }
+    } else {
+      return false;
+    }
+  }
+
+  private isPartOfTobagoOptions(element: Element): boolean {
+    if (element) {
+      if (element.classList.contains(this.CssClass.TOBAGO_OPTIONS)
+        && this.id === element.getAttribute("name")) {
+        return true;
+      } else {
+        return element.parentElement ? this.isPartOfTobagoOptions(element.parentElement) : false;
+      }
+    } else {
+      return false;
+    }
+  }
+
+  private isDeleted(element: Element): boolean {
+    return element.closest("html") === null;
+  }
+
+  private showDropdown(): void {
+    if (this.dropdownMenu && !this.dropdownMenu.classList.contains(this.CssClass.SHOW)) {
+      this.selectField.classList.add(this.CssClass.SHOW);
+      this.selectField.ariaExpanded = "true";
+      this.dropdownMenu.classList.add(this.CssClass.SHOW);
+      this.updateDropdownMenuWidth();
+      this.popper.update();
+    }
+  }
+
+  private hideDropdown(): void {
+    if (this.dropdownMenu?.classList.contains(this.CssClass.SHOW)) {
+      this.selectField.classList.remove(this.CssClass.SHOW);
+      this.selectField.ariaExpanded = "false";
+      this.dropdownMenu.classList.remove(this.CssClass.SHOW);
+    }
+  }
+
+  private resizeEvent(event: UIEvent): void {
+    this.updateDropdownMenuWidth();
+  }
+
+  private updateDropdownMenuWidth(): void {
+    if (this.dropdownMenu) {
+      this.dropdownMenu.style.width = `${this.selectField.offsetWidth}px`;
+    }
+  }
+
+  private focusEvent(): void {
+    if (!this.hiddenSelect.disabled) {
+      if (!this.classList.contains(this.CssClass.TOBAGO_FOCUS)) {
+        this.setFocus(true);
+        this.showDropdown();
+      }
+    }
+  }
+
+  private blurEvent(event: FocusEvent): void {
+    if (event.relatedTarget !== null) {
+      //relatedTarget is the new focused element; null indicate a mouseclick or an inactive browser window
+      if (!this.isPartOfSelectField(event.relatedTarget as Element)
+        && !this.isPartOfTobagoOptions(event.relatedTarget as Element)) {
+        this.setFocus(false);
+        this.hideDropdown();
+      }
+    }
+  }
+
+  private setFocus(focus: boolean): void {
+    if (focus) {
+      this.classList.add(this.CssClass.TOBAGO_FOCUS);
+    } else {
+      this.classList.remove(this.CssClass.TOBAGO_FOCUS);
+    }
+  }
+
+  private initList() {
+    const tbody = this.tbody;
+    tbody.addEventListener("click", this.select.bind(this));
+    tbody.querySelectorAll("tr").forEach((row: HTMLTableRowElement) => {
+      // row stuff
+    });
+  }
+}
+
+document.addEventListener("tobago.init", function (event: Event): void {
+  if (window.customElements.get("tobago-select-many-list") == null) {
+    window.customElements.define("tobago-select-many-list", SelectManyList);
+  }
+});