Skip to content

feat(DockView): add onPanelVisibleChanged event#981

Merged
ArgoZhang merged 8 commits intomasterfrom
dev-only-visible
Apr 23, 2026
Merged

feat(DockView): add onPanelVisibleChanged event#981
ArgoZhang merged 8 commits intomasterfrom
dev-only-visible

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Apr 23, 2026

Link issues

fixes #979

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Update DockView panel visibility handling and panel synchronization for more robust lifecycle management and state updates.

New Features:

  • Trigger tab loading based on panel visibility changes when using the 'onlyWhenVisible' renderer and DockView is initialized.
  • Mark newly added panels to groups as inactive by default when created via group helpers.

Bug Fixes:

  • Ensure delayed initialization and visibility handlers safely no-op when the DockView instance has been disposed to avoid runtime errors.
  • Fix panel removal by locating panels using their key instead of direct object reference equality.
  • Make DOM event attachment and queries for dockview layout click handling resilient when elements are absent.
  • Avoid overwriting existing panel configuration with undefined values when toggling components.
  • Ensure closed panels move their content back to the template only when both the element exists and the move-to-template flag is set.

Enhancements:

  • Unify visibility and active-state handling into a single visibility-change observer that updates configuration, drawer titles, and always-render panels.
  • Speed up DockView initialization follow-up work by removing the artificial 100ms delay.
  • Clean up debugging noise by removing console logging from Dockview group parameter updates.

Copilot AI review requested due to automatic review settings April 23, 2026 01:09
@bb-auto bb-auto Bot added the enhancement New feature or request label Apr 23, 2026
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Apr 23, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 23, 2026

Reviewer's Guide

Adds a new panel visibility change handling flow for DockView, refactors panel lifecycle logic to be driven by visibility instead of active state, hardens dockview initialization and disposal handling, and adjusts panel toggling to merge options safely without overwriting existing values with undefined.

Sequence diagram for DockView panel visibility change handling

sequenceDiagram
    participant Panel
    participant PanelApi as PanelApi
    participant Group
    participant Dockview
    participant LoadTabsEvent as LoadTabs

    Panel->>PanelApi: onDidVisibilityChange(handler)
    Note over PanelApi,Dockview: Visibility change event fires
    PanelApi->>Dockview: handler({ isVisible })
    Dockview->>Dockview: if _isDisposed return
    Dockview->>Dockview: renderer = params.options.renderer
    alt renderer is onlyWhenVisible and _inited
        alt isVisible
            Dockview->>Dockview: saveConfig(accessor)
            Dockview->>Dockview: visiblePanels = groups.map(...)
            Dockview->>LoadTabs: _loadTabs.fire(visiblePanelKeys)
        else not isVisible
            Dockview->>Panel: appendTemplatePanelEle(panel)
        end
    end
    alt isVisible and group.floatType is drawer
        Dockview->>Group: setDrawerTitle(group)
    end
    Dockview->>Dockview: handler = setTimeout(..., 0)
    Dockview->>Dockview: clearTimeout(handler)
    Dockview->>Dockview: if _isDisposed return
    Dockview->>Panel: moveAlwaysRenderPanel(panel)
Loading

State diagram for Dockview initialization and disposal

stateDiagram-v2
    [*] --> Constructed

    Constructed --> InitializingTimeoutPending: initDockview called

    state InitializingTimeoutPending {
        [*] --> Waiting
        Waiting --> FinishedInit: timeout handler executes
        Waiting --> Aborted: dockview._isDisposed becomes true before handler
    }

    InitializingTimeoutPending --> Disposed: dockview._isDisposed set before handler

    FinishedInit --> Inited: _inited = true and _initialized.fire()
    Aborted --> Disposed: handler sees _isDisposed, sets dockview = null

    Inited --> Disposed: later disposal sets _isDisposed = true

    Disposed --> Disposed: Any visibility or init handler checks _isDisposed and returns
Loading

File-Level Changes

Change Details Files
Unify panel visibility and active-state handling to drive config saving, visible tab loading, and drawer title updates from visibility changes while making the logic disposal-safe.
  • Replace onDidActiveChange listener with onDidVisibilityChange and route both config saving and template panel movement through the visibility callback.
  • On visibility change, when renderer is 'onlyWhenVisible' and dockview is initialized, fire _loadTabs with currently visible panel keys when the panel becomes visible, or move the panel back to the template when it becomes hidden.
  • Ensure visibility handler is no-op after dockview disposal and guard moveAlwaysRenderPanel with a timeout that clears itself and checks for disposal.
  • Update drawer title changes to be triggered by visibility instead of active state.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js
Improve dockview initialization safety and responsiveness, and make DOM event wiring more defensive.
  • In initDockview, after the initial timeout, check dockview._isDisposed before accessing panels/groups, and null out dockview reference on disposal.
  • Make click handler attachment to '.dv-branch-node' and related querySelectorAll calls null-safe using optional chaining to avoid errors when elements are missing.
  • Reduce the post-initialization delay from 100ms to 0ms to run setup as soon as possible after the microtask queue.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js
Make toggling components merge new options into existing panels without overwriting defined values with undefined/null fields.
  • Introduce a cleanUndefined helper that strips null/undefined properties from an object before merging.
  • When creating or reusing a panel in toggleComponent, prefer an existing panel from dockview.params.panels if present, and merge top-level and params-level values using cleanUndefined to preserve existing, defined values.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js
Adjust panel deletion and group-panel close behavior to be key-based and more robust when moving content back to templates.
  • Change deletePanel to locate the panel in dockview.params.panels by matching params.key instead of object identity, ensuring correct removal when panel instances are not the same object.
  • Simplify closePanel moveToTemplate logic to first check that content.element exists and move titleMenuEle and content.element back into the template when appropriate.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-extensions.js
Ensure newly added panels in groups are created inactive by default.
  • When adding a panel to an existing group or creating a new group, pass inactive: true in the options to dockview.addPanel so that the new panel does not become active immediately.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArgoZhang ArgoZhang merged commit ee30e0f into master Apr 23, 2026
4 checks passed
@ArgoZhang ArgoZhang deleted the dev-only-visible branch April 23, 2026 01:10
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The cleanUndefined helper filters out both null and undefined (v != null), which changes semantics if callers intentionally pass null to clear a value; consider restricting this to undefined only (e.g., v !== undefined) or documenting the null-stripping behavior explicitly.
  • In initDockview, setting dockview = null after checking dockview._isDisposed only reassigns the local variable and has no effect on the actual instance; you can drop this assignment to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `cleanUndefined` helper filters out both `null` and `undefined` (`v != null`), which changes semantics if callers intentionally pass `null` to clear a value; consider restricting this to `undefined` only (e.g., `v !== undefined`) or documenting the null-stripping behavior explicitly.
- In `initDockview`, setting `dockview = null` after checking `dockview._isDisposed` only reassigns the local variable and has no effect on the actual instance; you can drop this assignment to avoid confusion.

## Individual Comments

### Comment 1
<location path="src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js" line_range="230-231" />
<code_context>
     }
 }
-
+const cleanUndefined = (obj) => Object.fromEntries(
+    Object.entries(obj).filter(([, v]) => v != null)
+);
 const toggleComponent = (dockview, options) => {
</code_context>
<issue_to_address>
**issue (bug_risk):** Filtering with `v != null` drops both `undefined` and `null`, which changes semantics when `null` is intentional.

Using `v != null` here also strips intentional `null` values, so callers can’t explicitly reset a property to `null`. If `null` is meaningful and only `undefined` should be removed, use `v !== undefined` instead, or make this behavior explicit in the function’s name/contract.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +230 to +231
const cleanUndefined = (obj) => Object.fromEntries(
Object.entries(obj).filter(([, v]) => v != null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Filtering with v != null drops both undefined and null, which changes semantics when null is intentional.

Using v != null here also strips intentional null values, so callers can’t explicitly reset a property to null. If null is meaningful and only undefined should be removed, use v !== undefined instead, or make this behavior explicit in the function’s name/contract.

Copy link
Copy Markdown

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 PR enhances the DockView JavaScript integration to better react to panel visibility changes (supporting an onPanelVisibleChanged-style event flow) and hardens lifecycle behaviors to avoid running callbacks after a DockView instance is disposed.

Changes:

  • Switch panel event handling to rely on visibility changes (and consolidate related side effects like template moves / tab loading).
  • Add disposal guards and make some DOM event wiring more resilient.
  • Adjust panel add/remove/update behaviors (key-based removal, merging option updates, adding panels as inactive) and bump DockView package version.

Reviewed changes

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

Show a summary per file
File Description
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js Adds disposed guard during init, adjusts init timing/click handler wiring, and introduces cleanUndefined for option merging.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js Consolidates panel visibility handling, adjusts when config/tabs are updated, and fixes panel deletion to be key-based.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js Adds panels as inactive when inserting into existing/new groups.
src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-extensions.js Removes debug logging and simplifies closePanel template move logic.
src/components/BootstrapBlazor.DockView/BootstrapBlazor.DockView.csproj Updates package version from beta to stable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +140 to 145
dockview.element.querySelector('&>.dv-dockview>.dv-branch-node')?.addEventListener('click', function(e) {
this.parentElement.querySelectorAll('&>.dv-resize-container-drawer, &>.dv-render-overlay-float-drawer')?.forEach(item => {
item.classList.remove('active')
})
this.closest('.bb-dockview').querySelectorAll('&>.bb-dockview-aside>.bb-dockview-aside-button').forEach(item => {
this.closest('.bb-dockview').querySelectorAll('&>.bb-dockview-aside>.bb-dockview-aside-button')?.forEach(item => {
item.classList.remove('active')
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

The optional chaining here is applied to the result of querySelectorAll, but querySelectorAll always returns a (non-null) NodeList; the real null/undefined risks are this.parentElement and this.closest('.bb-dockview'). As written, this can still throw if parentElement or closest(...) is null. Consider optional-chaining those objects instead (and drop the redundant ?. on querySelectorAll).

Copilot uses AI. Check for mistakes.
}

const cleanUndefined = (obj) => Object.fromEntries(
Object.entries(obj).filter(([, v]) => v != null)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

cleanUndefined currently filters out both undefined and null values (v != null). If callers use null intentionally (e.g., to reset a param to its default), this merge will silently ignore that update and keep the previous value. If the intent is only to avoid overwriting with undefined, filter with v !== undefined (or rename the helper to reflect “nullish” behavior).

Suggested change
Object.entries(obj).filter(([, v]) => v != null)
Object.entries(obj).filter(([, v]) => v !== undefined)

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +21
if (renderer === 'onlyWhenVisible' && dockview._inited) {
if (isVisible) {
saveConfig(panel.accessor)
const visiblePanels = dockview.groups.map(g => g.panels.find(p => p.params.isActive) || g.panels.find(p => p.api.isVisible))
dockview._loadTabs?.fire(visiblePanels.filter(p => Boolean(p)).map(p => p.params.key));
}
else {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

saveConfig is now only triggered on visibility changes when renderer === 'onlyWhenVisible'. For renderer === 'partial' or 'always', switching tabs will still change which panel is active/visible, but config won’t be persisted to localStorage anymore (active panel can be lost on reload). Consider saving config on the relevant active/visibility transition regardless of renderer (keeping any maximized/initialization guards as needed).

Suggested change
if (renderer === 'onlyWhenVisible' && dockview._inited) {
if (isVisible) {
saveConfig(panel.accessor)
const visiblePanels = dockview.groups.map(g => g.panels.find(p => p.params.isActive) || g.panels.find(p => p.api.isVisible))
dockview._loadTabs?.fire(visiblePanels.filter(p => Boolean(p)).map(p => p.params.key));
}
else {
if (dockview._inited) {
if (isVisible) {
saveConfig(panel.accessor)
const visiblePanels = dockview.groups.map(g => g.panels.find(p => p.params.isActive) || g.panels.find(p => p.api.isVisible))
dockview._loadTabs?.fire(visiblePanels.filter(p => Boolean(p)).map(p => p.params.key));
}
else if (renderer === 'onlyWhenVisible') {

Copilot uses AI. Check for mistakes.
Comment on lines 8 to 10
observePanelActiveChange(panel)
}
const observePanelActiveChange = panel => {
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

observePanelActiveChange no longer observes “active” changes (it now subscribes to onDidVisibilityChange). Renaming this helper (and its call site) to reflect visibility semantics would make the code easier to follow and reduce confusion for future maintenance.

Suggested change
observePanelActiveChange(panel)
}
const observePanelActiveChange = panel => {
observePanelVisibilityChange(panel)
}
const observePanelVisibilityChange = panel => {

Copilot uses AI. Check for mistakes.

<PropertyGroup>
<Version>10.0.8-beta06</Version>
<Version>10.0.8</Version>
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

PR metadata is inconsistent (description says fixes #980, while the referenced PR text says fixes #979), and this diff also changes the package version from 10.0.8-beta06 to 10.0.8. If a stable release is intended, please align the issue reference(s) and call out the version bump explicitly; otherwise consider keeping the pre-release suffix for a feature PR.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(DockView): add onPanelVisibleChanged event

3 participants