@apexnative/navigation 1.0.0-alpha.10 → 1.0.0-alpha.11
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/CHANGELOG.md +12 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementDefinition.kt +9 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementPlanner.kt +20 -15
- package/docs-md/containers/arrangement.md +17 -1
- package/docs-md/index.json +4 -0
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +185 -13
- package/ios/Layouts/Screen/ANNScreenSettling.h +3 -1
- package/ios/Layouts/Screen/ANNScreenSettling.mm +38 -4
- package/lib/module/api/screens/arrangement.js +5 -2
- package/lib/module/api/screens/arrangement.js.map +1 -1
- package/lib/typescript/api/screens/arrangement.d.ts +12 -2
- package/lib/typescript/api/screens/arrangement.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/screens/arrangement.ts +17 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-alpha.11](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.10...v1.0.0-alpha.11) (2026-10-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* a pair's primary can take the trailing side or the bottom of a split ([#73](https://github.com/apexnative/navigation/issues/73)) ([90ddcd3](https://github.com/apexnative/navigation/commit/90ddcd3c62fa5878cc98651ca7a2cfcdf4645197))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ios:** a pair and its insets settle at once when iPhone Duo folds or unfolds ([#71](https://github.com/apexnative/navigation/issues/71)) ([ad87ce1](https://github.com/apexnative/navigation/commit/ad87ce1669b55dfb6ad65bedfb8e119f08abb04e))
|
|
14
|
+
|
|
3
15
|
## [1.0.0-alpha.10](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.9...v1.0.0-alpha.10) (2026-10-30)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -11,7 +11,10 @@ data class ArrangementDefinition(
|
|
|
11
11
|
val axes: Axes,
|
|
12
12
|
/** `split`: the primary's share along the split, 0–1, where a fold doesn't decide. */
|
|
13
13
|
val fraction: Float,
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The edge the primary takes. `split`: leading or top puts it first, trailing or bottom second; `overlay`: its edge
|
|
16
|
+
* when the pair goes side by side at a fold.
|
|
17
|
+
*/
|
|
15
18
|
val primaryEdge: Edge,
|
|
16
19
|
/** What a window too small for a pair shows. */
|
|
17
20
|
val compact: Compact,
|
|
@@ -36,8 +39,9 @@ data class ArrangementDefinition(
|
|
|
36
39
|
@JvmStatic
|
|
37
40
|
fun parse(data: JSONObject): ArrangementDefinition {
|
|
38
41
|
val fraction = data.optDouble("fraction", DEFAULT_FRACTION).takeUnless { it.isNaN() } ?: DEFAULT_FRACTION
|
|
42
|
+
val style = if (data.optString("style") == "overlay") Style.OVERLAY else Style.SPLIT
|
|
39
43
|
return ArrangementDefinition(
|
|
40
|
-
style =
|
|
44
|
+
style = style,
|
|
41
45
|
axes = when (data.optString("axes")) {
|
|
42
46
|
"horizontal" -> Axes.HORIZONTAL
|
|
43
47
|
"vertical" -> Axes.VERTICAL
|
|
@@ -48,7 +52,9 @@ data class ArrangementDefinition(
|
|
|
48
52
|
"leading" -> Edge.LEADING
|
|
49
53
|
"top" -> Edge.TOP
|
|
50
54
|
"bottom" -> Edge.BOTTOM
|
|
51
|
-
|
|
55
|
+
"trailing" -> Edge.TRAILING
|
|
56
|
+
// The default of each style: the primary first in a split, at the trailing side of an overlay.
|
|
57
|
+
else -> if (style == Style.SPLIT) Edge.LEADING else Edge.TRAILING
|
|
52
58
|
},
|
|
53
59
|
compact = if (data.optString("compact") == "pair") Compact.PAIR else Compact.PRIMARY,
|
|
54
60
|
)
|
|
@@ -45,8 +45,9 @@ internal fun WindowFolds.Fold.across(area: Rect): PairFold? {
|
|
|
45
45
|
/**
|
|
46
46
|
* Decides the frames of a pair. A window too small for a pair (`largeWindow` false: Material's compact width or
|
|
47
47
|
* height) shows the primary alone with `compact: 'primary'`. Otherwise, and with `compact: 'pair'`: `split` lies side
|
|
48
|
-
* by side when wider than tall, else one above the other, the primary at the leading side or on top with
|
|
49
|
-
* `fraction`; a fold across the pair puts the boundary on it, and
|
|
48
|
+
* by side when wider than tall, else one above the other, the primary first (at the leading side or on top) or, with
|
|
49
|
+
* `primaryEdge` trailing or bottom, second, with its `fraction`; a fold across the pair puts the boundary on it, and
|
|
50
|
+
* the screens on its two sides, off its bounds.
|
|
50
51
|
* `overlay`: both the whole size, the primary over the secondary; side by side at a fold that the device is partly
|
|
51
52
|
* folded at, or that hides what is under it, the primary on `primaryEdge`. Where the axes don't allow the way the pair
|
|
52
53
|
* would lie, only the primary shows (an overlay stays one over the other).
|
|
@@ -67,9 +68,9 @@ internal object ArrangementPlanner {
|
|
|
67
68
|
val sideBySide = fold?.vertical ?: (width > height)
|
|
68
69
|
when {
|
|
69
70
|
!definition.allows(sideBySide) -> ArrangementPlan(Layout.SINGLE, whole, null)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
else -> byFraction(definition
|
|
71
|
+
fold != null ->
|
|
72
|
+
atFold(fold, width, height, primaryFirst(definition.primaryEdge, fold.vertical, rtl))
|
|
73
|
+
else -> byFraction(definition, width, height, sideBySide, rtl)
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
Style.OVERLAY ->
|
|
@@ -92,27 +93,31 @@ internal object ArrangementPlanner {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
private fun byFraction(
|
|
95
|
-
|
|
96
|
+
definition: ArrangementDefinition,
|
|
96
97
|
width: Int,
|
|
97
98
|
height: Int,
|
|
98
99
|
sideBySide: Boolean,
|
|
99
100
|
rtl: Boolean,
|
|
100
101
|
): ArrangementPlan {
|
|
102
|
+
val first = primaryFirst(definition.primaryEdge, sideBySide, rtl)
|
|
101
103
|
if (!sideBySide) {
|
|
102
|
-
val share = (height * fraction).roundToInt()
|
|
103
|
-
|
|
104
|
+
val share = (height * definition.fraction).roundToInt()
|
|
105
|
+
val boundary = if (first) share else height - share
|
|
106
|
+
val top = Rect(0, 0, width, boundary)
|
|
107
|
+
val bottom = Rect(0, boundary, width, height)
|
|
108
|
+
return ArrangementPlan(Layout.STACKED, if (first) top else bottom, if (first) bottom else top)
|
|
104
109
|
}
|
|
105
|
-
val share = (width * fraction).roundToInt()
|
|
106
|
-
// The primary
|
|
107
|
-
val boundary = if (
|
|
110
|
+
val share = (width * definition.fraction).roundToInt()
|
|
111
|
+
// The primary's part on the left or on the right: the leading side is the right one from right to left.
|
|
112
|
+
val boundary = if (first) share else width - share
|
|
108
113
|
val left = Rect(0, 0, boundary, height)
|
|
109
114
|
val right = Rect(boundary, 0, width, height)
|
|
110
|
-
return ArrangementPlan(Layout.SIDE_BY_SIDE, if (
|
|
115
|
+
return ArrangementPlan(Layout.SIDE_BY_SIDE, if (first) left else right, if (first) right else left)
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
// Whether the primary takes the left part of a vertical fold
|
|
114
|
-
//
|
|
115
|
-
// trailing.
|
|
118
|
+
// Whether the primary takes the left part of a vertical fold (or of a pair side by side), or the top part of a
|
|
119
|
+
// horizontal one (or of a pair one above the other). An edge of the other axis counts as the nearest one: leading
|
|
120
|
+
// as top and top as leading, trailing as bottom and bottom as trailing.
|
|
116
121
|
private fun primaryFirst(edge: Edge, vertical: Boolean, rtl: Boolean): Boolean {
|
|
117
122
|
val first = edge == Edge.LEADING || edge == Edge.TOP
|
|
118
123
|
return if (vertical) first != rtl else first
|
|
@@ -140,7 +140,7 @@ The type argument is the props of the pair, which the commands check as for any
|
|
|
140
140
|
|
|
141
141
|
- **`axes`**: `'horizontal'` side by side only, `'vertical'` one above the other only, `'both'` by default. Where the window allows neither, only the primary shows.
|
|
142
142
|
- **`fraction`**: the primary's share of a split, above 0 and below 1, half by default. At a fold the fold decides.
|
|
143
|
-
- **`primaryEdge`**:
|
|
143
|
+
- **`primaryEdge`**: the side the primary takes. In a split `'leading'`, the default, puts it first, `'trailing'` second; in an overlay that goes side by side, `'trailing'` by default.
|
|
144
144
|
- **`compact`**: what a window that isn't large shows: `'primary'`, the default, or `'pair'`.
|
|
145
145
|
|
|
146
146
|
A pair has two screens, never more: in a `defineArrangement`, the primary's own `arrangement` doesn't count. Every key: [`defineArrangement`](/api/arrangement/defineArrangement).
|
|
@@ -163,6 +163,22 @@ Where only the primary shows, the secondary is still there, out of the window, a
|
|
|
163
163
|
|
|
164
164
|
With `compact: 'pair'` of `defineArrangement` the pair shows everywhere: on a phone held upright, one above the other.
|
|
165
165
|
|
|
166
|
+
### Which screen shows alone, and on which side
|
|
167
|
+
|
|
168
|
+
The primary leads the pair: its bar is the pair's, and it is the screen that shows alone outside a large window. To have the other screen alone on a phone or on iPhone Duo folded, make that one the primary. `primaryEdge` then puts it on the side the layout asks for:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// The list on the leading side, the details on
|
|
172
|
+
// the trailing one; folded, the details alone
|
|
173
|
+
ItemWithList: defineArrangement<{ id: string }>({
|
|
174
|
+
primary: 'Details',
|
|
175
|
+
secondary: 'List',
|
|
176
|
+
primaryEdge: 'trailing',
|
|
177
|
+
}),
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The bar stays the details' bar folded and open, so opening iPhone Duo changes nothing in it: the list comes in beside the details, which keep their place in the window. One setting serves both ways a split lies: `'trailing'` and `'bottom'` put the primary second, at the trailing side or at the bottom; `'leading'` and `'top'` put it first.
|
|
181
|
+
|
|
166
182
|
## Split and overlay
|
|
167
183
|
|
|
168
184
|
`style` says how the two share the window:
|
package/docs-md/index.json
CHANGED
|
@@ -48,6 +48,14 @@ static NSDirectionalRectEdge ANNOppositeEdge(NSDirectionalRectEdge edge) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// `primaryEdge` of a split: whether the primary goes second, at the trailing side or at the bottom. A split has no edge
|
|
52
|
+
// of its own: UIKit puts the view of its primary placement first along either axis (measured on iPhone Duo), so the
|
|
53
|
+
// edge decides which screen goes into that placement.
|
|
54
|
+
static BOOL ANNSecondaryFirst(NSDictionary *data) {
|
|
55
|
+
return ![@"overlay" isEqual:data[@"style"]] &&
|
|
56
|
+
([@"trailing" isEqual:data[@"primaryEdge"]] || [@"bottom" isEqual:data[@"primaryEdge"]]);
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
// Overlay view properties with the edge a view takes when the pair goes side by side.
|
|
52
60
|
static UIOverlayArrangementViewProperties *ANNOverlayProperties(UIOverlayArrangement *overlay,
|
|
53
61
|
NSDirectionalRectEdge edge)
|
|
@@ -95,8 +103,11 @@ static UIArrangement *ANNArrangementOfData(NSDictionary *data) API_AVAILABLE(ios
|
|
|
95
103
|
height.preferred = dimension;
|
|
96
104
|
primary.height = height;
|
|
97
105
|
}
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
// The share is the primary screen's, in whichever placement it is.
|
|
107
|
+
UIArrangementViewControllerViewPlacement placement =
|
|
108
|
+
ANNSecondaryFirst(data) ? UIArrangementViewControllerViewPlacementSecondary
|
|
109
|
+
: UIArrangementViewControllerViewPlacementPrimary;
|
|
110
|
+
[split setViewProperties:primary forPlacement:placement];
|
|
100
111
|
}
|
|
101
112
|
return split;
|
|
102
113
|
}
|
|
@@ -109,6 +120,38 @@ static UIArrangement *ANNPrimaryAloneArrangement(void) API_AVAILABLE(ios(27.1))
|
|
|
109
120
|
return split;
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
// A size range from `minimum` to `maximum`, preferring `preferred`.
|
|
124
|
+
static UISplitArrangementDimensionRange *ANNDimensionRange(UISplitArrangementDimension *minimum,
|
|
125
|
+
UISplitArrangementDimension *preferred,
|
|
126
|
+
UISplitArrangementDimension *maximum)
|
|
127
|
+
API_AVAILABLE(ios(27.1)) {
|
|
128
|
+
UISplitArrangementDimensionRange *range = [UISplitArrangementDimensionRange new];
|
|
129
|
+
range.minimum = minimum;
|
|
130
|
+
range.preferred = preferred;
|
|
131
|
+
range.maximum = maximum;
|
|
132
|
+
return range;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The primary alone in the secondary placement, with the primary placement empty. UIKit keeps the empty placement's
|
|
136
|
+
// share of a split (measured on iPhone Duo folded: the primary in the lower half, the upper one blank), so the empty
|
|
137
|
+
// placement gets no room and the primary all of it, along either axis, which no shape of the window leaves out.
|
|
138
|
+
static UIArrangement *ANNSecondPlacementAloneArrangement(void) API_AVAILABLE(ios(27.1)) {
|
|
139
|
+
UISplitArrangement *split = [UISplitArrangement splitArrangement];
|
|
140
|
+
split.axes = UIAxisHorizontal | UIAxisVertical;
|
|
141
|
+
UISplitArrangementDimension *none = [UISplitArrangementDimension absoluteDimension:0];
|
|
142
|
+
UISplitArrangementDimension *all = [UISplitArrangementDimension fractionalDimension:1];
|
|
143
|
+
UISplitArrangementViewProperties *empty = [split.defaultViewProperties copy];
|
|
144
|
+
empty.width = ANNDimensionRange(none, none, none);
|
|
145
|
+
empty.height = ANNDimensionRange(none, none, none);
|
|
146
|
+
[split setViewProperties:empty forPlacement:UIArrangementViewControllerViewPlacementPrimary];
|
|
147
|
+
UISplitArrangementViewProperties *primary = [split.defaultViewProperties copy];
|
|
148
|
+
primary.width = ANNDimensionRange(all, all, all);
|
|
149
|
+
primary.height = ANNDimensionRange(all, all, all);
|
|
150
|
+
[split setViewProperties:primary
|
|
151
|
+
forPlacement:UIArrangementViewControllerViewPlacementSecondary];
|
|
152
|
+
return split;
|
|
153
|
+
}
|
|
154
|
+
|
|
112
155
|
// Room for a pair: both size classes regular (iPad, iPhone Duo unfolded). Nil without a size class yet: the view
|
|
113
156
|
// controller is not in a window.
|
|
114
157
|
static NSNumber *ANNIsLargeWindow(UITraitCollection *traits) {
|
|
@@ -183,9 +226,20 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
183
226
|
// (`compact: 'primary'`).
|
|
184
227
|
UIArrangement *_arrangement;
|
|
185
228
|
BOOL _primaryAloneWhenSmall;
|
|
229
|
+
// `primaryEdge` of a split at the trailing side or the bottom: the secondary is in UIKit's primary placement, the
|
|
230
|
+
// primary in its secondary one. The primary alone keeps its placement and the secondary leaves its own, so the
|
|
231
|
+
// primary never changes its container at a fold. The axes the pair may use, for the primary alone then.
|
|
232
|
+
BOOL _secondaryFirst;
|
|
233
|
+
UIAxis _axes;
|
|
234
|
+
// The size a running size transition leads to, zero without one: the shape the pair decides its axes by.
|
|
235
|
+
CGSize _transitionSize;
|
|
186
236
|
// What UIKit has now, once an arrangement went to it.
|
|
187
237
|
BOOL _arranged;
|
|
188
238
|
BOOL _primaryAlone;
|
|
239
|
+
// The scene's size classes the pair takes for a size transition, and which transition set them last: only that
|
|
240
|
+
// one's end takes them away.
|
|
241
|
+
BOOL _sceneSizeClasses;
|
|
242
|
+
NSUInteger _sceneSizeClassesTransition;
|
|
189
243
|
}
|
|
190
244
|
|
|
191
245
|
- (instancetype)initWithLayoutInfo:(ANNLayoutInfo *)layoutInfo
|
|
@@ -218,14 +272,16 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
218
272
|
((ANNComponentViewController *)screen).arrangement = self;
|
|
219
273
|
}
|
|
220
274
|
}
|
|
221
|
-
|
|
275
|
+
_secondaryFirst = ANNSecondaryFirst(data);
|
|
276
|
+
_axes = ANNArrangementAxes(data[@"axes"]);
|
|
277
|
+
[self setViewController:_secondaryFirst ? secondary : primary
|
|
222
278
|
forPlacement:UIArrangementViewControllerViewPlacementPrimary];
|
|
223
|
-
[self setViewController:secondary
|
|
279
|
+
[self setViewController:_secondaryFirst ? primary : secondary
|
|
224
280
|
forPlacement:UIArrangementViewControllerViewPlacementSecondary];
|
|
225
281
|
_arrangement = ANNArrangementOfData(data);
|
|
226
282
|
_primaryAloneWhenSmall = ![@"pair" isEqual:data[@"compact"]];
|
|
227
283
|
// Before it is in a window: the window's size classes, which it will most likely have.
|
|
228
|
-
[self arrangeForTraits:ANNUtils.mainWindow.traitCollection
|
|
284
|
+
[self arrangeForTraits:ANNUtils.mainWindow.traitCollection];
|
|
229
285
|
if (!_arranged) {
|
|
230
286
|
[self updateArrangement:_arrangement animated:NO];
|
|
231
287
|
}
|
|
@@ -239,19 +295,33 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
239
295
|
// `compact: 'primary'`: the pair shows only in a large window; in any other (an iPhone, iPhone Duo folded) the primary
|
|
240
296
|
// shows alone, and the secondary waits out of the window until the window is large (iPhone Duo unfolded). With
|
|
241
297
|
// `compact: 'pair'` UIKit lays the pair out everywhere.
|
|
242
|
-
- (void)arrangeForTraits:(UITraitCollection *)traits
|
|
298
|
+
- (void)arrangeForTraits:(UITraitCollection *)traits {
|
|
243
299
|
NSNumber *large = ANNIsLargeWindow(traits);
|
|
244
300
|
if (!large) {
|
|
245
301
|
return;
|
|
246
302
|
}
|
|
247
|
-
BOOL alone = _primaryAloneWhenSmall && !large.boolValue
|
|
303
|
+
BOOL alone = (_primaryAloneWhenSmall && !large.boolValue) ||
|
|
304
|
+
(_secondaryFirst && ![self axesAllowSize:[self pairSize]]);
|
|
248
305
|
if (_arranged && alone == _primaryAlone) {
|
|
249
306
|
return;
|
|
250
307
|
}
|
|
251
308
|
BOOL changed = _arranged;
|
|
252
309
|
_arranged = YES;
|
|
253
310
|
_primaryAlone = alone;
|
|
254
|
-
|
|
311
|
+
if (_secondaryFirst) {
|
|
312
|
+
// The secondary leaves the window with its placement, as a split with no axis takes it out otherwise.
|
|
313
|
+
UIViewController *first = alone ? nil : _secondary;
|
|
314
|
+
if ([self viewControllerForPlacement:UIArrangementViewControllerViewPlacementPrimary] !=
|
|
315
|
+
first) {
|
|
316
|
+
[self setViewController:first
|
|
317
|
+
forPlacement:UIArrangementViewControllerViewPlacementPrimary
|
|
318
|
+
animated:NO];
|
|
319
|
+
}
|
|
320
|
+
[self updateArrangement:alone ? ANNSecondPlacementAloneArrangement() : _arrangement
|
|
321
|
+
animated:NO];
|
|
322
|
+
} else {
|
|
323
|
+
[self updateArrangement:alone ? ANNPrimaryAloneArrangement() : _arrangement animated:NO];
|
|
324
|
+
}
|
|
255
325
|
// A secondary that leaves the window renders no content: the pair waits for it no more.
|
|
256
326
|
if (alone && _secondaryCounted) {
|
|
257
327
|
_secondaryCounted = NO;
|
|
@@ -264,8 +334,103 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
264
334
|
}
|
|
265
335
|
}
|
|
266
336
|
|
|
337
|
+
// Where the axes leave the pair no room, UIKit shows its primary placement alone: with the secondary first that would
|
|
338
|
+
// be the secondary, so the pair takes it out itself, by the shape UIKit goes by (side by side in a wide window, one
|
|
339
|
+
// above the other in a tall one, measured on iPhone Duo).
|
|
340
|
+
- (BOOL)axesAllowSize:(CGSize)size {
|
|
341
|
+
if ((_axes & UIAxisHorizontal) && (_axes & UIAxisVertical)) {
|
|
342
|
+
return YES;
|
|
343
|
+
}
|
|
344
|
+
BOOL wide = size.width > size.height;
|
|
345
|
+
return (_axes & (wide ? UIAxisHorizontal : UIAxisVertical)) != 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The pair's size: where a size transition leads, its own once laid out, the window's before.
|
|
349
|
+
- (CGSize)pairSize {
|
|
350
|
+
if (!CGSizeEqualToSize(_transitionSize, CGSizeZero)) {
|
|
351
|
+
return _transitionSize;
|
|
352
|
+
}
|
|
353
|
+
CGSize size = self.viewIfLoaded.bounds.size;
|
|
354
|
+
return size.width > 0 && size.height > 0 ? size : ANNUtils.mainWindow.bounds.size;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// New size classes come with a new window size, which leads the pair to its new place. UIKit's own animation of the
|
|
358
|
+
// arrangement would aim at a layout it gives only in between (on iPhone Duo, one above the other while the window
|
|
359
|
+
// still has its old size classes) and play on after the pair took its real one: the screens lagged by half a second.
|
|
267
360
|
- (void)sizeClassesDidChange {
|
|
268
|
-
[self arrangeForTraits:self.traitCollection
|
|
361
|
+
[self arrangeForTraits:self.traitCollection];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// On iPhone Duo the scene has its new size classes as the size transition starts, and the pair gets them only after
|
|
365
|
+
// UIKit laid it out in the new size: the primary first filled the whole open window, then gave half of it to the
|
|
366
|
+
// secondary. For the transition the pair takes the scene's size classes, so it lies as it will from its first layout.
|
|
367
|
+
// Only where the pair has the window's size classes: a sheet or a split view's column has its own.
|
|
368
|
+
- (void)viewWillTransitionToSize:(CGSize)size
|
|
369
|
+
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
|
|
370
|
+
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
|
371
|
+
// A new shape may leave the axes no room, or give it back: the pair takes it before its first layout in the size.
|
|
372
|
+
if (_secondaryFirst) {
|
|
373
|
+
_transitionSize = size;
|
|
374
|
+
[self arrangeForTraits:self.traitCollection];
|
|
375
|
+
__weak ANNArrangementViewController *weakPair = self;
|
|
376
|
+
void (^sized)(id<UIViewControllerTransitionCoordinatorContext>) =
|
|
377
|
+
^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
378
|
+
ANNArrangementViewController *pair = weakPair;
|
|
379
|
+
if (pair && CGSizeEqualToSize(pair->_transitionSize, size)) {
|
|
380
|
+
pair->_transitionSize = CGSizeZero;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
[coordinator animateAlongsideTransition:nil completion:sized];
|
|
384
|
+
}
|
|
385
|
+
UITraitCollection *scene = self.viewIfLoaded.window.windowScene.traitCollection;
|
|
386
|
+
NSNumber *large = ANNIsLargeWindow(self.traitCollection);
|
|
387
|
+
NSNumber *largeScene = scene ? ANNIsLargeWindow(scene) : nil;
|
|
388
|
+
if (!large || !largeScene || ![self hasWindowSizeClasses]) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// The size classes the pair has now are the scene's of an earlier transition, still running: this one keeps them
|
|
392
|
+
// until its own end.
|
|
393
|
+
if ([large isEqual:largeScene] && !_sceneSizeClasses) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
_sceneSizeClasses = YES;
|
|
397
|
+
NSUInteger transition = ++_sceneSizeClassesTransition;
|
|
398
|
+
self.traitOverrides.horizontalSizeClass = scene.horizontalSizeClass;
|
|
399
|
+
self.traitOverrides.verticalSizeClass = scene.verticalSizeClass;
|
|
400
|
+
[self updateTraitsIfNeeded];
|
|
401
|
+
__weak ANNArrangementViewController *weakSelf = self;
|
|
402
|
+
void (^ended)(id<UIViewControllerTransitionCoordinatorContext>) =
|
|
403
|
+
^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
|
404
|
+
[weakSelf dropSceneSizeClassesOfTransition:transition];
|
|
405
|
+
};
|
|
406
|
+
[coordinator animateAlongsideTransition:nil completion:ended];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
- (void)dropSceneSizeClassesOfTransition:(NSUInteger)transition {
|
|
410
|
+
if (!_sceneSizeClasses || transition != _sceneSizeClassesTransition) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
_sceneSizeClasses = NO;
|
|
414
|
+
[self.traitOverrides removeTrait:UITraitHorizontalSizeClass.class];
|
|
415
|
+
[self.traitOverrides removeTrait:UITraitVerticalSizeClass.class];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// The pair's size classes are the window's: it stands in the window's root, not in a presented layer, and no split
|
|
419
|
+
// view's column is between them.
|
|
420
|
+
- (BOOL)hasWindowSizeClasses {
|
|
421
|
+
UIWindow *window = self.viewIfLoaded.window;
|
|
422
|
+
if (!window) {
|
|
423
|
+
return NO;
|
|
424
|
+
}
|
|
425
|
+
UIViewController *top = self;
|
|
426
|
+
for (UIViewController *parent = self.parentViewController; parent;
|
|
427
|
+
parent = parent.parentViewController) {
|
|
428
|
+
if ([parent isKindOfClass:UISplitViewController.class]) {
|
|
429
|
+
return NO;
|
|
430
|
+
}
|
|
431
|
+
top = parent;
|
|
432
|
+
}
|
|
433
|
+
return top == window.rootViewController;
|
|
269
434
|
}
|
|
270
435
|
|
|
271
436
|
// The first state, before the pair is laid out: as UIKit lays it out in the window's shape (measured on iPhone Duo),
|
|
@@ -433,13 +598,17 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
433
598
|
|
|
434
599
|
- (void)viewDidLayoutSubviews {
|
|
435
600
|
[super viewDidLayoutSubviews];
|
|
601
|
+
// A size of its own without a transition (a column of a split view): the axes may allow another shape now.
|
|
602
|
+
if (_secondaryFirst) {
|
|
603
|
+
[self arrangeForTraits:self.traitCollection];
|
|
604
|
+
}
|
|
436
605
|
[self setNeedsStateUpdate];
|
|
437
606
|
}
|
|
438
607
|
|
|
439
608
|
// In the window now: its own size classes, which the window's stood in for.
|
|
440
609
|
- (void)viewWillAppear:(BOOL)animated {
|
|
441
610
|
[super viewWillAppear:animated];
|
|
442
|
-
[self arrangeForTraits:self.traitCollection
|
|
611
|
+
[self arrangeForTraits:self.traitCollection];
|
|
443
612
|
}
|
|
444
613
|
|
|
445
614
|
- (void)viewDidAppear:(BOOL)animated {
|
|
@@ -479,10 +648,12 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
479
648
|
if (!self.viewIfLoaded.window || _screensDestroyed) {
|
|
480
649
|
return;
|
|
481
650
|
}
|
|
651
|
+
// By the screens' placements: with `primaryEdge` at the trailing side or the bottom they are the other way round,
|
|
652
|
+
// and a secondary out of its placement has none.
|
|
482
653
|
UIArrangementViewState *primaryState =
|
|
483
|
-
[self stateForPlacement:
|
|
654
|
+
[self stateForPlacement:[self placementForViewController:_primary]];
|
|
484
655
|
UIArrangementViewState *secondaryState =
|
|
485
|
-
[self stateForPlacement:
|
|
656
|
+
[self stateForPlacement:[self placementForViewController:_secondary]];
|
|
486
657
|
CGRect primaryFrame = ANNShownFrame(_primary.viewIfLoaded, self.view);
|
|
487
658
|
CGRect secondaryFrame = ANNShownFrame(_secondary.viewIfLoaded, self.view);
|
|
488
659
|
BOOL primaryShown = ANNIsShown(_primary, primaryState, primaryFrame);
|
|
@@ -498,7 +669,8 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
498
669
|
CGRect overlap = CGRectIntersection(primaryFrame, secondaryFrame);
|
|
499
670
|
if (!CGRectIsNull(overlap) && overlap.size.width > 1 && overlap.size.height > 1) {
|
|
500
671
|
layout = @"overlay";
|
|
501
|
-
// UIKit puts the primary over the secondary (zIndex 1 and 0)
|
|
672
|
+
// UIKit puts the primary over the secondary (zIndex 1 and 0); an overlay keeps the screens in their own
|
|
673
|
+
// placements.
|
|
502
674
|
primaryFront = primaryState.zIndex >= secondaryState.zIndex;
|
|
503
675
|
secondaryFront = !primaryFront;
|
|
504
676
|
} else {
|
|
@@ -18,7 +18,9 @@ extern NSNotificationName const ANNScreenSettlingDidEndNotification;
|
|
|
18
18
|
+ (void)beginWithTransition:(id<UIViewControllerTransitionCoordinator>)coordinator;
|
|
19
19
|
|
|
20
20
|
/// The vertical bar's edge changed: the size transition, when one follows, comes a little later. The hinge's moves
|
|
21
|
-
/// begin the settling too (`ANNHingeDidMoveNotification`): UIKit may give a new safe area before either.
|
|
21
|
+
/// begin the settling too (`ANNHingeDidMoveNotification`): UIKit may give a new safe area before either. Once the size
|
|
22
|
+
/// transition ended the screens are on their new display: the hinge, turning on to its end, holds nothing until it
|
|
23
|
+
/// rests (a new state of the hinge still does), and the settling ends after a shorter quiet time.
|
|
22
24
|
+ (void)beginForTraitChange;
|
|
23
25
|
|
|
24
26
|
/// Whether the screens hold their insets now.
|
|
@@ -8,6 +8,9 @@ NSNotificationName const ANNScreenSettlingDidEndNotification = @"ANNScreenSettli
|
|
|
8
8
|
// How long the values of the screens stand still before the settling ends. On iPhone Duo UIKit's last step of a fold
|
|
9
9
|
// comes some 30 ms after the size transition's end, and the size transition some 50 ms after the bar's new edge.
|
|
10
10
|
static const CFTimeInterval ANNScreenSettlingQuietTime = 0.15;
|
|
11
|
+
// Once the size transition ended, UIKit's last steps come within some 30 ms: the screens are on the new display by
|
|
12
|
+
// then, and every held moment shows them with the old one's insets.
|
|
13
|
+
static const CFTimeInterval ANNScreenSettlingQuietTimeAfterTransition = 0.05;
|
|
11
14
|
// The settling holds the insets no longer than this after its last start, whatever keeps changing.
|
|
12
15
|
static const CFTimeInterval ANNScreenSettlingLongest = 1.0;
|
|
13
16
|
// The degrees the hinge turns before it counts as a move: a device held half open trembles a little.
|
|
@@ -16,6 +19,11 @@ static const double ANNScreenSettlingHingeStep = 2;
|
|
|
16
19
|
static BOOL ANNSettling;
|
|
17
20
|
// The size transitions that began and haven't ended: each screen counts its own call of the same transition.
|
|
18
21
|
static NSInteger ANNSettlingTransitions;
|
|
22
|
+
// A size transition of this settling ended: the hinge, still turning, no longer holds the insets.
|
|
23
|
+
static BOOL ANNSettlingTransitionEnded;
|
|
24
|
+
// Until when the hinge's turns after a size transition count for nothing: the hinge goes on turning to its end once
|
|
25
|
+
// the display switched, and holds nothing until it rests a while.
|
|
26
|
+
static CFTimeInterval ANNSettlingHingeIgnoredUntil;
|
|
19
27
|
static CFTimeInterval ANNSettlingStart;
|
|
20
28
|
static CFTimeInterval ANNSettlingLastChange;
|
|
21
29
|
// Only the check scheduled last runs.
|
|
@@ -51,13 +59,22 @@ static NSNumber *ANNSettlingHingeAngle;
|
|
|
51
59
|
return;
|
|
52
60
|
}
|
|
53
61
|
double turned = fabs(angle.doubleValue - ANNSettlingHingeAngle.doubleValue);
|
|
54
|
-
BOOL
|
|
55
|
-
|
|
62
|
+
BOOL newState = ![state isEqual:ANNSettlingHingeState];
|
|
63
|
+
BOOL moved =
|
|
64
|
+
newState || !angle || !ANNSettlingHingeAngle || turned >= ANNScreenSettlingHingeStep;
|
|
56
65
|
if (!moved) {
|
|
57
66
|
return;
|
|
58
67
|
}
|
|
59
68
|
ANNSettlingHingeState = state;
|
|
60
69
|
ANNSettlingHingeAngle = angle;
|
|
70
|
+
// A new state may bring a new layout; a turn after a size transition only goes on to the hinge's end.
|
|
71
|
+
CFTimeInterval now = CACurrentMediaTime();
|
|
72
|
+
BOOL afterTransition = (ANNSettling && ANNSettlingTransitionEnded) ||
|
|
73
|
+
now < ANNSettlingHingeIgnoredUntil;
|
|
74
|
+
if (!newState && afterTransition) {
|
|
75
|
+
ANNSettlingHingeIgnoredUntil = now + ANNScreenSettlingQuietTime;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
61
78
|
[self beginForTraitChange];
|
|
62
79
|
}
|
|
63
80
|
|
|
@@ -66,6 +83,7 @@ static NSNumber *ANNSettlingHingeAngle;
|
|
|
66
83
|
return;
|
|
67
84
|
}
|
|
68
85
|
[self begin];
|
|
86
|
+
ANNSettlingTransitionEnded = NO;
|
|
69
87
|
ANNSettlingTransitions += 1;
|
|
70
88
|
NSUInteger epoch = ANNSettlingEpoch;
|
|
71
89
|
void (^ended)(id<UIViewControllerTransitionCoordinatorContext>) =
|
|
@@ -74,8 +92,12 @@ static NSNumber *ANNSettlingHingeAngle;
|
|
|
74
92
|
return;
|
|
75
93
|
}
|
|
76
94
|
ANNSettlingTransitions = MAX(0, ANNSettlingTransitions - 1);
|
|
95
|
+
if (ANNSettlingTransitions == 0) {
|
|
96
|
+
ANNSettlingTransitionEnded = YES;
|
|
97
|
+
ANNSettlingHingeIgnoredUntil = CACurrentMediaTime() + ANNScreenSettlingQuietTime;
|
|
98
|
+
}
|
|
77
99
|
[ANNScreenSettling valuesDidChange];
|
|
78
|
-
[ANNScreenSettling checkAfter:
|
|
100
|
+
[ANNScreenSettling checkAfter:[ANNScreenSettling quietTime]];
|
|
79
101
|
};
|
|
80
102
|
[coordinator animateAlongsideTransition:nil completion:ended];
|
|
81
103
|
// The longest still holds should the transition's end never come.
|
|
@@ -84,10 +106,21 @@ static NSNumber *ANNSettlingHingeAngle;
|
|
|
84
106
|
|
|
85
107
|
// Only iPhone Duo has a vertical bar: no need to ask for the hinge.
|
|
86
108
|
+ (void)beginForTraitChange {
|
|
109
|
+
// After the transition the bar's new edge is one of its last steps: it holds the values a little longer only.
|
|
110
|
+
if (ANNSettling && ANNSettlingTransitionEnded) {
|
|
111
|
+
[self valuesDidChange];
|
|
112
|
+
[self checkAfter:[self quietTime]];
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
87
115
|
[self begin];
|
|
88
116
|
[self checkAfter:ANNScreenSettlingQuietTime];
|
|
89
117
|
}
|
|
90
118
|
|
|
119
|
+
+ (CFTimeInterval)quietTime {
|
|
120
|
+
return ANNSettlingTransitionEnded ? ANNScreenSettlingQuietTimeAfterTransition
|
|
121
|
+
: ANNScreenSettlingQuietTime;
|
|
122
|
+
}
|
|
123
|
+
|
|
91
124
|
+ (void)begin {
|
|
92
125
|
CFTimeInterval now = CACurrentMediaTime();
|
|
93
126
|
ANNSettling = YES;
|
|
@@ -121,10 +154,11 @@ static NSNumber *ANNSettlingHingeAngle;
|
|
|
121
154
|
}
|
|
122
155
|
CFTimeInterval now = CACurrentMediaTime();
|
|
123
156
|
CFTimeInterval longest = ANNSettlingStart + ANNScreenSettlingLongest;
|
|
124
|
-
CFTimeInterval quiet = ANNSettlingLastChange +
|
|
157
|
+
CFTimeInterval quiet = ANNSettlingLastChange + [self quietTime];
|
|
125
158
|
if (now >= longest || (ANNSettlingTransitions == 0 && now >= quiet)) {
|
|
126
159
|
ANNSettling = NO;
|
|
127
160
|
ANNSettlingTransitions = 0;
|
|
161
|
+
ANNSettlingTransitionEnded = NO;
|
|
128
162
|
ANNSettlingEpoch += 1;
|
|
129
163
|
[NSNotificationCenter.defaultCenter postNotificationName:ANNScreenSettlingDidEndNotification
|
|
130
164
|
object:nil];
|
|
@@ -44,14 +44,17 @@ export function isArrangement(value) {
|
|
|
44
44
|
export function toEngineArrangement(primary, arrangement) {
|
|
45
45
|
const full = arrangement;
|
|
46
46
|
checkFraction(full.fraction);
|
|
47
|
+
const style = arrangement.style ?? 'split';
|
|
47
48
|
return {
|
|
48
49
|
primary,
|
|
49
50
|
secondary: arrangement.secondary,
|
|
50
51
|
secondaryProps: arrangement.secondaryProps,
|
|
51
|
-
style
|
|
52
|
+
style,
|
|
52
53
|
axes: full.axes ?? 'both',
|
|
53
54
|
fraction: full.fraction ?? 0.5,
|
|
54
|
-
|
|
55
|
+
// Where UIKit puts the primary of each style without an edge set: first in a split, at the trailing side of an
|
|
56
|
+
// overlay that goes side by side.
|
|
57
|
+
primaryEdge: full.primaryEdge ?? (style === 'split' ? 'leading' : 'trailing'),
|
|
55
58
|
compact: full.compact ?? 'primary'
|
|
56
59
|
};
|
|
57
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["ARRANGEMENT","Symbol","defineArrangement","definition","checkFraction","fraction","isArrangement","value","toEngineArrangement","primary","arrangement","full","
|
|
1
|
+
{"version":3,"names":["ARRANGEMENT","Symbol","defineArrangement","definition","checkFraction","fraction","isArrangement","value","toEngineArrangement","primary","arrangement","full","style","secondary","secondaryProps","axes","primaryEdge","compact","undefined","Error"],"sourceRoot":"../../../../src","sources":["api/screens/arrangement.ts"],"mappings":";;AAEA,MAAMA,WAAW,GAAGC,MAAM,CAAC,mCAAmC,CAAC;;AAE/D;AACA;AACA;AACA;AACA;;AAkBA;;AA4BA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAC/BC,UAAwC,EACpB;EACpBC,aAAa,CAACD,UAAU,CAACE,QAAQ,CAAC;EAClC,OAAO;IAAE,CAACL,WAAW,GAAG,IAAI;IAAEG;EAAW,CAAC;AAC5C;AAEA,OAAO,SAASG,aAAaA,CAACC,KAAc,EAA6B;EACvE,OAAO,OAAOA,KAAK,KAAK,QAAQ,IAAIA,KAAK,KAAK,IAAI,IAAIP,WAAW,IAAIO,KAAK;AAC5E;;AAEA;AACA,OAAO,SAASC,mBAAmBA,CACjCC,OAAe,EACfC,WAAgE,EAC7C;EACnB,MAAMC,IAAI,GAAGD,WAAyC;EACtDN,aAAa,CAACO,IAAI,CAACN,QAAQ,CAAC;EAC5B,MAAMO,KAAK,GAAGF,WAAW,CAACE,KAAK,IAAI,OAAO;EAC1C,OAAO;IACLH,OAAO;IACPI,SAAS,EAAEH,WAAW,CAACG,SAAS;IAChCC,cAAc,EAAEJ,WAAW,CAACI,cAAc;IAC1CF,KAAK;IACLG,IAAI,EAAEJ,IAAI,CAACI,IAAI,IAAI,MAAM;IACzBV,QAAQ,EAAEM,IAAI,CAACN,QAAQ,IAAI,GAAG;IAC9B;IACA;IACAW,WAAW,EAAEL,IAAI,CAACK,WAAW,KAAKJ,KAAK,KAAK,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IAC7EK,OAAO,EAAEN,IAAI,CAACM,OAAO,IAAI;EAC3B,CAAC;AACH;AAEA,SAASb,aAAaA,CAACC,QAA4B,EAAE;EACnD,IAAIA,QAAQ,KAAKa,SAAS,IAAI,EAAEb,QAAQ,GAAG,CAAC,IAAIA,QAAQ,GAAG,CAAC,CAAC,EAAE;IAC7D,MAAM,IAAIc,KAAK,CACb,yCAAyCd,QAAQ,+BACnD,CAAC;EACH;AACF","ignoreList":[]}
|
|
@@ -23,13 +23,23 @@ export interface ScreenArrangement<Props extends object = {}> {
|
|
|
23
23
|
}
|
|
24
24
|
/** A pair under a name of its own, with every setting: `defineArrangement`. */
|
|
25
25
|
export interface ArrangementDefinition<Props extends object = {}> extends ScreenArrangement<Props> {
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The screen that owns the bar: its title, buttons, toolbar and status bar are the pair's. It shows alone outside a
|
|
28
|
+
* large window (`compact: 'primary'`), and `primaryEdge` puts it on either side. A name of `screens`.
|
|
29
|
+
*/
|
|
27
30
|
primary: string;
|
|
28
31
|
/** The axes the pair may use. Where the window allows none of them, only the primary shows. @default 'both' */
|
|
29
32
|
axes?: 'horizontal' | 'vertical' | 'both';
|
|
30
33
|
/** `split`: the primary's share along the split, above 0 and below 1, where the fold does not decide. @default 0.5 */
|
|
31
34
|
fraction?: number;
|
|
32
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The edge the primary takes. `split`: `leading` or `top` puts the primary first, at the leading side or on top;
|
|
37
|
+
* `trailing` or `bottom` puts it second, at the trailing side or at the bottom (one setting for both ways the pair
|
|
38
|
+
* may lie). `overlay`: the primary's edge when the pair goes side by side; an edge of the other axis counts as the
|
|
39
|
+
* nearest one (`top` as leading, `bottom` as trailing). Whatever the edge, the primary is the screen that shows
|
|
40
|
+
* alone outside a large window with `compact: 'primary'`: to show the other screen there, make it the primary.
|
|
41
|
+
* @default 'leading' for split, 'trailing' for overlay
|
|
42
|
+
*/
|
|
33
43
|
primaryEdge?: 'leading' | 'trailing' | 'top' | 'bottom';
|
|
34
44
|
/**
|
|
35
45
|
* Outside a large window: `primary` — the primary alone, as a plain screen; `pair` — the pair as the system lays it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrangement.d.ts","sourceRoot":"","sources":["../../../../src/api/screens/arrangement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAsB,CAAC;AAE9D,QAAA,MAAM,WAAW,eAA8C,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE;IAC1D;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC1C;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE,CAAE,SAAQ,iBAAiB,CAAC,KAAK,CAAC;IAChG
|
|
1
|
+
{"version":3,"file":"arrangement.d.ts","sourceRoot":"","sources":["../../../../src/api/screens/arrangement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAsB,CAAC;AAE9D,QAAA,MAAM,WAAW,eAA8C,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE;IAC1D;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC1C;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE,CAAE,SAAQ,iBAAiB,CAAC,KAAK,CAAC;IAChG;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,+GAA+G;IAC/G,IAAI,CAAC,EAAE,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;IAC1C,sHAAsH;IACtH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,CAAC;IACxD;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC9B;AAED,yDAAyD;AACzD,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE;IACpD,QAAQ,CAAC,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAClD,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,SAAS,MAAM,GAAG,EAAE,EACzD,UAAU,EAAE,qBAAqB,CAAC,KAAK,CAAC,GACvC,WAAW,CAAC,KAAK,CAAC,CAGpB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAEvE;AAED,0GAA0G;AAC1G,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,iBAAiB,CAAC,GAAG,CAAC,GAAG,qBAAqB,CAAC,GAAG,CAAC,GAC/D,iBAAiB,CAgBnB"}
|
package/package.json
CHANGED
|
@@ -26,13 +26,23 @@ export interface ScreenArrangement<Props extends object = {}> {
|
|
|
26
26
|
|
|
27
27
|
/** A pair under a name of its own, with every setting: `defineArrangement`. */
|
|
28
28
|
export interface ArrangementDefinition<Props extends object = {}> extends ScreenArrangement<Props> {
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The screen that owns the bar: its title, buttons, toolbar and status bar are the pair's. It shows alone outside a
|
|
31
|
+
* large window (`compact: 'primary'`), and `primaryEdge` puts it on either side. A name of `screens`.
|
|
32
|
+
*/
|
|
30
33
|
primary: string;
|
|
31
34
|
/** The axes the pair may use. Where the window allows none of them, only the primary shows. @default 'both' */
|
|
32
35
|
axes?: 'horizontal' | 'vertical' | 'both';
|
|
33
36
|
/** `split`: the primary's share along the split, above 0 and below 1, where the fold does not decide. @default 0.5 */
|
|
34
37
|
fraction?: number;
|
|
35
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* The edge the primary takes. `split`: `leading` or `top` puts the primary first, at the leading side or on top;
|
|
40
|
+
* `trailing` or `bottom` puts it second, at the trailing side or at the bottom (one setting for both ways the pair
|
|
41
|
+
* may lie). `overlay`: the primary's edge when the pair goes side by side; an edge of the other axis counts as the
|
|
42
|
+
* nearest one (`top` as leading, `bottom` as trailing). Whatever the edge, the primary is the screen that shows
|
|
43
|
+
* alone outside a large window with `compact: 'primary'`: to show the other screen there, make it the primary.
|
|
44
|
+
* @default 'leading' for split, 'trailing' for overlay
|
|
45
|
+
*/
|
|
36
46
|
primaryEdge?: 'leading' | 'trailing' | 'top' | 'bottom';
|
|
37
47
|
/**
|
|
38
48
|
* Outside a large window: `primary` — the primary alone, as a plain screen; `pair` — the pair as the system lays it
|
|
@@ -85,14 +95,17 @@ export function toEngineArrangement(
|
|
|
85
95
|
): EngineArrangement {
|
|
86
96
|
const full = arrangement as ArrangementDefinition<any>;
|
|
87
97
|
checkFraction(full.fraction);
|
|
98
|
+
const style = arrangement.style ?? 'split';
|
|
88
99
|
return {
|
|
89
100
|
primary,
|
|
90
101
|
secondary: arrangement.secondary,
|
|
91
102
|
secondaryProps: arrangement.secondaryProps,
|
|
92
|
-
style
|
|
103
|
+
style,
|
|
93
104
|
axes: full.axes ?? 'both',
|
|
94
105
|
fraction: full.fraction ?? 0.5,
|
|
95
|
-
|
|
106
|
+
// Where UIKit puts the primary of each style without an edge set: first in a split, at the trailing side of an
|
|
107
|
+
// overlay that goes side by side.
|
|
108
|
+
primaryEdge: full.primaryEdge ?? (style === 'split' ? 'leading' : 'trailing'),
|
|
96
109
|
compact: full.compact ?? 'primary',
|
|
97
110
|
};
|
|
98
111
|
}
|