Skip to content

Conversation

@wyu71
Copy link
Contributor

@wyu71 wyu71 commented Dec 31, 2025

  • Added keyboard focus handling for staging notification buttons
  • Implemented Tab cycling between staging area, header, and notification list
  • Fixed button unloading issue during Tab navigation

Log: enable Tab focus for staging area buttons in notification center
pms: BUG-345531

Summary by Sourcery

Improve keyboard Tab navigation and focus handling across the notification center, staging area, and notification items.

Bug Fixes:

  • Fix loss of the close button when Tab navigating by keeping its loader active while the button has focus.
  • Ensure staging area notification buttons can be focused and cycled with Tab and Shift+Tab without breaking focus.

Enhancements:

  • Add structured Tab and Shift+Tab focus cycling between staging area, header controls, and the main notification list.
  • Propagate external hover/focus state into notification item content via a dedicated property instead of overriding close visibility.
  • Expose helper functions and signals on notification center and staging components to programmatically focus first/last items and header buttons for accessibility.

- Added keyboard focus handling for staging notification buttons
- Implemented Tab cycling between staging area, header, and notification list
- Fixed button unloading issue during Tab navigation

Log: enable Tab focus for staging area buttons in notification center
pms: BUG-345531
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 31, 2025

Reviewer's Guide

Implements full keyboard Tab/Shift+Tab focus cycling between the staging area, header, and notification list in the notification center, and fixes the staging close button loader being unloaded while focused by refactoring hover/focus handling in notification item content.

Sequence diagram for Tab navigation from staging item to header

sequenceDiagram
    actor User
    participant NotifyStaging_root as NotifyStaging
    participant ListView_view as ListView_view
    participant OverlapNotify as OverlapNotify
    participant NotifyCenter_root as NotifyCenter
    participant Header as Header

    User->>OverlapNotify: Press Tab key
    OverlapNotify->>OverlapNotify: Keys.onTabPressed(event)
    OverlapNotify->>OverlapNotify: overlapNotify.focusFirstButton()
    alt InternalButtonFocused
        OverlapNotify-->>User: event.accepted = true
    else NoInternalButton
        OverlapNotify->>OverlapNotify: Qt.callLater(focusFirstButtonAgain)
        OverlapNotify->>NotifyStaging_root: navigateToNextItem(index)
        NotifyStaging_root->>ListView_view: Update currentIndex
        NotifyStaging_root->>NotifyStaging_root: Qt.callLater(focus next item)
        alt NextItemExists
            NotifyStaging_root->>ListView_view: nextItem.forceActiveFocus()
            ListView_view-->>User: Focus moves to next staging item
        else LastItemReached
            NotifyStaging_root->>NotifyCenter_root: gotoHeaderFirst()
            NotifyCenter_root->>Header: focusFirstButton()
            Header-->>User: Focus moves to header first button
        end
    end
Loading

Sequence diagram for Shift+Tab navigation from header to staging or list

sequenceDiagram
    actor User
    participant Header as Header
    participant NotifyCenter_root as NotifyCenter
    participant NotifyStaging_root as NotifyStaging
    participant NotifyView as NotifyView

    User->>Header: Press Shift+Tab
    Header->>NotifyCenter_root: gotoStagingLast()
    alt StagingHasItems (notifyStaging.viewCount > 0)
        NotifyCenter_root->>NotifyStaging_root: focusLastButton()
        NotifyStaging_root-->>User: Focus moves to last button in last staging item
    else NoStagingItems
        alt ViewHasItems (notifyCenter.viewCount > 0)
            NotifyCenter_root->>NotifyView: focusViewLastItem()
            NotifyView-->>User: Focus moves to last notification item
        else NoViewItems
            NotifyCenter_root->>NotifyCenter_root: focusHeaderLast()
            NotifyCenter_root-->>User: Focus remains on header last button
        end
    end
Loading

Class diagram for updated notification center focus logic

classDiagram
    class NotifyStaging {
        property var model
        readonly property int viewCount
        signal gotoHeaderFirst()
        signal gotoHeaderLast()
        function focusFirstItem()
        function focusLastButton()
        function navigateToNextItem(currentIndex)
        function navigateToPrevItem(currentIndex)
    }

    class NotifyCenter {
        property alias viewPanelShown
        property int maxViewHeight
        property int stagingViewCount
        readonly property int viewCount
        signal gotoStagingLast()
        signal gotoStagingFirst()
        function focusHeaderFirst()
        function focusHeaderLast()
        function focusViewLastItem()
    }

    class NotifyItemContent {
        implicitHeight
        readonly property int maxFocusRetries
        property bool parentHovered
        property bool closeVisible
        property int miniContentHeight
        property bool enableDismissed
        property alias clearButton
    }

    class OverlapNotify {
        property bool activeFocus
        property bool impl_hovered
    }

    class NormalNotify {
        property bool activeFocus
        property bool impl_hovered
    }

    class ClearLoader {
        property bool active
        property var item
    }

    NotifyCenter "1" o-- "1" NotifyStaging : contains
    NotifyCenter "1" o-- "1" Header : uses
    NotifyCenter "1" o-- "1" NotificationListView : uses

    NotifyStaging "1" o-- "1" ListView : manages
    NotifyStaging "1" --> "many" OverlapNotify : items

    OverlapNotify "1" o-- "1" NotifyItemContent : wraps
    NormalNotify "1" o-- "1" NotifyItemContent : wraps

    NotifyItemContent "1" o-- "1" ClearLoader : owns

    class Header {
        function focusFirstButton()
        function focusLastButton()
    }

    class NotificationListView {
        readonly property int viewCount
        function focusLastItem()
        function focusItemAtIndex(index)
    }

    class ListView {
        property int count
        property int currentIndex
        function itemAtIndex(index)
    }

    ClearLoader : active depends on closeVisible
    NotifyItemContent : closeVisible = activeFocus || impl.hovered || parentHovered || clearLoader.item.activeFocus
    OverlapNotify : parentHovered = impl.hovered || activeFocus
    NormalNotify : parentHovered = impl.hovered || activeFocus
Loading

Flow diagram for focus routing between staging, header, and notification list

flowchart LR
    subgraph Window_main
        subgraph NotifyCenter
            Header[Header]
            View[NotificationListView]
            Staging[NotifyStaging]
        end
    end

    StagingFirst[Staging first item]
    StagingLastButton[Staging last button]
    ViewFirst[List first item]
    ViewLast[List last item]
    HeaderFirst[Header first button]
    HeaderLast[Header last button]

    StagingFirst -->|Shift+Tab| HeaderLast
    StagingLastButton -->|Tab when last item| HeaderFirst

    HeaderFirst -->|Tab| ViewFirst
    HeaderLast -->|Shift+Tab| StagingLastButton

    ViewLast -->|Tab| StagingFirst
    ViewFirst -->|Shift+Tab| HeaderLast

    HeaderFirst -->|Tab when no list items| StagingFirst
    HeaderLast -->|Shift+Tab when no staging items and list has items| ViewLast

    HeaderLast -->|Shift+Tab when no staging items and no list items| HeaderLast

    ViewLast -->|Tab when no staging items| HeaderFirst
Loading

File-Level Changes

Change Details Files
Add explicit focus navigation helpers and Tab/Shift+Tab handling for items in the staging area so focus can move between items and out to the header.
  • Introduce gotoHeaderFirst/gotoHeaderLast signals on the staging FocusScope to communicate focus transitions to the header
  • Add helper functions to focus the first/last staging item and to navigate to the next/previous item with safe use of view.itemAtIndex and Qt.callLater
  • Wire Keys.onTabPressed/Keys.onBacktabPressed on each staging item to first traverse internal buttons, then move focus to sibling items or out to header using the new helpers
  • Connect item-level gotoNextItem/gotoPrevItem signals to staging-level navigation functions or internal focus
panels/notification/center/NotifyStaging.qml
Integrate staging focus into the overall notification center Tab order, including header and main notification list, and define what happens when there are no staging items.
  • Expose viewCount and new gotoStagingFirst/gotoStagingLast signals on NotifyCenter to coordinate focus transitions with staging and the main list
  • Add focusHeaderFirst/focusHeaderLast/focusViewLastItem helper functions to encapsulate focus moves for header and list
  • Adjust header gotoFirstNotify/gotoLastNotify handlers to route focus into staging when present, otherwise to header or list as appropriate
  • In main.qml, hook staging and center signals so Shift+Tab from header can go to last staging item or list, and Tab from list can cycle to first staging item or back to header when staging is empty
panels/notification/center/NotifyCenter.qml
panels/notification/center/package/main.qml
Prevent the notification close button loader from unloading while its button has focus, and decouple close visibility from hover to support keyboard focus scenarios.
  • Add parentHovered property to NotifyItemContent and include it in closeVisible so external hover/focus can keep the close button visible
  • Update the clearLoader.active binding to also stay active when its loaded item has activeFocus, avoiding loader teardown during Tab navigation
  • Pass parentHovered from OverlapNotify and NormalNotify instead of overriding closeVisible, using their own hovered/activeFocus state
panels/notification/plugin/NotifyItemContent.qml
panels/notification/center/OverlapNotify.qml
panels/notification/center/NormalNotify.qml

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

Copy link

@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 left some high level feedback:

  • The focus management helpers in NotifyStaging.qml (focusFirstItem, focusLastButton, navigateToNextItem, navigateToPrevItem) use Qt.callLater in several places; consider consolidating the focus-change logic into a single utility or reducing nested callLater calls to avoid hard-to-debug timing issues when the model changes quickly.
  • focusFirstItem checks firstItem.enabled before focusing, but navigateToNextItem/navigateToPrevItem do not skip disabled items; if disabled notifications are possible, it would be more robust to mirror that check (and potentially loop to the next enabled item) so keyboard navigation never lands on an unusable control.
  • The naming of signals like gotoHeaderFirst/gotoHeaderLast in NotifyCenter.qml and NotifyStaging.qml no longer fully matches their behavior (sometimes they route to staging instead of the header); consider renaming or adding brief comments near the signal definitions to clarify the actual navigation path and reduce confusion for future maintainers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The focus management helpers in `NotifyStaging.qml` (`focusFirstItem`, `focusLastButton`, `navigateToNextItem`, `navigateToPrevItem`) use `Qt.callLater` in several places; consider consolidating the focus-change logic into a single utility or reducing nested `callLater` calls to avoid hard-to-debug timing issues when the model changes quickly.
- `focusFirstItem` checks `firstItem.enabled` before focusing, but `navigateToNextItem`/`navigateToPrevItem` do not skip disabled items; if disabled notifications are possible, it would be more robust to mirror that check (and potentially loop to the next enabled item) so keyboard navigation never lands on an unusable control.
- The naming of signals like `gotoHeaderFirst`/`gotoHeaderLast` in `NotifyCenter.qml` and `NotifyStaging.qml` no longer fully matches their behavior (sometimes they route to staging instead of the header); consider renaming or adding brief comments near the signal definitions to clarify the actual navigation path and reduce confusion for future maintainers.

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.

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.

1 participant