Skip to content

Tabs/Accordion, modelValue is id now and we have v-model:index. #2716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

xvaara
Copy link
Contributor

@xvaara xvaara commented May 27, 2025

fixes #2497
fixes #2705

Describe the PR

A clear and concise description of what the pull request does.

Small replication

A small replication or video walkthrough can help demonstrate the changes made. This is optional, but can help observe the intended changes. A mentioned issue that contains a replication also works.

PR checklist

What kind of change does this PR introduce? (check at least one)

  • Bugfix 🐛 - fix(...)
  • Feature - feat(...)
  • ARIA accessibility - fix(...)
  • Documentation update - docs(...)
  • Other (please describe)

The PR fulfills these requirements:

  • Pull request title and all commits follow the Conventional Commits convention or has an override in this pull request body This is very important, as the CHANGELOG is generated from these messages, and determines the next version type. Pull requests that do not follow conventional commits or do not have an override will be denied

Summary by CodeRabbit

  • New Features

    • Accordion components now support selecting multiple items at once, using either item index or id.
    • Tabs components introduce more flexible control with new props for active tab by index or id.
    • Tabs expose methods to programmatically activate or deactivate tabs.
  • Enhancements

    • Accordion and Tabs APIs have updated props and events for improved two-way binding and synchronization.
    • Tabs now support unmounting inactive tab content for better performance via the new unmountLazy prop.
    • Improved synchronization and event handling in Tabs, including cancelable activation events with detailed parameters.
    • Accordion items register and unregister dynamically to maintain accurate state and order.
    • Updated demos to use explicit v-model:index bindings for clearer state management.
    • Improved accessibility and usability in tab and accordion demos.
  • Bug Fixes

    • Improved synchronization between selected indices and ids in Tabs and Accordion components.
  • Documentation

    • Updated documentation to reflect new event signatures and prop changes for Tabs and Accordion components.
  • Tests

    • Added and updated tests to cover new multi-select and synchronization behaviors in Accordion and Tabs.

xvaara added 7 commits May 26, 2025 23:55
… the old value

fix(BTabs): fix initial value and some ssr errors
fix(BTab): @click event is now preventable.
fix(BTabs): reactivity in v-for fixed
fix(BTabs): fix focus on keyboard navigation
feat(BTab): expose activate and deactivate
feat((BAccordion): modelValue is array id prop free is true
feat(BAccordion): add model index to control the index of open item/items. If free is true it is an array of open items
fix(BAccordion): reactivity working in v-for
fix(BAccordion): ssr fixes.
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Copy link

coderabbitai bot commented May 27, 2025

Walkthrough

This change refactors and extends the APIs and internal logic of the BAccordion and BTabs components. It introduces index-based and multi-selection support for BAccordion, updates prop and event signatures for both components, synchronizes index and ID models, and revises related documentation, tests, and type definitions to align with the new behaviors.

Changes

Files/Groups Change Summary
BAccordion.vue, BAccordionItem.vue, accordion.spec.ts, types, utils/keys.ts Add index-based and multi-selection support, synchronize modelValue/index, update registration and events.
BTabs.vue, BTab.vue, tabs.spec.ts, types, utils/keys.ts Refactor tab activation logic, replace modelValue with index/modelValue, update events and registration.
ComponentProps.ts, Tab.ts Update prop and interface types for new API, add internalId, adjust function types to values, update refs.
accordion.data.ts, tabs.data.ts Document new/changed props and events for BAccordion and BTabs.
TabsCustomLinks.vue, TabsExternalControls.vue, TTabs.vue, TabsDynamic.vue Update usage to bind v-model:index, adjust tab close/add button placement and styling.
tabs.md Update documentation for activate-tab event argument changes.

Sequence Diagram(s)

sequenceDiagram
    participant Parent as Parent Component
    participant BAccordion as BAccordion
    participant BAccordionItem as BAccordionItem

    Parent->>BAccordion: Bind v-model and/or v-model:index
    BAccordion->>BAccordionItem: Provide openItem, setOpenItem, setCloseItem, registerAccordionItem
    BAccordionItem->>BAccordion: registerAccordionItem(id, el)
    BAccordionItem->>BAccordion: setOpenItem(id) / setCloseItem(id)
    BAccordion-->>Parent: Emit update:model-value / update:index
    BAccordionItem->>BAccordion: unregisterAccordionItem(id)
Loading
sequenceDiagram
    participant Parent as Parent Component
    participant BTabs as BTabs
    participant BTab as BTab

    Parent->>BTabs: Bind v-model:index and/or v-model
    BTabs->>BTab: Provide activeIndex, activeId, registerTab, activateTab
    BTab->>BTabs: registerTab(tab)
    BTab->>BTabs: activateTab(internalId)
    BTabs-->>Parent: Emit activate-tab(newTabId, prevTabId, newTabIndex, prevTabIndex, bvEvent)
Loading

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Update BAccordion so closing all items sets model to undefined (#2497)
Allow BTabs to set and retain initial active tab via index (#2705)

Possibly related PRs

  • bootstrap-vue-next/bootstrap-vue-next#2663: Fixes initial modelValue assignment logic in BAccordionItem, related to synchronization of open state, but does not implement multi-selection or index-based control.

Suggested reviewers

  • VividLemon

Poem

In the garden of code where tabs and accordions play,
Now indices and IDs can both lead the way.
Multiple panels may open, or tabs jump to three—
With props and events in sweet harmony.
Bugs are now bunnies, hopping away!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@xvaara xvaara mentioned this pull request May 27, 2025
7 tasks
Copy link

pkg-pr-new bot commented May 27, 2025

bsvn-vite-ts

npm i https://pkg.pr.new/bootstrap-vue-next/bootstrap-vue-next@2716
npm i https://pkg.pr.new/bootstrap-vue-next/bootstrap-vue-next/@bootstrap-vue-next/nuxt@2716

commit: a72df00

@xvaara xvaara changed the title Fix-tabs Tabs/Accordion, modelValue is id now and we have v-model:index. May 27, 2025
@xvaara xvaara requested review from Copilot, dwgray and VividLemon and removed request for Copilot and dwgray May 27, 2025 19:44
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request updates the Tabs and Accordion components to use IDs (modelValue) instead of indices for active items and introduces the new v-model:index syntax throughout the codebase. Key changes include:

  • Updating TypeScript types and component APIs so that modelValue represents an id (or an array of ids) rather than an index.
  • Refactoring internal tab registration to use a new internalId and adapting event signatures, particularly for the activate-tab event.
  • Updating documentation, tests, and demo examples to reflect the new v-model:index usage.

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/bootstrap-vue-next/src/utils/keys.ts Added ShallowRef import to support new functionality.
packages/bootstrap-vue-next/src/types/Tab.ts Introduced internalId and updated onClick, titleItemClass, titleLinkAttrs, and titleLinkClass definitions.
packages/bootstrap-vue-next/src/types/ComponentProps.ts Updated props types for BTabs and accordion components, switching modelValue from a numeric index to a string id.
packages/bootstrap-vue-next/src/components/BTabs/* Refactored tab registration, activation, and watcher logic to support the new v-model:index API, including changes in key binding and event signature.
packages/bootstrap-vue-next/src/components/BAccordion/* Adjusted accordion item handling for free mode and synchronized index/modelValue, plus updated injection functions.
apps/playground/src/components/Comps/TTabs.vue, apps/docs/* Updated examples and documentation to use the new v-model:index syntax.
Comments suppressed due to low confidence (2)

packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue:384

  • [nitpick] The watcher on activeId contains logic that adjusts activeIndex based on tab order. It may benefit from being refactored into a helper function for improved clarity and maintainability.
watch(activeId, (newValue, oldValue) => {

packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue:227

  • [nitpick] The branching logic in setOpenItem based on the free prop and modelValue type is somewhat convoluted; consider simplifying or adding detailed comments to clarify how modelValue is expected to behave in free vs. non-free modes.
setOpenItem: (id: string) => {

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (2)
apps/docs/src/docs/components/tabs.md (1)

239-246: 💡 Verification agent

🧩 Analysis chain

Verify consistency of the BTabs API migration across the codebase.

The documentation update looks correct, but ensure all BTabs component usages have been migrated to the new API.


🏁 Script executed:

#!/bin/bash
# Description: Verify all BTabs components use the new v-model:index syntax
# Expected: All BTabs should use v-model:index instead of plain v-model

echo "=== Checking for old v-model syntax on BTabs ==="
rg -A 2 '<BTabs.*v-model="' --type vue || echo "No old syntax found (good!)"

echo -e "\n=== Checking for new v-model:index syntax ==="
rg -A 2 '<BTabs.*v-model:index=' --type vue

echo -e "\n=== Checking for any BTabs without explicit model binding ==="
rg -A 2 '<BTabs(?![^>]*v-model)' --type vue

Length of output: 838


I’ll recheck all BTabs usages in .vue files with more reliable patterns:


🏁 Script executed:

#!/bin/bash
echo "=== All BTabs usages in .vue files ==="
rg -n -g '*.vue' '<BTabs'

echo -e "\n=== BTabs with old plain v-model syntax ==="
rg -n -g '*.vue' '<BTabs[^>]*v-model="' || echo "None found"

echo -e "\n=== BTabs with new v-model:index syntax ==="
rg -n -g '*.vue' '<BTabs[^>]*v-model:index=' || echo "None found"

echo -e "\n=== BTabs without any v-model binding ==="
rg -n -g '*.vue' '<BTabs' | grep -v 'v-model' | sed 's/$/  <-- missing binding/' \
  || echo "All BTabs have explicit binding"

Length of output: 6050


Ensure BTabs usages are updated to the new v-model API

Several demo and playground components still instantiate <BTabs> without any v-model:index (or v-model:active-id) binding. Please update each of these to use the new API—for example:

<BTabs v-model:index="tabIndex" …>

and define the corresponding tabIndex (e.g. const tabIndex = ref(0)) in their <script setup> blocks.

Files requiring fixes:

  • apps/docs/src/docs/components/demo/TabsJustified.vue
  • apps/docs/src/docs/components/demo/TabsBottom.vue
  • apps/docs/src/docs/components/demo/TabsDynamic.vue
  • apps/docs/src/docs/components/demo/TabsVerticalWidth.vue
  • apps/docs/src/docs/components/demo/TabsVerticalEnd.vue
  • apps/docs/src/docs/components/demo/TabsVertical.vue
  • apps/docs/src/docs/components/demo/TabsPills.vue
  • apps/docs/src/docs/components/demo/TabsPictures.vue
  • apps/docs/src/docs/components/demo/TabsOverview.vue
  • apps/docs/src/docs/components/demo/TabsLazyAll.vue
  • apps/docs/src/docs/components/demo/TabsLazy.vue
  • apps/docs/src/docs/components/demo/TabsFill.vue
  • apps/docs/src/docs/components/demo/TabsEmpty.vue
  • apps/docs/src/docs/components/demo/TabsCustom.vue
  • apps/docs/src/docs/components/demo/TabsAlignment.vue
  • apps/docs/src/docs/components/demo/TabsActive.vue
  • apps/playground/src/components/Comps/TTabs.vue (lines 50 and 118)
🧰 Tools
🪛 LanguageTool

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - preTabId: The id of th...

(GOING_TO_WILL)


[style] ~243-~243: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...newTabIndex: The index of the tab that is going to be activated - prevTabIndex`: The inde...

(GOING_TO_WILL)

packages/bootstrap-vue-next/src/types/ComponentProps.ts (1)

116-125: 💡 Verification agent

🧩 Analysis chain

Consider documenting the breaking changes and migration path.

The changes to BAccordionProps introduce multi-selection support which is a nice enhancement. However, changing modelValue from string to string | string[] is a breaking change that will affect existing consumers.

Ensure that the migration guide documents these breaking changes. Run the following script to check if there's documentation for this change:


🏁 Script executed:

#!/bin/bash
# Search for migration guide or changelog entries about these breaking changes
fd -e md -e mdx | xargs rg -l "BAccordion.*modelValue.*breaking|migration.*accordion" -A 5

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Search all markdown files for references to Accordion or migration guidance
fd -e md -e mdx | xargs rg -n "Accordion" -A2
fd -e md -e mdx | xargs rg -n "migration" -A2

Length of output: 20933


Document BAccordion API Breaking Changes

The migration guide and changelog don’t yet mention that:

  • The modelValue prop on BAccordion has changed from string to string | string[] (enabling multiple selection).
  • A new index?: number | number[] prop is available to control panels by index.

Please update:

CHANGELOG.md (e.g. under “### Breaking Changes”):

- **BAccordionProps**: 
  - `modelValue` now accepts `string | string[]` (was `string`).
  - Added `index?: number | number[]` for index-based single or multiple panels.

apps/docs/src/docs/migration-guide.md under the “### BAccordion” section:

#### BAccordion
- `modelValue` now accepts both `string` and `string[]`. If you upgrade from single to multi-select, update your `v-model` usage to handle arrays.
- Added `index?: number | number[]` to programmatically control active items by their index(es).

These additions will ensure consumers see the required steps when migrating.

🧹 Nitpick comments (7)
apps/docs/src/docs/components/tabs.md (1)

239-246: Documentation correctly updated, but address minor style issues.

The documentation properly reflects the expanded activate-tab event signature with both ID and index parameters. However, there are minor style improvements needed:

Apply this diff to address the style issues:

-tab from activating. The `activate-tab` event is emitted with five arguments:
+tab from activating. The `activate-tab` event is emitted with five arguments:

-- `newTabId`: The id of the tab that is going to be activated
-- `preTabId`: The id of the currently active tab
+- `newTabId`: The id of the tab that will be activated
+- `preTabId`: The id of the currently active tab
🧰 Tools
🪛 LanguageTool

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - preTabId: The id of th...

(GOING_TO_WILL)


[style] ~243-~243: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...newTabIndex: The index of the tab that is going to be activated - prevTabIndex`: The inde...

(GOING_TO_WILL)

packages/bootstrap-vue-next/src/components/BAccordion/BAccordionItem.vue (1)

141-147: Consider simplifying the mounted hook logic.

The current logic in onMounted checks for a specific condition that might already be handled by the watchers. Since the openInParent computed property and watchers handle synchronization, this mounted hook might be redundant.

-onMounted(() => {
-  if (!modelValue.value && parentData?.openItem.value === computedId.value) {
-    nextTick(() => {
-      modelValue.value = true
-    })
-  }
-})
+// The watchers handle synchronization, so explicit mounted logic may not be needed
packages/bootstrap-vue-next/src/types/ComponentProps.ts (1)

714-756: Breaking API changes in tab components need clear documentation.

The changes to BTabProps and BTabsProps represent significant breaking changes:

  • BTabProps: lazyOnceunmountLazy (semantic change)
  • BTabsProps: activeId removed, modelValue type changed from number to string

These changes will require consumers to update their code.

Would you like me to help generate TypeScript migration examples or a codemod to help users migrate from the old API to the new one?

packages/bootstrap-vue-next/src/components/BTabs/BTab.vue (2)

99-103: Remove commented code or implement the watcher if needed.

This commented watcher suggests incomplete implementation. If prop changes need to trigger tab updates, implement the watcher. Otherwise, remove the commented code.

-// watch(() => props, () => {
-//   if (!parentData) return
-//   updateTab()
-// })

69-90: Consider optimizing the tab registration approach.

The current implementation creates a new computed ref on each updateTab() call. Since updateTab() is only called once during component initialization, this is acceptable. However, if you plan to make props reactive (as suggested by the commented watcher), consider creating the computed ref once and reusing it.

If you need reactive prop updates, consider this optimization:

+const tab = computed(
+  () =>
+    ({
+      internalId: internalId.value,
+      id: computedId.value,
+      active: activeModel.value,
+      buttonId: computedButtonId.value,
+      disabled: props.disabled,
+      title: props.title,
+      titleComponent: slots.title,
+      titleItemClass: props.titleItemClass,
+      titleLinkAttrs: props.titleLinkAttrs,
+      titleLinkClass: props.titleLinkClass,
+      onClick: attrs.onClick,
+      el,
+    }) as TabType
+)
+
 function updateTab() {
   if (!parentData) return
-  parentData.registerTab(
-    computed(
-      () =>
-        ({
-          internalId: internalId.value,
-          id: computedId.value,
-          active: activeModel.value,
-          buttonId: computedButtonId.value,
-          disabled: props.disabled,
-          title: props.title,
-          titleComponent: slots.title,
-          titleItemClass: props.titleItemClass,
-          titleLinkAttrs: props.titleLinkAttrs,
-          titleLinkClass: props.titleLinkClass,
-          onClick: attrs.onClick,
-          el,
-        }) as TabType
-    )
-  )
+  parentData.registerTab(tab)
 }
packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (1)

117-206: Consider extracting synchronization logic for better maintainability.

The watchers for index and modelValue contain complex logic that could be extracted into helper functions for better readability and testability.

Would you like me to help refactor this synchronization logic into smaller, more focused helper functions? This would improve code maintainability and make unit testing easier.

🧰 Tools
🪛 GitHub Check: test-lint

[warning] 156-156:
Unexpected console statement

packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (1)

290-292: Optional chaining is redundant with typeof check

The optional chaining on line 292 is unnecessary since you already check that onClick is a function on line 290.

-    tabs.value[index].onClick?.(event)
+    tabs.value[index].onClick(event)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 790679d and 9de81a9.

📒 Files selected for processing (16)
  • apps/docs/src/data/components/accordion.data.ts (3 hunks)
  • apps/docs/src/data/components/tabs.data.ts (4 hunks)
  • apps/docs/src/docs/components/demo/TabsCustomLinks.vue (1 hunks)
  • apps/docs/src/docs/components/demo/TabsDynamic.vue (1 hunks)
  • apps/docs/src/docs/components/demo/TabsExternalControls.vue (1 hunks)
  • apps/docs/src/docs/components/tabs.md (1 hunks)
  • apps/playground/src/components/Comps/TTabs.vue (3 hunks)
  • packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (3 hunks)
  • packages/bootstrap-vue-next/src/components/BAccordion/BAccordionItem.vue (4 hunks)
  • packages/bootstrap-vue-next/src/components/BAccordion/accordion.spec.ts (2 hunks)
  • packages/bootstrap-vue-next/src/components/BTabs/BTab.vue (6 hunks)
  • packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (10 hunks)
  • packages/bootstrap-vue-next/src/components/BTabs/tabs.spec.ts (1 hunks)
  • packages/bootstrap-vue-next/src/types/ComponentProps.ts (3 hunks)
  • packages/bootstrap-vue-next/src/types/Tab.ts (1 hunks)
  • packages/bootstrap-vue-next/src/utils/keys.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/bootstrap-vue-next/src/types/Tab.ts (2)
packages/bootstrap-vue-next/src/types/index.ts (1)
  • TabType (62-62)
packages/bootstrap-vue-next/src/types/AnyValuedAttributes.ts (1)
  • ClassValue (2-2)
packages/bootstrap-vue-next/src/utils/keys.ts (1)
packages/bootstrap-vue-next/src/types/Tab.ts (1)
  • TabType (4-18)
packages/bootstrap-vue-next/src/types/ComponentProps.ts (1)
packages/bootstrap-vue-next/src/types/AnyValuedAttributes.ts (1)
  • ClassValue (2-2)
🪛 LanguageTool
apps/docs/src/docs/components/tabs.md

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - preTabId: The id of th...

(GOING_TO_WILL)

🪛 GitHub Check: test-lint
packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue

[warning] 213-213:
Use array destructuring


[warning] 156-156:
Unexpected console statement


[warning] 90-90:
Unexpected console statement

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (26)
apps/docs/src/docs/components/demo/TabsExternalControls.vue (1)

5-5: LGTM! API migration correctly implemented.

The change from v-model="tabIndex" to v-model:index="tabIndex" properly aligns with the new BTabs component API that separates index and ID models for better control.

apps/docs/src/docs/components/demo/TabsCustomLinks.vue (1)

3-3: LGTM! Consistent API migration.

The change to v-model:index="tabIndex" maintains consistency with the new BTabs API across demo components.

apps/playground/src/components/Comps/TTabs.vue (2)

5-5: LGTM! API migration correctly implemented.

The change to v-model:index="tabIndex" follows the consistent pattern for the new BTabs API.


92-92: Excellent demonstration of enhanced dual binding capability.

These lines showcase the new BTabs API's ability to simultaneously track both the active tab index and ID through dual binding (v-model:index and v-model:active-id). This provides more granular control over tab state management.

Also applies to: 126-126

packages/bootstrap-vue-next/src/components/BTabs/tabs.spec.ts (1)

531-531: Test binding syntax correctly updated for new BTabs API.

The v-model binding syntax has been properly updated to reflect the new BTabs component API:

  • v-model:index="index" for explicit index binding
  • v-model="id" for the default model (active tab ID)

This change aligns with the broader refactor where modelValue now represents the active tab's ID as a string rather than an index number.

apps/docs/src/docs/components/demo/TabsDynamic.vue (2)

7-17: Improved UX by moving close button to tab title.

The close button placement in the tab title is more intuitive and follows common UI patterns. The styling with negative margin and link classes provides appropriate visual integration.


23-31: Enhanced new tab button styling and accessibility.

The change from BNavItem to BButton with proper Bootstrap utility classes improves the button appearance. The role="presentation" attribute is correctly applied for accessibility.

packages/bootstrap-vue-next/src/components/BAccordion/accordion.spec.ts (4)

4-4: Correct import addition for new test functionality.

The import of BAccordionItem is properly added to support the new test cases.


42-47: Good test coverage for the new free prop.

Simple test verifies the free prop is correctly set and accessible.


49-72: Well-structured complex test component for multi-selection testing.

The test component properly demonstrates the new dual v-model binding pattern with both modelValue and index props, which aligns with the new accordion API.


105-133: Comprehensive test for multi-selection behavior.

The test thoroughly verifies the array-based state management when free=true, including:

  • Initial state verification
  • Adding selections
  • Removing selections
  • Proper async handling with timeouts and $nextTick

The test logic correctly validates that both modelValue and index arrays stay synchronized.

packages/bootstrap-vue-next/src/utils/keys.ts (4)

1-1: Appropriate import addition for enhanced type safety.

The ComputedRef import is correctly added to support the updated injection key interfaces.


58-58: Enhanced type safety for tab registration.

The change from Readonly<Ref<TabType>> to ComputedRef<TabType> provides better type safety and aligns with the component refactoring where tabs use computed properties for registration.


98-98: Proper type expansion for multi-selection support.

The openItem type correctly expanded from string | undefined to string | string[] | undefined to support multiple open accordion items when free=true.


104-106: Well-designed new methods for accordion item management.

The three new methods provide comprehensive accordion item lifecycle management:

  • setCloseItem: For closing specific items
  • registerAccordionItem: For registering items with DOM element reference
  • unregisterAccordionItem: For cleanup when items are removed

The method signatures are appropriate and include necessary type constraints.

packages/bootstrap-vue-next/src/types/Tab.ts (1)

1-18: Breaking changes are properly aligned with the tabs refactoring goals.

The interface changes are well-structured and improve type safety. The addition of internalId for robust internal identification and the simplification from function-returned values to direct values make the API cleaner.

Note for migration guide: These are breaking changes that will require updates to existing code:

  • New required internalId property
  • Properties changed from functions to direct values
  • onClick handler signature change
  • el property now uses Vue's Ref wrapper
apps/docs/src/data/components/accordion.data.ts (1)

23-82: Well-designed API for multi-selection accordion support.

The addition of index and modelValue props with support for both single and array values provides excellent flexibility. The corresponding events are properly documented and align with Vue's v-model conventions.

apps/docs/src/data/components/tabs.data.ts (2)

19-24: Good prop rename for clarity.

The rename from lazyOnce to unmountLazy better describes the behavior - content is unmounted when inactive rather than cached.


68-73:

❓ Verification inconclusive

Verify consistency between index and modelValue handling.

The switch from activeId to index prop and changing modelValue from number to string represents a significant API change. The description notes that "Id (modelValue) takes priority over index" which is good for clarity.

Ensure examples and tests are updated to reflect these breaking changes:

Also applies to: 134-137


🏁 Script executed:

#!/bin/bash
# Description: Check for outdated usage patterns that need updating

# Search for activeId prop usage that needs to be updated
rg -A 3 'activeId\s*[:=]' --type vue --type ts

# Search for v-model usage on BTabs that might expect numbers
ast-grep --pattern 'BTabs v-model=$_'

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Retry searching for outdated activeId usage in .vue and .ts files
rg -A 3 'activeId\s*[:=]' -g '*.vue' -g '*.ts'

# Search for BTabs v-model usage in .vue files
rg -A 3 '<BTabs[^>]*v-model' -g '*.vue'

Length of output: 4331


Update BTabs examples and tests for the new index/modelValue API

We still have live usages of the old activeId binding alongside v-model:index. Please verify and update these examples and any tests to align with the switch from activeIdindex (number) and the change of modelValue to a string ID:

• apps/playground/src/components/Comps/TTabs.vue
– Remove or adjust v-model:active-id="activeId" if you no longer intend to demonstrate ID-based activation, or confirm it now binds to a string.
– Ensure v-model:index="tabIndex" continues to work with numeric indexes.

• apps/docs/src/docs/components/demo/TabsExternalControls.vue
– Already uses v-model:index; confirm examples reflect the TypeScript change (index: number).

• apps/docs/src/docs/components/demo/TabsCustomLinks.vue
– Same as above: verify v-model:index examples and descriptions mention the index-based prop.

• packages/bootstrap-vue-next/src/components/BTabs
– Check any unit or integration tests under this folder to ensure they expect modelValue (string ID) and/or index (number) rather than the old activeId prop.

You can re-run these searches to catch any residual patterns:

rg -A3 'activeId' -g '*.vue' -g '*.ts'
rg -A3 '<BTabs[^>]*v-model' -g '*.vue'
packages/bootstrap-vue-next/src/components/BAccordion/BAccordionItem.vue (2)

125-132: Excellent handling of single and multi-selection modes.

The logic correctly checks whether the parent's openItem is an array to support both single and multiple open accordion items. Good defensive programming with the Array.isArray check.


134-139: Good lifecycle management for accordion item registration.

Registering and unregistering the item with the parent ensures proper state management and prevents memory leaks.

packages/bootstrap-vue-next/src/components/BTabs/BTab.vue (2)

138-153: Good defensive programming with disabled state check!

Excellent addition of the disabled state check to prevent activating disabled tabs. This improves the component's robustness.


166-173: Well-designed public API exposure.

The activate() and deactivate() methods provide a clean programmatic interface for external tab control, which enhances the component's flexibility.

packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (2)

213-213: Use array destructuring for cleaner code.

Minor style improvement as suggested by the linter.

-        modelValue.value = modelValue.value[0]
+        modelValue.value = [modelValue.value][0]

Wait, this suggestion doesn't make sense. The current code is correct. The linter might be suggesting:

-        modelValue.value = modelValue.value[0]
+        const [firstValue] = modelValue.value
+        modelValue.value = firstValue

But the original code is cleaner. You can safely ignore this linting suggestion or disable it for this line.

🧰 Tools
🪛 GitHub Check: test-lint

[warning] 213-213:
Use array destructuring


110-206: Well-implemented bidirectional synchronization logic.

The synchronization between index and modelValue is comprehensive and handles edge cases well:

  • Validates indices before updating
  • Supports both single and multi-selection modes
  • Uses nextTick appropriately for invalid values
  • Implements areEqual for proper array comparison

This is a solid implementation of complex state management.

🧰 Tools
🪛 GitHub Check: test-lint

[warning] 156-156:
Unexpected console statement

packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (1)

200-200: Temporary IDs could cause issues with external references

The premount-${index} temporary IDs might cause issues if external code tries to reference tabs before they're fully mounted.

Consider using a more robust ID generation strategy or document this behavior clearly.

Copy link
Member

@dwgray dwgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this PR addresses the issues I raised in #2383. There is some cleanup remaining in the migration-guide and docs files, I am happy to handle that after this PR goes through.

Aside from the minor comments I added, it looks like the AI is raising flags on race conditions in a couple of places, I didn't take the time to get my head wrapped around them, but let me know if you'd like a second opinion on them and I'll try to grab some time to do that.

@xvaara
Copy link
Contributor Author

xvaara commented May 28, 2025

Aside from the minor comments I added, it looks like the AI is raising flags on race conditions in a couple of places, I didn't take the time to get my head wrapped around them, but let me know if you'd like a second opinion on them and I'll try to grab some time to do that.

@dwgray @VividLemon I'd like your input on the race conditions. Especially I got some hangs with infinite loops when I prevented inside the activate-tab emit, but they should work in the committed version.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (1)

90-93: Console statements should be replaced with proper error handling.

This matches a previous review comment that hasn't been addressed. Console statements should not be left in production code.

Apply the suggested fix from the previous review:

-      if (next.length !== modelValue.value.length) {
-        if (import.meta.env.DEV) {
-          console.warn('[BAccordion] Unknown item id in v-model:', modelValue.value)
-        }
-      }
+      if (next.length !== modelValue.value.length) {
+        if (import.meta.env.DEV) {
+          console.warn('[BAccordion] Unknown item id in v-model:', modelValue.value)
+        }
+        // Optionally emit an event for production error tracking
+        // emit('error', { type: 'unknown-item-id', value: modelValue.value })
+      }
🧰 Tools
🪛 GitHub Check: test-lint

[warning] 91-91:
Unexpected console statement

🧹 Nitpick comments (2)
apps/docs/src/docs/components/tabs.md (1)

241-243: Consider style improvements for better readability.

Based on static analysis feedback, consider these minor style improvements:

- - `newTabId`: The id of the tab that is going to be activated
- - `prevTabId`: The id of the currently active tab
+ - `newTabId`: The id of the tab that will be activated
+ - `prevTabId`: The id of the currently active tab
🧰 Tools
🪛 LanguageTool

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - prevTabId: The id of t...

(GOING_TO_WILL)


[style] ~243-~243: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...newTabIndex: The index of the tab that is going to be activated - prevTabIndex`: The inde...

(GOING_TO_WILL)

packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (1)

342-395: Consider minor optimization in nextIndex calculation.

The activeIndex watcher logic is well-structured for handling complex activation scenarios. However, there's a small redundant calculation:

  const index = nextIndex(newValue, newValue > oldValue ? 1 : -1)
  if (index !== newValue) {
    previousIndex = oldValue
    activeIndex.value = index
    return
  }
  const tabEvent = new BvEvent('activate-tab', {cancelable: true})
  emit(
    'activate-tab',
    tabs.value[index]?.id,
    tabs.value[previousIndex ?? oldValue]?.id,
    index,
    previousIndex ?? oldValue,
    tabEvent
  )

The logic is correct, but consider storing previousIndex ?? oldValue in a variable to avoid repetition.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9de81a9 and a72df00.

📒 Files selected for processing (5)
  • apps/docs/src/data/components/tabs.data.ts (4 hunks)
  • apps/docs/src/docs/components/tabs.md (1 hunks)
  • packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (3 hunks)
  • packages/bootstrap-vue-next/src/components/BTabs/BTab.vue (6 hunks)
  • packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bootstrap-vue-next/src/components/BTabs/BTab.vue
🧰 Additional context used
🧠 Learnings (2)
apps/docs/src/data/components/tabs.data.ts (1)
Learnt from: xvaara
PR: bootstrap-vue-next/bootstrap-vue-next#2716
File: packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue:384-404
Timestamp: 2025-05-28T07:57:19.884Z
Learning: In BTabs component (packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue), the activeIndex and activeId watchers are intentionally designed with separation of concerns: activeIndex watcher handles activate-tab event emission and complex validation logic, while activeId watcher is kept simple for synchronization between activeId and activeIndex values only.
packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (3)
Learnt from: xvaara
PR: bootstrap-vue-next/bootstrap-vue-next#2716
File: packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue:384-404
Timestamp: 2025-05-28T07:57:19.884Z
Learning: In BTabs component (packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue), the activeIndex and activeId watchers are intentionally designed with separation of concerns: activeIndex watcher handles activate-tab event emission and complex validation logic, while activeId watcher is kept simple for synchronization between activeId and activeIndex values only.
Learnt from: xvaara
PR: bootstrap-vue-next/bootstrap-vue-next#2716
File: packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue:240-261
Timestamp: 2025-05-28T07:01:55.082Z
Learning: In BTabs component (packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue), the complex initialization logic with updateInitialActiveIndex and updateInitialActiveId flags is necessary for SSR compatibility. Tab initialization cannot be deferred to mounted lifecycle because tabs must be available for server-side rendering of the initial HTML state.
Learnt from: xvaara
PR: bootstrap-vue-next/bootstrap-vue-next#2716
File: packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue:425-430
Timestamp: 2025-05-28T07:32:45.637Z
Learning: In BTabs component (packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue), the activeIndex synchronization within sortTabs() is necessary and correct. When sorting tabs by DOM position, indices change, so activeIndex must be updated immediately to maintain consistency with activeId. This is not an unwanted side effect but a required consequence of the sorting operation.
🪛 GitHub Check: test-lint
packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue

[warning] 214-214:
Use array destructuring


[warning] 91-91:
Unexpected console statement

🪛 LanguageTool
apps/docs/src/docs/components/tabs.md

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - prevTabId: The id of t...

(GOING_TO_WILL)


[style] ~243-~243: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...newTabIndex: The index of the tab that is going to be activated - prevTabIndex`: The inde...

(GOING_TO_WILL)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (20)
packages/bootstrap-vue-next/src/components/BAccordion/BAccordion.vue (5)

52-70: VNode tracking implementation looks solid.

The slot watching and VNode filtering logic is well-structured. Using nextTick for sorting after slot updates ensures DOM is ready.


119-220: Review synchronization logic for potential race conditions.

The dual-watcher setup between index and modelValue creates potential for infinite loops or race conditions, as mentioned in the PR comments. While the areEqual checks help prevent some redundant updates, the complex conditional logic could still cause issues.

Consider these improvements:

  1. Use a single source of truth with computed values instead of dual watchers
  2. Add debugging flags to track watcher execution order
  3. Consider using watchSyncEffect or a state machine pattern

The array destructuring on line 214 should also be updated per the linter warning:

-        modelValue.value = modelValue.value[0]
+        [modelValue.value] = [modelValue.value[0]]

Would you like me to generate a verification script to test for race conditions or help refactor this synchronization logic?

🧰 Tools
🪛 GitHub Check: test-lint

[warning] 214-214:
Use array destructuring


206-206: Deep watching is appropriate here.

The deep watch on modelValue is necessary to detect changes in array values, and since the data is typically simple (strings/arrays of strings), the performance impact should be minimal.


222-272: Injection implementation handles edge cases well.

The setOpenItem and setCloseItem methods properly handle all combinations of free mode, array/single values, and state transitions. The registration system with automatic sorting when all items are registered is well-designed.


112-117: Simple and effective equality utility.

The areEqual function appropriately handles both array and primitive comparisons for preventing unnecessary watcher updates.

apps/docs/src/docs/components/tabs.md (1)

239-246: Documentation accurately reflects the enhanced event signature.

The updated documentation correctly describes the expanded activate-tab event with five arguments, providing clear descriptions for each parameter.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~241-~241: Loose punctuation mark.
Context: ...itted with five arguments: - newTabId: The id of the tab that is going to be a...

(UNLIKELY_OPENING_PUNCTUATION)


[style] ~241-~241: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...: - newTabId: The id of the tab that is going to be activated - prevTabId: The id of t...

(GOING_TO_WILL)


[style] ~243-~243: Use ‘will’ instead of ‘going to’ if the following action is certain.
Context: ...newTabIndex: The index of the tab that is going to be activated - prevTabIndex`: The inde...

(GOING_TO_WILL)

apps/docs/src/data/components/tabs.data.ts (5)

19-24: LGTM! Property rename aligns with component implementation.

The lazyOnce to unmountLazy rename with updated description correctly reflects the new lazy rendering behavior where content is unmounted when inactive.


68-73: LGTM! Index prop introduction supports dual-model architecture.

The new index prop with number type and -1 default provides explicit control over tab selection by index, complementing the ID-based modelValue.


135-138: LGTM! ModelValue type change supports ID-based selection.

Changing modelValue from number to string enables ID-based tab selection, which is more semantically meaningful and stable than index-based selection.


209-220: LGTM! New index event enables dual binding support.

The update:index event properly supports v-model:index binding for index-based control, complementing the ID-based modelValue.


226-251: Enhanced event signature provides comprehensive context.

The expanded activate-tab event with both ID and index parameters for new and previous tabs offers developers complete context for handling tab activation.

packages/bootstrap-vue-next/src/components/BTabs/BTabs.vue (9)

33-46: LGTM! Template updates align with component refactor.

The template changes correctly use internalId for keys and remove function calls from prop bindings, improving performance by eliminating unnecessary computations on each render.


87-142: Well-structured dual-model implementation.

The separation of activeIndex and activeId models with proper defaults provides a clean API that supports both index-based and ID-based tab control.


147-165: Reactive tab elements tracking improves state management.

The tabElementsArray tracking with proper slot watching ensures the component responds correctly to dynamic tab changes, which is essential for proper DOM synchronization.


225-264: Complex initialization logic necessary for SSR compatibility.

Based on previous discussions, this initialization complexity with flags is required for SSR compatibility where tabs must be available before the mounted lifecycle. The logic properly handles the various initialization states.


290-304: Simplified click handler improves maintainability.

The updated click handler correctly processes the onClick callback and sets activeIndex directly, eliminating the need for the previous activateTab function complexity.


397-422: ActiveId watcher maintains clean separation of concerns.

Based on retrieved learnings, this watcher is intentionally kept simple for synchronization purposes while the activeIndex watcher handles complex event logic. The implementation correctly maintains this architectural separation.


424-441: Tab registration handles dynamic updates effectively.

The registration logic properly handles both initial registration and updates, with appropriate sorting triggered only when needed. The initialization flag prevents premature sorting.


443-448: ActiveIndex synchronization in sortTabs is architecturally sound.

Based on retrieved learnings, this synchronization is necessary and correct. When sorting changes tab positions, activeIndex must be updated immediately to maintain consistency with activeId.


464-471: ActivateTab method provides clean programmatic interface.

The method correctly uses internalId for tab lookup and delegates to the activeIndex model, maintaining consistency with the component's state management approach.

@dwgray
Copy link
Member

dwgray commented May 29, 2025

Aside from the minor comments I added, it looks like the AI is raising flags on race conditions in a couple of places, I didn't take the time to get my head wrapped around them, but let me know if you'd like a second opinion on them and I'll try to grab some time to do that.

@dwgray @VividLemon I'd like your input on the race conditions. Especially I got some hangs with infinite loops when I prevented inside the activate-tab emit, but they should work in the committed version.

I just spent some time getting my head wrapped around the watchers for activeIndex and activeId, which I believe was the remaining issue from coderabbitai. I don't see a cleaner way to do this, the fundamental issue of having two names for the same object, each of which can be set in multiple ways is challenging. But I think it's the right dev experience, so it's worth the code complexity in this case. I also did not see anything that I would expect to raise a race condition, but of course, those are awfully hard to find by observation.

I'm good with this going through.

Copy link
Member

@dwgray dwgray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you've addressed all of the code issues that coderabbitui brought up, so I'm good with this PR - great work on handling the multiple ways of addressing tabs, that's a hard problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BTabs: can't set initial value BAccordion issue when using v-model
2 participants