-
Notifications
You must be signed in to change notification settings - Fork 35
feat: Add hybrid mode for compact content in expanded state #42
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
Conversation
* feat: add showCompactContentInExpandedMode for hybrid notch layouts Adds support for "hybrid" layouts where compact leading/trailing content remains visible while in expanded state. This enables use cases like dictation UIs with waveform indicators alongside the notch while transcript content shows below. Changes: - Add `showCompactContentInExpandedMode` property (defaults to false) - Add init parameter for convenience - Implement symmetric layout in NotchView for hybrid expanded mode - Add comprehensive documentation for layout logic - Add test cases for notch and floating styles Also fixes: - Prevent continuation leak in hide() when closePanelTask is cancelled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Make NSScreen extensions public Expose `hasNotch`, `notchSize`, `notchFrame`, `menubarHeight`, and `screenWithMouse` for client apps that need to detect notch presence and adjust their behavior accordingly (e.g., always using expand() instead of compact() on non-notch Macs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Auto-enable hybrid mode in floating style for UX consistency When compact() is called in floating mode, instead of hiding the window, expand with hybrid mode enabled. This ensures compact indicators (like waveforms, status icons, cancel buttons) remain visible on non-notch Macs, providing consistent UX across all Mac hardware. Previously, non-notch Mac users would not see compact content at all since floating mode has no physical notch to flank. Now they see the same indicators as notch Mac users, displayed alongside the expanded content. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Add hybrid mode support for floating style fallback - Add internal floatingHybridModeActive flag to avoid mutating user property - Add isHybridModeEnabled computed property for unified hybrid mode check - Update compact() to auto-enable hybrid mode on floating style - Add compact indicators overlay to NotchlessView for floating panels - Fix animation when transitioning to hybrid mode from expanded state - Add test cases for hybrid mode and floating fallback behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add compactCenterContent for floating fallback mode - Add compactCenterContent property for center UI in floating fallback - Add compactCenter case to DynamicNotchSection enum - Update NotchlessView to show indicators row with center content - Remove top inset gap when in hybrid mode - Reset floatingHybridModeActive on hide to prevent state persistence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: smooth direct transition from expanded to compact state Remove intermediate hide step when transitioning from expanded to compact, eliminating the visual flash. Uses conversionAnimation for seamless morphing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Prevent race condition when cancelling hide task Only call deinitializeWindow() if task wasn't cancelled, since a new window may have been opened by expand()/compact() after cancellation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Await hybrid mode animation to prevent race with hide() Changed fire-and-forget Task to awaited MainActor.run and added sleep to wait for animation completion before compact() returns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: make isHovering property public for client observation Allow clients to observe hover state changes via Combine to implement hover-based activation patterns (e.g., activate panel when mouse enters). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: comprehensive improvements for hybrid mode reliability Thread Safety & Race Conditions: - Use structured concurrency with MainActor.run instead of unstructured Tasks - Make state checks atomic in floating mode logic - Add task cancellation on all early return paths Memory Management: - Track observeScreenParameters Task and cancel in deinit - Use weak self capture to prevent retain cycles - Only reinitialize window when state is hidden Animation & State Transitions: - Extract animationDuration constant from DynamicNotchStyle - Make compact<->expanded transitions symmetric (both direct) - Reset floatingHybridModeActive even when already expanded - Move flag reset after animation completes to prevent visual glitch API Improvements: - Make floatingHybridModeActive private(set) - Optimize redundant hybrid mode animations - Add max retry count for hover-blocked hide operations View Fixes: - Constrain compact indicators row height in NotchlessView Tests: - Add comprehensive assertions for state verification - Add tests for flag resets, computed property logic, rapid transitions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix and docs: add hybrid mode documentation to README and DocC and fix code review issues (#2) * fix: preserve floatingHybridModeActive when compact() calls _expand() from hidden state The floatingHybridModeActive flag was being reset in _expand() before the guard check, which broke the floating fallback when compact() was called from .hidden state. The fix adds a resetHybridMode parameter to _expand() that defaults to true but is set to false when called from _compact(), preserving the hybrid mode flag set by the floating fallback logic. Also adds a test case to verify compact() works correctly from hidden state. * docs: add hybrid mode documentation to README and DocC Document the new hybrid mode feature: - README: Add Hybrid Mode section with code example and floating style behavior - DocC: Add Hybrid Mode section explaining the feature and automatic fallback * fix: improve floating hybrid mode reliability and documentation - Clarify README wording: indicators are "displayed when requested" not "always visible" - Fix icon sizing in floating hybrid mode by adding proper safeAreaInsets - Fix rapid state transition rendering by using opacity instead of conditional rendering - Add animation delay in test for state transition reliability - Apply swiftformat 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: prevent compact indicator layout compression during rapid transitions Add .fixedSize() and .layoutPriority(1) to compact leading/trailing content in floating view so SwiftUI preserves their intrinsic sizes even during rapid state changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> * fix: improve code quality and accessibility - Remove unused skipHide parameter from _expand() and _compact() - Add .accessibilityHidden() to opacity-hidden views for VoiceOver - Fix rapid transition rendering by always rendering compact row (use frame height instead of conditional rendering to prevent SwiftUI view identity corruption) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve floating mode icon layout and animation smoothness - Apply animation at VStack level for smooth container resize when hiding top row - Use explicit frame constraints for compact icons instead of fixedSize() - Disable matchedGeometryEffect in hybrid mode to prevent icon animation conflicts - Forward objectWillChange from internal DynamicNotch to DynamicNotchInfo - Reset shouldSkipHideWhenConverting when compactLeading is explicitly changed - Animate floatingHybridModeActive reset with conversionAnimation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve code quality, fix race conditions, and refine test behavior - Fix race condition in closePanelTask that caused missing trailing icon on rapid transitions - Add explicit frame constraints for compact icons in NotchView (fixes gradient sizing in notch mode) - Update gradient test to skip compact() in floating mode (tested separately in hybrid mode tests) - Expose showCompactContentInExpandedMode parameter on DynamicNotchInfo - Remove duplicated doc comments in DynamicNotchStyle - Update comment to reflect actual purpose (removed inset references) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change frame constraints from fixed width to minWidth for compact leading/trailing content in both NotchView and NotchlessView. This allows text content like "Pasted" to display fully instead of being clipped to icon size. - NotchView: content expands away from notch (leading→left, trailing→right) - NotchlessView: add fixedSize() and layoutPriority(1) for layout stability - Guard compactIconSize with max(..., 0) to prevent negative values 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Reset namespace in deinitializeWindow() to prevent stale references - Switch to conditional rendering with transition for compact indicators row instead of height 0 + clipped approach which caused layout issues - Remove unnecessary fixedSize() and layoutPriority() modifiers The previous approach of always rendering the row with height 0 and clipped caused the trailing content to not render on subsequent window recreations during rapid state transitions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make disableCompactLeading and disableCompactTrailing properties public so developers can dynamically hide compact views on DynamicNotch instances - Fix floating mode (NotchlessView) to use conditional rendering instead of opacity, ensuring disabled views don't reserve space - Use HStack spacing for cleaner layout when views are conditionally removed No breaking changes - existing DynamicNotchInfo API continues to work unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
|
Hi! As you mentioned in your description, this PR goes well beyond "Add hybrid mode for compact content in expanded state." Since PRs are squashed, it includes too many changes to review at once and also introduces breaking changes to the public API. Would you be able to split this into smaller, more focused PRs? Also, please open tickets (issues) before writing PRs for new features. That helps determine whether something should be added to this package in the first place! In this case, a hybrid mode isn’t planned, as that’s not how iOS handles things. As noted in the documentation, "[DynamicNotchKit] attempts to provide a similar experience to iOS's Dynamic Island [...]". A better solution might be allowing the view to expand beside the notch, rather than keeping a static compact view visible. |
Summary
This PR introduces hybrid mode - a layout option that displays compact leading/trailing content alongside the expanded notch panel. This enables use cases like dictation UIs with persistent waveform indicators, or media players with always-visible controls.
Key additions:
showCompactContentInExpandedModeproperty for hybrid layoutsNSScreenextensions for notch detection (hasNotch,notchSize,notchFrame)isHoveringproperty for hover-based activation patternsMotivation
When building interfaces like voice dictation UIs, developers need status indicators (waveforms, cancel buttons) that remain visible while expanded content displays below. Previously, compact content was only visible in
.compactstate on notch Macs and invisible entirely on non-notch Macs.This PR solves both:
Usage
On non-notch Macs,
compact()automatically enables hybrid mode in floating style.Changes
Features
showCompactContentInExpandedMode- compact content visible in expanded statecompactCenterContent- optional center content for floating hybrid modeNSScreenextensions for notch detectionisHoveringfor hover observationdisableCompactLeadinganddisableCompactTrailingproperties for dynamic compact view controlBug Fixes
hide()whenclosePanelTaskis cancelledCode Quality
MainActor.runanimationDurationconstant extracted fromDynamicNotchStyle.accessibilityHidden()for VoiceOver)Test Plan
Breaking Changes
None. All APIs are additive with sensible defaults.
Demo