@mpgd/adapter-ait 0.10.0 → 0.11.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.
@@ -6,6 +6,17 @@
6
6
  * commerce: those paths must be verified in the Toss app sandbox/console.
7
7
  */
8
8
  declare function unsupported(): boolean;
9
+ /**
10
+ * Local browsers have no native safe-area constant. Throwing preserves the
11
+ * wrapper's CSS `env(safe-area-inset-*)` fallback while keeping ESM exports in
12
+ * parity with the SDK surface consumed by the adapter.
13
+ */
14
+ export declare const SafeArea: {
15
+ get(): never;
16
+ subscribe(_input: {
17
+ readonly onEvent: (insets: unknown) => void;
18
+ }): () => void;
19
+ };
9
20
  export declare const Storage: {
10
21
  getItem(key: string): Promise<string | null>;
11
22
  setItem(key: string, value: string): Promise<void>;
@@ -12,6 +12,19 @@ function unsupported() {
12
12
  function noOpSubscription() {
13
13
  return () => { };
14
14
  }
15
+ /**
16
+ * Local browsers have no native safe-area constant. Throwing preserves the
17
+ * wrapper's CSS `env(safe-area-inset-*)` fallback while keeping ESM exports in
18
+ * parity with the SDK surface consumed by the adapter.
19
+ */
20
+ export const SafeArea = {
21
+ get() {
22
+ throw new Error('SafeArea is unavailable in the Apps in Toss local mock.');
23
+ },
24
+ subscribe(_input) {
25
+ return noOpSubscription();
26
+ },
27
+ };
15
28
  export const Storage = {
16
29
  async getItem(key) {
17
30
  return localStorageValues.get(key) ?? null;
@@ -0,0 +1,54 @@
1
+ /** Raw Apps in Toss safe-area values expressed in CSS pixels. */
2
+ export interface AitSafeAreaInsets {
3
+ readonly top: number;
4
+ readonly right: number;
5
+ readonly bottom: number;
6
+ readonly left: number;
7
+ }
8
+ /** Minimal SDK surface kept injectable for deterministic wrapper tests. */
9
+ export interface AitSafeAreaSource {
10
+ get(): unknown;
11
+ subscribe(input: {
12
+ readonly onEvent: (insets: unknown) => void;
13
+ }): () => void;
14
+ }
15
+ /** Minimal CSS declaration surface required by the bridge. */
16
+ export interface AitSafeAreaCssStyle {
17
+ getPropertyValue(property: string): string;
18
+ getPropertyPriority?(property: string): string;
19
+ setProperty(property: string, value: string, priority?: string): void;
20
+ removeProperty(property: string): string;
21
+ }
22
+ export interface InstallAitSafeAreaCssVariablesOptions {
23
+ readonly source?: AitSafeAreaSource;
24
+ readonly style?: AitSafeAreaCssStyle;
25
+ /** Override only when a host version or custom navigation treatment has a measured band. */
26
+ readonly navigationControlBandHeightPx?: number;
27
+ /** Override only when native navigation uses a measured right-edge gap. */
28
+ readonly navigationControlEdgeGapPx?: number;
29
+ }
30
+ /**
31
+ * Apps in Toss positions the game navigation controls immediately below the
32
+ * native top safe area. Keep full-width app chrome below this additional band.
33
+ */
34
+ export declare const aitNavigationControlBandHeightPx = 52;
35
+ /** Apps in Toss navigation controls retain this edge gap from the safe right inset. */
36
+ export declare const aitNavigationControlEdgeGapPx = 10;
37
+ /** CSS contract shared by generated AIT wrappers and full-bleed games. */
38
+ export declare const aitSafeAreaCssVariables: Readonly<{
39
+ readonly top: '--mpgd-safe-area-top';
40
+ readonly right: '--mpgd-safe-area-right';
41
+ readonly bottom: '--mpgd-safe-area-bottom';
42
+ readonly left: '--mpgd-safe-area-left';
43
+ readonly navigationControlTop: '--mpgd-ait-navigation-control-top';
44
+ readonly navigationControlRight: '--mpgd-ait-navigation-control-right';
45
+ readonly navigationContentTop: '--mpgd-ait-navigation-content-top';
46
+ }>;
47
+ /**
48
+ * Mirror the official AIT `SafeArea` snapshot into CSS custom properties and
49
+ * keep them current when the WebView changes presentation or orientation.
50
+ *
51
+ * CSS `env(safe-area-inset-*)` remains the wrapper's pre-SDK fallback. Invalid
52
+ * or unavailable native values never overwrite that fallback.
53
+ */
54
+ export declare function installAitSafeAreaCssVariables(options?: InstallAitSafeAreaCssVariablesOptions): () => void;
@@ -0,0 +1,132 @@
1
+ import { SafeArea } from '@apps-in-toss/web-framework';
2
+ /**
3
+ * Apps in Toss positions the game navigation controls immediately below the
4
+ * native top safe area. Keep full-width app chrome below this additional band.
5
+ */
6
+ export const aitNavigationControlBandHeightPx = 52;
7
+ /** Apps in Toss navigation controls retain this edge gap from the safe right inset. */
8
+ export const aitNavigationControlEdgeGapPx = 10;
9
+ /** CSS contract shared by generated AIT wrappers and full-bleed games. */
10
+ export const aitSafeAreaCssVariables = Object.freeze({
11
+ top: '--mpgd-safe-area-top',
12
+ right: '--mpgd-safe-area-right',
13
+ bottom: '--mpgd-safe-area-bottom',
14
+ left: '--mpgd-safe-area-left',
15
+ navigationControlTop: '--mpgd-ait-navigation-control-top',
16
+ navigationControlRight: '--mpgd-ait-navigation-control-right',
17
+ navigationContentTop: '--mpgd-ait-navigation-content-top',
18
+ });
19
+ const maximumSafeAreaInsetPx = 4096;
20
+ const maximumCssPixelOptionPx = 4096;
21
+ /**
22
+ * Mirror the official AIT `SafeArea` snapshot into CSS custom properties and
23
+ * keep them current when the WebView changes presentation or orientation.
24
+ *
25
+ * CSS `env(safe-area-inset-*)` remains the wrapper's pre-SDK fallback. Invalid
26
+ * or unavailable native values never overwrite that fallback.
27
+ */
28
+ export function installAitSafeAreaCssVariables(options = {}) {
29
+ const navigationControlBandHeightPx = normalizeCssPixelOption(options.navigationControlBandHeightPx, aitNavigationControlBandHeightPx, 'navigationControlBandHeightPx');
30
+ const navigationControlEdgeGapPx = normalizeCssPixelOption(options.navigationControlEdgeGapPx, aitNavigationControlEdgeGapPx, 'navigationControlEdgeGapPx');
31
+ const style = options.style ?? resolveDocumentRootStyle();
32
+ if (style === undefined) {
33
+ return () => { };
34
+ }
35
+ const source = options.source ?? SafeArea;
36
+ const previous = rememberCssVariables(style);
37
+ let active = true;
38
+ let unsubscribe = () => { };
39
+ const apply = (input) => {
40
+ if (!active) {
41
+ return;
42
+ }
43
+ const insets = normalizeAitSafeAreaInsets(input);
44
+ if (insets === null) {
45
+ return;
46
+ }
47
+ style.setProperty(aitSafeAreaCssVariables.top, formatCssPixels(insets.top));
48
+ style.setProperty(aitSafeAreaCssVariables.right, formatCssPixels(insets.right));
49
+ style.setProperty(aitSafeAreaCssVariables.bottom, formatCssPixels(insets.bottom));
50
+ style.setProperty(aitSafeAreaCssVariables.left, formatCssPixels(insets.left));
51
+ style.setProperty(aitSafeAreaCssVariables.navigationControlTop, formatCssPixels(insets.top));
52
+ style.setProperty(aitSafeAreaCssVariables.navigationControlRight, formatCssPixels(insets.right + navigationControlEdgeGapPx));
53
+ style.setProperty(aitSafeAreaCssVariables.navigationContentTop, formatCssPixels(insets.top + navigationControlBandHeightPx));
54
+ };
55
+ try {
56
+ apply(source.get());
57
+ }
58
+ catch {
59
+ // Local browsers and older Toss hosts keep the CSS env() fallback.
60
+ }
61
+ try {
62
+ const subscribed = source.subscribe({ onEvent: apply });
63
+ unsubscribe = typeof subscribed === 'function' ? subscribed : () => { };
64
+ }
65
+ catch {
66
+ // A missing native event bridge must not prevent the game from booting.
67
+ }
68
+ return () => {
69
+ if (!active) {
70
+ return;
71
+ }
72
+ active = false;
73
+ try {
74
+ unsubscribe();
75
+ }
76
+ finally {
77
+ restoreCssVariables(style, previous);
78
+ }
79
+ };
80
+ }
81
+ function normalizeAitSafeAreaInsets(input) {
82
+ if (typeof input !== 'object' || input === null) {
83
+ return null;
84
+ }
85
+ const record = input;
86
+ const top = normalizeInset(record.top);
87
+ const right = normalizeInset(record.right);
88
+ const bottom = normalizeInset(record.bottom);
89
+ const left = normalizeInset(record.left);
90
+ if (top === null || right === null || bottom === null || left === null) {
91
+ return null;
92
+ }
93
+ return { top, right, bottom, left };
94
+ }
95
+ function normalizeInset(input) {
96
+ return typeof input === 'number'
97
+ && Number.isFinite(input)
98
+ && input >= 0
99
+ && input <= maximumSafeAreaInsetPx
100
+ ? input
101
+ : null;
102
+ }
103
+ function normalizeCssPixelOption(input, fallback, name) {
104
+ const value = input ?? fallback;
105
+ if (!Number.isFinite(value) || value < 0 || value > maximumCssPixelOptionPx) {
106
+ throw new TypeError(`${name} must be a non-negative finite CSS pixel value.`);
107
+ }
108
+ return value;
109
+ }
110
+ function formatCssPixels(value) {
111
+ return `${Object.is(value, -0) ? 0 : value}px`;
112
+ }
113
+ function resolveDocumentRootStyle() {
114
+ return typeof document === 'undefined' ? undefined : document.documentElement.style;
115
+ }
116
+ function rememberCssVariables(style) {
117
+ return Object.values(aitSafeAreaCssVariables).map((property) => ({
118
+ property,
119
+ value: style.getPropertyValue(property),
120
+ priority: style.getPropertyPriority?.(property) ?? '',
121
+ }));
122
+ }
123
+ function restoreCssVariables(style, previous) {
124
+ for (const entry of previous) {
125
+ if (entry.value.length === 0) {
126
+ style.removeProperty(entry.property);
127
+ }
128
+ else {
129
+ style.setProperty(entry.property, entry.value, entry.priority);
130
+ }
131
+ }
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/adapter-ait",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Apps in Toss WebView platform adapter for mpgd games.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,10 @@
45
45
  "types": "./dist/local-mock.d.ts",
46
46
  "default": "./dist/local-mock.js"
47
47
  },
48
+ "./safe-area": {
49
+ "types": "./dist/safe-area.d.ts",
50
+ "default": "./dist/safe-area.js"
51
+ },
48
52
  "./package.json": "./package.json"
49
53
  },
50
54
  "files": [