@erkamyaman/capacitor-foldable 8.0.4 → 8.2.0
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 +121 -12
- package/android/src/main/kotlin/io/github/erkamyaman/foldable/FoldablePlugin.kt +31 -1
- package/dist/docs.json +176 -4
- package/dist/esm/css.d.ts +11 -0
- package/dist/esm/css.js +29 -0
- package/dist/esm/css.js.map +1 -1
- package/dist/esm/definitions.d.ts +95 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/polyfill.js +15 -1
- package/dist/esm/polyfill.js.map +1 -1
- package/dist/esm/web.d.ts +7 -0
- package/dist/esm/web.js +7 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +50 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +50 -1
- package/dist/plugin.js.map +1 -1
- package/ionic-tabs.css +2 -1
- package/ios/Sources/FoldablePlugin/Foldable.swift +41 -1
- package/ios/Sources/FoldablePlugin/FoldableBridgeViewController.swift +41 -0
- package/ios/Sources/FoldablePlugin/FoldablePlugin.swift +51 -6
- package/ios/Sources/FoldablePlugin/ReservedRegionFoldProvider.swift +42 -11
- package/ios/Sources/FoldablePlugin/UIKitFoldSource.swift +3 -3
- package/ios/Tests/FoldablePluginTests/FoldableTests.swift +4 -0
- package/ios/Tests/FoldablePluginTests/ReservedRegionFoldProviderTests.swift +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,19 @@ window.viewport.segments; // two DOMRects when the fold splits the web view
|
|
|
96
96
|
| `@media (vertical-viewport-segments: 2)` | `.vertical-viewport-segments-2` |
|
|
97
97
|
| `env(viewport-segment-width 0 0)` | `var(--viewport-segment-width-0-0)` |
|
|
98
98
|
| Native bars moved to the side (iPhone Duo) | `.vertical-bars-leading`, `.vertical-bars-trailing` |
|
|
99
|
+
| The phone is being folded right now | `.folding` |
|
|
100
|
+
|
|
101
|
+
The segment variables only exist while the device is half-open. To lay out along the fold whether it's flat or half-open, such as a game on one side and its controls on the other, the polyfill also sets the fold's position whenever there is one:
|
|
102
|
+
|
|
103
|
+
| Fold | With the polyfill |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| Direction | `.fold-vertical`, `.fold-horizontal` |
|
|
106
|
+
| Position and size | `var(--fold-left)`, `var(--fold-top)`, `var(--fold-width)`, `var(--fold-height)` |
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
.fold-vertical .game { width: var(--fold-left); }
|
|
110
|
+
.fold-vertical .controls { left: calc(var(--fold-left) + var(--fold-width)); }
|
|
111
|
+
```
|
|
99
112
|
|
|
100
113
|
Using Ionic's `ion-tabs`? `import '@erkamyaman/capacitor-foldable/ionic-tabs.css'` moves it to the side the way native tab bars do on iPhone Duo. See [Ionic tabs](docs/iphone-duo.md#ionic-tabs).
|
|
101
114
|
|
|
@@ -104,6 +117,12 @@ Using Ionic's `ion-tabs`? `import '@erkamyaman/capacitor-foldable/ionic-tabs.css
|
|
|
104
117
|
| <img src="https://raw.githubusercontent.com/erkamyaman/capacitor-foldable/main/docs/images/ionic-before-book.png" width="300" alt="Ionic tab bar across the fold in book posture"> | <img src="https://raw.githubusercontent.com/erkamyaman/capacitor-foldable/main/docs/images/ionic-after-book.png" width="300" alt="Ionic tab bar as a pill on the side in book posture"> |
|
|
105
118
|
| <img src="https://raw.githubusercontent.com/erkamyaman/capacitor-foldable/main/docs/images/ionic-before-closed.png" width="220" alt="Ionic tab bar at the bottom of the closed iPhone Duo"> | <img src="https://raw.githubusercontent.com/erkamyaman/capacitor-foldable/main/docs/images/ionic-after-closed.png" width="220" alt="Ionic tab bar as a pill under the clock on the closed iPhone Duo"> |
|
|
106
119
|
|
|
120
|
+
## Apps built with it
|
|
121
|
+
|
|
122
|
+
- **[Hinge Guess](https://github.com/erkamyaman/hinge-guess)** is a small game: fold the phone to a target angle and score how close you got. Ionic Angular, native tabs, and the plugin behind the dial.
|
|
123
|
+
- **[examples](examples)** in this repo is a catalogue of one-file demos, one per API, laid out as two pages around the crease on iPhone Duo.
|
|
124
|
+
- **[example-app](example-app)** is the plain single-screen app that shows every value the plugin reports at once.
|
|
125
|
+
|
|
107
126
|
## Guides
|
|
108
127
|
|
|
109
128
|
- [Examples](docs/examples.md)
|
|
@@ -118,6 +137,8 @@ Using Ionic's `ion-tabs`? `import '@erkamyaman/capacitor-foldable/ionic-tabs.css
|
|
|
118
137
|
* [`isDeviceFoldable()`](#isdevicefoldable)
|
|
119
138
|
* [`getFoldState()`](#getfoldstate)
|
|
120
139
|
* [`getHingeAngle()`](#gethingeangle)
|
|
140
|
+
* [`getReservedRegions()`](#getreservedregions)
|
|
141
|
+
* [`setVerticalBarBehavior(...)`](#setverticalbarbehavior)
|
|
121
142
|
* [`getSizeClass()`](#getsizeclass)
|
|
122
143
|
* [`getDisplayModes()`](#getdisplaymodes)
|
|
123
144
|
* [`getBarPlacement()`](#getbarplacement)
|
|
@@ -129,6 +150,7 @@ Using Ionic's `ion-tabs`? `import '@erkamyaman/capacitor-foldable/ionic-tabs.css
|
|
|
129
150
|
* [`addListener('hingeAngleChange', ...)`](#addlistenerhingeanglechange-)
|
|
130
151
|
* [`addListener('sizeClassChange', ...)`](#addlistenersizeclasschange-)
|
|
131
152
|
* [`addListener('displayModeChange', ...)`](#addlistenerdisplaymodechange-)
|
|
153
|
+
* [`addListener('foldingChange', ...)`](#addlistenerfoldingchange-)
|
|
132
154
|
* [`addListener('barPlacementChange', ...)`](#addlistenerbarplacementchange-)
|
|
133
155
|
* [Interfaces](#interfaces)
|
|
134
156
|
* [Type Aliases](#type-aliases)
|
|
@@ -175,20 +197,61 @@ fold information.
|
|
|
175
197
|
### getHingeAngle()
|
|
176
198
|
|
|
177
199
|
```typescript
|
|
178
|
-
getHingeAngle() => Promise<{ angle: number | null; }>
|
|
200
|
+
getHingeAngle() => Promise<{ angle: number | null; status: HingeStatus | null; }>
|
|
179
201
|
```
|
|
180
202
|
|
|
181
203
|
Read the angle between the two halves of the device, in degrees: `0` when
|
|
182
204
|
closed, `180` when flat. Resolves to `{ angle: null }` on devices without a
|
|
183
205
|
hinge angle sensor, on web, and on iOS before 27.1.
|
|
184
206
|
|
|
185
|
-
**Returns:** <code>Promise<{ angle: number | null; }></code>
|
|
207
|
+
**Returns:** <code>Promise<{ angle: number | null; status: <a href="#hingestatus">HingeStatus</a> | null; }></code>
|
|
186
208
|
|
|
187
209
|
**Since:** 7.0.0
|
|
188
210
|
|
|
189
211
|
--------------------
|
|
190
212
|
|
|
191
213
|
|
|
214
|
+
### getReservedRegions()
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
getReservedRegions() => Promise<{ regions: ReservedRegion[]; }>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Read every region the system reserves on this display: the fold and
|
|
221
|
+
anything covering the screen, active or not. iPhone Duo reports the fold,
|
|
222
|
+
the vertical status bar area and the under-display camera. Resolves to an
|
|
223
|
+
empty list on Android, on web and on iOS before 27.1.
|
|
224
|
+
|
|
225
|
+
**Returns:** <code>Promise<{ regions: ReservedRegion[]; }></code>
|
|
226
|
+
|
|
227
|
+
**Since:** 8.2.0
|
|
228
|
+
|
|
229
|
+
--------------------
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### setVerticalBarBehavior(...)
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
setVerticalBarBehavior(options: { behavior: 'automatic' | 'disabled'; }) => Promise<{ applied: boolean; }>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Choose whether iPhone Duo may move this app's bars to the side of the
|
|
239
|
+
display. `'disabled'` keeps everything horizontal, including the status
|
|
240
|
+
bar. Resolves to `{ applied: false }` when the app does not use
|
|
241
|
+
`FoldableBridgeViewController`, on Android, on web and on iOS before 27.1.
|
|
242
|
+
See the iPhone Duo guide for the one-line storyboard change.
|
|
243
|
+
|
|
244
|
+
| Param | Type |
|
|
245
|
+
| ------------- | ----------------------------------------------------- |
|
|
246
|
+
| **`options`** | <code>{ behavior: 'automatic' \| 'disabled'; }</code> |
|
|
247
|
+
|
|
248
|
+
**Returns:** <code>Promise<{ applied: boolean; }></code>
|
|
249
|
+
|
|
250
|
+
**Since:** 8.2.0
|
|
251
|
+
|
|
252
|
+
--------------------
|
|
253
|
+
|
|
254
|
+
|
|
192
255
|
### getSizeClass()
|
|
193
256
|
|
|
194
257
|
```typescript
|
|
@@ -391,6 +454,30 @@ the user folded or unfolded the device. Never fires on iOS and web.
|
|
|
391
454
|
--------------------
|
|
392
455
|
|
|
393
456
|
|
|
457
|
+
### addListener('foldingChange', ...)
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
addListener(eventName: 'foldingChange', listenerFunc: (event: { folding: boolean; }) => void) => Promise<PluginListenerHandle>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Listen for the device being folded or unfolded. `folding` turns `true` as
|
|
464
|
+
soon as the hinge starts moving and `false` about half a second after it
|
|
465
|
+
stops, so an app can pause animations or heavy work while the screen is in
|
|
466
|
+
motion. Only fires where a hinge angle is available: Android foldables with
|
|
467
|
+
a hinge sensor, and iPhone Duo on iOS 27.1 or later.
|
|
468
|
+
|
|
469
|
+
| Param | Type |
|
|
470
|
+
| ------------------ | ------------------------------------------------------ |
|
|
471
|
+
| **`eventName`** | <code>'foldingChange'</code> |
|
|
472
|
+
| **`listenerFunc`** | <code>(event: { folding: boolean; }) => void</code> |
|
|
473
|
+
|
|
474
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
475
|
+
|
|
476
|
+
**Since:** 8.2.0
|
|
477
|
+
|
|
478
|
+
--------------------
|
|
479
|
+
|
|
480
|
+
|
|
394
481
|
### addListener('barPlacementChange', ...)
|
|
395
482
|
|
|
396
483
|
```typescript
|
|
@@ -417,16 +504,30 @@ Only fires on iOS 27.1 or later.
|
|
|
417
504
|
|
|
418
505
|
#### FoldState
|
|
419
506
|
|
|
420
|
-
| Prop | Type
|
|
421
|
-
| ---------------------- |
|
|
422
|
-
| **`state`** | <code>'flat' \| 'half-opened' \| 'closed'</code>
|
|
423
|
-
| **`isSeparating`** | <code>boolean</code>
|
|
424
|
-
| **`posture`** | <code>'flat' \| 'tabletop' \| 'book'</code>
|
|
425
|
-
| **`hingeOrientation`** | <code>'horizontal' \| 'vertical'</code>
|
|
426
|
-
| **`hingeBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code>
|
|
427
|
-
| **`occludedBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code>
|
|
428
|
-
| **`activeDisplay`** | <code>'inner' \| 'outer'</code>
|
|
429
|
-
| **`cameraBounds`** | <code>{ x: number; y: number; width: number; height: number; }[]</code>
|
|
507
|
+
| Prop | Type | Description | Since |
|
|
508
|
+
| ---------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
509
|
+
| **`state`** | <code>'flat' \| 'half-opened' \| 'closed'</code> | Posture of the fold. `'closed'` is never reported today: a device shut on its cover display reports `'flat'`. | 7.0.0 |
|
|
510
|
+
| **`isSeparating`** | <code>boolean</code> | Whether the fold splits the web view into two areas: `true` when half-opened, or when the hinge has a physical gap. | 7.0.0 |
|
|
511
|
+
| **`posture`** | <code>'flat' \| 'tabletop' \| 'book'</code> | How the device is held: `'tabletop'` when half-opened with a horizontal hinge, like a laptop, `'book'` when half-opened with a vertical hinge, and `'flat'` otherwise. | 7.0.0 |
|
|
512
|
+
| **`hingeOrientation`** | <code>'horizontal' \| 'vertical'</code> | Direction of the hinge relative to the window, so it flips when the device rotates. Omitted when there is no fold. | 7.0.0 |
|
|
513
|
+
| **`hingeBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code> | Position of the fold in CSS pixels, relative to the web view. Zero wide (or zero tall) on a seamless fold. Omitted when there is no fold. | 7.0.0 |
|
|
514
|
+
| **`occludedBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code> | Area of the web view the hinge covers, in CSS pixels. Only present on devices with a physical gap. | 7.0.0 |
|
|
515
|
+
| **`activeDisplay`** | <code>'inner' \| 'outer'</code> | Which display is showing the app, on a device with an inner and an outer display such as iPhone Duo. Reported on iPhone Duo (iOS 27.1 or later) and omitted elsewhere. | 7.0.0 |
|
|
516
|
+
| **`cameraBounds`** | <code>{ x: number; y: number; width: number; height: number; }[]</code> | Areas of the web view something covers, in CSS pixels. On Android these are the display cutouts. On iPhone Duo these are the active occlusion regions: the camera in use, and the strip the system keeps for the vertical status bar. Use `getReservedRegions()` when you need to tell them apart. Omitted when there are none. | 7.0.0 |
|
|
517
|
+
| **`hingeMargins`** | <code>{ top: number; right: number; bottom: number; left: number; }</code> | Space the system asks you to keep clear around the fold, in CSS pixels. `hingeBounds` covers the crease plus these margins, so the crease itself is `hingeBounds` shrunk by them. iPhone Duo reports 20 on each side of a vertical fold. Omitted on Android and where there is no fold. | 8.2.0 |
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
#### ReservedRegion
|
|
521
|
+
|
|
522
|
+
| Prop | Type | Description | Since |
|
|
523
|
+
| -------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
|
524
|
+
| **`kind`** | <code>'division' \| 'occlusion'</code> | `'division'` is the fold itself. `'occlusion'` is something covering the display, such as a camera or the vertical status bar area. | 8.2.0 |
|
|
525
|
+
| **`isActive`** | <code>boolean</code> | Whether the region applies right now. An inactive division is a flat fold, and an inactive occlusion is a camera that is not in use. | 8.2.0 |
|
|
526
|
+
| **`x`** | <code>number</code> | | |
|
|
527
|
+
| **`y`** | <code>number</code> | | |
|
|
528
|
+
| **`width`** | <code>number</code> | | |
|
|
529
|
+
| **`height`** | <code>number</code> | | |
|
|
530
|
+
| **`margins`** | <code>{ top: number; right: number; bottom: number; left: number; }</code> | Space to keep clear around the region, in CSS pixels. | 8.2.0 |
|
|
430
531
|
|
|
431
532
|
|
|
432
533
|
#### SizeClass
|
|
@@ -464,6 +565,14 @@ Only fires on iOS 27.1 or later.
|
|
|
464
565
|
### Type Aliases
|
|
465
566
|
|
|
466
567
|
|
|
568
|
+
#### HingeStatus
|
|
569
|
+
|
|
570
|
+
How far open the hinge is, as the system sees it. Note that it can lag the
|
|
571
|
+
angle: iOS keeps reporting `'closed'` for a moment after the phone opens.
|
|
572
|
+
|
|
573
|
+
<code>'closed' | 'partiallyOpen' | 'fullyOpen'</code>
|
|
574
|
+
|
|
575
|
+
|
|
467
576
|
#### DisplayModeStatus
|
|
468
577
|
|
|
469
578
|
<code>'unsupported' | 'unavailable' | 'available' | 'active'</code>
|
|
@@ -39,6 +39,8 @@ class FoldablePlugin : Plugin() {
|
|
|
39
39
|
private var displayModes: DisplayModes? = null
|
|
40
40
|
private var lastKnownState: FoldState? = null
|
|
41
41
|
private var hingeAngleJob: Job? = null
|
|
42
|
+
private var foldingJob: Job? = null
|
|
43
|
+
private var isFolding = false
|
|
42
44
|
private var lastKnownAngle: Float? = null
|
|
43
45
|
private var lastSizeClass: SizeClass? = null
|
|
44
46
|
private var lastDisplayModes: DisplayModeStatuses? = null
|
|
@@ -106,6 +108,16 @@ class FoldablePlugin : Plugin() {
|
|
|
106
108
|
)
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
@PluginMethod
|
|
112
|
+
fun setVerticalBarBehavior(call: PluginCall) {
|
|
113
|
+
call.resolve(JSObject().put("applied", false))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@PluginMethod
|
|
117
|
+
fun getReservedRegions(call: PluginCall) {
|
|
118
|
+
call.resolve(JSObject().put("regions", JSArray()))
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
@PluginMethod
|
|
110
122
|
fun getFoldState(call: PluginCall) {
|
|
111
123
|
lastKnownState?.let {
|
|
@@ -234,7 +246,7 @@ class FoldablePlugin : Plugin() {
|
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
private fun updateHingeAngleUpdates() {
|
|
237
|
-
if (!hasListeners(HINGE_ANGLE_CHANGE)) {
|
|
249
|
+
if (!hasListeners(HINGE_ANGLE_CHANGE) && !hasListeners(FOLDING_CHANGE)) {
|
|
238
250
|
hingeAngleJob?.cancel()
|
|
239
251
|
hingeAngleJob = null
|
|
240
252
|
lastKnownAngle = null
|
|
@@ -250,6 +262,7 @@ class FoldablePlugin : Plugin() {
|
|
|
250
262
|
implementation.hingeAngles().conflate().collect { angle ->
|
|
251
263
|
lastKnownAngle = angle
|
|
252
264
|
notifyListeners(HINGE_ANGLE_CHANGE, angleResult(angle))
|
|
265
|
+
notifyFolding()
|
|
253
266
|
delay(HINGE_ANGLE_FRAME_MS)
|
|
254
267
|
}
|
|
255
268
|
} finally {
|
|
@@ -259,6 +272,21 @@ class FoldablePlugin : Plugin() {
|
|
|
259
272
|
}
|
|
260
273
|
}
|
|
261
274
|
|
|
275
|
+
/** `true` while the hinge keeps moving, `false` once it has been still for a moment. */
|
|
276
|
+
private fun notifyFolding() {
|
|
277
|
+
if (!isFolding) {
|
|
278
|
+
isFolding = true
|
|
279
|
+
notifyListeners(FOLDING_CHANGE, JSObject().put("folding", true))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
foldingJob?.cancel()
|
|
283
|
+
foldingJob = scope.launch {
|
|
284
|
+
delay(FOLDING_SETTLE_MS)
|
|
285
|
+
isFolding = false
|
|
286
|
+
notifyListeners(FOLDING_CHANGE, JSObject().put("folding", false))
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
@SuppressLint("SetJavaScriptEnabled")
|
|
263
291
|
private fun dualScreenWebView(context: Context, url: String): WebView = WebView(context).apply {
|
|
264
292
|
settings.javaScriptEnabled = true
|
|
@@ -326,6 +354,8 @@ class FoldablePlugin : Plugin() {
|
|
|
326
354
|
private companion object {
|
|
327
355
|
const val FIRST_EMISSION_TIMEOUT_MS = 1_000L
|
|
328
356
|
const val HINGE_ANGLE_CHANGE = "hingeAngleChange"
|
|
357
|
+
const val FOLDING_CHANGE = "foldingChange"
|
|
358
|
+
const val FOLDING_SETTLE_MS = 500L
|
|
329
359
|
const val HINGE_ANGLE_FRAME_MS = 16L
|
|
330
360
|
const val SIZE_CLASS_CHANGE = "sizeClassChange"
|
|
331
361
|
const val DISPLAY_MODE_CHANGE = "displayModeChange"
|
package/dist/docs.json
CHANGED
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"name": "getHingeAngle",
|
|
42
|
-
"signature": "() => Promise<{ angle: number | null; }>",
|
|
42
|
+
"signature": "() => Promise<{ angle: number | null; status: HingeStatus | null; }>",
|
|
43
43
|
"parameters": [],
|
|
44
|
-
"returns": "Promise<{ angle: number | null; }>",
|
|
44
|
+
"returns": "Promise<{ angle: number | null; status: HingeStatus | null; }>",
|
|
45
45
|
"tags": [
|
|
46
46
|
{
|
|
47
47
|
"name": "since",
|
|
@@ -49,9 +49,49 @@
|
|
|
49
49
|
}
|
|
50
50
|
],
|
|
51
51
|
"docs": "Read the angle between the two halves of the device, in degrees: `0` when\nclosed, `180` when flat. Resolves to `{ angle: null }` on devices without a\nhinge angle sensor, on web, and on iOS before 27.1.",
|
|
52
|
-
"complexTypes": [
|
|
52
|
+
"complexTypes": [
|
|
53
|
+
"HingeStatus"
|
|
54
|
+
],
|
|
53
55
|
"slug": "gethingeangle"
|
|
54
56
|
},
|
|
57
|
+
{
|
|
58
|
+
"name": "getReservedRegions",
|
|
59
|
+
"signature": "() => Promise<{ regions: ReservedRegion[]; }>",
|
|
60
|
+
"parameters": [],
|
|
61
|
+
"returns": "Promise<{ regions: ReservedRegion[]; }>",
|
|
62
|
+
"tags": [
|
|
63
|
+
{
|
|
64
|
+
"name": "since",
|
|
65
|
+
"text": "8.2.0"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"docs": "Read every region the system reserves on this display: the fold and\nanything covering the screen, active or not. iPhone Duo reports the fold,\nthe vertical status bar area and the under-display camera. Resolves to an\nempty list on Android, on web and on iOS before 27.1.",
|
|
69
|
+
"complexTypes": [
|
|
70
|
+
"ReservedRegion"
|
|
71
|
+
],
|
|
72
|
+
"slug": "getreservedregions"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "setVerticalBarBehavior",
|
|
76
|
+
"signature": "(options: { behavior: 'automatic' | 'disabled'; }) => Promise<{ applied: boolean; }>",
|
|
77
|
+
"parameters": [
|
|
78
|
+
{
|
|
79
|
+
"name": "options",
|
|
80
|
+
"docs": "",
|
|
81
|
+
"type": "{ behavior: 'automatic' | 'disabled'; }"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"returns": "Promise<{ applied: boolean; }>",
|
|
85
|
+
"tags": [
|
|
86
|
+
{
|
|
87
|
+
"name": "since",
|
|
88
|
+
"text": "8.2.0"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"docs": "Choose whether iPhone Duo may move this app's bars to the side of the\ndisplay. `'disabled'` keeps everything horizontal, including the status\nbar. Resolves to `{ applied: false }` when the app does not use\n`FoldableBridgeViewController`, on Android, on web and on iOS before 27.1.\nSee the iPhone Duo guide for the one-line storyboard change.",
|
|
92
|
+
"complexTypes": [],
|
|
93
|
+
"slug": "setverticalbarbehavior"
|
|
94
|
+
},
|
|
55
95
|
{
|
|
56
96
|
"name": "getSizeClass",
|
|
57
97
|
"signature": "() => Promise<SizeClass>",
|
|
@@ -284,6 +324,34 @@
|
|
|
284
324
|
],
|
|
285
325
|
"slug": "addlistenerdisplaymodechange-"
|
|
286
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"name": "addListener",
|
|
329
|
+
"signature": "(eventName: 'foldingChange', listenerFunc: (event: { folding: boolean; }) => void) => Promise<PluginListenerHandle>",
|
|
330
|
+
"parameters": [
|
|
331
|
+
{
|
|
332
|
+
"name": "eventName",
|
|
333
|
+
"docs": "",
|
|
334
|
+
"type": "'foldingChange'"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "listenerFunc",
|
|
338
|
+
"docs": "",
|
|
339
|
+
"type": "(event: { folding: boolean; }) => void"
|
|
340
|
+
}
|
|
341
|
+
],
|
|
342
|
+
"returns": "Promise<PluginListenerHandle>",
|
|
343
|
+
"tags": [
|
|
344
|
+
{
|
|
345
|
+
"name": "since",
|
|
346
|
+
"text": "8.2.0"
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
"docs": "Listen for the device being folded or unfolded. `folding` turns `true` as\nsoon as the hinge starts moving and `false` about half a second after it\nstops, so an app can pause animations or heavy work while the screen is in\nmotion. Only fires where a hinge angle is available: Android foldables with\na hinge sensor, and iPhone Duo on iOS 27.1 or later.",
|
|
350
|
+
"complexTypes": [
|
|
351
|
+
"PluginListenerHandle"
|
|
352
|
+
],
|
|
353
|
+
"slug": "addlistenerfoldingchange-"
|
|
354
|
+
},
|
|
287
355
|
{
|
|
288
356
|
"name": "addListener",
|
|
289
357
|
"signature": "(eventName: 'barPlacementChange', listenerFunc: (placement: BarPlacement) => void) => Promise<PluginListenerHandle>",
|
|
@@ -416,9 +484,94 @@
|
|
|
416
484
|
"name": "since"
|
|
417
485
|
}
|
|
418
486
|
],
|
|
419
|
-
"docs": "Areas of the web view
|
|
487
|
+
"docs": "Areas of the web view something covers, in CSS pixels. On Android these are\nthe display cutouts. On iPhone Duo these are the active occlusion regions:\nthe camera in use, and the strip the system keeps for the vertical status\nbar. Use `getReservedRegions()` when you need to tell them apart. Omitted\nwhen there are none.",
|
|
420
488
|
"complexTypes": [],
|
|
421
489
|
"type": "{ x: number; y: number; width: number; height: number; }[] | undefined"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "hingeMargins",
|
|
493
|
+
"tags": [
|
|
494
|
+
{
|
|
495
|
+
"text": "8.2.0",
|
|
496
|
+
"name": "since"
|
|
497
|
+
}
|
|
498
|
+
],
|
|
499
|
+
"docs": "Space the system asks you to keep clear around the fold, in CSS pixels.\n`hingeBounds` covers the crease plus these margins, so the crease itself is\n`hingeBounds` shrunk by them. iPhone Duo reports 20 on each side of a\nvertical fold. Omitted on Android and where there is no fold.",
|
|
500
|
+
"complexTypes": [],
|
|
501
|
+
"type": "{ top: number; right: number; bottom: number; left: number; } | undefined"
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"name": "ReservedRegion",
|
|
507
|
+
"slug": "reservedregion",
|
|
508
|
+
"docs": "",
|
|
509
|
+
"tags": [],
|
|
510
|
+
"methods": [],
|
|
511
|
+
"properties": [
|
|
512
|
+
{
|
|
513
|
+
"name": "kind",
|
|
514
|
+
"tags": [
|
|
515
|
+
{
|
|
516
|
+
"text": "8.2.0",
|
|
517
|
+
"name": "since"
|
|
518
|
+
}
|
|
519
|
+
],
|
|
520
|
+
"docs": "`'division'` is the fold itself. `'occlusion'` is something covering the\ndisplay, such as a camera or the vertical status bar area.",
|
|
521
|
+
"complexTypes": [],
|
|
522
|
+
"type": "'division' | 'occlusion'"
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
"name": "isActive",
|
|
526
|
+
"tags": [
|
|
527
|
+
{
|
|
528
|
+
"text": "8.2.0",
|
|
529
|
+
"name": "since"
|
|
530
|
+
}
|
|
531
|
+
],
|
|
532
|
+
"docs": "Whether the region applies right now. An inactive division is a flat fold,\nand an inactive occlusion is a camera that is not in use.",
|
|
533
|
+
"complexTypes": [],
|
|
534
|
+
"type": "boolean"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"name": "x",
|
|
538
|
+
"tags": [],
|
|
539
|
+
"docs": "",
|
|
540
|
+
"complexTypes": [],
|
|
541
|
+
"type": "number"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
"name": "y",
|
|
545
|
+
"tags": [],
|
|
546
|
+
"docs": "",
|
|
547
|
+
"complexTypes": [],
|
|
548
|
+
"type": "number"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"name": "width",
|
|
552
|
+
"tags": [],
|
|
553
|
+
"docs": "",
|
|
554
|
+
"complexTypes": [],
|
|
555
|
+
"type": "number"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"name": "height",
|
|
559
|
+
"tags": [],
|
|
560
|
+
"docs": "",
|
|
561
|
+
"complexTypes": [],
|
|
562
|
+
"type": "number"
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"name": "margins",
|
|
566
|
+
"tags": [
|
|
567
|
+
{
|
|
568
|
+
"text": "8.2.0",
|
|
569
|
+
"name": "since"
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
"docs": "Space to keep clear around the region, in CSS pixels.",
|
|
573
|
+
"complexTypes": [],
|
|
574
|
+
"type": "{ top: number; right: number; bottom: number; left: number; }"
|
|
422
575
|
}
|
|
423
576
|
]
|
|
424
577
|
},
|
|
@@ -556,6 +709,25 @@
|
|
|
556
709
|
],
|
|
557
710
|
"enums": [],
|
|
558
711
|
"typeAliases": [
|
|
712
|
+
{
|
|
713
|
+
"name": "HingeStatus",
|
|
714
|
+
"slug": "hingestatus",
|
|
715
|
+
"docs": "How far open the hinge is, as the system sees it. Note that it can lag the\nangle: iOS keeps reporting `'closed'` for a moment after the phone opens.",
|
|
716
|
+
"types": [
|
|
717
|
+
{
|
|
718
|
+
"text": "'closed'",
|
|
719
|
+
"complexTypes": []
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
"text": "'partiallyOpen'",
|
|
723
|
+
"complexTypes": []
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"text": "'fullyOpen'",
|
|
727
|
+
"complexTypes": []
|
|
728
|
+
}
|
|
729
|
+
]
|
|
730
|
+
},
|
|
559
731
|
{
|
|
560
732
|
"name": "DisplayModeStatus",
|
|
561
733
|
"slug": "displaymodestatus",
|
package/dist/esm/css.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { SegmentRect } from './segments';
|
|
2
2
|
export declare const CSS_CLASSES: string[];
|
|
3
3
|
export declare const BAR_CLASSES: string[];
|
|
4
|
+
export declare const FOLD_CLASSES: string[];
|
|
5
|
+
export declare const FOLD_VARIABLES: string[];
|
|
6
|
+
export declare function foldCssFor(hingeBounds: SegmentRect | undefined, hingeOrientation: 'horizontal' | 'vertical' | undefined, hingeMargins?: {
|
|
7
|
+
top: number;
|
|
8
|
+
right: number;
|
|
9
|
+
bottom: number;
|
|
10
|
+
left: number;
|
|
11
|
+
}): {
|
|
12
|
+
variables: Record<string, string>;
|
|
13
|
+
className: string | null;
|
|
14
|
+
};
|
|
4
15
|
export declare function barClassFor(edge: 'leading' | 'trailing' | null): string | null;
|
|
5
16
|
export declare function verticalTabBarBottom(cameras: SegmentRect[], viewportHeight: number): number;
|
|
6
17
|
export declare function cssFor(segments: SegmentRect[], posture: 'continuous' | 'folded'): {
|
package/dist/esm/css.js
CHANGED
|
@@ -5,6 +5,35 @@ export const CSS_CLASSES = [
|
|
|
5
5
|
'device-posture-folded',
|
|
6
6
|
];
|
|
7
7
|
export const BAR_CLASSES = ['vertical-bars-leading', 'vertical-bars-trailing'];
|
|
8
|
+
export const FOLD_CLASSES = ['fold-vertical', 'fold-horizontal'];
|
|
9
|
+
export const FOLD_VARIABLES = [
|
|
10
|
+
'--fold-left',
|
|
11
|
+
'--fold-top',
|
|
12
|
+
'--fold-width',
|
|
13
|
+
'--fold-height',
|
|
14
|
+
'--fold-margin-top',
|
|
15
|
+
'--fold-margin-right',
|
|
16
|
+
'--fold-margin-bottom',
|
|
17
|
+
'--fold-margin-left',
|
|
18
|
+
];
|
|
19
|
+
export function foldCssFor(hingeBounds, hingeOrientation, hingeMargins) {
|
|
20
|
+
if (!hingeBounds || !hingeOrientation)
|
|
21
|
+
return { variables: {}, className: null };
|
|
22
|
+
const margins = hingeMargins !== null && hingeMargins !== void 0 ? hingeMargins : { top: 0, right: 0, bottom: 0, left: 0 };
|
|
23
|
+
return {
|
|
24
|
+
variables: {
|
|
25
|
+
'--fold-left': `${hingeBounds.x}px`,
|
|
26
|
+
'--fold-top': `${hingeBounds.y}px`,
|
|
27
|
+
'--fold-width': `${hingeBounds.width}px`,
|
|
28
|
+
'--fold-height': `${hingeBounds.height}px`,
|
|
29
|
+
'--fold-margin-top': `${margins.top}px`,
|
|
30
|
+
'--fold-margin-right': `${margins.right}px`,
|
|
31
|
+
'--fold-margin-bottom': `${margins.bottom}px`,
|
|
32
|
+
'--fold-margin-left': `${margins.left}px`,
|
|
33
|
+
},
|
|
34
|
+
className: `fold-${hingeOrientation}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
8
37
|
export function barClassFor(edge) {
|
|
9
38
|
return edge ? `vertical-bars-${edge}` : null;
|
|
10
39
|
}
|
package/dist/esm/css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/css.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gCAAgC;IAChC,8BAA8B;IAC9B,2BAA2B;IAC3B,uBAAuB;CACxB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,uBAAuB,EAAE,wBAAwB,CAAC,CAAC;AAE/E,MAAM,UAAU,WAAW,CAAC,IAAmC;IAC7D,OAAO,IAAI,CAAC,CAAC,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,UAAU,oBAAoB,CAAC,OAAsB,EAAE,cAAsB;IACjF,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,IAAI,CAAC,MAAM;QAAE,OAAO,cAAc,CAAC;IACnC,OAAO,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,MAAM,CACpB,QAAuB,EACvB,OAAgC;IAEhC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,SAAS,GAA2B,EAAE,CAAC;IAE7C,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC;QACrD,SAAS,CAAC,0BAA0B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC;QACjE,SAAS,CAAC,2BAA2B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC;QAClE,SAAS,CAAC,6BAA6B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;QACrF,SAAS,CAAC,4BAA4B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC;QACnF,SAAS,CAAC,4BAA4B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC;QACvE,SAAS,CAAC,6BAA6B,MAAM,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAC;IAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC;IAC5F,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC","sourcesContent":["import type { SegmentRect } from './segments';\n\nexport const CSS_CLASSES = [\n 'horizontal-viewport-segments-2',\n 'vertical-viewport-segments-2',\n 'device-posture-continuous',\n 'device-posture-folded',\n];\n\nexport const BAR_CLASSES = ['vertical-bars-leading', 'vertical-bars-trailing'];\n\nexport function barClassFor(edge: 'leading' | 'trailing' | null): string | null {\n return edge ? `vertical-bars-${edge}` : null;\n}\n\nconst TAB_BAR_MARGIN = 24;\nconst CAMERA_GAP = 15;\n\nexport function verticalTabBarBottom(cameras: SegmentRect[], viewportHeight: number): number {\n const below = cameras.filter((camera) => camera.y > viewportHeight / 2);\n const camera = below.sort((a, b) => a.width * a.height - b.width * b.height)[0];\n if (!camera) return TAB_BAR_MARGIN;\n return Math.max(TAB_BAR_MARGIN, Math.round(viewportHeight - camera.y + CAMERA_GAP));\n}\n\nexport function cssFor(\n segments: SegmentRect[],\n posture: 'continuous' | 'folded',\n): { variables: Record<string, string>; classes: string[] } {\n const stacked = segments.length > 1 && segments[1].y > segments[0].y;\n const variables: Record<string, string> = {};\n\n segments.forEach((segment, index) => {\n const suffix = stacked ? `0-${index}` : `${index}-0`;\n variables[`--viewport-segment-top-${suffix}`] = `${segment.y}px`;\n variables[`--viewport-segment-left-${suffix}`] = `${segment.x}px`;\n variables[`--viewport-segment-bottom-${suffix}`] = `${segment.y + segment.height}px`;\n variables[`--viewport-segment-right-${suffix}`] = `${segment.x + segment.width}px`;\n variables[`--viewport-segment-width-${suffix}`] = `${segment.width}px`;\n variables[`--viewport-segment-height-${suffix}`] = `${segment.height}px`;\n });\n\n const classes = [`device-posture-${posture}`];\n if (segments.length > 1) {\n classes.push(stacked ? 'vertical-viewport-segments-2' : 'horizontal-viewport-segments-2');\n }\n\n return { variables, classes };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/css.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SegmentRect } from './segments';\n\nexport const CSS_CLASSES = [\n 'horizontal-viewport-segments-2',\n 'vertical-viewport-segments-2',\n 'device-posture-continuous',\n 'device-posture-folded',\n];\n\nexport const BAR_CLASSES = ['vertical-bars-leading', 'vertical-bars-trailing'];\n\nexport const FOLD_CLASSES = ['fold-vertical', 'fold-horizontal'];\n\nexport const FOLD_VARIABLES = [\n '--fold-left',\n '--fold-top',\n '--fold-width',\n '--fold-height',\n '--fold-margin-top',\n '--fold-margin-right',\n '--fold-margin-bottom',\n '--fold-margin-left',\n];\n\nexport function foldCssFor(\n hingeBounds: SegmentRect | undefined,\n hingeOrientation: 'horizontal' | 'vertical' | undefined,\n hingeMargins?: { top: number; right: number; bottom: number; left: number },\n): { variables: Record<string, string>; className: string | null } {\n if (!hingeBounds || !hingeOrientation) return { variables: {}, className: null };\n\n const margins = hingeMargins ?? { top: 0, right: 0, bottom: 0, left: 0 };\n\n return {\n variables: {\n '--fold-left': `${hingeBounds.x}px`,\n '--fold-top': `${hingeBounds.y}px`,\n '--fold-width': `${hingeBounds.width}px`,\n '--fold-height': `${hingeBounds.height}px`,\n '--fold-margin-top': `${margins.top}px`,\n '--fold-margin-right': `${margins.right}px`,\n '--fold-margin-bottom': `${margins.bottom}px`,\n '--fold-margin-left': `${margins.left}px`,\n },\n className: `fold-${hingeOrientation}`,\n };\n}\n\nexport function barClassFor(edge: 'leading' | 'trailing' | null): string | null {\n return edge ? `vertical-bars-${edge}` : null;\n}\n\nconst TAB_BAR_MARGIN = 24;\nconst CAMERA_GAP = 15;\n\nexport function verticalTabBarBottom(cameras: SegmentRect[], viewportHeight: number): number {\n const below = cameras.filter((camera) => camera.y > viewportHeight / 2);\n const camera = below.sort((a, b) => a.width * a.height - b.width * b.height)[0];\n if (!camera) return TAB_BAR_MARGIN;\n return Math.max(TAB_BAR_MARGIN, Math.round(viewportHeight - camera.y + CAMERA_GAP));\n}\n\nexport function cssFor(\n segments: SegmentRect[],\n posture: 'continuous' | 'folded',\n): { variables: Record<string, string>; classes: string[] } {\n const stacked = segments.length > 1 && segments[1].y > segments[0].y;\n const variables: Record<string, string> = {};\n\n segments.forEach((segment, index) => {\n const suffix = stacked ? `0-${index}` : `${index}-0`;\n variables[`--viewport-segment-top-${suffix}`] = `${segment.y}px`;\n variables[`--viewport-segment-left-${suffix}`] = `${segment.x}px`;\n variables[`--viewport-segment-bottom-${suffix}`] = `${segment.y + segment.height}px`;\n variables[`--viewport-segment-right-${suffix}`] = `${segment.x + segment.width}px`;\n variables[`--viewport-segment-width-${suffix}`] = `${segment.width}px`;\n variables[`--viewport-segment-height-${suffix}`] = `${segment.height}px`;\n });\n\n const classes = [`device-posture-${posture}`];\n if (segments.length > 1) {\n classes.push(stacked ? 'vertical-viewport-segments-2' : 'horizontal-viewport-segments-2');\n }\n\n return { variables, classes };\n}\n"]}
|