feat(select,tree-select): master checkbox "select all" (#DS-3969) - #1879
Conversation
Adds a `selectAll` input that renders the "select all" row above the options — until now the master checkbox only existed hand-rolled inside the filter-bar pipes. The row acts on the options the user can actually toggle, and its tri-state is derived from the very same set, so the checkbox and the toggle can never disagree. Under an active search that means the matches only; for the tree it is the whole data set (collapsed branches included) without a query, and the visible nodes with one. Ctrl/Cmd + A becomes a two-way toggle while `selectAll` is on, and both entry points emit a single `selectionChange` for the whole batch, followed by `onSelectAll`. Note: `KbqTreeSelection.selectAllOptions` now leaves out the nodes a filter hides, and the default Ctrl/Cmd + A handler of `kbq-select` no longer sweeps up non-selectable options.
A follow-up review of the select-all master checkbox (added in de0893f) found several correctness and accessibility gaps: the row went keyboard-inert whenever it held real DOM focus (KbqOption swallowed Space/Enter before the panel's toggle handler ever saw it), virtual scroll and preselected values let it silently select only a fraction of the data while still showing "checked", and the tree's row ignored the component's own `disabled` state. The keyboard fix also repairs the identical latent bug in filter-bar's pipe rows, which share the same primitive. Also fixes a locale-lookup crash on custom locale data, a stale onSelectAll JSDoc, a shouldShowSearch() render race in tree-select, and adds role="checkbox"/aria-checked to the row so screen readers can announce its state. Adds Playwright coverage (including two real-browser keyboard regression tests) and ~20 Jest cases for the gaps above: standalone KbqTreeSelection usage, disabled nodes, indeterminate+allowDeselect, Ctrl/Cmd+A search-text selection, and keyboard boundaries. Splits the single combined docs example into basic/search/disabled variants for both select and tree-select, and expands the "Master checkbox" section to spell out the tri-state behaviour and indeterminate-click semantics.
|
Visit the preview URL for this PR (updated for commit cd23578): https://koobiq-next--prs-1879-0zbudodw.web.app (expires Sat, 15 Aug 2026 13:28:12 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c |
🚨 E2E tests failedReview the report for details. 💡 Comment |
There was a problem hiding this comment.
Pull request overview
This PR introduces a built-in “Select all” master checkbox row for both kbq-select and kbq-tree-select/kbq-tree-selection, aligning click and Ctrl/Cmd+A behavior, and adding locale support, documentation, examples, and test coverage.
Changes:
- Added
selectAllinputs and master checkbox rows (with tri-state visual state) toKbqSelectandKbqTreeSelection, plus unified keyboard toggling behavior. - Introduced shared core utilities/types for select-all state (
getSelectAllState) and addedselect.selectAllto locales. - Added/updated unit tests, Playwright e2e coverage, and docs/examples for select-all scenarios (default, search-filtered, disabled items).
Reviewed changes
Copilot reviewed 42 out of 42 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tools/public_api_guard/components/tree.api.md | Public API snapshot updates for tree select-all support. |
| tools/public_api_guard/components/tree-select.api.md | Public API snapshot updates for tree-select selectAll input. |
| tools/public_api_guard/components/select.api.md | Public API snapshot updates for select selectAll input and new APIs. |
| tools/public_api_guard/components/core.api.md | Public API snapshot updates for locale/type + getSelectAllState. |
| tools/cspell-locales/ru.json | Adds RU spelling entry used in docs (“виртуализированный”). |
| packages/e2e/routes.ts | Registers new select/tree-select e2e routes. |
| packages/docs-examples/example-module.ts | Adds new docs examples to examples registry + lazy imports. |
| packages/docs-examples/components/tree-select/tree-select-select-all/tree-select-select-all-example.ts | New tree-select “select all” example. |
| packages/docs-examples/components/tree-select/tree-select-select-all-search/tree-select-select-all-search-example.ts | New tree-select “select all + search” example. |
| packages/docs-examples/components/tree-select/tree-select-select-all-disabled/tree-select-select-all-disabled-example.ts | New tree-select “select all + disabled nodes” example. |
| packages/docs-examples/components/tree-select/index.ts | Exports/registers new tree-select examples. |
| packages/docs-examples/components/select/select-select-all/select-select-all-example.ts | New select “select all” example. |
| packages/docs-examples/components/select/select-select-all-search/select-select-all-search-example.ts | New select “select all + search” example. |
| packages/docs-examples/components/select/select-select-all-disabled/select-select-all-disabled-example.ts | New select “select all + disabled options” example. |
| packages/docs-examples/components/select/index.ts | Exports/registers new select examples. |
| packages/components/tree/tree-selection.component.ts | Implements tree “select all” row, state, locale label integration. |
| packages/components/tree/tree-selection.component.spec.ts | Unit tests for tree selection select-all behavior (incl. filtering/disabled/keyboard). |
| packages/components/tree/tree-option.component.ts | Adds selectAllRow handling and level override for synthetic row. |
| packages/components/tree-select/tree-select.ru.md | Documents tree-select master checkbox behavior (RU). |
| packages/components/tree-select/tree-select.en.md | Documents tree-select master checkbox behavior (EN). |
| packages/components/tree-select/tree-select.component.ts | Wires selectAll input into underlying tree + emits onSelectAll. |
| packages/components/tree-select/tree-select.component.spec.ts | Unit tests for tree-select selectAll rendering/state/toggling/keyboard. |
| packages/components/tree-select/e2e.ts | Adds e2e component for tree-select select-all states. |
| packages/components/tree-select/e2e.playwright-spec.ts | Playwright coverage for tree-select select-all visuals + keyboard regression. |
| packages/components/select/select.ru.md | Documents select master checkbox behavior (RU). |
| packages/components/select/select.html | Renders select-all row in the panel template. |
| packages/components/select/select.en.md | Documents select master checkbox behavior (EN). |
| packages/components/select/select.component.ts | Implements select-all row, navigation list, locale label, and batch propagation. |
| packages/components/select/select.component.spec.ts | Unit tests for select selectAll rendering/state/toggling/keyboard + unsupported modes. |
| packages/components/select/e2e.ts | Adds e2e component for select select-all states. |
| packages/components/select/e2e.playwright-spec.ts | Playwright coverage for select select-all visuals + keyboard regression. |
| packages/components/core/selection/select-all.ts | Adds getSelectAllState + strengthens adapter contract docs. |
| packages/components/core/selection/select-all.spec.ts | Unit tests for getSelectAllState and toggle behavior. |
| packages/components/core/option/option.ts | Adjusts keydown handling to not swallow Enter/Space on disabled/non-selectable options. |
| packages/components/core/locales/types.ts | Adds KbqSelectLocaleConfiguration type (shared select/tree selectAll label). |
| packages/components/core/locales/tk-TM.ts | Adds select.selectAll to Turkmen locale. |
| packages/components/core/locales/ru-RU.ts | Adds select.selectAll to Russian locale. |
| packages/components/core/locales/pt-BR.ts | Adds select.selectAll to Portuguese (BR) locale. |
| packages/components/core/locales/es-LA.ts | Adds select.selectAll to Spanish (LA) locale. |
| packages/components/core/locales/en-US.ts | Adds select.selectAll to English (US) locale. |
| packages/components-dev/tree-select/module.ts | Adds new tree-select dev examples to dev page. |
| packages/components-dev/select/module.ts | Adds new select dev examples to dev page. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
/approve-snapshots |
|
🔄 Updating snapshots. |
|
✅ Snapshots updated! |
Reshapes the "Select all" section to the agreed layout: a short intro stating the feature is off by default, the plain example, and then a second one showing that the control can display a label of its own once everything is selected instead of listing every selected value. The per-behaviour "with search" / "with disabled" examples are dropped — that behaviour is described in prose instead, so the section stays as short as the design calls for. The label example projects `kbq-select-trigger` only while `allOptionsSelected` is `true`, so the control falls back to its default trigger the moment the selection becomes partial. Doing so surfaced a real defect: `KbqTreeSelection.allOptionsSelected` and `selectAllState` threw a TypeError when read before the `treeControl` input had been assigned — which is exactly what a template reference in the trigger does on the pass that assigns it. They now report an empty state instead, with a regression test covering it.
…idth
The label examples projected the custom trigger through an `<ng-container>`, which produces no
DOM element — so the `kbq-select-trigger { width: 100% }` rule in _select.scss had nothing to
apply to. The label and the chevron ended up bunched together on the left with ~210px of dead
space to the right of the arrow, instead of label-left / arrow-right.
Using the element form fixes it: measured in both dev apps, the arrow now sits flush against
the right edge of the matcher (0px of trailing space) in every state — custom label, default
fallback after a partial selection, and back — matching the untouched reference examples.
Spells the requirement out in the prose too, since projecting an `ng-container` here is an easy
and silent mistake to repeat.
The infix drops its left padding for a non-empty multiple select because the tags carry an indent of their own. A projected `kbq-select-trigger` replaces the tag list outright, so nothing was left to supply it and the label sat 6px tighter to the border than a single select's value — visible in the new "select all" label examples. Restores the standard infix padding for that case, leaving the tag and placeholder paths untouched. Measured across both dev apps, every trigger now starts 12px from the control border: placeholder, tags, single, multiline matcher, and the custom trigger alike.
Addresses the Copilot review comment on #1879. `localeService.changes` is a BehaviorSubject on a root-provided service, so subscribing without a teardown kept every destroyed select reachable and let `updateLocaleParams` run — markForCheck-ing a dead view — on the next locale switch. `KbqSelect` was the only component in the repo missing the operator; the other 13 call sites, including `KbqTreeSelect` and `KbqTreeSelection`, already pipe through `takeUntilDestroyed`. The line predates this branch, but it sits in the block this PR touches and the fix is a one-liner, so it lands here rather than as a follow-up. Covered by a test that asserts `getParams('select')` is no longer called after destroy; reverting the operator makes it fail.
| } | ||
|
|
||
| /** Rebuilds the key manager's list: the "select all" row first, then the projected options. */ | ||
| private syncNavigableOptions(): void { |
There was a problem hiding this comment.
При открытии с клавиатуры activeItem - всегда второй элемент списка (не "выбрать все"). В tree-select такой проблемы нет
Screen.Recording.2026-08-12.at.10.11.48.mov
The row is created together with the overlay, so the highlight `openPanel()` applies is computed against a navigable list that does not hold it yet and comes to rest on the option below it. Re-apply the highlight the moment the row joins the list, from whichever of the view query and `options.changes` brings it in.
`selectAllState` re-filters the whole option list on every read, and the master checkbox row read it three times plus `allOptionsSelected` — which is the same walk asking whether the state is `checked`. Hoist it into a `@let` and derive the selected class from it.
No description provided.