fix(Toolbar): forward disabled to ToolbarButton + reactive focusable count#2735
Conversation
…count ToolbarToggleItem was not passing its disabled prop to ToolbarButton, causing RovingFocusItem to always register as focusable. This made ToolbarRoot tabbable even when all toggle items were disabled. Additionally, RovingFocusItem only updated focusableItemsCount on mount/unmount, so dynamic disabled changes were not reflected reactively. Fixes: - ToolbarToggleItem forwards disabled to ToolbarButton - RovingFocusItem watches focusable prop and updates count reactively - Added tests for all-disabled and mixed-disabled toolbar scenarios Closes unovue#2728
📝 WalkthroughWalkthrough
ChangesDisabled ToolbarToggleItem tabindex fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/core/src/Toolbar/Toolbar.test.ts (1)
32-96: ⚡ Quick winConsider adding a test for dynamic disabled state changes.
The current tests verify the static/initial disabled state, which covers the primary bug. To fully validate the reactive watcher added to
RovingFocusItem, consider adding a test that:
- Mounts a toolbar with enabled items
- Programmatically changes items to disabled after mount
- Verifies the toolbar root's
tabindexupdates to"-1"reactivelyThis would increase confidence that the watcher correctly handles runtime state changes.
🧪 Example test structure
it('should update tabindex reactively when items are disabled after mount', async () => { const TestComponent = defineComponent({ components: { ToolbarRoot, ToolbarToggleGroup, ToolbarToggleItem }, setup() { const model = ref([]) const allDisabled = ref(false) return { model, allDisabled } }, template: ` <ToolbarRoot aria-label="Test toolbar"> <ToolbarToggleGroup v-model="model" type="multiple"> <ToolbarToggleItem value="bold" :disabled="allDisabled">Bold</ToolbarToggleItem> </ToolbarToggleGroup> </ToolbarRoot> `, }) const wrapper = mount(TestComponent, { attachTo: document.body }) await nextTick() const root = wrapper.find('[role="toolbar"]') expect(root.attributes('tabindex')).toBe('0') // Dynamically disable await wrapper.vm.allDisabled = true await nextTick() expect(root.attributes('tabindex')).toBe('-1') wrapper.unmount() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/Toolbar/Toolbar.test.ts` around lines 32 - 96, Add a new test case after the existing tests in the describe block that validates reactive disabled state changes. Create a TestComponent with a reactive ref (like allDisabled) controlling the disabled property of ToolbarToggleItem, mount it with an enabled item initially, verify the toolbar root has tabindex of '0', then programmatically set the disabled state to true and await nextTick, and finally verify the tabindex reactively updates to '-1' to confirm the watcher in RovingFocusItem properly handles runtime state changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/core/src/Toolbar/Toolbar.test.ts`:
- Around line 32-96: Add a new test case after the existing tests in the
describe block that validates reactive disabled state changes. Create a
TestComponent with a reactive ref (like allDisabled) controlling the disabled
property of ToolbarToggleItem, mount it with an enabled item initially, verify
the toolbar root has tabindex of '0', then programmatically set the disabled
state to true and await nextTick, and finally verify the tabindex reactively
updates to '-1' to confirm the watcher in RovingFocusItem properly handles
runtime state changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e6fcd8bc-525d-4659-a78f-3f934f07a633
📒 Files selected for processing (3)
packages/core/src/RovingFocus/RovingFocusItem.vuepackages/core/src/Toolbar/Toolbar.test.tspackages/core/src/Toolbar/ToolbarToggleItem.vue
🔗 Resolves #2728
❓ Type of change
📚 Description
When all
ToolbarToggleItemchildren aredisabled, theToolbarRootstill receivestabindex="0"and remains reachable via keyboard Tab navigation.Root cause:
ToolbarToggleItemwas not forwarding itsdisabledprop toToolbarButton, soRovingFocusItemalways receivedfocusable=trueregardless of the item's disabled state.Additionally:
RovingFocusItemonly updatedfocusableItemsCounton mount/unmount — dynamicdisabledchanges after mount were not reflected.Fixes:
ToolbarToggleItemnow forwardsdisabledprop toToolbarButtonRovingFocusItemnow watchesfocusableprop and updatesfocusableItemsCountreactivelySummary by CodeRabbit
Bug Fixes
Tests