1 Commits
Author SHA1 Message Date
TheWitness 2e05b98b1f Add select2 for enhanced/searchable dropdowns (#8051)
* feat: add select2 for enhanced/searchable dropdowns

Vendors select2 4.1.0 (MIT, https://select2.org) as a new front-end
dependency, following this branch's existing npm-synced-to-include/js
convention:

- package.json: added "select2": "4.1.0" to dependencies (alongside the
  other npm-tracked front-end libs, so Dependabot can track it).
- build/sync-js.mjs: added node_modules/select2/dist/js/select2.full.js ->
  include/js/select2.js to the asset map (the "full" build bundles its
  jquery-mousewheel dependency, so no extra package is needed).
- include/js/select2.js: the synced output (select2.full.js, unminified,
  matching this repo's "vendor unminified where possible" convention).

CSS, following the existing per-theme vendor-CSS convention (jquery-ui.css,
jquery.multiselect.css, etc. - none of which are npm-synced either):

- include/themes/<theme>/select2.css: the upstream select2.css (structural/
  functional rules), copied unmodified into all 10 themes.
- lib/html.php: wired both the JS and (per-theme) CSS into the page header,
  next to the other jQuery UI plugin includes.
- include/themes/<theme>/main.css: since Select2 4.x publishes no jQuery UI
  theme/adapter, appended a small override block per theme (widget border/
  background/text, hover, and active/highlighted colors) reusing each
  theme's own jquery-ui.css/.ui-state-active colors, so Select2 dropdowns
  match that theme's native jQuery UI look instead of Select2's generic
  default styling.

* feat: initialize select2() widgets in applySkin()

Wires up the select2/select2-nosearch/select2-tags/select2-multi/
select2-multi-tags/select2-callback marker classes so pages can opt in
to a Select2-enhanced dropdown just by adding the class, matching the
existing markup-driven convention used by other jQuery UI widget
setup in this function.

Dialog-hosted selects pass dropdownParent so the dropdown attaches to
the ui-dialog widget instead of escaping under it (a well-known
Select2 + jQuery UI dialog z-index/stacking issue).

* fix: remove accidental theme:'bootstrap' from select2-multi init

* fix: address Copilot review findings on the select2 PR

- package-lock.json: added the select2@4.1.0 entry (registry resolved URL +
  integrity hash) so `npm ci` no longer rejects the package.json/lockfile
  mismatch.
- .gitignore / include/js/select2.js: select2.js is a generated npm-synced
  build output like the other managed libraries, so it belongs in the
  generated-asset ignore list, not committed. Untracked the copy that had
  been committed; `npm run build:js` regenerates it locally.
- build/README.md: documented select2 among the npm-managed libraries, and
  clarified that the per-theme select2.css copies are committed unmodified
  upstream CSS (unlike jquery-ui/jstree's Cacti-owned theme CSS).
- include/themes/paper-plane/main.css: highlighted/focused Select2 options
  were the same white as the dropdown background, giving no visible focus
  indicator; changed to the theme's own hover color (#dadada / #212121).
- include/themes/sunrise/main.css: same issue; changed to this theme's
  established menu focus color (#3C8A3C, matching .ui-menu .ui-state-focus)
  with white text.

* fix: address 3 material Copilot review findings on select2 init

- Exclude the select2/select2-nosearch/select2-tags/select2-multi/
  select2-multi-tags/select2-callback marker classes from
  setSelectMenus()'s generic $('select')... loop, so Select2-marked
  single-selects are no longer first hidden/wrapped by jQuery UI
  Selectmenu before Select2 wraps them again.
- select2-callback: Select2 calls the ajax.url callback with a context
  that is not the original <select>, so $(this).data('callback') read
  undefined inside it. Capture the element's callback URL into a local
  variable before calling .select2() and use that instead.
- select2-callback: it was the only initializer that never checked for a
  jQuery UI dialog ancestor, so its dropdown always appended to body and
  could be hidden behind a dialog's overlay. Set dropdownParent the same
  way the other five initializers do.

* fix: proactively apply 2 material findings from the 1.2.x PR review

Copilot's review of the companion 1.2.x PR (#8052) found 2 bugs in this
same select2-init code that also apply here unchanged:

- Select2 renders its replacement as <span class="select2 select2-container">,
  so the plain class selectors (.select2, .select2-nosearch, etc.) also
  matched Select2's own generated wrappers on every later applySkin() call
  (e.g. during AJAX navigation), re-initializing and nesting wrappers.
  Scoped all six selectors to `select.<marker-class>` so only the original
  <select> elements are ever matched.
- select2-callback's ajax response was never normalized: Cacti's existing
  callback endpoints (e.g. lib/auth.php) return a plain array of
  {label, value, id} objects, but Select2 requires {results: [{id, text}]}.
  Added a processResults mapping so callback-backed dropdowns actually
  populate.
- select2-callback now also sets dropdownParent unconditionally (dialog
  ancestor, or document.body otherwise) instead of leaving it unset outside
  dialogs.
2026-09-22 08:27:00 -04:00