@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 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.0.tgz
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; unsigned NativeScript app builds with device metadata/resources; physical runtime unverified |
95
- | Android arm64-v8a | Native regressions and plain consumer pass on API 36 phone; compiled renderer and OS input pass on API 35 emulator |
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.
@@ -5,7 +5,7 @@
5
5
  },
6
6
  {
7
7
  "name": "README.md",
8
- "sha256": "0af0f3a50fefd5e824485839af5164d34d8c2290c8b6460076dfff22bd4b0e11"
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": "34fb6dda40f485790f31c5bd9cf3f326210a0de554a524f094ccf482881f966f"
20
+ "sha256": "d13429fc04649ff489a432a62567f98712329fdcddbb2300f94ddcf0c4789fee"
21
21
  },
22
22
  {
23
23
  "name": "common.js",
24
- "sha256": "cf8977b5fe06cc9d0261abb75e20b94aaa0ff7954d73af63a5b44b0b6eb8e653"
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": "51ddcf90dcedd44e0969b3ab03105ab10b59d06da6fee9d73085133ca00b9a88"
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": "3f15c5a3256cf20fa6a978870b91190b36ac0665d5f492735f362189acd0aab7"
124
+ "sha256": "e00c4473dff497b63e543625ffd222bfbd0ae5b27e9c35878ec272e2511484ed"
117
125
  },
118
126
  {
119
127
  "name": "index.android.js",
120
- "sha256": "19fb2e17271993c8763168848dae0ff07a141caced1f9e9dea438d23f6b6254a"
128
+ "sha256": "b8ad86a976604e6a243bdaef886a3babc537bdf95435a47c234366a386c64d4b"
121
129
  },
122
130
  {
123
131
  "name": "index.d.ts",
124
- "sha256": "9a3a48d1ec30073b638c5a3c9d938f1057b165fec78718f19122ceae0303aa3c"
132
+ "sha256": "13312d150fd9c555cbf56b6beb10fdbb05d6da8526601c5733858578706580d3"
125
133
  },
126
134
  {
127
135
  "name": "index.ios.d.ts",
128
- "sha256": "3f15c5a3256cf20fa6a978870b91190b36ac0665d5f492735f362189acd0aab7"
136
+ "sha256": "e00c4473dff497b63e543625ffd222bfbd0ae5b27e9c35878ec272e2511484ed"
129
137
  },
130
138
  {
131
139
  "name": "index.ios.js",
132
- "sha256": "19fb2e17271993c8763168848dae0ff07a141caced1f9e9dea438d23f6b6254a"
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": "96c4689365ad71a501be27e4a6fec670010ab20ad1f17a89f278074bc9f7ce1d"
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": "2e4d4b880a783edd188d34bf9fe2080f5ac2b5be8eaa3816b951f4300ef813f0"
628
+ "sha256": "c9037f017498a60bc1309791d5ff2ba59bb21ef6f580c1268936b0f5f68a2753"
601
629
  },
602
630
  {
603
631
  "name": "properties.d.ts",
604
- "sha256": "b116664afb558fe507bdaf713d9c63fdc313fc26098ae3f9007e72dbb5d4e3a5"
632
+ "sha256": "62ac264bbb130baf45d6daabaa7f89740690556c62173531752339922e2f3a5c"
605
633
  },
606
634
  {
607
635
  "name": "properties.js",
608
- "sha256": "cece51c25bff12a46cdb8639a3ae94573a0cbbb592de491a212cf1f23da85fd0"
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": "2fed5e19e29a6dee0c81c8f22728acdd56950d9f8865b9974d234733c5c38167"
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;
@@ -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,3 @@
1
+ import type { View } from '@nativescript/core';
2
+ import type { CssEnvironmentSource } from './css-environment';
3
+ export declare function nativeCssEnvironmentSource(view: View): CssEnvironmentSource;
@@ -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,3 @@
1
+ import type { View } from '@nativescript/core';
2
+ import type { CssEnvironmentSource } from './css-environment';
3
+ export declare function nativeCssEnvironmentSource(view: View): CssEnvironmentSource;
@@ -0,0 +1,3 @@
1
+ import type { View } from '@nativescript/core';
2
+ import type { CssEnvironmentSource } from './css-environment';
3
+ export declare function nativeCssEnvironmentSource(view: View): CssEnvironmentSource;
@@ -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.0",
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). Physical-device OS input is not yet qualified.
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 = split(trimmed);
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
- set.call(this, value);
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();