mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-05 16:04:43 +00:00
* feat(app): follow system vertical bars and fold divisions Keep custom navigation controls outside system content areas and use active division regions for temporary panel allocation. Normal sidebar preferences resume when the division disappears. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): show window actions in the active portal scope The bottom-sheet provider creates its own portal context. Register the window action host within that context so symbols reach the root host. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): hide workspace bar actions behind compact panels Compact panels keep the workspace route focused while covering its content. Hide its portal actions until the workspace panel is active again. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): continue the sidebar surface across divisions Keep the fold free of controls while painting it as part of the sidebar, with the shared divider at the workspace edge. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): omit tab count beside the full tab rail The full tab rail already exposes navigation. Use the same compact/full header selection for the vertical action list. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): preserve folded navigation and bar action lifetime Use resolved sidebar visibility for desktop activity, and keep bar controls mounted while compact panels cover them so pending menu work survives. Carry circular button chrome inside the portal and exercise the rendered boundary in Chromium. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * fix(app): own script actions across header placement changes Keep one script action instance on the workspace screen so pending restarts survive removal of either menu. Pass activity through the bar portal so covered menus close their overlays without taking workflow state with them. Co-authored-by: Elsa Granger <zeyugao@outlook.com> * test(app): exercise script ownership through the workspace Replace synthetic ownership fixtures with a real workspace, script menu and isolated terminal worker. Hold terminal exit until the compact header is removed so moving pending state back into the menu fails the regression. Exercise portal activity with production menu and header controls. Co-authored-by: Elsa Granger <zeyugao@outlook.com> --------- Co-authored-by: Elsa Granger <zeyugao@outlook.com>
111 lines
3.1 KiB
Swift
111 lines
3.1 KiB
Swift
import ExpoModulesCore
|
|
import UIKit
|
|
|
|
public final class PaseoWindowLayoutModule: Module {
|
|
public func definition() -> ModuleDefinition {
|
|
Name("PaseoWindowLayout")
|
|
View(WindowLayoutView.self) {
|
|
Events("onGeometryChange")
|
|
}
|
|
}
|
|
}
|
|
|
|
final class WindowLayoutView: ExpoView {
|
|
let onGeometryChange = EventDispatcher()
|
|
private var updateScheduled = false
|
|
private var previousPayload: [String: Any]?
|
|
private var divisions: [CGRect] = []
|
|
private var barGuide: UILayoutGuide?
|
|
private var barEdge = "none"
|
|
private var measuredBounds = CGRect.zero
|
|
|
|
required init(appContext: AppContext? = nil) {
|
|
super.init(appContext: appContext)
|
|
isUserInteractionEnabled = false
|
|
#if compiler(>=6.4)
|
|
if #available(iOS 27.1, *) {
|
|
registerForTraitChanges(UITraitCollection.systemTraitsAffectingVerticalBarEdge) {
|
|
(view: WindowLayoutView, _) in
|
|
view.setNeedsUpdateConstraints()
|
|
view.scheduleUpdate()
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
override func didMoveToWindow() {
|
|
super.didMoveToWindow()
|
|
previousPayload = nil
|
|
setNeedsUpdateConstraints()
|
|
scheduleUpdate()
|
|
}
|
|
|
|
override func safeAreaInsetsDidChange() {
|
|
super.safeAreaInsetsDidChange()
|
|
setNeedsUpdateConstraints()
|
|
scheduleUpdate()
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
if bounds != measuredBounds {
|
|
measuredBounds = bounds
|
|
setNeedsUpdateConstraints()
|
|
}
|
|
scheduleUpdate()
|
|
}
|
|
|
|
override func updateConstraints() {
|
|
#if compiler(>=6.4)
|
|
if #available(iOS 27.1, *) {
|
|
// UIKit tracks these reads and invalidates constraints when regions change.
|
|
divisions = reservedRegions(kind: .division).compactMap { $0.isActive ? $0.frame : nil }
|
|
let edge: String
|
|
switch traitCollection.verticalBarEdge {
|
|
case .leading: edge = "leading"
|
|
case .trailing: edge = "trailing"
|
|
default: edge = "none"
|
|
}
|
|
if edge != barEdge {
|
|
barEdge = edge
|
|
barGuide = edge == "none" ? nil : layoutGuide(
|
|
for: .bar(onEdge: edge == "trailing" ? .right : .left, extent: 44)
|
|
)
|
|
}
|
|
}
|
|
#endif
|
|
super.updateConstraints()
|
|
scheduleUpdate()
|
|
}
|
|
|
|
private func scheduleUpdate() {
|
|
guard !updateScheduled else { return }
|
|
updateScheduled = true
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self else { return }
|
|
self.updateScheduled = false
|
|
self.publish()
|
|
}
|
|
}
|
|
|
|
private func publish() {
|
|
guard window != nil, bounds.width > 0, bounds.height > 0 else { return }
|
|
let bar = barGuide?.layoutFrame
|
|
var barPayload: Any = NSNull()
|
|
if let bar, bar.width > 0, bar.height > 0 { barPayload = rect(bar) }
|
|
let payload: [String: Any] = [
|
|
"bounds": rect(bounds),
|
|
"verticalBarEdge": barEdge,
|
|
"verticalBar": barPayload,
|
|
"divisions": divisions.map(rect)
|
|
]
|
|
guard !NSDictionary(dictionary: payload).isEqual(to: previousPayload ?? [:]) else { return }
|
|
previousPayload = payload
|
|
onGeometryChange(payload)
|
|
}
|
|
|
|
private func rect(_ frame: CGRect) -> [String: CGFloat] {
|
|
["x": frame.minX, "y": frame.minY, "width": frame.width, "height": frame.height]
|
|
}
|
|
}
|