@glassly/miniapp 0.3.7-dev.894 → 0.3.7-dev.897
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/dist/globals.d.ts +16 -4
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/react/capsuleHeaderLayout.d.ts +8 -1
- package/dist/react/capsuleHeaderLayout.d.ts.map +1 -1
- package/dist/react/capsuleHeaderLayout.js +11 -0
- package/dist/react/capsuleHeaderLayout.js.map +1 -1
- package/dist/react/useCapsuleHeaderStyle.d.ts +3 -1
- package/dist/react/useCapsuleHeaderStyle.d.ts.map +1 -1
- package/dist/react/useCapsuleHeaderStyle.js +3 -1
- package/dist/react/useCapsuleHeaderStyle.js.map +1 -1
- package/dist/react/useSafeArea.d.ts +13 -7
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +39 -10
- package/dist/react/useSafeArea.js.map +1 -1
- package/package.json +2 -2
- package/src/globals.ts +16 -4
- package/src/react/capsuleHeaderLayout.ts +16 -1
- package/src/react/useCapsuleHeaderStyle.ts +3 -1
- package/src/react/useSafeArea.ts +49 -14
package/dist/globals.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ export interface MiniappCapsuleMenuRect {
|
|
|
16
16
|
left: number;
|
|
17
17
|
width: number;
|
|
18
18
|
height: number;
|
|
19
|
+
/**
|
|
20
|
+
* `corner` floats over the page's top-right; `rail` sits in the iPhone
|
|
21
|
+
* Duo's status rail, inside the side safe-area inset and clear of the page.
|
|
22
|
+
* Absent on hosts older than the field.
|
|
23
|
+
*/
|
|
24
|
+
placement?: "corner" | "rail";
|
|
19
25
|
}
|
|
20
26
|
export type MiniappColorScheme = "light" | "dark";
|
|
21
27
|
export interface HostGlobals {
|
|
@@ -29,12 +35,18 @@ export interface HostGlobals {
|
|
|
29
35
|
miniappLocal?: boolean;
|
|
30
36
|
/** True when the miniapp is running via the dev workflow (QR sideload). */
|
|
31
37
|
miniappDeveloperMode?: boolean;
|
|
32
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Safe-area insets around the WebView content. Miniapps should pad
|
|
40
|
+
* accordingly. On an iPhone Duo the status rail is an 84px side inset. The
|
|
41
|
+
* host updates this and dispatches `glassly:safe-area-change` when they
|
|
42
|
+
* change (a Duo folding or unfolding).
|
|
43
|
+
*/
|
|
33
44
|
safeAreaInsets?: MiniappSafeAreaInsets;
|
|
34
45
|
/**
|
|
35
|
-
* Bounding rect (in CSS pixels) of the
|
|
36
|
-
*
|
|
37
|
-
* avoid placing interactive elements beneath it.
|
|
46
|
+
* Bounding rect (in CSS pixels) of the capsule menu the host draws over the
|
|
47
|
+
* WebView: the top-right corner, or the Duo's status rail (see `placement`).
|
|
48
|
+
* Miniapps should avoid placing interactive elements beneath it. Updated
|
|
49
|
+
* with `safeAreaInsets`.
|
|
38
50
|
*/
|
|
39
51
|
capsuleMenu?: MiniappCapsuleMenuRect;
|
|
40
52
|
/**
|
package/dist/globals.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AAEjD,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,qFAAqF;IACrF,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,2EAA2E;IAC3E,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAA;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAA;CACjC;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,OAAO,CAAC,EAAE,WAAW,CAAA;KACtB;CACF;AAED,wEAAwE;AACxE,wBAAgB,cAAc,IAAI,WAAW,CAG5C"}
|
package/dist/globals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globals.js","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"globals.js","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiEH,wEAAwE;AACxE,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,EAAE,CAAA;IAC5C,OAAO,MAAM,CAAC,OAAO,IAAI,EAAE,CAAA;AAC7B,CAAC"}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import type { MiniappCapsuleMenuRect } from "../globals";
|
|
1
|
+
import type { MiniappCapsuleMenuRect, MiniappSafeAreaInsets } from "../globals";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the capsule menu floats over the page content: false when it sits
|
|
4
|
+
* in a side safe-area inset (the iPhone Duo's status rail), where it covers
|
|
5
|
+
* nothing the page lays out. Hosts that predate `placement` report a corner
|
|
6
|
+
* rect on the Duo, which lands inside the rail inset all the same.
|
|
7
|
+
*/
|
|
8
|
+
export declare function capsuleOverContent(capsuleMenu: MiniappCapsuleMenuRect, viewportWidth: number, insets: MiniappSafeAreaInsets): boolean;
|
|
2
9
|
export declare function getCapsuleHeaderPaddingRight(capsuleMenu: MiniappCapsuleMenuRect, viewportWidth: number, safeAreaRight: number, rightGap: number): number;
|
|
3
10
|
//# sourceMappingURL=capsuleHeaderLayout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capsuleHeaderLayout.d.ts","sourceRoot":"","sources":["../../src/react/capsuleHeaderLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"capsuleHeaderLayout.d.ts","sourceRoot":"","sources":["../../src/react/capsuleHeaderLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAE,qBAAqB,EAAC,MAAM,YAAY,CAAA;AAE7E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,sBAAsB,EACnC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAGT;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,sBAAsB,EACnC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,MAAM,CAGR"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the capsule menu floats over the page content: false when it sits
|
|
3
|
+
* in a side safe-area inset (the iPhone Duo's status rail), where it covers
|
|
4
|
+
* nothing the page lays out. Hosts that predate `placement` report a corner
|
|
5
|
+
* rect on the Duo, which lands inside the rail inset all the same.
|
|
6
|
+
*/
|
|
7
|
+
export function capsuleOverContent(capsuleMenu, viewportWidth, insets) {
|
|
8
|
+
if (capsuleMenu.placement === "rail")
|
|
9
|
+
return false;
|
|
10
|
+
return capsuleMenu.left < viewportWidth - insets.right && capsuleMenu.left + capsuleMenu.width > insets.left;
|
|
11
|
+
}
|
|
1
12
|
export function getCapsuleHeaderPaddingRight(capsuleMenu, viewportWidth, safeAreaRight, rightGap) {
|
|
2
13
|
const capsuleOffsetFromContentRight = viewportWidth - capsuleMenu.left - safeAreaRight;
|
|
3
14
|
return Math.max(rightGap, capsuleOffsetFromContentRight + rightGap);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capsuleHeaderLayout.js","sourceRoot":"","sources":["../../src/react/capsuleHeaderLayout.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,4BAA4B,CAC1C,WAAmC,EACnC,aAAqB,EACrB,aAAqB,EACrB,QAAgB;IAEhB,MAAM,6BAA6B,GAAG,aAAa,GAAG,WAAW,CAAC,IAAI,GAAG,aAAa,CAAA;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,6BAA6B,GAAG,QAAQ,CAAC,CAAA;AACrE,CAAC"}
|
|
1
|
+
{"version":3,"file":"capsuleHeaderLayout.js","sourceRoot":"","sources":["../../src/react/capsuleHeaderLayout.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmC,EACnC,aAAqB,EACrB,MAA6B;IAE7B,IAAI,WAAW,CAAC,SAAS,KAAK,MAAM;QAAE,OAAO,KAAK,CAAA;IAClD,OAAO,WAAW,CAAC,IAAI,GAAG,aAAa,GAAG,MAAM,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAA;AAC9G,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,WAAmC,EACnC,aAAqB,EACrB,aAAqB,EACrB,QAAgB;IAEhB,MAAM,6BAA6B,GAAG,aAAa,GAAG,WAAW,CAAC,IAAI,GAAG,aAAa,CAAA;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,6BAA6B,GAAG,QAAQ,CAAC,CAAA;AACrE,CAAC"}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Use this when you want full control over header markup but want the
|
|
6
6
|
* alignment math done for you. If you just want a stock header, use
|
|
7
|
-
* <MiniappHeader> instead.
|
|
7
|
+
* <MiniappHeader> instead. When the capsule sits in the iPhone Duo's status
|
|
8
|
+
* rail, outside the page, the header takes the fallback height and margin
|
|
9
|
+
* and reserves nothing on the right.
|
|
8
10
|
*
|
|
9
11
|
* Example:
|
|
10
12
|
* const style = useCapsuleHeaderStyle()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCapsuleHeaderStyle.d.ts","sourceRoot":"","sources":["../../src/react/useCapsuleHeaderStyle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useCapsuleHeaderStyle.d.ts","sourceRoot":"","sources":["../../src/react/useCapsuleHeaderStyle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,OAAO,CAAA;AAKxC,MAAM,WAAW,4BAA4B;IAC3C,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,aAAa,CAuB/F"}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Use this when you want full control over header markup but want the
|
|
6
6
|
* alignment math done for you. If you just want a stock header, use
|
|
7
|
-
* <MiniappHeader> instead.
|
|
7
|
+
* <MiniappHeader> instead. When the capsule sits in the iPhone Duo's status
|
|
8
|
+
* rail, outside the page, the header takes the fallback height and margin
|
|
9
|
+
* and reserves nothing on the right.
|
|
8
10
|
*
|
|
9
11
|
* Example:
|
|
10
12
|
* const style = useCapsuleHeaderStyle()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCapsuleHeaderStyle.js","sourceRoot":"","sources":["../../src/react/useCapsuleHeaderStyle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useCapsuleHeaderStyle.js","sourceRoot":"","sources":["../../src/react/useCapsuleHeaderStyle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAC,4BAA4B,EAAC,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AAazC,MAAM,UAAU,qBAAqB,CAAC,UAAwC,EAAE;IAC9E,MAAM,EAAC,WAAW,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,cAAc,GAAG,EAAE,EAAE,iBAAiB,GAAG,EAAE,EAAC,GAAG,OAAO,CAAA;IAC9F,MAAM,EAAC,MAAM,EAAE,WAAW,EAAC,GAAG,WAAW,EAAE,CAAA;IAE3C,MAAM,MAAM,GAAG,WAAW,EAAE,MAAM,IAAI,cAAc,CAAA;IACpD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,iBAAiB,GAAG,MAAM,GAAG,CAAC,CAAA;IACnH,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAA;IAClD,MAAM,YAAY,GAAG,WAAW;QAC9B,CAAC,CAAC,OAAO,MAAM,KAAK,WAAW;YAC7B,CAAC,CAAC,WAAW,CAAC,KAAK,GAAG,QAAQ;YAC9B,CAAC,CAAC,4BAA4B,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC;QACxF,CAAC,CAAC,WAAW,CAAA;IAEf,OAAO;QACL,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,CAAC;QACb,UAAU,EAAE,QAAQ;QACpB,cAAc,EAAE,eAAe;QAC/B,WAAW,EAAE,WAAW;QACxB,YAAY;QACZ,SAAS;QACT,MAAM;KACP,CAAA;AACH,CAAC"}
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
* @fileoverview useSafeArea — React hook exposing the host's safe-area insets
|
|
3
3
|
* and the capsule menu rect to miniapp UIs.
|
|
4
4
|
*
|
|
5
|
-
* The host injects these via window.Glassly before content loads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The host injects these via window.Glassly before content loads and updates
|
|
6
|
+
* them when they change (an iPhone Duo folding moves its status rail),
|
|
7
|
+
* dispatching `glassly:safe-area-change`; the hook re-renders on that event
|
|
8
|
+
* and on viewport resizes.
|
|
8
9
|
*/
|
|
9
10
|
import { type MiniappCapsuleMenuRect, type MiniappSafeAreaInsets } from "../globals";
|
|
10
11
|
export interface UseSafeAreaResult {
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Pixel insets around the WebView content. Apply as padding on your root
|
|
14
|
+
* element. On an iPhone Duo the status rail is an 84px left or right inset:
|
|
15
|
+
* let backgrounds run under it and stop content at it.
|
|
16
|
+
*/
|
|
12
17
|
insets: MiniappSafeAreaInsets;
|
|
13
18
|
/**
|
|
14
|
-
* Bounding rect of the host's
|
|
15
|
-
* Null when the host doesn't render one (e.g. older builds)
|
|
16
|
-
*
|
|
19
|
+
* Bounding rect of the host's capsule menu when it floats over the page
|
|
20
|
+
* (top-right). Null when the host doesn't render one (e.g. older builds) or
|
|
21
|
+
* draws it outside the content, in the Duo's status rail. Use this to avoid
|
|
22
|
+
* placing clickable content underneath the menu.
|
|
17
23
|
*/
|
|
18
24
|
capsuleMenu: MiniappCapsuleMenuRect | null;
|
|
19
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSafeArea.d.ts","sourceRoot":"","sources":["../../src/react/useSafeArea.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useSafeArea.d.ts","sourceRoot":"","sources":["../../src/react/useSafeArea.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAiB,KAAK,sBAAsB,EAAE,KAAK,qBAAqB,EAAC,MAAM,YAAY,CAAA;AAKlG,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,EAAE,qBAAqB,CAAA;IAC7B;;;;;OAKG;IACH,WAAW,EAAE,sBAAsB,GAAG,IAAI,CAAA;CAC3C;AAmBD,wBAAgB,WAAW,IAAI,iBAAiB,CAoB/C"}
|
|
@@ -2,21 +2,50 @@
|
|
|
2
2
|
* @fileoverview useSafeArea — React hook exposing the host's safe-area insets
|
|
3
3
|
* and the capsule menu rect to miniapp UIs.
|
|
4
4
|
*
|
|
5
|
-
* The host injects these via window.Glassly before content loads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The host injects these via window.Glassly before content loads and updates
|
|
6
|
+
* them when they change (an iPhone Duo folding moves its status rail),
|
|
7
|
+
* dispatching `glassly:safe-area-change`; the hook re-renders on that event
|
|
8
|
+
* and on viewport resizes.
|
|
8
9
|
*/
|
|
9
|
-
import { useState } from "react";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
10
11
|
import { getHostGlobals } from "../globals";
|
|
12
|
+
import { capsuleOverContent } from "./capsuleHeaderLayout";
|
|
11
13
|
const EMPTY_INSETS = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
14
|
+
function readSafeArea() {
|
|
15
|
+
const globals = getHostGlobals();
|
|
16
|
+
const insets = globals.safeAreaInsets ?? EMPTY_INSETS;
|
|
17
|
+
const capsuleMenu = globals.capsuleMenu ?? null;
|
|
18
|
+
const viewportWidth = typeof window === "undefined" ? Number.POSITIVE_INFINITY : window.innerWidth;
|
|
19
|
+
return {
|
|
20
|
+
insets,
|
|
21
|
+
capsuleMenu: capsuleMenu && capsuleOverContent(capsuleMenu, viewportWidth, insets) ? capsuleMenu : null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function sameRect(a, b) {
|
|
25
|
+
if (a === b)
|
|
26
|
+
return true;
|
|
27
|
+
if (!a || !b)
|
|
28
|
+
return false;
|
|
29
|
+
return Object.keys(a).every((key) => a[key] === b[key]);
|
|
30
|
+
}
|
|
12
31
|
export function useSafeArea() {
|
|
13
|
-
const [result] = useState(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
const [result, setResult] = useState(readSafeArea);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const update = () => setResult((previous) => {
|
|
35
|
+
const next = readSafeArea();
|
|
36
|
+
return sameRect(previous.insets, next.insets) && sameRect(previous.capsuleMenu, next.capsuleMenu)
|
|
37
|
+
? previous
|
|
38
|
+
: next;
|
|
39
|
+
});
|
|
40
|
+
// The host may have updated the globals between the first render and now.
|
|
41
|
+
update();
|
|
42
|
+
window.addEventListener("glassly:safe-area-change", update);
|
|
43
|
+
window.addEventListener("resize", update);
|
|
44
|
+
return () => {
|
|
45
|
+
window.removeEventListener("glassly:safe-area-change", update);
|
|
46
|
+
window.removeEventListener("resize", update);
|
|
18
47
|
};
|
|
19
|
-
});
|
|
48
|
+
}, []);
|
|
20
49
|
return result;
|
|
21
50
|
}
|
|
22
51
|
//# sourceMappingURL=useSafeArea.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSafeArea.js","sourceRoot":"","sources":["../../src/react/useSafeArea.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useSafeArea.js","sourceRoot":"","sources":["../../src/react/useSafeArea.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAC,cAAc,EAA0D,MAAM,YAAY,CAAA;AAClG,OAAO,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAA;AAExD,MAAM,YAAY,GAA0B,EAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAA;AAkBlF,SAAS,YAAY;IACnB,MAAM,OAAO,GAAG,cAAc,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,IAAI,YAAY,CAAA;IACrD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAA;IAC/C,MAAM,aAAa,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAA;IAClG,OAAO;QACL,MAAM;QACN,WAAW,EAAE,WAAW,IAAI,kBAAkB,CAAC,WAAW,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;KACxG,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAmB,CAAW,EAAE,CAAW;IAC1D,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1B,OAAQ,MAAM,CAAC,IAAI,CAAC,CAAC,CAAiB,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAoB,YAAY,CAAC,CAAA;IACrE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;YACrB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAA;YAC3B,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC;gBAC/F,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,IAAI,CAAA;QACV,CAAC,CAAC,CAAA;QACJ,0EAA0E;QAC1E,MAAM,EAAE,CAAA;QACR,MAAM,CAAC,gBAAgB,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;QAC3D,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACzC,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;YAC9D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAC9C,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/miniapp",
|
|
3
|
-
"version": "0.3.7-dev.
|
|
3
|
+
"version": "0.3.7-dev.897",
|
|
4
4
|
"description": "SDK for building Glassly local miniapps (browser/WebView)",
|
|
5
5
|
"author": "Glassly Labs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@glassly/cloud-protocol": "0.3.7-dev.
|
|
76
|
+
"@glassly/cloud-protocol": "0.3.7-dev.897",
|
|
77
77
|
"eventemitter3": "^5.0.1"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
package/src/globals.ts
CHANGED
|
@@ -18,6 +18,12 @@ export interface MiniappCapsuleMenuRect {
|
|
|
18
18
|
left: number
|
|
19
19
|
width: number
|
|
20
20
|
height: number
|
|
21
|
+
/**
|
|
22
|
+
* `corner` floats over the page's top-right; `rail` sits in the iPhone
|
|
23
|
+
* Duo's status rail, inside the side safe-area inset and clear of the page.
|
|
24
|
+
* Absent on hosts older than the field.
|
|
25
|
+
*/
|
|
26
|
+
placement?: "corner" | "rail"
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export type MiniappColorScheme = "light" | "dark"
|
|
@@ -33,12 +39,18 @@ export interface HostGlobals {
|
|
|
33
39
|
miniappLocal?: boolean
|
|
34
40
|
/** True when the miniapp is running via the dev workflow (QR sideload). */
|
|
35
41
|
miniappDeveloperMode?: boolean
|
|
36
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Safe-area insets around the WebView content. Miniapps should pad
|
|
44
|
+
* accordingly. On an iPhone Duo the status rail is an 84px side inset. The
|
|
45
|
+
* host updates this and dispatches `glassly:safe-area-change` when they
|
|
46
|
+
* change (a Duo folding or unfolding).
|
|
47
|
+
*/
|
|
37
48
|
safeAreaInsets?: MiniappSafeAreaInsets
|
|
38
49
|
/**
|
|
39
|
-
* Bounding rect (in CSS pixels) of the
|
|
40
|
-
*
|
|
41
|
-
* avoid placing interactive elements beneath it.
|
|
50
|
+
* Bounding rect (in CSS pixels) of the capsule menu the host draws over the
|
|
51
|
+
* WebView: the top-right corner, or the Duo's status rail (see `placement`).
|
|
52
|
+
* Miniapps should avoid placing interactive elements beneath it. Updated
|
|
53
|
+
* with `safeAreaInsets`.
|
|
42
54
|
*/
|
|
43
55
|
capsuleMenu?: MiniappCapsuleMenuRect
|
|
44
56
|
/**
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
import type {MiniappCapsuleMenuRect} from "../globals"
|
|
1
|
+
import type {MiniappCapsuleMenuRect, MiniappSafeAreaInsets} from "../globals"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether the capsule menu floats over the page content: false when it sits
|
|
5
|
+
* in a side safe-area inset (the iPhone Duo's status rail), where it covers
|
|
6
|
+
* nothing the page lays out. Hosts that predate `placement` report a corner
|
|
7
|
+
* rect on the Duo, which lands inside the rail inset all the same.
|
|
8
|
+
*/
|
|
9
|
+
export function capsuleOverContent(
|
|
10
|
+
capsuleMenu: MiniappCapsuleMenuRect,
|
|
11
|
+
viewportWidth: number,
|
|
12
|
+
insets: MiniappSafeAreaInsets,
|
|
13
|
+
): boolean {
|
|
14
|
+
if (capsuleMenu.placement === "rail") return false
|
|
15
|
+
return capsuleMenu.left < viewportWidth - insets.right && capsuleMenu.left + capsuleMenu.width > insets.left
|
|
16
|
+
}
|
|
2
17
|
|
|
3
18
|
export function getCapsuleHeaderPaddingRight(
|
|
4
19
|
capsuleMenu: MiniappCapsuleMenuRect,
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Use this when you want full control over header markup but want the
|
|
6
6
|
* alignment math done for you. If you just want a stock header, use
|
|
7
|
-
* <MiniappHeader> instead.
|
|
7
|
+
* <MiniappHeader> instead. When the capsule sits in the iPhone Duo's status
|
|
8
|
+
* rail, outside the page, the header takes the fallback height and margin
|
|
9
|
+
* and reserves nothing on the right.
|
|
8
10
|
*
|
|
9
11
|
* Example:
|
|
10
12
|
* const style = useCapsuleHeaderStyle()
|
package/src/react/useSafeArea.ts
CHANGED
|
@@ -2,35 +2,70 @@
|
|
|
2
2
|
* @fileoverview useSafeArea — React hook exposing the host's safe-area insets
|
|
3
3
|
* and the capsule menu rect to miniapp UIs.
|
|
4
4
|
*
|
|
5
|
-
* The host injects these via window.Glassly before content loads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The host injects these via window.Glassly before content loads and updates
|
|
6
|
+
* them when they change (an iPhone Duo folding moves its status rail),
|
|
7
|
+
* dispatching `glassly:safe-area-change`; the hook re-renders on that event
|
|
8
|
+
* and on viewport resizes.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import {useState} from "react"
|
|
11
|
+
import {useEffect, useState} from "react"
|
|
11
12
|
|
|
12
13
|
import {getHostGlobals, type MiniappCapsuleMenuRect, type MiniappSafeAreaInsets} from "../globals"
|
|
14
|
+
import {capsuleOverContent} from "./capsuleHeaderLayout"
|
|
13
15
|
|
|
14
16
|
const EMPTY_INSETS: MiniappSafeAreaInsets = {top: 0, bottom: 0, left: 0, right: 0}
|
|
15
17
|
|
|
16
18
|
export interface UseSafeAreaResult {
|
|
17
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Pixel insets around the WebView content. Apply as padding on your root
|
|
21
|
+
* element. On an iPhone Duo the status rail is an 84px left or right inset:
|
|
22
|
+
* let backgrounds run under it and stop content at it.
|
|
23
|
+
*/
|
|
18
24
|
insets: MiniappSafeAreaInsets
|
|
19
25
|
/**
|
|
20
|
-
* Bounding rect of the host's
|
|
21
|
-
* Null when the host doesn't render one (e.g. older builds)
|
|
22
|
-
*
|
|
26
|
+
* Bounding rect of the host's capsule menu when it floats over the page
|
|
27
|
+
* (top-right). Null when the host doesn't render one (e.g. older builds) or
|
|
28
|
+
* draws it outside the content, in the Duo's status rail. Use this to avoid
|
|
29
|
+
* placing clickable content underneath the menu.
|
|
23
30
|
*/
|
|
24
31
|
capsuleMenu: MiniappCapsuleMenuRect | null
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
function readSafeArea(): UseSafeAreaResult {
|
|
35
|
+
const globals = getHostGlobals()
|
|
36
|
+
const insets = globals.safeAreaInsets ?? EMPTY_INSETS
|
|
37
|
+
const capsuleMenu = globals.capsuleMenu ?? null
|
|
38
|
+
const viewportWidth = typeof window === "undefined" ? Number.POSITIVE_INFINITY : window.innerWidth
|
|
39
|
+
return {
|
|
40
|
+
insets,
|
|
41
|
+
capsuleMenu: capsuleMenu && capsuleOverContent(capsuleMenu, viewportWidth, insets) ? capsuleMenu : null,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sameRect<T extends object>(a: T | null, b: T | null): boolean {
|
|
46
|
+
if (a === b) return true
|
|
47
|
+
if (!a || !b) return false
|
|
48
|
+
return (Object.keys(a) as (keyof T)[]).every((key) => a[key] === b[key])
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
export function useSafeArea(): UseSafeAreaResult {
|
|
28
|
-
const [result] = useState<UseSafeAreaResult>(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
const [result, setResult] = useState<UseSafeAreaResult>(readSafeArea)
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const update = () =>
|
|
55
|
+
setResult((previous) => {
|
|
56
|
+
const next = readSafeArea()
|
|
57
|
+
return sameRect(previous.insets, next.insets) && sameRect(previous.capsuleMenu, next.capsuleMenu)
|
|
58
|
+
? previous
|
|
59
|
+
: next
|
|
60
|
+
})
|
|
61
|
+
// The host may have updated the globals between the first render and now.
|
|
62
|
+
update()
|
|
63
|
+
window.addEventListener("glassly:safe-area-change", update)
|
|
64
|
+
window.addEventListener("resize", update)
|
|
65
|
+
return () => {
|
|
66
|
+
window.removeEventListener("glassly:safe-area-change", update)
|
|
67
|
+
window.removeEventListener("resize", update)
|
|
33
68
|
}
|
|
34
|
-
})
|
|
69
|
+
}, [])
|
|
35
70
|
return result
|
|
36
71
|
}
|