feat(core): added SelectionController#6487
Conversation
🦋 Changeset detectedLatest commit: b2f34c7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Coverage Report for CI Build 29260633738Warning No base build found for commit Coverage: 96.254%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats💛 - Coveralls |
…e/spectrum-web-components into nikkimk/simple-selection-controller
📚 Branch Preview Links🔍 Gen1 Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
…ate branch Split off to nikkimk/tabs-focusgroup-refactor: this branch keeps only SelectionController and its Tabs click/Enter-Space/selected-property integration. Tabs' arrow-key/Home/End navigation reverts to the hand-rolled implementation, and FocusgroupNavigationController's `reason` field addition (needed for that refactor, not for this one) reverts along with it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rajdeepc
left a comment
There was a problem hiding this comment.
Nice work! Before merging, I would love to align on scope coz this is a fairly general abstraction built for one consumer (Tabs), since accordion/menu/picker are explicitly deferred to LiveSelectionController.
Also we are not validating against a second real use case yet.
My recommendation on this PR would be to ship a smaller v1 scoped to what Tabs needs (single mode + keydownActivation + confirmSelectionChange), and add multiple/single-toggle/selectAll/isSelectable back in once a second consumer actually needs them.
| * @see https://www.w3.org/WAI/ARIA/apg/patterns/listbox/ | ||
| * @see https://www.w3.org/WAI/ARIA/apg/patterns/radio/ | ||
| */ | ||
| export class SelectionController implements ReactiveController { |
There was a problem hiding this comment.
Given Tabs is the only consumer today, what do you think about trimming this to a smaller first version and adding multiple, single-toggle, selectAll/clearAll, and isSelectable later, once there's a real second consumer to design against?
There was a problem hiding this comment.
Good call on multiple/selectAll. I split those out to a separate branch (nikkimk/selection-controller-multiple-mode), so this PR is now single-mode + single-toggle only, no selectAll/clearAll-for-multiple. I fixed PR description to match the components that this controller is for now that we've separated out the controller into two separate controllers.
On single-toggle and isSelectable specifically: I'd like to keep those in this PR. single-toggle is a real, distinct interaction (optional single choice vs. mandatory), not a speculative extra, and we will need it soon as we do more radio selectable items. isSelectable is a zero-cost override (unused by Tabs, no code path exercised unless a consumer supplies it).
| private getScopedRawItems(): HTMLElement[] { | ||
| return this.options | ||
| .getItems() | ||
| .filter((el) => this.isNodeWithinHostScope(el)); | ||
| } |
There was a problem hiding this comment.
This still walks the DOM (isNodeWithinHostScope) for every item on every click/keydown and that's the O(n) cost the comment above says we're avoiding for large lists (the picker/virtualized case mentioned in the isSelectable doc).
| * hand-rolled in this class (see `focusByDelta` / `updateRovingTabindex`); | ||
| * this controller only owns click and Enter/Space activation. | ||
| */ | ||
| private readonly _selection = new SelectionController(this, { |
There was a problem hiding this comment.
This gives us a second state container (_selection's internal set) alongside this.selected, synced in both directions (_syncSelectionController down, onSelectionChange up), plus the selectTarget escape hatch for markup-seeded tabs. Compared to the updateCheckedState this replaces, I'm not sure Tabs itself gets simpler. Is this payoff for future customers apart from Tabs? Is that what you are thinking here?
| private readonly _selection = new SelectionController(this, { | ||
| getItems: () => this._tabs as HTMLElement[], | ||
| selectItem: (item) => { | ||
| (item as TabLike).selected = true; |
There was a problem hiding this comment.
Please help me understand here. SelectionControllers model assumes items never change their own selected state independently but here TabLike.selected is still a public property and it can be overriden externally by tab.selected and this will desync the controller's cache.
Would you be okay to write a test for this?
| @@ -0,0 +1,9 @@ | |||
| --- | |||
| '@adobe/spectrum-wc': patch | |||
There was a problem hiding this comment.
I would point this to minor. :)
…branch Split off to nikkimk/selection-controller-multiple-mode: this branch keeps only `single` and `single-toggle` modes. `multiple` mode touched the controller's core transition logic (not just a bolt-on demo), so removing it also removes the mode-switch normalization in setOptions() (it only ever mattered for the multiple-to-single-item transition, which can no longer happen) and selectAll(), which was multiple-only. - src/selection-controller.ts: SelectionMode narrows to 'single' | 'single-toggle'; removed selectAll() and the multiple-mode branches in setSelectedItem()/toggleItem()/applyClickOrKey(); removed the now provably-dead setOptions() mode-switch collapse (selectedItems can never exceed one item without multiple mode). - stories/demo-hosts.ts: removed the DemoSelectionFilterTags (multiple mode) demo; DemoSelectionModeSwitcher now switches only between single and single-toggle; DemoSelectionEligibility moved from multiple to single-toggle mode (unrelated to what it tests - eligibility exclusion - but needed a mode that still exists). - stories.ts / test.ts: removed the MultipleMode story/test; rewrote ModeSwitchingTest to demonstrate the single vs single-toggle behavioral difference instead of the removed multi-select collapse. - selection-controller.mdx: removed the Multiple mode section and all now-inaccurate mentions of multiple/selectAll in the API reference. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Bump changeset from patch to minor. - Fix the O(n) host-scope walk on the click/keydown hot path: handleClickCapture/handleKeyDownCapture now search the consumer's raw, unfiltered item list for the composedPath hit, then defer the isNodeWithinHostScope check to that single resolved item, instead of scope-filtering every item on every click/keydown regardless of whether it hits a participant. Matches the O(1)-on-the-hit intent the neighboring isSelectableItem comment already documented. - Add a Tabs test (ExternalTabSelectedMutationTest) documenting that a tab's own `selected` property is still publicly settable, and that setting it directly bypasses `_selection` and desyncs from `tabs.selected` until the next resync (any `_syncSelectionController` trigger forces every scoped item back in line via a full mutator re-scan, not just the item that changed). `multiple` mode (the other half of the scope-reduction ask) was already split into nikkimk/selection-controller-multiple-mode before this PR was opened. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Since we have the live selection controller and a use case for it, I'm going to pause work on this one until we have a use case that doesn't require live selection. |
Description
swc-tabsto leverage selection controller.Motivation and context
This controller could handle selection for radio groups, and segmented controls.
Related issue(s)
Screenshots (if appropriate)
Author's checklist
Reviewer's checklist
patch,minor, ormajorfeaturesManual review test cases
Selection controller
Tabs
Is the creation and implementation of this controller useful for tabs, accordions, radio groups, swatch groups with selectable swatches, button groups that function as radio groups, menus, and pickers? Or is it easier just to let each custom element handle its own selection management?
Device review