@rdlabo/ionic-theme-ios27 1.2.0-2 → 1.2.0-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -1
- package/dist/focus-controller/index.d.ts +4 -2
- package/dist/focus-controller/index.d.ts.map +1 -1
- package/dist/focus-controller/index.js +100 -57
- package/dist/focus-controller/index.js.map +1 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -60
- package/dist/index.js.map +1 -0
- package/dist/native/components/index.d.ts +13 -10
- package/dist/native/components/index.d.ts.map +1 -1
- package/dist/native/components/index.js +39 -41
- package/dist/native/components/index.js.map +1 -0
- package/dist/native/components/ion-back-button.d.ts +6 -1
- package/dist/native/components/ion-back-button.d.ts.map +1 -1
- package/dist/native/components/ion-back-button.js +19 -13
- package/dist/native/components/ion-back-button.js.map +1 -0
- package/dist/native/components/ion-button.d.ts +8 -2
- package/dist/native/components/ion-button.d.ts.map +1 -1
- package/dist/native/components/ion-button.js +22 -17
- package/dist/native/components/ion-button.js.map +1 -0
- package/dist/native/components/ion-buttons.d.ts +8 -2
- package/dist/native/components/ion-buttons.d.ts.map +1 -1
- package/dist/native/components/ion-buttons.js +29 -33
- package/dist/native/components/ion-buttons.js.map +1 -0
- package/dist/native/components/ion-fab.d.ts +6 -1
- package/dist/native/components/ion-fab.d.ts.map +1 -1
- package/dist/native/components/ion-fab.js +131 -151
- package/dist/native/components/ion-fab.js.map +1 -0
- package/dist/native/components/ion-menu-button.d.ts +8 -2
- package/dist/native/components/ion-menu-button.d.ts.map +1 -1
- package/dist/native/components/ion-menu-button.js +29 -19
- package/dist/native/components/ion-menu-button.js.map +1 -0
- package/dist/native/components/ion-segment.d.ts +6 -1
- package/dist/native/components/ion-segment.d.ts.map +1 -1
- package/dist/native/components/ion-segment.js +29 -23
- package/dist/native/components/ion-segment.js.map +1 -0
- package/dist/native/components/ion-tab-bar.d.ts +6 -1
- package/dist/native/components/ion-tab-bar.d.ts.map +1 -1
- package/dist/native/components/ion-tab-bar.js +49 -40
- package/dist/native/components/ion-tab-bar.js.map +1 -0
- package/dist/native/components/searchable-tabs.d.ts +17 -15
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +382 -435
- package/dist/native/components/searchable-tabs.js.map +1 -0
- package/dist/native/definitions.d.ts +118 -111
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/definitions.js +0 -1
- package/dist/native/foldable/fold-state.d.ts +5 -0
- package/dist/native/foldable/fold-state.d.ts.map +1 -0
- package/dist/native/foldable/fold-state.js +14 -0
- package/dist/native/foldable/fold-state.js.map +1 -0
- package/dist/native/foldable/types.d.ts +12 -0
- package/dist/native/foldable/types.d.ts.map +1 -0
- package/dist/native/foldable/types.js +0 -0
- package/dist/native/index.d.ts +6 -3
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +228 -204
- package/dist/native/index.js.map +1 -0
- package/dist/native/prehide.d.ts +4 -2
- package/dist/native/prehide.d.ts.map +1 -1
- package/dist/native/prehide.js +220 -242
- package/dist/native/prehide.js.map +1 -0
- package/dist/native/runtime.d.ts +3 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +626 -724
- package/dist/native/runtime.js.map +1 -0
- package/dist/native/shared/candidate.d.ts +16 -11
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +96 -95
- package/dist/native/shared/candidate.js.map +1 -0
- package/dist/native/shared/crossfade.d.ts +6 -4
- package/dist/native/shared/crossfade.d.ts.map +1 -1
- package/dist/native/shared/crossfade.js +49 -45
- package/dist/native/shared/crossfade.js.map +1 -0
- package/dist/native/shared/dom.d.ts +7 -5
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +163 -211
- package/dist/native/shared/dom.js.map +1 -0
- package/dist/native/shared/events.d.ts +8 -6
- package/dist/native/shared/events.d.ts.map +1 -1
- package/dist/native/shared/events.js +12 -7
- package/dist/native/shared/events.js.map +1 -0
- package/dist/native/shared/icons.d.ts +4 -2
- package/dist/native/shared/icons.d.ts.map +1 -1
- package/dist/native/shared/icons.js +59 -53
- package/dist/native/shared/icons.js.map +1 -0
- package/dist/native/shared/modal.d.ts +3 -1
- package/dist/native/shared/modal.d.ts.map +1 -1
- package/dist/native/shared/modal.js +156 -139
- package/dist/native/shared/modal.js.map +1 -0
- package/dist/native/vertical-bars-web.d.ts +3 -1
- package/dist/native/vertical-bars-web.d.ts.map +1 -1
- package/dist/native/vertical-bars-web.js +332 -346
- package/dist/native/vertical-bars-web.js.map +1 -0
- package/dist/native-integration/index.d.ts +10 -8
- package/dist/native-integration/index.d.ts.map +1 -1
- package/dist/native-integration/index.js +102 -100
- package/dist/native-integration/index.js.map +1 -0
- package/dist/native-integration/transition.d.ts +3 -1
- package/dist/native-integration/transition.d.ts.map +1 -1
- package/dist/native-integration/transition.js +15 -6
- package/dist/native-integration/transition.js.map +1 -0
- package/dist/searchbar/index.d.ts +4 -2
- package/dist/searchbar/index.d.ts.map +1 -1
- package/dist/searchbar/index.js +64 -56
- package/dist/searchbar/index.js.map +1 -0
- package/dist/segment/index.d.ts +3 -1
- package/dist/segment/index.d.ts.map +1 -1
- package/dist/segment/index.js +465 -324
- package/dist/segment/index.js.map +1 -0
- package/dist/sheets-of-glass/animations.d.ts +13 -12
- package/dist/sheets-of-glass/animations.d.ts.map +1 -1
- package/dist/sheets-of-glass/animations.js +496 -306
- package/dist/sheets-of-glass/animations.js.map +1 -0
- package/dist/sheets-of-glass/index.d.ts +3 -1
- package/dist/sheets-of-glass/index.d.ts.map +1 -1
- package/dist/sheets-of-glass/index.js +294 -357
- package/dist/sheets-of-glass/index.js.map +1 -0
- package/dist/sheets-of-glass/interfaces.d.ts +11 -9
- package/dist/sheets-of-glass/interfaces.d.ts.map +1 -1
- package/dist/sheets-of-glass/interfaces.js +0 -1
- package/dist/tab-bar-searchable/animations/enter.d.ts +4 -2
- package/dist/tab-bar-searchable/animations/enter.d.ts.map +1 -1
- package/dist/tab-bar-searchable/animations/enter.js +50 -69
- package/dist/tab-bar-searchable/animations/enter.js.map +1 -0
- package/dist/tab-bar-searchable/animations/leave.d.ts +4 -2
- package/dist/tab-bar-searchable/animations/leave.d.ts.map +1 -1
- package/dist/tab-bar-searchable/animations/leave.js +48 -62
- package/dist/tab-bar-searchable/animations/leave.js.map +1 -0
- package/dist/tab-bar-searchable/index.d.ts +5 -3
- package/dist/tab-bar-searchable/index.d.ts.map +1 -1
- package/dist/tab-bar-searchable/index.js +125 -109
- package/dist/tab-bar-searchable/index.js.map +1 -0
- package/dist/transition/index.d.ts +9 -8
- package/dist/transition/index.d.ts.map +1 -1
- package/dist/transition/index.js +203 -192
- package/dist/transition/index.js.map +1 -0
- package/dist/transition/ios.transition.d.ts +7 -5
- package/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +15 -11
- package/dist/transition/ios.transition.js.map +1 -0
- package/dist/utils.d.ts +10 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +58 -66
- package/dist/utils.js.map +1 -0
- package/dist/vertical-bars.d.ts +5 -4
- package/dist/vertical-bars.js +4 -2
- package/docs/iphone-duo-with-original-theme.md +18 -29
- package/docs/iphone-duo.md +44 -204
- package/docs/native-ui-shell.md +15 -11
- package/docs/special-markup.md +2 -0
- package/docs/vertical-bars.md +283 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +1 -1
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +61 -16
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +118 -0
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +17 -8
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +2 -0
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +40 -0
- package/package.json +9 -6
- package/src/native/components/index.ts +3 -2
- package/src/native/components/ion-button.ts +3 -2
- package/src/native/components/ion-buttons.ts +4 -3
- package/src/native/components/ion-menu-button.ts +3 -2
- package/src/native/components/searchable-tabs.ts +8 -11
- package/src/native/definitions.ts +22 -1
- package/src/native/foldable/fold-state.ts +9 -0
- package/src/native/foldable/types.ts +4 -0
- package/src/native/index.ts +17 -3
- package/src/native/prehide.ts +3 -1
- package/src/native/runtime.ts +14 -9
- package/src/native/shared/candidate.ts +39 -10
- package/src/native/shared/dom.ts +4 -4
- package/src/styles/vertical-bars.scss +12 -2
- package/src/tab-bar-searchable/index.ts +40 -9
- package/src/vertical-bars.ts +9 -1
- package/dist/vertical-bars.d.ts.map +0 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@available(iOS 26.0, *)
|
|
6
|
+
final class ShellVerticalSearchModel: ObservableObject {
|
|
7
|
+
@Published private(set) var configuration: ShellSearch
|
|
8
|
+
@Published var text: String
|
|
9
|
+
@Published var presented: Bool
|
|
10
|
+
@Published var focused: Bool
|
|
11
|
+
private weak var railView: UIView?
|
|
12
|
+
private let activate: (String) -> Void
|
|
13
|
+
private let changed: (String, ShellSearchPhase, String, Bool, Int) -> Int
|
|
14
|
+
private let inputDelegate = ShellSearchInputDelegate()
|
|
15
|
+
private var editingSequence = 0
|
|
16
|
+
private var valueVersion: Int
|
|
17
|
+
|
|
18
|
+
init(_ configuration: ShellSearch, in railView: UIView,
|
|
19
|
+
activate: @escaping (String) -> Void,
|
|
20
|
+
changed: @escaping (String, ShellSearchPhase, String, Bool, Int) -> Int) {
|
|
21
|
+
self.configuration = configuration
|
|
22
|
+
self.railView = railView
|
|
23
|
+
self.activate = activate
|
|
24
|
+
self.changed = changed
|
|
25
|
+
text = configuration.value
|
|
26
|
+
presented = configuration.available && configuration.active
|
|
27
|
+
focused = configuration.focused
|
|
28
|
+
valueVersion = configuration.valueVersion
|
|
29
|
+
inputDelegate.clear = { [weak self] in self?.emit(.clear) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var ownsKeyboardChrome: Bool { presented && (focused || textField?.isFirstResponder == true) }
|
|
33
|
+
|
|
34
|
+
private var textField: UISearchTextField? {
|
|
35
|
+
func find(_ view: UIView) -> UISearchTextField? {
|
|
36
|
+
guard !view.isHidden, view.alpha > 0.01 else { return nil }
|
|
37
|
+
if let field = view as? UISearchTextField { return field }
|
|
38
|
+
for child in view.subviews {
|
|
39
|
+
if let field = find(child) { return field }
|
|
40
|
+
}
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
return railView.flatMap(find)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func apply(_ next: ShellSearch) {
|
|
47
|
+
let previous = configuration
|
|
48
|
+
configuration = next
|
|
49
|
+
if next.valueVersion != valueVersion || next.editSequence >= editingSequence {
|
|
50
|
+
valueVersion = next.valueVersion
|
|
51
|
+
text = next.value
|
|
52
|
+
}
|
|
53
|
+
presented = next.available && next.active
|
|
54
|
+
// The first Web acknowledgement still carries the old focus state.
|
|
55
|
+
if !next.active { focused = false }
|
|
56
|
+
else if previous.focused != next.focused { focused = next.focused }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func input(_ value: String) {
|
|
60
|
+
text = value
|
|
61
|
+
emit(.input)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func present(_ value: Bool) {
|
|
65
|
+
presented = value
|
|
66
|
+
if value != configuration.active {
|
|
67
|
+
activate(value ? configuration.trigger.id : configuration.closeId)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func focus(_ value: Bool) {
|
|
72
|
+
focused = value
|
|
73
|
+
emit(value ? .focus : .blur)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func commit() { emit(.commit) }
|
|
77
|
+
|
|
78
|
+
func configureField() {
|
|
79
|
+
guard let field = textField else { return }
|
|
80
|
+
field.accessibilityIdentifier = configuration.id
|
|
81
|
+
field.accessibilityLabel = configuration.field.accessibilityLabel
|
|
82
|
+
field.isEnabled = !configuration.disabled
|
|
83
|
+
field.autocapitalizationType = .none
|
|
84
|
+
field.autocorrectionType = .no
|
|
85
|
+
field.spellCheckingType = .no
|
|
86
|
+
field.clearButtonMode = .always
|
|
87
|
+
if field.delegate !== inputDelegate {
|
|
88
|
+
inputDelegate.original = field.delegate
|
|
89
|
+
field.delegate = inputDelegate
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func emit(_ phase: ShellSearchPhase) {
|
|
94
|
+
editingSequence = changed(configuration.id, phase, text,
|
|
95
|
+
textField?.markedTextRange != nil, valueVersion)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@available(iOS 26.0, *)
|
|
100
|
+
struct ShellVerticalSearchModifier: ViewModifier {
|
|
101
|
+
@ObservedObject var model: ShellVerticalSearchModel
|
|
102
|
+
@FocusState private var focused: Bool
|
|
103
|
+
|
|
104
|
+
@ViewBuilder func body(content: Content) -> some View {
|
|
105
|
+
if model.configuration.available {
|
|
106
|
+
content
|
|
107
|
+
.searchable(text: Binding(get: { model.text }, set: model.input),
|
|
108
|
+
isPresented: Binding(get: { model.presented }, set: model.present),
|
|
109
|
+
placement: .toolbar, prompt: Text(model.configuration.placeholder))
|
|
110
|
+
.searchToolbarBehavior(.minimize)
|
|
111
|
+
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
|
112
|
+
.searchFocused($focused)
|
|
113
|
+
.onSubmit(of: .search, model.commit)
|
|
114
|
+
.onChange(of: focused) { _, value in model.focus(value) }
|
|
115
|
+
.onChange(of: model.focused) { _, value in focused = value }
|
|
116
|
+
} else { content }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -46,7 +46,7 @@ public class IonicNativeUIShellPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDele
|
|
|
46
46
|
self.host?.isHidden = true
|
|
47
47
|
self.verticalBars?.view.isHidden = true
|
|
48
48
|
}
|
|
49
|
-
var searchOwnsKeyboard =
|
|
49
|
+
var searchOwnsKeyboard = self.verticalBars?.ownsKeyboardChrome == true
|
|
50
50
|
self.searchControllers.values.forEach { controller in
|
|
51
51
|
if controller.ownsKeyboardChrome { searchOwnsKeyboard = true }
|
|
52
52
|
if !keyboard { controller.surface.isHidden = true }
|
|
@@ -195,6 +195,7 @@ public class IonicNativeUIShellPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDele
|
|
|
195
195
|
let existing = Set(self.controls.keys)
|
|
196
196
|
let verticalBars = snapshot.controls.filter { $0.placement == .verticalBars }
|
|
197
197
|
let snapshots = snapshot.controls.filter { $0.placement != .verticalBars }
|
|
198
|
+
let verticalSearchActive = verticalBars.contains { $0.search.map { $0.available && $0.active } ?? false }
|
|
198
199
|
let width = snapshot.viewportWidth
|
|
199
200
|
self.revision = next
|
|
200
201
|
if snapshots.isEmpty && verticalBars.isEmpty {
|
|
@@ -210,12 +211,16 @@ public class IonicNativeUIShellPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDele
|
|
|
210
211
|
var fabs: [(ShellFab, ShellControl)] = []
|
|
211
212
|
var searches: [(ShellSearchControlling, ShellControl, CGRect, CGRect, UIView?, Bool)] = []
|
|
212
213
|
var rejectedSearches: [String] = []
|
|
213
|
-
if verticalBars.isEmpty || self.keyboardVisible {
|
|
214
|
+
if verticalBars.isEmpty || (self.keyboardVisible && !verticalSearchActive) {
|
|
214
215
|
self.verticalBars?.detach()
|
|
215
216
|
self.verticalBars = nil
|
|
216
217
|
if self.keyboardVisible { rejectedControls.append(contentsOf: verticalBars.map(\.id)) }
|
|
217
218
|
} else if let owner = self.bridge?.viewController {
|
|
218
|
-
let rail = self.verticalBars ?? ShellVerticalBarsController(
|
|
219
|
+
let rail = self.verticalBars ?? ShellVerticalBarsController(
|
|
220
|
+
activate: { [weak self] id in self?.activate(id) },
|
|
221
|
+
changed: { [weak self] id, phase, value, composing, valueVersion in
|
|
222
|
+
self?.searchChanged(id, phase: phase, value: value, composing: composing, valueVersion: valueVersion) ?? 0
|
|
223
|
+
})
|
|
219
224
|
self.verticalBars = rail
|
|
220
225
|
rail.attach(to: owner, in: owner.view)
|
|
221
226
|
if let frame = snapshot.verticalBarFrame {
|
|
@@ -274,11 +279,7 @@ public class IonicNativeUIShellPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDele
|
|
|
274
279
|
controller = ShellSearchController()
|
|
275
280
|
controller.activate = { [weak self] id in self?.activate(id) }
|
|
276
281
|
controller.changed = { [weak self] id, phase, value, composing, valueVersion in
|
|
277
|
-
|
|
278
|
-
self.sequence += 1
|
|
279
|
-
self.notifyListeners("search", data: ["id": id, "phase": phase.rawValue, "value": value,
|
|
280
|
-
"composing": composing, "valueVersion": valueVersion, "sequence": self.sequence, "revision": self.revision])
|
|
281
|
-
return self.sequence
|
|
282
|
+
self?.searchChanged(id, phase: phase, value: value, composing: composing, valueVersion: valueVersion) ?? 0
|
|
282
283
|
}
|
|
283
284
|
controller.attach(to: owner, in: owner.view)
|
|
284
285
|
self.searchControllers[id] = controller
|
|
@@ -379,6 +380,14 @@ public class IonicNativeUIShellPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDele
|
|
|
379
380
|
activate(itemId)
|
|
380
381
|
}
|
|
381
382
|
|
|
383
|
+
private func searchChanged(_ id: String, phase: ShellSearchPhase, value: String,
|
|
384
|
+
composing: Bool, valueVersion: Int) -> Int {
|
|
385
|
+
sequence += 1
|
|
386
|
+
notifyListeners("search", data: ["id": id, "phase": phase.rawValue, "value": value,
|
|
387
|
+
"composing": composing, "valueVersion": valueVersion, "sequence": sequence, "revision": revision])
|
|
388
|
+
return sequence
|
|
389
|
+
}
|
|
390
|
+
|
|
382
391
|
private func activate(_ id: String) {
|
|
383
392
|
sequence += 1
|
|
384
393
|
notifyListeners("activate", data: ["id": id, "revision": revision, "sequence": sequence])
|
|
@@ -99,6 +99,7 @@ struct ShellBadge: Decodable, Equatable {
|
|
|
99
99
|
|
|
100
100
|
struct ShellItemContent: Decodable, Equatable {
|
|
101
101
|
enum IconPosition: String, Decodable { case leading, trailing, top }
|
|
102
|
+
enum ButtonFill: String, Decodable { case clear, solid, outline }
|
|
102
103
|
let id: String
|
|
103
104
|
var label: String
|
|
104
105
|
let accessibilityLabel: String
|
|
@@ -107,6 +108,7 @@ struct ShellItemContent: Decodable, Equatable {
|
|
|
107
108
|
let fontSize: Double
|
|
108
109
|
let fontWeight: Double
|
|
109
110
|
let color: String
|
|
111
|
+
let buttonFill: ButtonFill?
|
|
110
112
|
let backgroundColor: String?
|
|
111
113
|
let borderColor: String?
|
|
112
114
|
let borderWidth: CGFloat?
|
|
@@ -53,16 +53,19 @@ final class ShellSnapshotTests: XCTestCase {
|
|
|
53
53
|
guard #available(iOS 26.0, *) else { throw XCTSkip("Requires SwiftUI adaptive controls") }
|
|
54
54
|
let controls = try decode([control(["kind": "ion-buttons", "items": [
|
|
55
55
|
item(["backgroundColor": "rgb(0, 122, 255)"]),
|
|
56
|
+
item(["id": "clear", "buttonFill": "clear"]),
|
|
56
57
|
item(["id": "outline", "borderColor": "rgb(255, 0, 0)", "borderWidth": 2.0])
|
|
57
58
|
]])]).controls
|
|
58
59
|
XCTAssertEqual(controls.first?.items.first?.content.backgroundColor, "rgb(0, 122, 255)")
|
|
59
60
|
let model = ShellVerticalBarsModel()
|
|
60
61
|
model.apply(controls, rendering: ShellRendering())
|
|
61
62
|
XCTAssertNotNil(model.groups.first?.items.first?.background)
|
|
63
|
+
XCTAssertEqual(model.groups.first?.items.map(\.clear), [false, true, false])
|
|
62
64
|
XCTAssertNotNil(model.groups.first?.items.last?.borderColor)
|
|
63
65
|
XCTAssertEqual(model.groups.first?.items.last?.borderWidth, 2)
|
|
64
66
|
model.apply(try decode([control(["kind": "ion-button"])]).controls, rendering: ShellRendering())
|
|
65
67
|
XCTAssertNil(model.groups.first?.items.first?.background)
|
|
68
|
+
XCTAssertEqual(model.groups.first?.items.first?.clear, false)
|
|
66
69
|
XCTAssertNil(model.groups.first?.items.first?.borderColor)
|
|
67
70
|
XCTAssertEqual(model.groups.first?.items.first?.borderWidth, 0)
|
|
68
71
|
}
|
|
@@ -607,4 +610,41 @@ final class ShellSnapshotTests: XCTestCase {
|
|
|
607
610
|
XCTAssertEqual(phases, ["input"])
|
|
608
611
|
}
|
|
609
612
|
|
|
613
|
+
func testVerticalSearchBridgesInputFocusAndDismissalWithoutStaleWebEchoes() throws {
|
|
614
|
+
guard #available(iOS 26.0, *) else { throw XCTSkip("Requires native glass search") }
|
|
615
|
+
func node(active: Bool, value: String = "", version: Int = 0) throws -> ShellControl {
|
|
616
|
+
let search: JSObject = ["id": "search", "field": item(),
|
|
617
|
+
"trigger": item(["id": "trigger"]), "closeId": "close", "active": active,
|
|
618
|
+
"available": true, "focused": false, "value": value, "placeholder": "Search",
|
|
619
|
+
"disabled": false, "editSequence": 0, "valueVersion": version]
|
|
620
|
+
return try XCTUnwrap(decode([control(["kind": "ion-tab-bar", "placement": "vertical-bars",
|
|
621
|
+
"search": search, "items": [item(["id": "first", "selected": true]), item(["id": "second"])]])]).controls.first)
|
|
622
|
+
}
|
|
623
|
+
var actions: [String] = []
|
|
624
|
+
var values: [String] = []
|
|
625
|
+
let rail = UIView()
|
|
626
|
+
let model = ShellVerticalSearchModel(try XCTUnwrap(node(active: false).search), in: rail,
|
|
627
|
+
activate: { actions.append($0) },
|
|
628
|
+
changed: { _, _, value, _, _ in values.append(value); return values.count })
|
|
629
|
+
model.present(true)
|
|
630
|
+
XCTAssertEqual(actions, ["trigger"])
|
|
631
|
+
model.apply(try XCTUnwrap(node(active: true).search))
|
|
632
|
+
XCTAssertTrue(model.presented)
|
|
633
|
+
model.input("native query")
|
|
634
|
+
XCTAssertEqual(values, ["native query"])
|
|
635
|
+
model.apply(try XCTUnwrap(node(active: true).search))
|
|
636
|
+
XCTAssertEqual(model.text, "native query")
|
|
637
|
+
// The first presentation acknowledgement must not undo native focus.
|
|
638
|
+
model.focus(true)
|
|
639
|
+
model.apply(try XCTUnwrap(node(active: true).search))
|
|
640
|
+
XCTAssertTrue(model.focused)
|
|
641
|
+
model.apply(try XCTUnwrap(node(active: true, value: "application", version: 1).search))
|
|
642
|
+
XCTAssertEqual(model.text, "application")
|
|
643
|
+
model.present(false)
|
|
644
|
+
XCTAssertEqual(actions, ["trigger", "close"])
|
|
645
|
+
model.apply(try XCTUnwrap(node(active: false).search))
|
|
646
|
+
XCTAssertFalse(model.presented)
|
|
647
|
+
XCTAssertFalse(model.focused)
|
|
648
|
+
}
|
|
649
|
+
|
|
610
650
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdlabo/ionic-theme-ios27",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.2.0-
|
|
4
|
+
"version": "1.2.0-4",
|
|
5
5
|
"description": "iOS27 Theme for Ionic Framework",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -34,13 +34,14 @@
|
|
|
34
34
|
"build": "npm run docgen && npm run build:css && npm run build:ts",
|
|
35
35
|
"prebuild:css": "rdlabo-copy-structured-list src/styles/utils/structured-list.scss",
|
|
36
36
|
"build:css": "rm -rf dist/css && sass src/styles:dist/css --style=compressed --no-source-map",
|
|
37
|
-
"build:ts": "tsc",
|
|
38
|
-
"docgen": "docgen --project tsconfig.docgen.json --api NativeUIShellHandle --output-readme docs/native-ui-shell.md && docgen --project tsconfig.docgen.json --api VerticalControlAreaHandle --output-readme docs/
|
|
37
|
+
"build:ts": "tsc --noEmit && rdlabo-build-theme",
|
|
38
|
+
"docgen": "docgen --project tsconfig.docgen.json --api NativeUIShellHandle --output-readme docs/native-ui-shell.md && docgen --project tsconfig.docgen.json --api VerticalControlAreaHandle --output-readme docs/vertical-bars.md",
|
|
39
39
|
"build:demo": "npm run build && cd demo && npm install && npm run build -- --configuration=production",
|
|
40
40
|
"lint": "prettier --check \"./**/*.{scss,ts}\" && prettier --parser angular --check \"./**/*.html\"",
|
|
41
41
|
"fmt": "prettier --write \"./**/*.{scss,ts}\" && prettier --parser angular --write \"./**/*.html\"",
|
|
42
42
|
"release": "np --no-tests --no-publish",
|
|
43
|
-
"prepublishOnly": "npm run build"
|
|
43
|
+
"prepublishOnly": "npm run build",
|
|
44
|
+
"test:esm": "rdlabo-check-esm"
|
|
44
45
|
},
|
|
45
46
|
"repository": {
|
|
46
47
|
"type": "git",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
},
|
|
61
62
|
"homepage": "https://docs.rdlabo.dev/projects/ionic-theme-ios27",
|
|
62
63
|
"dependencies": {
|
|
63
|
-
"@rdlabo/ionic-theme-utils": "0.1.
|
|
64
|
+
"@rdlabo/ionic-theme-utils": "0.1.2"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"@capacitor/core": "^8.5.2",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"np": "^10.2.0",
|
|
72
73
|
"prettier": "^3.6.2",
|
|
73
74
|
"sass": "^1.93.2",
|
|
75
|
+
"tsdown": "^0.23.0",
|
|
74
76
|
"typescript": "^5.9.3",
|
|
75
77
|
"wrangler": "^4.125.0"
|
|
76
78
|
},
|
|
@@ -87,5 +89,6 @@
|
|
|
87
89
|
"ios": {
|
|
88
90
|
"src": "ios"
|
|
89
91
|
}
|
|
90
|
-
}
|
|
92
|
+
},
|
|
93
|
+
"type": "module"
|
|
91
94
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions';
|
|
1
2
|
import * as button from './ion-button';
|
|
2
3
|
import * as buttons from './ion-buttons';
|
|
3
4
|
import * as backButton from './ion-back-button';
|
|
@@ -29,7 +30,7 @@ export const motionSelector = [
|
|
|
29
30
|
|
|
30
31
|
export const isVerticalBarsCandidate = isVerticalBarsSource;
|
|
31
32
|
|
|
32
|
-
export const readCandidate = (element: HTMLElement, id: Identify): Candidate | undefined => {
|
|
33
|
+
export const readCandidate = (element: HTMLElement, id: Identify, options: VerticalControlAreaOptions = {}): Candidate | undefined => {
|
|
33
34
|
const verticalBars = isVerticalBarsCandidate(element);
|
|
34
35
|
if (
|
|
35
36
|
(!element.classList.contains('ios') && !verticalBars) ||
|
|
@@ -47,5 +48,5 @@ export const readCandidate = (element: HTMLElement, id: Identify): Candidate | u
|
|
|
47
48
|
)
|
|
48
49
|
return;
|
|
49
50
|
if (element.contains(element.ownerDocument.activeElement)) return;
|
|
50
|
-
return components.find((component) => component.tag === element.localName)?.read(element, id);
|
|
51
|
+
return components.find((component) => component.tag === element.localName)?.read(element, id, options);
|
|
51
52
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions';
|
|
1
2
|
import { modalUsesVerticalBars } from '../shared/modal';
|
|
2
3
|
import { createCandidate, appendItem } from '../shared/candidate';
|
|
3
4
|
import type { Candidate, Identify } from '../shared/candidate';
|
|
@@ -5,7 +6,7 @@ import { inFixedToolbar, isVerticalBarsToolbarAction, isVerticalBarsToolbarGroup
|
|
|
5
6
|
|
|
6
7
|
export const tag = 'ion-button';
|
|
7
8
|
|
|
8
|
-
export const read = (element: HTMLElement, id: Identify): Candidate | undefined => {
|
|
9
|
+
export const read = (element: HTMLElement, id: Identify, options: VerticalControlAreaOptions = {}): Candidate | undefined => {
|
|
9
10
|
const button = element as HTMLIonButtonElement;
|
|
10
11
|
const verticalBars =
|
|
11
12
|
!element.closest('ion-menu, ion-popover') && modalUsesVerticalBars(element) && !!element.closest('ion-app.ios-theme-vertical-bars');
|
|
@@ -18,6 +19,6 @@ export const read = (element: HTMLElement, id: Identify): Candidate | undefined
|
|
|
18
19
|
(!verticalBars && button.classList.contains('ion-color'))
|
|
19
20
|
)
|
|
20
21
|
return;
|
|
21
|
-
const candidate = createCandidate(element, tag, id);
|
|
22
|
+
const candidate = createCandidate(element, tag, id, options);
|
|
22
23
|
return appendItem(candidate, element, id) ? candidate : undefined;
|
|
23
24
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions';
|
|
1
2
|
import { modalUsesVerticalBars } from '../shared/modal';
|
|
2
3
|
import { createCandidate, appendItem } from '../shared/candidate';
|
|
3
4
|
import type { Candidate, Identify } from '../shared/candidate';
|
|
@@ -7,10 +8,10 @@ import * as menuButton from './ion-menu-button';
|
|
|
7
8
|
export const tag = 'ion-buttons';
|
|
8
9
|
export const tracksMotion = true;
|
|
9
10
|
|
|
10
|
-
export const read = (element: HTMLElement, id: Identify): Candidate | undefined => {
|
|
11
|
+
export const read = (element: HTMLElement, id: Identify, options: VerticalControlAreaOptions = {}): Candidate | undefined => {
|
|
11
12
|
if (!inFixedToolbar(element)) return;
|
|
12
13
|
let children = childElements(element);
|
|
13
|
-
if (children.length === 1) return menuButton.read(element, id);
|
|
14
|
+
if (children.length === 1) return menuButton.read(element, id, options);
|
|
14
15
|
const verticalBars =
|
|
15
16
|
!element.closest('ion-menu, ion-popover') && modalUsesVerticalBars(element) && !!element.closest('ion-app.ios-theme-vertical-bars');
|
|
16
17
|
if (verticalBars && !isVerticalBarsToolbarGroup(element)) return;
|
|
@@ -25,7 +26,7 @@ export const read = (element: HTMLElement, id: Identify): Candidate | undefined
|
|
|
25
26
|
)
|
|
26
27
|
)
|
|
27
28
|
return;
|
|
28
|
-
const candidate = createCandidate(element, tag, id);
|
|
29
|
+
const candidate = createCandidate(element, tag, id, options);
|
|
29
30
|
if (verticalBars && children.length !== childElements(element).length) candidate.sources = children;
|
|
30
31
|
for (const child of children) {
|
|
31
32
|
const supported = child.matches(menuButton.tag)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createCandidate, appendItem } from '../shared/candidate';
|
|
2
2
|
import type { Candidate, Identify } from '../shared/candidate';
|
|
3
|
+
import type { VerticalControlAreaOptions } from '../definitions';
|
|
3
4
|
import { childElements, inFixedToolbar } from '../shared/dom';
|
|
4
5
|
|
|
5
6
|
export const tag = 'ion-menu-button';
|
|
@@ -13,11 +14,11 @@ export const append = (candidate: Candidate, button: HTMLIonMenuButtonElement, i
|
|
|
13
14
|
return !!content && !!appendItem(candidate, button, id, content);
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
export const read = (group: HTMLElement, id: Identify): Candidate | undefined => {
|
|
17
|
+
export const read = (group: HTMLElement, id: Identify, options: VerticalControlAreaOptions = {}): Candidate | undefined => {
|
|
17
18
|
const children = group.matches('ion-buttons') ? childElements(group) : [];
|
|
18
19
|
if (!inFixedToolbar(group) || children.length !== 1) return;
|
|
19
20
|
const button = children[0];
|
|
20
21
|
if (!button?.matches(`${tag}${group.closest('ion-app.ios-theme-vertical-bars') ? '' : '.ios'}`)) return;
|
|
21
|
-
const candidate = createCandidate(group, tag, id);
|
|
22
|
+
const candidate = createCandidate(group, tag, id, options);
|
|
22
23
|
return append(candidate, button as HTMLIonMenuButtonElement, id) ? candidate : undefined;
|
|
23
24
|
};
|
|
@@ -167,7 +167,12 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
167
167
|
keyboard(visible: boolean) {
|
|
168
168
|
keyboardVisible = visible;
|
|
169
169
|
if (!visible) {
|
|
170
|
-
|
|
170
|
+
for (const [binding, state] of states) {
|
|
171
|
+
// Ionic can wait for the pre-keyboard height indefinitely after rotation.
|
|
172
|
+
// Release the visibility override we own when native search's keyboard closes.
|
|
173
|
+
if (state.keyboardHidden) binding.tabBar.classList.remove('tab-bar-hidden');
|
|
174
|
+
state.keyboardHidden = false;
|
|
175
|
+
}
|
|
171
176
|
releaseKeyboardResize();
|
|
172
177
|
}
|
|
173
178
|
schedule();
|
|
@@ -202,15 +207,7 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
202
207
|
};
|
|
203
208
|
for (const binding of bindings) {
|
|
204
209
|
const candidate = candidates.find((c) => c.element === binding.tabBar);
|
|
205
|
-
|
|
206
|
-
// would leave searchable tabs without any search control.
|
|
207
|
-
if (
|
|
208
|
-
!candidate ||
|
|
209
|
-
candidate.control.search ||
|
|
210
|
-
candidate.control.placement === 'vertical-bars' ||
|
|
211
|
-
binding.tabBar.getAttribute('slot') !== 'bottom'
|
|
212
|
-
)
|
|
213
|
-
continue;
|
|
210
|
+
if (!candidate || candidate.control.search || binding.tabBar.getAttribute('slot') !== 'bottom') continue;
|
|
214
211
|
const fab = binding.trigger.parentElement;
|
|
215
212
|
// Search also supports the existing page-level fixed FAB, outside scrolling content.
|
|
216
213
|
if (
|
|
@@ -441,8 +438,8 @@ export const createSearchSupport = (doc: Document, id: (element: Element) => str
|
|
|
441
438
|
},
|
|
442
439
|
retire(binding: NativeSearchBinding) {
|
|
443
440
|
const state = states.get(binding);
|
|
441
|
+
// Keep ownership until keyboardWillHide can release this class.
|
|
444
442
|
if (state?.keyboardHidden && keyboardVisible) binding.tabBar.classList.add('tab-bar-hidden');
|
|
445
|
-
if (state) state.keyboardHidden = false;
|
|
446
443
|
if (state?.focused) {
|
|
447
444
|
state.focused = false;
|
|
448
445
|
state.input.dispatchEvent(new FocusEvent('blur'));
|
|
@@ -10,7 +10,26 @@ export interface NativeUIShellStatus {
|
|
|
10
10
|
reason?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export interface
|
|
13
|
+
export interface VerticalControlAreaOptions {
|
|
14
|
+
/** Appearance of native vertical ion-button and ion-menu-button actions.
|
|
15
|
+
* `system` (default) uses SwiftUI styling and template icons; `source` projects supported Ionic fill and colors.
|
|
16
|
+
* Local data-projection="source|system" or ios-theme-projection-source / ios-theme-projection-system take precedence:
|
|
17
|
+
* the button itself, then its nearest ion-buttons, then this option. On the same element, a valid attribute wins;
|
|
18
|
+
* otherwise system wins if both classes are present. Invalid attribute values are ignored.
|
|
19
|
+
* Local settings update live; removing them restores inheritance. Actions, disabled state and grouping are preserved.
|
|
20
|
+
* Does not affect back buttons, tabs, FABs, horizontal controls, source elements or Web clones.
|
|
21
|
+
*/
|
|
22
|
+
buttonProjection?: 'source' | 'system';
|
|
23
|
+
/** Default fill for native vertical ion-button actions resolved to `source`, including local overrides.
|
|
24
|
+
* Use `solid` for Ionic's default design, or `null` (also the omitted default) for the iOS theme's glass design.
|
|
25
|
+
* Applies when fill is omitted or `default`, outside ion-buttons. Inside ion-buttons the default is clear.
|
|
26
|
+
* Explicit clear, solid and outline take precedence; clear and outline cannot be configured as defaults.
|
|
27
|
+
* Source elements and Web clones are unchanged.
|
|
28
|
+
*/
|
|
29
|
+
buttonDefaultFill?: 'solid' | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface NativeUIShellOptions extends VerticalControlAreaOptions {
|
|
14
33
|
/** Enables Native UI Shell globally. Defaults to `true`. */
|
|
15
34
|
enabled?: boolean;
|
|
16
35
|
/** Controls eligible for native projection. Omit to enable every control; when present, only `true` controls are enabled. */
|
|
@@ -82,6 +101,8 @@ export interface ShellItem extends Frame {
|
|
|
82
101
|
fontSize: number;
|
|
83
102
|
fontWeight: number;
|
|
84
103
|
color: string;
|
|
104
|
+
/** Resolved ion-button fill for vertical projection; clear omits the native glass background. */
|
|
105
|
+
buttonFill?: 'clear' | 'solid' | 'outline';
|
|
85
106
|
backgroundColor?: string;
|
|
86
107
|
borderColor?: string;
|
|
87
108
|
borderWidth?: number;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FoldState } from './types';
|
|
2
|
+
|
|
3
|
+
/** Keep posture on ion-app so descendants can follow it through CSS. */
|
|
4
|
+
export function applyFoldStateClasses(root: HTMLElement, fold: FoldState): void {
|
|
5
|
+
for (const state of ['flat', 'half-opened', 'closed']) {
|
|
6
|
+
root.classList.toggle(`ios-theme-fold-${state}`, fold.state === state);
|
|
7
|
+
}
|
|
8
|
+
root.classList.toggle('ios-theme-fold-expanded', fold.state === 'half-opened' || (fold.state === 'flat' && !!fold.hingeBounds));
|
|
9
|
+
}
|
package/src/native/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
VerticalBarEdge,
|
|
8
8
|
VerticalBarPlacement,
|
|
9
9
|
VerticalControlAreaHandle,
|
|
10
|
+
VerticalControlAreaOptions,
|
|
10
11
|
WebViewMetrics,
|
|
11
12
|
} from './definitions';
|
|
12
13
|
import { createRuntime } from './runtime';
|
|
@@ -23,6 +24,7 @@ export type {
|
|
|
23
24
|
VerticalBarEdge,
|
|
24
25
|
VerticalBarPlacement,
|
|
25
26
|
VerticalControlAreaHandle,
|
|
27
|
+
VerticalControlAreaOptions,
|
|
26
28
|
WebViewMetrics,
|
|
27
29
|
} from './definitions';
|
|
28
30
|
|
|
@@ -160,9 +162,18 @@ export const setVerticalControlAreaPlacement = (placement: VerticalBarEdge | Ver
|
|
|
160
162
|
document.defaultView?.dispatchEvent(new Event('nativeUIShellRefresh'));
|
|
161
163
|
};
|
|
162
164
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Call once after ion-app is mounted. Ionic markup remains the source of truth.
|
|
167
|
+
* Native vertical buttons default to SwiftUI appearance.
|
|
168
|
+
* See VerticalControlAreaOptions for source styling and local overrides.
|
|
169
|
+
*/
|
|
170
|
+
export const enableVerticalControlArea = async (options: VerticalControlAreaOptions = {}): Promise<VerticalControlAreaHandle> => {
|
|
171
|
+
const handle = await enableNativeUIShell({
|
|
172
|
+
buttonProjection: options.buttonProjection,
|
|
173
|
+
buttonDefaultFill: options.buttonDefaultFill,
|
|
174
|
+
controls: { tabs: true, toolbar: true },
|
|
175
|
+
verticalBarsOnly: true,
|
|
176
|
+
});
|
|
166
177
|
return {
|
|
167
178
|
getStatus: () => handle.getStatus(),
|
|
168
179
|
suspend: () => handle.suspend(),
|
|
@@ -188,6 +199,9 @@ export const enableNativeUIShell = (options: NativeUIShellOptions = {}): Promise
|
|
|
188
199
|
const controls = options.controls;
|
|
189
200
|
const configuration = JSON.stringify([
|
|
190
201
|
options.verticalBarsOnly === true,
|
|
202
|
+
options.buttonProjection ?? 'system',
|
|
203
|
+
// Local source overrides can use this even when the startup projection is system.
|
|
204
|
+
options.buttonDefaultFill ?? null,
|
|
191
205
|
...(['tabs', 'toolbar', 'segment', 'fab'] as const).map((component) => !controls || controls[component] === true),
|
|
192
206
|
]);
|
|
193
207
|
if (active && activeConfiguration !== configuration)
|
package/src/native/prehide.ts
CHANGED
|
@@ -74,6 +74,8 @@ export const prehideVerticalBarsToolbarSources = (doc: Document): { suspend: ()
|
|
|
74
74
|
};
|
|
75
75
|
const sources = scope.matches('ion-back-button') ? [scope] : Array.from(scope.querySelectorAll<HTMLElement>(sourceSelector));
|
|
76
76
|
sources.forEach((element) => {
|
|
77
|
+
// Shell opt-out is temporary; decide initial placement once it is lifted.
|
|
78
|
+
if (isShellDisabled(element)) return;
|
|
77
79
|
if (!modalUsesVerticalBars(element) || element.closest(overlays) || (scope.matches('.ion-page') && routedPage(element) !== scope))
|
|
78
80
|
return;
|
|
79
81
|
if (element.matches('ion-buttons')) {
|
|
@@ -228,7 +230,7 @@ export const prehideVerticalBarsToolbarSources = (doc: Document): { suspend: ()
|
|
|
228
230
|
childList: true,
|
|
229
231
|
attributes: true,
|
|
230
232
|
attributeOldValue: true,
|
|
231
|
-
attributeFilter: ['class', 'hidden', 'inert', 'icon', 'color'],
|
|
233
|
+
attributeFilter: ['class', 'data-shell', 'hidden', 'inert', 'icon', 'color'],
|
|
232
234
|
});
|
|
233
235
|
return {
|
|
234
236
|
suspend: () => {
|
package/src/native/runtime.ts
CHANGED
|
@@ -172,14 +172,14 @@ export const createRuntime = async (
|
|
|
172
172
|
measuringPointerPages.add(pointerPage);
|
|
173
173
|
pointerPage.style.setProperty('pointer-events', 'auto', 'important');
|
|
174
174
|
try {
|
|
175
|
-
candidate = readCandidate(element, id);
|
|
175
|
+
candidate = readCandidate(element, id, options);
|
|
176
176
|
} finally {
|
|
177
177
|
if (previous) pointerPage.style.setProperty('pointer-events', previous, priority);
|
|
178
178
|
else pointerPage.style.removeProperty('pointer-events');
|
|
179
179
|
if (!hadStyle && !pointerPage.style.length) pointerPage.removeAttribute('style');
|
|
180
180
|
win.setTimeout(() => measuringPointerPages.delete(pointerPage), 0);
|
|
181
181
|
}
|
|
182
|
-
} else candidate = readCandidate(element, id);
|
|
182
|
+
} else candidate = readCandidate(element, id, options);
|
|
183
183
|
if (candidate && isVerticalBarsCandidate(element)) {
|
|
184
184
|
if (!nativeVerticalBars()) return undefined;
|
|
185
185
|
candidate.control.placement = 'vertical-bars';
|
|
@@ -608,24 +608,29 @@ export const createRuntime = async (
|
|
|
608
608
|
'focusout',
|
|
609
609
|
])
|
|
610
610
|
on(doc, name);
|
|
611
|
-
|
|
612
|
-
|
|
611
|
+
const refreshLayout = () => {
|
|
612
|
+
rejected = new WeakMap();
|
|
613
|
+
forceRefresh = true;
|
|
613
614
|
schedule();
|
|
615
|
+
};
|
|
616
|
+
on(doc, 'visibilitychange', () => {
|
|
617
|
+
if (doc.hidden) {
|
|
618
|
+
getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
|
|
619
|
+
schedule();
|
|
620
|
+
} else refreshLayout();
|
|
614
621
|
});
|
|
615
|
-
on(win, 'resize');
|
|
622
|
+
on(win, 'resize', refreshLayout);
|
|
616
623
|
on(win, 'keyboardWillShow', () => search.keyboard(true));
|
|
617
624
|
on(win, 'keyboardWillHide', () => search.keyboard(false));
|
|
618
625
|
on(win, 'scroll');
|
|
619
626
|
on(win.matchMedia('(prefers-color-scheme: dark)'), 'change');
|
|
620
627
|
on(win, 'nativeUIShellRefresh', (event) => {
|
|
621
|
-
rejected = new WeakMap();
|
|
622
628
|
if ((event as Event & { retireSearch?: boolean }).retireSearch)
|
|
623
629
|
getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
|
|
624
|
-
|
|
625
|
-
schedule();
|
|
630
|
+
refreshLayout();
|
|
626
631
|
});
|
|
627
632
|
if (win.visualViewport) {
|
|
628
|
-
on(win.visualViewport, 'resize');
|
|
633
|
+
on(win.visualViewport, 'resize', refreshLayout);
|
|
629
634
|
on(win.visualViewport, 'scroll');
|
|
630
635
|
}
|
|
631
636
|
const handle: NativeUIShellHandle = {
|