@octane-xplat/nativescript-mason-core 1.0.0-beta.0 → 1.0.0-beta.1
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 +88 -3
- package/artifact-inventory.json +42 -14
- package/common.d.ts +3 -0
- package/common.js +15 -1
- package/css-environment.d.ts +49 -0
- package/css-environment.js +309 -0
- package/foreign-layout.js +2 -0
- package/index.android.d.ts +2 -0
- package/index.android.js +1 -0
- package/index.d.ts +2 -0
- package/index.ios.d.ts +2 -0
- package/index.ios.js +1 -0
- package/native-environment.android.d.ts +3 -0
- package/native-environment.android.js +69 -0
- package/native-environment.d.ts +3 -0
- package/native-environment.ios.d.ts +3 -0
- package/native-environment.ios.js +65 -0
- package/package.json +4 -1
- package/plugin-README.md +38 -1
- package/properties.d.ts +1 -1
- package/properties.js +6 -4
- package/style.js +20 -4
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ pnpm pack --pack-destination /path/to/local-artifacts
|
|
|
29
29
|
Install the resulting tarball in a NativeScript application:
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
pnpm add /path/to/local-artifacts/octane-xplat-nativescript-mason-core-1.0.0-beta.
|
|
32
|
+
pnpm add /path/to/local-artifacts/octane-xplat-nativescript-mason-core-1.0.0-beta.1.tgz
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Use NativeScript controls inside a Mason container:
|
|
@@ -91,8 +91,8 @@ checkout for publishing.
|
|
|
91
91
|
| Target | Evidence |
|
|
92
92
|
| --- | --- |
|
|
93
93
|
| iOS arm64 Simulator | Native regressions, compiled TSRX/hooks/styles, actual HID taps/swipes and cleanup pass |
|
|
94
|
-
| iOS arm64 device | Framework clients link;
|
|
95
|
-
| Android arm64-v8a | Native regressions
|
|
94
|
+
| iOS arm64 device | Framework clients link; signed development app and native UI-test runner build; physical runtime unqualified |
|
|
95
|
+
| Android arm64-v8a | Native regressions, plain consumer, compiled TSRX/hooks/styles and real tap/swipe input pass on API 36 phone; API 35 emulator also passes |
|
|
96
96
|
| Windows / web | Not provided by this package |
|
|
97
97
|
|
|
98
98
|
NativeScript core 9.1.3 with Xplat's canonical patches is qualified through the
|
|
@@ -112,3 +112,88 @@ Source provenance and the byte-preserving module map live in
|
|
|
112
112
|
`tools/mason-core/README.md` in the source checkout. The agreed design is
|
|
113
113
|
`.agents/design/mason-core-extraction.md` in Octane Xplat. Upstream discussion:
|
|
114
114
|
https://github.com/triniwiz/nativescript-mason/issues/160.
|
|
115
|
+
|
|
116
|
+
## Safe-area CSS environment values
|
|
117
|
+
|
|
118
|
+
Starting in `1.0.0-beta.1`, loaded Mason container roots automatically supply
|
|
119
|
+
`env(safe-area-inset-top/right/bottom/left)` from their owning native window.
|
|
120
|
+
Observation starts only when a subtree uses `env()`, is shared within each
|
|
121
|
+
window, and ends when the root unloads. The styles below need no app binding.
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.safe-area-content {
|
|
125
|
+
padding-top: env(safe-area-inset-top, 0px);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For a custom presentation viewport, install an explicit source before the root
|
|
130
|
+
loads. It takes precedence over automatic observation. Call the returned
|
|
131
|
+
function when that host is disposed.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
View, bindCssEnvironment,
|
|
136
|
+
type CssEnvironmentSource,
|
|
137
|
+
} from '@octane-xplat/nativescript-mason-core';
|
|
138
|
+
|
|
139
|
+
export function connectWindow(root: View, source: CssEnvironmentSource) {
|
|
140
|
+
root.className = 'safe-area-content';
|
|
141
|
+
return bindCssEnvironment(root, source);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`source.read()` returns `{ top, right, bottom, left }` in device-independent
|
|
146
|
+
pixels, or `null` while unavailable. `source.subscribe(changed)` returns an
|
|
147
|
+
unsubscribe function. Environment values are scoped to that root's descendants;
|
|
148
|
+
a nested boundary may represent another window. Core owns substitution and
|
|
149
|
+
style re-evaluation; the host owns native observation, correct window selection
|
|
150
|
+
and disposal. It does not read a process-global screen or add a platform plugin.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { setCssEnvironment, type View, type CssSafeAreaInsets } from '@octane-xplat/nativescript-mason-core';
|
|
154
|
+
|
|
155
|
+
export function publishWindowInsets(root: View, insets: CssSafeAreaInsets | null) {
|
|
156
|
+
setCssEnvironment(root, insets);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A fallback applies when the requested value is unavailable. A real zero remains
|
|
161
|
+
zero. Nested math, box shorthands (`padding`, `margin`, `border-width`) and
|
|
162
|
+
NativeScript-expanded CSS variables retain their authored expressions when
|
|
163
|
+
insets change. Local overrides and removed declarations stop the replaced
|
|
164
|
+
expression from being replayed.
|
|
165
|
+
|
|
166
|
+
```css
|
|
167
|
+
.safe-area-content {
|
|
168
|
+
--content-top: env(safe-area-inset-top, 0px);
|
|
169
|
+
padding: calc(var(--content-top) + 8px) env(safe-area-inset-right, 0px)
|
|
170
|
+
env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Unknown names can use fallbacks; unsupported or unavailable values without a
|
|
175
|
+
fallback go through the existing invalid-length/diagnostic path. This is not
|
|
176
|
+
a general CSS invalid-at-computed-value or inheritance implementation. Loaded
|
|
177
|
+
views re-evaluate after reparenting; hosts that replace native views without a
|
|
178
|
+
load transition must explicitly refresh their subtree.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { refreshCssEnvironment, type View } from '@octane-xplat/nativescript-mason-core';
|
|
182
|
+
|
|
183
|
+
export function nativeViewRecreated(root: View) {
|
|
184
|
+
refreshCssEnvironment(root);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The automatic iOS source observes the owning `UIWindow` through a transparent,
|
|
189
|
+
non-interactive view. Android observes its native root before drawing, reads
|
|
190
|
+
system-bar/cutout insets through the existing AndroidX dependency, and converts
|
|
191
|
+
using that root's density. It excludes keyboard/IME insets and preserves other
|
|
192
|
+
inset listeners. No process-global active window or activity selects values.
|
|
193
|
+
|
|
194
|
+
Verification remains headless: parser, style-buffer transport, actual
|
|
195
|
+
NativeScript cascade, platform-adapter substitutes and host lifecycle tests.
|
|
196
|
+
Actual iOS/Android inset dispatch and layout remain unqualified. This resolver
|
|
197
|
+
covers Mason style buffers; it does not add `env()` support to ordinary
|
|
198
|
+
NativeScript controls or media queries. Xplat's qualification warning remains
|
|
199
|
+
until native layout is verified.
|
package/artifact-inventory.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
{
|
|
7
7
|
"name": "README.md",
|
|
8
|
-
"sha256": "
|
|
8
|
+
"sha256": "6cdcecbb9cf0185819ea7c56dddbd534f8f05373744c057aad959ae2fa3314df"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"name": "box-shadow.d.ts",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "common.d.ts",
|
|
20
|
-
"sha256": "
|
|
20
|
+
"sha256": "d13429fc04649ff489a432a62567f98712329fdcddbb2300f94ddcf0c4789fee"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"name": "common.js",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "17aadb88a703020ee9c3d22d5d85eb7d34df749fa7c3e701324c791027c73c62"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "css-colors.d.ts",
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
"name": "css-colors.js",
|
|
32
32
|
"sha256": "982aa3d87904f10205666ee0527cbed585c2c14bcf9efe11885a5abd477ee7b2"
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
"name": "css-environment.d.ts",
|
|
36
|
+
"sha256": "b3df509fc9ccfe6e1142125b99c1d6fb7d4eb5e3a0e3bd7deab7396d0d28acbe"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "css-environment.js",
|
|
40
|
+
"sha256": "209f221e056f3131bf9a22044f616d34f964fb036cf0bc798eefcc1df91f883d"
|
|
41
|
+
},
|
|
34
42
|
{
|
|
35
43
|
"name": "css-filter.d.ts",
|
|
36
44
|
"sha256": "ab1e35ece101aa71b1daa97bdd22046192e5763738ed2c931a578b8dc2baf364"
|
|
@@ -101,7 +109,7 @@
|
|
|
101
109
|
},
|
|
102
110
|
{
|
|
103
111
|
"name": "foreign-layout.js",
|
|
104
|
-
"sha256": "
|
|
112
|
+
"sha256": "da52a0a7c161d698447089fe35228a4c5f4b87d2033190a09ca809562eb87457"
|
|
105
113
|
},
|
|
106
114
|
{
|
|
107
115
|
"name": "gradient-stops.d.ts",
|
|
@@ -113,23 +121,23 @@
|
|
|
113
121
|
},
|
|
114
122
|
{
|
|
115
123
|
"name": "index.android.d.ts",
|
|
116
|
-
"sha256": "
|
|
124
|
+
"sha256": "e00c4473dff497b63e543625ffd222bfbd0ae5b27e9c35878ec272e2511484ed"
|
|
117
125
|
},
|
|
118
126
|
{
|
|
119
127
|
"name": "index.android.js",
|
|
120
|
-
"sha256": "
|
|
128
|
+
"sha256": "b8ad86a976604e6a243bdaef886a3babc537bdf95435a47c234366a386c64d4b"
|
|
121
129
|
},
|
|
122
130
|
{
|
|
123
131
|
"name": "index.d.ts",
|
|
124
|
-
"sha256": "
|
|
132
|
+
"sha256": "13312d150fd9c555cbf56b6beb10fdbb05d6da8526601c5733858578706580d3"
|
|
125
133
|
},
|
|
126
134
|
{
|
|
127
135
|
"name": "index.ios.d.ts",
|
|
128
|
-
"sha256": "
|
|
136
|
+
"sha256": "e00c4473dff497b63e543625ffd222bfbd0ae5b27e9c35878ec272e2511484ed"
|
|
129
137
|
},
|
|
130
138
|
{
|
|
131
139
|
"name": "index.ios.js",
|
|
132
|
-
"sha256": "
|
|
140
|
+
"sha256": "b8ad86a976604e6a243bdaef886a3babc537bdf95435a47c234366a386c64d4b"
|
|
133
141
|
},
|
|
134
142
|
{
|
|
135
143
|
"name": "ios-host-README.md",
|
|
@@ -147,13 +155,33 @@
|
|
|
147
155
|
"name": "mask.js",
|
|
148
156
|
"sha256": "162fda4410ccced5cd7d33a67bfc4d9d56aef276105e2b4d229d8111837a921b"
|
|
149
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"name": "native-environment.android.d.ts",
|
|
160
|
+
"sha256": "a3564bb48bfc7233b72b5196299cf2cab8c04b0e419da56ae675125cb70f1206"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "native-environment.android.js",
|
|
164
|
+
"sha256": "6031ffeb4082146b9a4ac04664f60cfb29779a56447440938240d0009c641c40"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"name": "native-environment.d.ts",
|
|
168
|
+
"sha256": "a3564bb48bfc7233b72b5196299cf2cab8c04b0e419da56ae675125cb70f1206"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "native-environment.ios.d.ts",
|
|
172
|
+
"sha256": "a3564bb48bfc7233b72b5196299cf2cab8c04b0e419da56ae675125cb70f1206"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "native-environment.ios.js",
|
|
176
|
+
"sha256": "65168574ef387a69d1e3751cb2b796fe7e28934f88d1e237ffa16bc20a43163a"
|
|
177
|
+
},
|
|
150
178
|
{
|
|
151
179
|
"name": "native-types-README.md",
|
|
152
180
|
"sha256": "314a146fe99695ea1e18efa631b015bb8f470975bafd4ab14306a05c5370d654"
|
|
153
181
|
},
|
|
154
182
|
{
|
|
155
183
|
"name": "package.json",
|
|
156
|
-
"sha256": "
|
|
184
|
+
"sha256": "0df647806eca042e2841873f6b2f30573b9c124742788ff519394d75a4d5a69f"
|
|
157
185
|
},
|
|
158
186
|
{
|
|
159
187
|
"name": "panel-helpers.d.ts",
|
|
@@ -597,15 +625,15 @@
|
|
|
597
625
|
},
|
|
598
626
|
{
|
|
599
627
|
"name": "plugin-README.md",
|
|
600
|
-
"sha256": "
|
|
628
|
+
"sha256": "c9037f017498a60bc1309791d5ff2ba59bb21ef6f580c1268936b0f5f68a2753"
|
|
601
629
|
},
|
|
602
630
|
{
|
|
603
631
|
"name": "properties.d.ts",
|
|
604
|
-
"sha256": "
|
|
632
|
+
"sha256": "62ac264bbb130baf45d6daabaa7f89740690556c62173531752339922e2f3a5c"
|
|
605
633
|
},
|
|
606
634
|
{
|
|
607
635
|
"name": "properties.js",
|
|
608
|
-
"sha256": "
|
|
636
|
+
"sha256": "01f0493980a53d789923eaab4f46909642d494b42344016aceeb5e039050353e"
|
|
609
637
|
},
|
|
610
638
|
{
|
|
611
639
|
"name": "pseudo.d.ts",
|
|
@@ -641,7 +669,7 @@
|
|
|
641
669
|
},
|
|
642
670
|
{
|
|
643
671
|
"name": "style.js",
|
|
644
|
-
"sha256": "
|
|
672
|
+
"sha256": "26b28c9f98c0047e970827f10c44875ef0b3a8efa18a0da4b64fcbae52d767c1"
|
|
645
673
|
},
|
|
646
674
|
{
|
|
647
675
|
"name": "symbols.d.ts",
|
package/common.d.ts
CHANGED
|
@@ -359,6 +359,9 @@ export declare class ViewBase extends CustomLayoutView implements AddChildFromBu
|
|
|
359
359
|
get textWrap(): "nowrap" | "wrap" | "balance";
|
|
360
360
|
set aspectRatio(value: number);
|
|
361
361
|
get aspectRatio(): number;
|
|
362
|
+
private _cssEnvironmentStop?;
|
|
363
|
+
onLoaded(): void;
|
|
364
|
+
onUnloaded(): void;
|
|
362
365
|
get filter(): string;
|
|
363
366
|
set filter(value: string);
|
|
364
367
|
set borderRadius(value: string | number | CoreTypes.LengthDipUnit | CoreTypes.LengthPxUnit);
|
package/common.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
var _a, _b, _c, _d;
|
|
2
|
+
import { isCssEnvironmentShorthand } from './css-environment';
|
|
3
|
+
import { registerAutomaticCssEnvironment } from './css-environment';
|
|
4
|
+
import { nativeCssEnvironmentSource } from './native-environment';
|
|
2
5
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
6
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
4
7
|
import { CustomLayoutView, Utils, View as NSView, getViewById, widthProperty, heightProperty, marginLeftProperty, marginRightProperty, marginTopProperty, marginBottomProperty, minWidthProperty, minHeightProperty, fontSizeProperty, fontWeightProperty, fontStyleProperty, colorProperty, Color, lineHeightProperty, letterSpacingProperty, textAlignmentProperty, textDecorationProperty, borderLeftWidthProperty, borderTopWidthProperty, borderRightWidthProperty, borderBottomWidthProperty, backgroundColorProperty, paddingLeftProperty, paddingRightProperty, paddingTopProperty, paddingBottomProperty, zIndexProperty, directionProperty, PseudoClassHandler, } from '@nativescript/core';
|
|
@@ -142,7 +145,7 @@ function findOwnerForNativeView(owner, nativeView) {
|
|
|
142
145
|
return null;
|
|
143
146
|
}
|
|
144
147
|
function masonLength(value) {
|
|
145
|
-
return isCssLength(value) ? value.css : value;
|
|
148
|
+
return isCssLength(value) && !isCssEnvironmentShorthand(value) ? value.css : value;
|
|
146
149
|
}
|
|
147
150
|
/** A core `Length` (number of dip, or `{ value, unit }`) as a CSS string. */
|
|
148
151
|
function lengthToCssString(value) {
|
|
@@ -1265,6 +1268,17 @@ export class ViewBase extends CustomLayoutView {
|
|
|
1265
1268
|
style.aspectRatio = value;
|
|
1266
1269
|
}
|
|
1267
1270
|
}
|
|
1271
|
+
onLoaded() {
|
|
1272
|
+
super.onLoaded();
|
|
1273
|
+
if (!this.parent?.[isMasonView_]) {
|
|
1274
|
+
this._cssEnvironmentStop ?? (this._cssEnvironmentStop = registerAutomaticCssEnvironment(this, () => nativeCssEnvironmentSource(this)));
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
onUnloaded() {
|
|
1278
|
+
super.onUnloaded();
|
|
1279
|
+
this._cssEnvironmentStop?.();
|
|
1280
|
+
this._cssEnvironmentStop = undefined;
|
|
1281
|
+
}
|
|
1268
1282
|
get filter() {
|
|
1269
1283
|
return this.style.filter;
|
|
1270
1284
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Native hosts supply logical (device-independent) pixels, scoped to one view subtree. */
|
|
2
|
+
export interface CssSafeAreaInsets {
|
|
3
|
+
top?: number;
|
|
4
|
+
right?: number;
|
|
5
|
+
bottom?: number;
|
|
6
|
+
left?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CssEnvironmentHost {
|
|
9
|
+
readonly parent?: CssEnvironmentHost | null;
|
|
10
|
+
on?(event: string, callback: () => void): void;
|
|
11
|
+
off?(event: string, callback: () => void): void;
|
|
12
|
+
}
|
|
13
|
+
/** The host owns window selection, native observation and subscription teardown. */
|
|
14
|
+
export interface CssEnvironmentSource {
|
|
15
|
+
read(): CssSafeAreaInsets | null;
|
|
16
|
+
subscribe(changed: () => void): () => void;
|
|
17
|
+
}
|
|
18
|
+
/** Core host lifecycle adapter: observe lazily, only when a subtree actually uses env(). */
|
|
19
|
+
export declare function registerAutomaticCssEnvironment(host: CssEnvironmentHost, create: () => CssEnvironmentSource): () => void;
|
|
20
|
+
declare const shorthand: unique symbol;
|
|
21
|
+
interface EnvironmentShorthand {
|
|
22
|
+
value: number;
|
|
23
|
+
unit: 'dip';
|
|
24
|
+
css: string;
|
|
25
|
+
[shorthand]: {
|
|
26
|
+
split: (value: string) => unknown[];
|
|
27
|
+
index: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Keep parse-time box shorthands pending until the effective longhand has a host context. */
|
|
31
|
+
export declare function cssEnvironmentShorthand(value: string, split: (value: string) => unknown[], count: number): unknown[] | undefined;
|
|
32
|
+
/** Direct Style box setters need the same deferred split as stylesheet shorthands. */
|
|
33
|
+
export declare function cssEnvironmentSides(value: string): unknown[] | undefined;
|
|
34
|
+
export declare function isCssEnvironmentShorthand(value: unknown): value is EnvironmentShorthand;
|
|
35
|
+
/** Publish a host's current safe area. Missing values are unavailable; zero is a real inset. */
|
|
36
|
+
export declare function setCssEnvironment(host: CssEnvironmentHost, insets: CssSafeAreaInsets | null): void;
|
|
37
|
+
/** Remove the host boundary and re-evaluate styles against their next ancestor context. */
|
|
38
|
+
export declare function clearCssEnvironment(host: CssEnvironmentHost): void;
|
|
39
|
+
/** Bind a host-owned native observer. Call the returned function when that host is disposed. */
|
|
40
|
+
export declare function bindCssEnvironment(host: CssEnvironmentHost, source: CssEnvironmentSource): () => void;
|
|
41
|
+
/** Substitute env() before the existing CSS math/unit parser; undefined means invalid/unavailable. */
|
|
42
|
+
export declare function resolveCssEnvironment(value: string, insets?: CssSafeAreaInsets): string | undefined;
|
|
43
|
+
/** Replay after reparenting/native recreation; publish changes through setCssEnvironment instead. */
|
|
44
|
+
export declare function refreshCssEnvironment(host: CssEnvironmentHost): void;
|
|
45
|
+
/** Core build adapter only: called at a leaf style setter, after cascade/var()/shorthand expansion. */
|
|
46
|
+
export declare function environmentStyleValue(target: object, name: string, value: unknown): unknown;
|
|
47
|
+
/** Stop replaying a released native style helper, including its lifecycle listeners. */
|
|
48
|
+
export declare function releaseCssEnvironmentStyle(target: object): void;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { splitMargin } from './css-shorthands';
|
|
2
|
+
import { reportCssDiagnostic } from './diagnostics';
|
|
3
|
+
const environments = new WeakMap();
|
|
4
|
+
const bindings = new WeakMap();
|
|
5
|
+
const automaticSources = new WeakMap();
|
|
6
|
+
/** Core host lifecycle adapter: observe lazily, only when a subtree actually uses env(). */
|
|
7
|
+
export function registerAutomaticCssEnvironment(host, create) {
|
|
8
|
+
const entry = { create, starting: false };
|
|
9
|
+
automaticSources.set(host, entry);
|
|
10
|
+
refreshCssEnvironment(host);
|
|
11
|
+
return () => {
|
|
12
|
+
if (automaticSources.get(host) !== entry)
|
|
13
|
+
return;
|
|
14
|
+
automaticSources.delete(host);
|
|
15
|
+
entry.stop?.();
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function ensureAutomaticEnvironment(owner) {
|
|
19
|
+
for (let host = owner; host; host = host.parent ?? undefined) {
|
|
20
|
+
const entry = automaticSources.get(host);
|
|
21
|
+
if (entry) {
|
|
22
|
+
if (!entry.stop && !entry.starting && !bindings.has(host) && !environments.has(host)) {
|
|
23
|
+
entry.starting = true;
|
|
24
|
+
try {
|
|
25
|
+
entry.stop = bindCssEnvironment(host, entry.create());
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
entry.starting = false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (environments.has(host))
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const sides = ['top', 'right', 'bottom', 'left'];
|
|
38
|
+
const ENV = /\benv\(/i;
|
|
39
|
+
const shorthand = Symbol('mason:environment-shorthand');
|
|
40
|
+
/** Keep parse-time box shorthands pending until the effective longhand has a host context. */
|
|
41
|
+
export function cssEnvironmentShorthand(value, split, count) {
|
|
42
|
+
if (!ENV.test(value))
|
|
43
|
+
return undefined;
|
|
44
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
45
|
+
value: 0, unit: 'dip', css: value, [shorthand]: { split, index },
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
/** Direct Style box setters need the same deferred split as stylesheet shorthands. */
|
|
49
|
+
export function cssEnvironmentSides(value) {
|
|
50
|
+
return cssEnvironmentShorthand(value, splitMargin, 4);
|
|
51
|
+
}
|
|
52
|
+
export function isCssEnvironmentShorthand(value) {
|
|
53
|
+
return !!value && typeof value === 'object' && shorthand in value;
|
|
54
|
+
}
|
|
55
|
+
function snapshot(insets) {
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const side of sides) {
|
|
58
|
+
const value = insets?.[side];
|
|
59
|
+
if (value !== undefined) {
|
|
60
|
+
if (!Number.isFinite(value) || value < 0)
|
|
61
|
+
throw new RangeError(`Invalid safe-area ${side} inset.`);
|
|
62
|
+
result[side] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/** Publish a host's current safe area. Missing values are unavailable; zero is a real inset. */
|
|
68
|
+
export function setCssEnvironment(host, insets) {
|
|
69
|
+
const next = snapshot(insets);
|
|
70
|
+
const previous = environments.get(host);
|
|
71
|
+
if (previous && sides.every((side) => previous[side] === next[side]))
|
|
72
|
+
return;
|
|
73
|
+
// An empty environment still establishes a window boundary: don't inherit another window's insets.
|
|
74
|
+
environments.set(host, next);
|
|
75
|
+
refreshCssEnvironment(host);
|
|
76
|
+
}
|
|
77
|
+
/** Remove the host boundary and re-evaluate styles against their next ancestor context. */
|
|
78
|
+
export function clearCssEnvironment(host) {
|
|
79
|
+
if (environments.delete(host))
|
|
80
|
+
refreshCssEnvironment(host);
|
|
81
|
+
}
|
|
82
|
+
/** Bind a host-owned native observer. Call the returned function when that host is disposed. */
|
|
83
|
+
export function bindCssEnvironment(host, source) {
|
|
84
|
+
if (bindings.has(host))
|
|
85
|
+
throw new Error('This host already has a CSS environment binding.');
|
|
86
|
+
const token = {};
|
|
87
|
+
bindings.set(host, token);
|
|
88
|
+
const update = () => {
|
|
89
|
+
if (bindings.get(host) === token)
|
|
90
|
+
setCssEnvironment(host, source.read());
|
|
91
|
+
};
|
|
92
|
+
let unsubscribe;
|
|
93
|
+
try {
|
|
94
|
+
update();
|
|
95
|
+
unsubscribe = source.subscribe(update);
|
|
96
|
+
update(); // Catch a change between the first read and subscription.
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
bindings.delete(host);
|
|
100
|
+
try {
|
|
101
|
+
unsubscribe?.();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
clearCssEnvironment(host);
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return () => {
|
|
109
|
+
if (bindings.get(host) !== token)
|
|
110
|
+
return;
|
|
111
|
+
bindings.delete(host);
|
|
112
|
+
try {
|
|
113
|
+
unsubscribe?.();
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
clearCssEnvironment(host);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function context(view) {
|
|
121
|
+
for (let current = view; current; current = current.parent ?? undefined) {
|
|
122
|
+
const value = environments.get(current);
|
|
123
|
+
if (value)
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
// Scan balanced CSS functions, preserving strings, comments and URL payloads.
|
|
129
|
+
function close(value, start) {
|
|
130
|
+
let depth = 1;
|
|
131
|
+
for (let i = start; i < value.length; i++) {
|
|
132
|
+
if (value[i] === '\\') {
|
|
133
|
+
i++;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (value[i] === '"' || value[i] === "'") {
|
|
137
|
+
const quote = value[i++];
|
|
138
|
+
while (i < value.length && value[i] !== quote) {
|
|
139
|
+
if (value[i] === '\\')
|
|
140
|
+
i++;
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (value[i] === '/' && value[i + 1] === '*') {
|
|
145
|
+
const end = value.indexOf('*/', i + 2);
|
|
146
|
+
if (end < 0)
|
|
147
|
+
return -1;
|
|
148
|
+
i = end + 1;
|
|
149
|
+
}
|
|
150
|
+
else if (value[i] === '(')
|
|
151
|
+
depth++;
|
|
152
|
+
else if (value[i] === ')' && --depth === 0)
|
|
153
|
+
return i;
|
|
154
|
+
}
|
|
155
|
+
return -1;
|
|
156
|
+
}
|
|
157
|
+
/** Substitute env() before the existing CSS math/unit parser; undefined means invalid/unavailable. */
|
|
158
|
+
export function resolveCssEnvironment(value, insets = {}) {
|
|
159
|
+
let result = '';
|
|
160
|
+
for (let i = 0; i < value.length;) {
|
|
161
|
+
if (value[i] === '\\') {
|
|
162
|
+
result += value.slice(i, i + 2);
|
|
163
|
+
i += 2;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (value[i] === '"' || value[i] === "'") {
|
|
167
|
+
const start = i;
|
|
168
|
+
const quote = value[i++];
|
|
169
|
+
while (i < value.length && value[i] !== quote) {
|
|
170
|
+
if (value[i] === '\\')
|
|
171
|
+
i++;
|
|
172
|
+
i++;
|
|
173
|
+
}
|
|
174
|
+
result += value.slice(start, ++i);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (value[i] === '/' && value[i + 1] === '*') {
|
|
178
|
+
const end = value.indexOf('*/', i + 2);
|
|
179
|
+
if (end < 0)
|
|
180
|
+
return undefined;
|
|
181
|
+
result += value.slice(i, end + 2);
|
|
182
|
+
i = end + 2;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const fn = /^([a-z_-][\w-]*)\(/i.exec(value.slice(i));
|
|
186
|
+
if (!fn) {
|
|
187
|
+
result += value[i++];
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const start = i + fn[0].length;
|
|
191
|
+
const end = close(value, start);
|
|
192
|
+
if (end < 0)
|
|
193
|
+
return undefined;
|
|
194
|
+
const name = fn[1].toLowerCase();
|
|
195
|
+
const body = value.slice(start, end);
|
|
196
|
+
let replacement;
|
|
197
|
+
if (name === 'url')
|
|
198
|
+
replacement = value.slice(i, end + 1);
|
|
199
|
+
else if (name === 'env') {
|
|
200
|
+
// The first comma separates the environment name from an arbitrary fallback token sequence.
|
|
201
|
+
const comma = body.indexOf(',');
|
|
202
|
+
const identifier = (comma < 0 ? body : body.slice(0, comma)).replace(/\/\*[\s\S]*?\*\//g, ' ').trim();
|
|
203
|
+
const args = /^(-?[_a-zA-Z][_a-zA-Z0-9-]*)(?:\s+\+?\d+)*$/.exec(identifier);
|
|
204
|
+
if (!args)
|
|
205
|
+
return undefined;
|
|
206
|
+
const side = /^safe-area-inset-(top|right|bottom|left)$/.exec(identifier)?.[1];
|
|
207
|
+
const inset = side && insets[side];
|
|
208
|
+
if (typeof inset === 'number' && Number.isFinite(inset) && inset >= 0)
|
|
209
|
+
replacement = `${inset}px`;
|
|
210
|
+
else if (comma >= 0)
|
|
211
|
+
replacement = resolveCssEnvironment(body.slice(comma + 1).trim(), insets);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const inner = resolveCssEnvironment(body, insets);
|
|
215
|
+
if (inner !== undefined)
|
|
216
|
+
replacement = `${fn[1]}(${inner})`;
|
|
217
|
+
}
|
|
218
|
+
if (replacement === undefined)
|
|
219
|
+
return undefined;
|
|
220
|
+
result += replacement;
|
|
221
|
+
i = end + 1;
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
const writesByTarget = new WeakMap();
|
|
226
|
+
const targets = new Set();
|
|
227
|
+
function replay(writes) {
|
|
228
|
+
const target = writes.target.deref();
|
|
229
|
+
if (target && writes.active) {
|
|
230
|
+
for (const [name, value] of Array.from(writes.values))
|
|
231
|
+
target[name] = value;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Replay after reparenting/native recreation; publish changes through setCssEnvironment instead. */
|
|
235
|
+
export function refreshCssEnvironment(host) {
|
|
236
|
+
for (const ref of targets) {
|
|
237
|
+
const writes = ref.deref();
|
|
238
|
+
if (!writes?.target.deref()) {
|
|
239
|
+
if (writes)
|
|
240
|
+
stopWrites(writes);
|
|
241
|
+
targets.delete(ref);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
for (let view = writes.owner?.deref(); view; view = view.parent ?? undefined) {
|
|
245
|
+
if (view === host) {
|
|
246
|
+
replay(writes);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** Core build adapter only: called at a leaf style setter, after cascade/var()/shorthand expansion. */
|
|
253
|
+
export function environmentStyleValue(target, name, value) {
|
|
254
|
+
const style = target;
|
|
255
|
+
const owner = style.viewRef?.get?.() ?? style.viewRef?.deref?.() ?? style.view;
|
|
256
|
+
const text = typeof value === 'string' ? value : value?.css;
|
|
257
|
+
let writes = writesByTarget.get(target);
|
|
258
|
+
if (typeof text !== 'string' || !ENV.test(text)) {
|
|
259
|
+
writes?.values.delete(name);
|
|
260
|
+
if (writes && !writes.values.size)
|
|
261
|
+
releaseCssEnvironmentStyle(target);
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
ensureAutomaticEnvironment(owner);
|
|
265
|
+
if (!writes) {
|
|
266
|
+
writes = {
|
|
267
|
+
target: new WeakRef(target), owner: owner && new WeakRef(owner), values: new Map(), active: true,
|
|
268
|
+
loaded() { writes.active = true; replay(writes); },
|
|
269
|
+
unloaded() { writes.active = false; },
|
|
270
|
+
};
|
|
271
|
+
writesByTarget.set(target, writes);
|
|
272
|
+
targets.add(new WeakRef(writes));
|
|
273
|
+
owner?.on?.('loaded', writes.loaded);
|
|
274
|
+
owner?.on?.('unloaded', writes.unloaded);
|
|
275
|
+
}
|
|
276
|
+
writes.values.set(name, value);
|
|
277
|
+
let resolved = resolveCssEnvironment(text, context(owner));
|
|
278
|
+
if (resolved !== undefined && isCssEnvironmentShorthand(value)) {
|
|
279
|
+
try {
|
|
280
|
+
const pending = value[shorthand];
|
|
281
|
+
const part = pending.split(resolved)[pending.index];
|
|
282
|
+
resolved = typeof part === 'string' ? part : undefined;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
resolved = undefined;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (resolved === undefined) {
|
|
289
|
+
reportCssDiagnostic({ kind: 'not-implemented', name, value: text,
|
|
290
|
+
detail: 'env() is unavailable or invalid and has no usable fallback; the host must publish its window safe area' });
|
|
291
|
+
return text;
|
|
292
|
+
}
|
|
293
|
+
return resolved;
|
|
294
|
+
}
|
|
295
|
+
/** Stop replaying a released native style helper, including its lifecycle listeners. */
|
|
296
|
+
export function releaseCssEnvironmentStyle(target) {
|
|
297
|
+
const writes = writesByTarget.get(target);
|
|
298
|
+
if (!writes)
|
|
299
|
+
return;
|
|
300
|
+
stopWrites(writes);
|
|
301
|
+
writesByTarget.delete(target);
|
|
302
|
+
}
|
|
303
|
+
function stopWrites(writes) {
|
|
304
|
+
const owner = writes.owner?.deref();
|
|
305
|
+
owner?.off?.('loaded', writes.loaded);
|
|
306
|
+
owner?.off?.('unloaded', writes.unloaded);
|
|
307
|
+
writes.values.clear();
|
|
308
|
+
writes.active = false;
|
|
309
|
+
}
|
package/foreign-layout.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { releaseCssEnvironmentStyle } from './css-environment';
|
|
1
2
|
import { masonLayout as layout } from './display-density';
|
|
2
3
|
import { widthProperty, heightProperty, minWidthProperty, minHeightProperty, maxWidthProperty, maxHeightProperty, marginTopProperty, marginRightProperty, marginBottomProperty, marginLeftProperty } from '@nativescript/core/ui/styling/style-properties';
|
|
3
4
|
import { alignSelfProperty, flexGrowProperty, flexShrinkProperty } from '@nativescript/core/ui/layouts/flexbox-layout';
|
|
@@ -141,6 +142,7 @@ export function releaseForeignLayout(view) {
|
|
|
141
142
|
}
|
|
142
143
|
if (!view?.[foreignStyle_])
|
|
143
144
|
return;
|
|
145
|
+
releaseCssEnvironmentStyle(view[foreignStyle_]);
|
|
144
146
|
view[foreignStyle_].resetState();
|
|
145
147
|
view[foreignStyle_] = undefined;
|
|
146
148
|
const style = view.style;
|
package/index.android.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { Scroll } from './scroll';
|
|
|
3
3
|
export { ViewBase, Event } from './common';
|
|
4
4
|
export type { ScrollBehavior, ScrollLogicalPosition, ScrollToOptions, ScrollIntoViewOptions } from './common';
|
|
5
5
|
export { Style } from './style';
|
|
6
|
+
export { setCssEnvironment, clearCssEnvironment, bindCssEnvironment, refreshCssEnvironment } from './css-environment';
|
|
7
|
+
export type { CssSafeAreaInsets, CssEnvironmentHost, CssEnvironmentSource } from './css-environment';
|
|
6
8
|
export { setCssDiagnostics, getCssDiagnostics, clearCssDiagnostics, formatCssDiagnostics, cssDiagnosticsEnabled } from './diagnostics';
|
|
7
9
|
export { setCssUnitContext, cssUnits } from './units';
|
|
8
10
|
export { registerAppFontsDirectory } from './fonts';
|
package/index.android.js
CHANGED
|
@@ -3,6 +3,7 @@ export { View } from './view';
|
|
|
3
3
|
export { Scroll } from './scroll';
|
|
4
4
|
export { ViewBase, Event } from './common';
|
|
5
5
|
export { Style } from './style';
|
|
6
|
+
export { setCssEnvironment, clearCssEnvironment, bindCssEnvironment, refreshCssEnvironment } from './css-environment';
|
|
6
7
|
export { setCssDiagnostics, getCssDiagnostics, clearCssDiagnostics, formatCssDiagnostics, cssDiagnosticsEnabled } from './diagnostics';
|
|
7
8
|
export { setCssUnitContext, cssUnits } from './units';
|
|
8
9
|
export { registerAppFontsDirectory } from './fonts';
|
package/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { Scroll } from './scroll';
|
|
|
4
4
|
export { ViewBase, Event } from './common';
|
|
5
5
|
export type { ScrollBehavior, ScrollLogicalPosition, ScrollToOptions, ScrollIntoViewOptions } from './common';
|
|
6
6
|
export { Style } from './style';
|
|
7
|
+
export { setCssEnvironment, clearCssEnvironment, bindCssEnvironment, refreshCssEnvironment } from './css-environment';
|
|
8
|
+
export type { CssSafeAreaInsets, CssEnvironmentHost, CssEnvironmentSource } from './css-environment';
|
|
7
9
|
export { setCssDiagnostics, getCssDiagnostics, clearCssDiagnostics, formatCssDiagnostics, cssDiagnosticsEnabled } from './diagnostics';
|
|
8
10
|
export { setCssUnitContext, cssUnits } from './units';
|
|
9
11
|
export { registerAppFontsDirectory } from './fonts';
|
package/index.ios.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { Scroll } from './scroll';
|
|
|
3
3
|
export { ViewBase, Event } from './common';
|
|
4
4
|
export type { ScrollBehavior, ScrollLogicalPosition, ScrollToOptions, ScrollIntoViewOptions } from './common';
|
|
5
5
|
export { Style } from './style';
|
|
6
|
+
export { setCssEnvironment, clearCssEnvironment, bindCssEnvironment, refreshCssEnvironment } from './css-environment';
|
|
7
|
+
export type { CssSafeAreaInsets, CssEnvironmentHost, CssEnvironmentSource } from './css-environment';
|
|
6
8
|
export { setCssDiagnostics, getCssDiagnostics, clearCssDiagnostics, formatCssDiagnostics, cssDiagnosticsEnabled } from './diagnostics';
|
|
7
9
|
export { setCssUnitContext, cssUnits } from './units';
|
|
8
10
|
export { registerAppFontsDirectory } from './fonts';
|
package/index.ios.js
CHANGED
|
@@ -3,6 +3,7 @@ export { View } from './view';
|
|
|
3
3
|
export { Scroll } from './scroll';
|
|
4
4
|
export { ViewBase, Event } from './common';
|
|
5
5
|
export { Style } from './style';
|
|
6
|
+
export { setCssEnvironment, clearCssEnvironment, bindCssEnvironment, refreshCssEnvironment } from './css-environment';
|
|
6
7
|
export { setCssDiagnostics, getCssDiagnostics, clearCssDiagnostics, formatCssDiagnostics, cssDiagnosticsEnabled } from './diagnostics';
|
|
7
8
|
export { setCssUnitContext, cssUnits } from './units';
|
|
8
9
|
export { registerAppFontsDirectory } from './fonts';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const roots = new WeakMap();
|
|
2
|
+
function readInsets(root) {
|
|
3
|
+
const native = root?.getRootWindowInsets();
|
|
4
|
+
if (!native)
|
|
5
|
+
return null;
|
|
6
|
+
// AndroidX is already a native core dependency. Its systemBars type excludes IME,
|
|
7
|
+
// including on API 23-29 where raw systemWindowInsetBottom can contain the keyboard.
|
|
8
|
+
const compat = androidx.core.view.WindowInsetsCompat.toWindowInsetsCompat(native, root);
|
|
9
|
+
const types = androidx.core.view.WindowInsetsCompat.Type;
|
|
10
|
+
const insets = compat.getInsets(types.systemBars() | types.displayCutout());
|
|
11
|
+
const density = root.getResources().getDisplayMetrics().density;
|
|
12
|
+
return { top: insets.top / density, right: insets.right / density, bottom: insets.bottom / density, left: insets.left / density };
|
|
13
|
+
}
|
|
14
|
+
function observeRoot(root, changed) {
|
|
15
|
+
let observation = roots.get(root);
|
|
16
|
+
if (!observation) {
|
|
17
|
+
const callbacks = new Set();
|
|
18
|
+
let previous = JSON.stringify(readInsets(root));
|
|
19
|
+
const listener = new android.view.ViewTreeObserver.OnPreDrawListener({
|
|
20
|
+
onPreDraw() {
|
|
21
|
+
const next = JSON.stringify(readInsets(root));
|
|
22
|
+
if (next !== previous) {
|
|
23
|
+
previous = next;
|
|
24
|
+
for (const callback of Array.from(callbacks))
|
|
25
|
+
callback();
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const observer = root.getViewTreeObserver();
|
|
31
|
+
observation = { callbacks, observer, listener };
|
|
32
|
+
roots.set(root, observation);
|
|
33
|
+
observer.addOnPreDrawListener(listener);
|
|
34
|
+
// Observe without replacing anyone's OnApplyWindowInsetsListener or consuming insets.
|
|
35
|
+
root.requestApplyInsets();
|
|
36
|
+
}
|
|
37
|
+
observation.callbacks.add(changed);
|
|
38
|
+
const current = observation;
|
|
39
|
+
return () => {
|
|
40
|
+
current.callbacks.delete(changed);
|
|
41
|
+
if (!current.callbacks.size) {
|
|
42
|
+
const observer = current.observer.isAlive() ? current.observer : root.getViewTreeObserver();
|
|
43
|
+
observer.removeOnPreDrawListener(current.listener);
|
|
44
|
+
roots.delete(root);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function nativeCssEnvironmentSource(view) {
|
|
49
|
+
const rootOfView = () => view.nativeViewProtected?.getRootView();
|
|
50
|
+
return {
|
|
51
|
+
read: () => readInsets(rootOfView()),
|
|
52
|
+
subscribe(changed) {
|
|
53
|
+
let root;
|
|
54
|
+
let stop;
|
|
55
|
+
const select = () => {
|
|
56
|
+
const next = rootOfView();
|
|
57
|
+
if (next !== root) {
|
|
58
|
+
stop?.();
|
|
59
|
+
root = next;
|
|
60
|
+
stop = next ? observeRoot(next, changed) : undefined;
|
|
61
|
+
}
|
|
62
|
+
changed();
|
|
63
|
+
};
|
|
64
|
+
view.on('layoutChanged', select);
|
|
65
|
+
select();
|
|
66
|
+
return () => { view.off('layoutChanged', select); stop?.(); };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const windows = new WeakMap();
|
|
2
|
+
let ObserverClass;
|
|
3
|
+
function observeWindow(window, changed) {
|
|
4
|
+
let observation = windows.get(window);
|
|
5
|
+
if (!observation) {
|
|
6
|
+
if (!ObserverClass) {
|
|
7
|
+
var SafeAreaObserver = (function (_super) {
|
|
8
|
+
__extends(SafeAreaObserver, _super);
|
|
9
|
+
function SafeAreaObserver() {
|
|
10
|
+
return _super !== null && _super.apply(this, arguments) || this;
|
|
11
|
+
}
|
|
12
|
+
SafeAreaObserver.prototype.safeAreaInsetsDidChange = function () { var _a; _super.prototype.safeAreaInsetsDidChange.call(this); (_a = this.changed) === null || _a === void 0 ? void 0 : _a.call(this); };
|
|
13
|
+
SafeAreaObserver.prototype.layoutSubviews = function () { var _a; _super.prototype.layoutSubviews.call(this); (_a = this.changed) === null || _a === void 0 ? void 0 : _a.call(this); };
|
|
14
|
+
return SafeAreaObserver;
|
|
15
|
+
}(UIView));
|
|
16
|
+
ObserverClass = SafeAreaObserver;
|
|
17
|
+
}
|
|
18
|
+
const observer = ObserverClass.new();
|
|
19
|
+
const callbacks = new Set();
|
|
20
|
+
observation = { callbacks, observer };
|
|
21
|
+
windows.set(window, observation);
|
|
22
|
+
observer.frame = window.bounds;
|
|
23
|
+
observer.autoresizingMask = 2 /* UIViewAutoresizing.FlexibleWidth */ | 16 /* UIViewAutoresizing.FlexibleHeight */;
|
|
24
|
+
observer.userInteractionEnabled = false;
|
|
25
|
+
observer.isAccessibilityElement = false;
|
|
26
|
+
observer.changed = () => { for (const callback of Array.from(callbacks))
|
|
27
|
+
callback(); };
|
|
28
|
+
window.addSubview(observer);
|
|
29
|
+
}
|
|
30
|
+
observation.callbacks.add(changed);
|
|
31
|
+
const current = observation;
|
|
32
|
+
return () => {
|
|
33
|
+
current.callbacks.delete(changed);
|
|
34
|
+
if (!current.callbacks.size) {
|
|
35
|
+
current.observer.changed = undefined;
|
|
36
|
+
current.observer.removeFromSuperview();
|
|
37
|
+
windows.delete(window);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function nativeCssEnvironmentSource(view) {
|
|
42
|
+
const windowOfView = () => view.nativeViewProtected?.window;
|
|
43
|
+
return {
|
|
44
|
+
read() {
|
|
45
|
+
const insets = windowOfView()?.safeAreaInsets;
|
|
46
|
+
return insets ? { top: insets.top, right: insets.right, bottom: insets.bottom, left: insets.left } : null;
|
|
47
|
+
},
|
|
48
|
+
subscribe(changed) {
|
|
49
|
+
let window;
|
|
50
|
+
let stop;
|
|
51
|
+
const select = () => {
|
|
52
|
+
const next = windowOfView();
|
|
53
|
+
if (next !== window) {
|
|
54
|
+
stop?.();
|
|
55
|
+
window = next;
|
|
56
|
+
stop = next ? observeWindow(next, changed) : undefined;
|
|
57
|
+
}
|
|
58
|
+
changed();
|
|
59
|
+
};
|
|
60
|
+
view.on('layoutChanged', select);
|
|
61
|
+
select();
|
|
62
|
+
return () => { view.off('layoutChanged', select); stop?.(); };
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/nativescript-mason-core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Independent NativeScript Mason layout, scrolling and foreign-view hosting",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "index",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
|
+
"masonCoreCapabilities": {
|
|
33
|
+
"safeAreaEnvironment": "owning-window-v1"
|
|
34
|
+
},
|
|
32
35
|
"masonCorePrototype": {
|
|
33
36
|
"nativeHosts": true,
|
|
34
37
|
"ios": {
|
package/plugin-README.md
CHANGED
|
@@ -179,7 +179,35 @@ and separately for simulators, emulators and physical devices.
|
|
|
179
179
|
|
|
180
180
|
OS-input checks pass on arm64 iOS 27 Simulator and the API 35 Android emulator.
|
|
181
181
|
Native row movement matches the reported scroll offset (137.3 points on iOS,
|
|
182
|
-
94.9 logical pixels on Android).
|
|
182
|
+
94.9 logical pixels on Android). The actual-Vite consumer also passes on the
|
|
183
|
+
API 36 physical Android phone
|
|
184
|
+
with registry core `1.0.0-beta.0` and current Xplat patches: a real inspector
|
|
185
|
+
WebSocket responds, an OS tap changes the hook-driven label width from about
|
|
186
|
+
90 to 130, and an OS swipe moves its native row by the reported 99.38 logical
|
|
187
|
+
pixels before cleanup. Run the prepared fixture with:
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
MASON_CORE_ANDROID_SERIAL=DEVICE_SERIAL pnpm exec nx run \
|
|
191
|
+
nativescript-mason-core:test.consumer.android.input.device --inspector
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Replace `DEVICE_SERIAL` with the authorized physical phone's adb serial.
|
|
195
|
+
The published beta also passes the actual-Vite fixture on arm64 iOS 27
|
|
196
|
+
Simulator: native HID tap changes width from 90 to 130 points, swipe produces
|
|
197
|
+
matching native row movement and scroll offset (169.29 points), and cleanup
|
|
198
|
+
runs once. With the dedicated simulator shut down and its resource reservation
|
|
199
|
+
held, run:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
pnpm exec nx run nativescript-mason-core:build.consumer.ios
|
|
203
|
+
pnpm exec nx run nativescript-mason-core:test.consumer.ios.input
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
iOS physical runtime and input remain unqualified. The signed development app
|
|
207
|
+
and a native UI-test runner build successfully. A temporary device test
|
|
208
|
+
passes its tap/count checks, but fails its final post-drag assertion; it has
|
|
209
|
+
not produced a passing consumer result. This does not resolve the separate
|
|
210
|
+
intermittent Android emulator cold-start failures.
|
|
183
211
|
|
|
184
212
|
## Build an unsigned iOS device app
|
|
185
213
|
|
|
@@ -195,3 +223,12 @@ disabled. It checks that the app selects the device framework, embeds the
|
|
|
195
223
|
device privacy bundle and generates the core bridge classes without optional
|
|
196
224
|
HTML/text/widget declarations. This is build/metadata evidence only; no iOS
|
|
197
225
|
physical-device runtime or distribution signing is qualified.
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
## Safe-area environment resolver
|
|
229
|
+
|
|
230
|
+
Core `1.0.0-beta.1` automatically binds loaded Mason roots to their owning
|
|
231
|
+
native window when a subtree first uses `env()`. See the package README for
|
|
232
|
+
window ownership, explicit source overrides, fallback semantics and headless
|
|
233
|
+
coverage. Actual native inset dispatch/layout remains unqualified; Xplat keeps
|
|
234
|
+
a qualification warning for this beta.
|
package/properties.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface MasonShorthand {
|
|
|
8
8
|
longhands: ShorthandLonghand[];
|
|
9
9
|
split(value: string): LonghandValue[];
|
|
10
10
|
}
|
|
11
|
-
export declare function shorthandConverter({ longhands, split }: MasonShorthand): (value: unknown) => [ShorthandLonghand, unknown][];
|
|
11
|
+
export declare function shorthandConverter({ cssName, longhands, split }: MasonShorthand): (value: unknown) => [ShorthandLonghand, unknown][];
|
|
12
12
|
export declare const textProperty: Property<ViewBase, string>;
|
|
13
13
|
export declare const displayProperty: CssProperty<Style, Display>;
|
|
14
14
|
export declare const filterProperty: CssProperty<Style, string>;
|
package/properties.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { environmentStyleValue, cssEnvironmentShorthand } from './css-environment';
|
|
1
2
|
import { CssProperty, CssAnimationProperty, InheritedCssProperty, backgroundColorProperty, Style, ShorthandProperty, Length as CoreLength, fontSizeProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, PercentLength as CorePercentLength, Trace, CoreTypes, unsetValue, verticalAlignmentProperty, textShadowProperty, Font, Property, makeParser, makeValidator, marginTopProperty, marginRightProperty, marginBottomProperty, marginLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty, borderTopWidthProperty, borderRightWidthProperty, borderBottomWidthProperty, borderLeftWidthProperty, minWidthProperty, minHeightProperty, widthProperty, heightProperty, lineHeightProperty, letterSpacingProperty, } from '@nativescript/core';
|
|
2
3
|
import { isMasonView_, touchAction_ } from './symbols';
|
|
3
4
|
/** Marks a Style whose size accessors already resolve Mason's units. */
|
|
@@ -43,7 +44,7 @@ function isMasonView(style) {
|
|
|
43
44
|
return false;
|
|
44
45
|
}
|
|
45
46
|
const CSS_WIDE_KEYWORDS = new Set(['initial', 'inherit', 'unset', 'revert']);
|
|
46
|
-
export function shorthandConverter({ longhands, split }) {
|
|
47
|
+
export function shorthandConverter({ cssName, longhands, split }) {
|
|
47
48
|
return (value) => {
|
|
48
49
|
const text = typeof value === 'number' ? String(value) : value;
|
|
49
50
|
if (typeof text !== 'string') {
|
|
@@ -55,7 +56,8 @@ export function shorthandConverter({ longhands, split }) {
|
|
|
55
56
|
if (!trimmed || CSS_WIDE_KEYWORDS.has(trimmed)) {
|
|
56
57
|
return longhands.map((property) => [property, trimmed]);
|
|
57
58
|
}
|
|
58
|
-
const parts =
|
|
59
|
+
const parts = (['padding', 'margin', 'border-width'].includes(cssName) &&
|
|
60
|
+
cssEnvironmentShorthand(trimmed, split, longhands.length)) || split(trimmed);
|
|
59
61
|
return longhands.map((property, i) => [property, parts[i]]);
|
|
60
62
|
};
|
|
61
63
|
}
|
|
@@ -908,7 +910,7 @@ function masonLengthDeclaration(value) {
|
|
|
908
910
|
function sizingKeywordLength(value) {
|
|
909
911
|
return isSizingKeyword(value) ? { unit: value.trim(), value: -2 } : undefined;
|
|
910
912
|
}
|
|
911
|
-
const MASON_SIZE_KEYS = [widthProperty, heightProperty].flatMap((property) => [property.name, property.cssName, property.cssLocalName]).filter((key) => typeof key === 'string' && key.length > 0);
|
|
913
|
+
const MASON_SIZE_KEYS = [widthProperty, heightProperty, minWidthProperty, minHeightProperty, maxWidthProperty, maxHeightProperty].flatMap((property) => [property.name, property.cssName, property.cssLocalName]).filter((key) => typeof key === 'string' && key.length > 0);
|
|
912
914
|
/**
|
|
913
915
|
* The stylesheet-facing accessor a `CssAnimationProperty` installs is
|
|
914
916
|
* non-configurable, so it can't be overridden on `Style.prototype`. Shadow it
|
|
@@ -935,7 +937,7 @@ function masonStyleProto(proto) {
|
|
|
935
937
|
set(value) {
|
|
936
938
|
beginStyleWrite(this, key, value);
|
|
937
939
|
try {
|
|
938
|
-
set.call(this, masonLengthDeclaration(value));
|
|
940
|
+
set.call(this, masonLengthDeclaration(environmentStyleValue(this, key, value)));
|
|
939
941
|
}
|
|
940
942
|
finally {
|
|
941
943
|
endStyleWrite();
|
package/style.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { environmentStyleValue, cssEnvironmentSides } from './css-environment';
|
|
1
2
|
import { layout as coreLayout } from '@nativescript/core/utils';
|
|
2
3
|
import { cssUnits } from './units';
|
|
3
4
|
import { displayDensity, masonLayout } from './display-density';
|
|
@@ -3374,7 +3375,7 @@ export class Style {
|
|
|
3374
3375
|
}
|
|
3375
3376
|
set inset(value) {
|
|
3376
3377
|
if (typeof value === 'string') {
|
|
3377
|
-
const sides = expandSidesShorthand(value);
|
|
3378
|
+
const sides = cssEnvironmentSides(value) || expandSidesShorthand(value);
|
|
3378
3379
|
if (sides) {
|
|
3379
3380
|
const wasInBatch = this.inBatch;
|
|
3380
3381
|
this.inBatch = true;
|
|
@@ -3592,7 +3593,7 @@ export class Style {
|
|
|
3592
3593
|
}
|
|
3593
3594
|
set margin(value) {
|
|
3594
3595
|
if (typeof value === 'string') {
|
|
3595
|
-
const sides = expandSidesShorthand(value);
|
|
3596
|
+
const sides = cssEnvironmentSides(value) || expandSidesShorthand(value);
|
|
3596
3597
|
if (sides) {
|
|
3597
3598
|
const wasInBatch = this.inBatch;
|
|
3598
3599
|
this.inBatch = true;
|
|
@@ -3810,7 +3811,7 @@ export class Style {
|
|
|
3810
3811
|
}
|
|
3811
3812
|
set padding(value) {
|
|
3812
3813
|
if (typeof value === 'string') {
|
|
3813
|
-
const sides = expandSidesShorthand(value);
|
|
3814
|
+
const sides = cssEnvironmentSides(value) || expandSidesShorthand(value);
|
|
3814
3815
|
if (sides) {
|
|
3815
3816
|
const wasInBatch = this.inBatch;
|
|
3816
3817
|
this.inBatch = true;
|
|
@@ -5716,6 +5717,10 @@ export class Style {
|
|
|
5716
5717
|
return false ? (this._windowsCss.get('padding') ?? '') : '';
|
|
5717
5718
|
}
|
|
5718
5719
|
set paddingCss(value) {
|
|
5720
|
+
if (typeof value === 'string' && /\benv\(/i.test(value)) {
|
|
5721
|
+
Reflect.set(this, 'padding', value);
|
|
5722
|
+
return;
|
|
5723
|
+
}
|
|
5719
5724
|
// a bare number is dip, matching other numeric setters; Android's
|
|
5720
5725
|
// native parser treats "px" as unscaled device pixels, "dip" as scaled
|
|
5721
5726
|
const strValue = typeof value === 'number' ? `${value}dip` : value;
|
|
@@ -5733,6 +5738,10 @@ export class Style {
|
|
|
5733
5738
|
return false ? (this._windowsCss.get('margin') ?? '') : '';
|
|
5734
5739
|
}
|
|
5735
5740
|
set marginCss(value) {
|
|
5741
|
+
if (typeof value === 'string' && /\benv\(/i.test(value)) {
|
|
5742
|
+
Reflect.set(this, 'margin', value);
|
|
5743
|
+
return;
|
|
5744
|
+
}
|
|
5736
5745
|
// See the comment on `paddingCss` above — same dip-vs-px mislabeling fix.
|
|
5737
5746
|
const strValue = typeof value === 'number' ? `${value}dip` : value;
|
|
5738
5747
|
this.setPseudoCssStringValue('margin', strValue, () => nodeHelper().setMarginCss(this.nativeView, strValue), () => (this.appleStyle.marginCss = strValue));
|
|
@@ -5749,6 +5758,10 @@ export class Style {
|
|
|
5749
5758
|
return false ? (this._windowsCss.get('inset') ?? '') : '';
|
|
5750
5759
|
}
|
|
5751
5760
|
set insetCss(value) {
|
|
5761
|
+
if (typeof value === 'string' && /\benv\(/i.test(value)) {
|
|
5762
|
+
Reflect.set(this, 'inset', value);
|
|
5763
|
+
return;
|
|
5764
|
+
}
|
|
5752
5765
|
// See the comment on `paddingCss` above — same dip-vs-px mislabeling fix.
|
|
5753
5766
|
const strValue = typeof value === 'number' ? `${value}dip` : value;
|
|
5754
5767
|
this.setPseudoCssStringValue('inset', strValue, () => nodeHelper().setInsetCss(this.nativeView, strValue), () => (this.appleStyle.insetCss = strValue));
|
|
@@ -7100,6 +7113,7 @@ export class Style {
|
|
|
7100
7113
|
}
|
|
7101
7114
|
}
|
|
7102
7115
|
{
|
|
7116
|
+
const environmentDelegates = new Set(["paddingCss", "marginCss", "insetCss", "background-color", "inset", "margin", "padding", "gridGap", "gap", "gridRowGap", "gridColumnGap", "box-shadow", "object-position", "border-style", "border-image", "font-stretch", "font-feature-settings", "word-spacing", "text-indent", "line-clamp", "-webkit-line-clamp", "transform-origin", "backdrop-filter", "mask-image", "mask-size", "mask-position", "mask-repeat", "mask-origin", "mask-clip", "mask-mode", "mask-composite", "clip-path", "mask-border-source", "mask-border-slice", "mask-border-width", "mask-border-outset", "mask-border-repeat", "mask-border-mode", "writing-mode", "unicode-bidi", "caret-color"]);
|
|
7103
7117
|
const proto = Style.prototype;
|
|
7104
7118
|
const names = Object.getOwnPropertyNames(proto);
|
|
7105
7119
|
for (let i = 0; i < names.length; i++) {
|
|
@@ -7113,7 +7127,9 @@ export class Style {
|
|
|
7113
7127
|
set(value) {
|
|
7114
7128
|
beginStyleWrite(this, name, value);
|
|
7115
7129
|
try {
|
|
7116
|
-
|
|
7130
|
+
const resolved = environmentDelegates.has(name) ? value :
|
|
7131
|
+
environmentStyleValue(this, name, value);
|
|
7132
|
+
set.call(this, resolved);
|
|
7117
7133
|
}
|
|
7118
7134
|
finally {
|
|
7119
7135
|
endStyleWrite();
|