@sigx/lynx-safe-area 0.4.2 → 0.4.4

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
@@ -1,25 +1,16 @@
1
1
  # @sigx/lynx-safe-area
2
-
3
2
  Safe-area insets (notch, home indicator, status bar, navigation bar, keyboard) for sigx-lynx. Native publisher on iOS + Android emits insets every time they change; the JS side surfaces them as a reactive BG signal, four per-edge `SharedValue`s for MT-driven layout, and CSS variables for utility-class styling.
4
-
5
3
  Mirrors React Native's `react-native-safe-area-context` API where it makes sense, but built for sigx-lynx's two-thread model so layout-bound insets don't bounce through the bridge.
6
-
7
4
  ## Install
8
-
9
5
  ```bash
10
6
  pnpm add @sigx/lynx-safe-area
11
7
  ```
12
-
13
8
  `sigx prebuild` auto-discovers the package, copies `SafeAreaPublisher.swift` / `SafeAreaPublisher.kt` into your `ios/` and `android/` source trees, and registers them in the auto-generated `GeneratedLifecyclePublishers.{swift,kt}` so they attach to every `LynxView` before first paint. No additional native wiring required.
14
-
15
9
  ## Quick start
16
-
17
10
  Wrap your app once, anywhere above the views that need insets:
18
-
19
11
  ```tsx
20
12
  import { defineApp } from '@sigx/lynx';
21
13
  import { SafeAreaProvider, SafeAreaView } from '@sigx/lynx-safe-area';
22
-
23
14
  defineApp(() => () => (
24
15
  <SafeAreaProvider>
25
16
  <SafeAreaView edges={['top', 'bottom']} class="bg-base-100">
@@ -28,64 +19,45 @@ defineApp(() => () => (
28
19
  </SafeAreaProvider>
29
20
  ));
30
21
  ```
31
-
32
22
  `<SafeAreaView>` reactively applies the current insets as `padding` (default) or `margin` to the configured `edges`. Inset-aware first paint: insets are seeded synchronously from `lynx.__globalProps` before render, so there's no flash of unsafe content.
33
-
34
23
  **Sensible layout defaults** — `<SafeAreaProvider>` defaults its host
35
24
  view to `height: 100vh` + `flex-direction: column`, and `<SafeAreaView>`
36
25
  defaults to flex-fill long-form. Consumers don't need to add inline
37
26
  `height: '100vh'` anchors or `flex-1` classes for the layout chain to
38
27
  work. Pass `style={…}` to override.
39
-
40
28
  ## API
41
-
42
29
  ### `<SafeAreaProvider>`
43
-
44
30
  Provides the context that hooks consume. Mount once at the app root.
45
-
46
31
  | Prop | Type | Notes |
47
32
  | ------- | --------------------------------- | ------------------------------------------- |
48
33
  | `class` | `string` | Forwarded to the host `<view>`. |
49
34
  | `style` | `Record<string, string \| number>` | Merged after the auto-injected CSS vars. |
50
-
51
35
  The host view exposes the current insets as CSS variables (`--sat`, `--sar`, `--sab`, `--sal`, `--safe-area-keyboard`) — handy for utility-class consumers:
52
-
53
36
  ```tsx
54
37
  <SafeAreaProvider>
55
38
  <view class="pt-[var(--sat)] pb-[var(--sab)]">…</view>
56
39
  </SafeAreaProvider>
57
40
  ```
58
-
59
41
  ### `<SafeAreaView>`
60
-
61
42
  Drop-in container that applies insets as padding or margin.
62
-
63
43
  | Prop | Type | Default |
64
44
  | ------- | --------------------------------- | -------------------------------- |
65
45
  | `edges` | `('top' \| 'right' \| 'bottom' \| 'left')[]` | All four sides |
66
46
  | `mode` | `'padding' \| 'margin'` | `'padding'` |
67
47
  | `class` | `string` | — |
68
48
  | `style` | `Record<string, string \| number>` | Merged after inset styles |
69
-
70
49
  Implementation note: applies insets via inline style (BG signal), not via `useAnimatedStyle`. `setStyleProperties` writes that affect layout fire **after** the first layout pass, and children that capture their frame eagerly (notably `<scroll-view>`) don't reflow when insets arrive that way. Inline style avoids the timing trap.
71
-
72
50
  ### `useSafeAreaInsets()`
73
-
74
51
  ```ts
75
52
  function useSafeAreaInsets(): PrimitiveSignal<EdgeInsets> | Computed<EdgeInsets>;
76
53
  ```
77
-
78
54
  Returns a BG-side reactive signal of `EdgeInsets`. Components calling this re-render on every inset change (rotation, keyboard show/hide, split-view resize on iPad).
79
-
80
55
  ```tsx
81
56
  const insets = useSafeAreaInsets();
82
57
  return () => <view style={{ paddingTop: `${insets.value.top}px` }}>…</view>;
83
58
  ```
84
-
85
59
  If no `<SafeAreaProvider>` is in scope, returns a signal seeded with `ZERO_INSETS` and warns in dev (so test/storybook fragments degrade gracefully instead of throwing).
86
-
87
60
  ### `useSafeAreaSharedValues()`
88
-
89
61
  ```ts
90
62
  function useSafeAreaSharedValues(): {
91
63
  top: SharedValue<number>;
@@ -94,32 +66,22 @@ function useSafeAreaSharedValues(): {
94
66
  left: SharedValue<number>;
95
67
  } | null;
96
68
  ```
97
-
98
69
  Per-edge `SharedValue`s for MT-driven `useAnimatedStyle` bindings. Use when an animation or gesture worklet needs the current inset on MT without a BG round-trip. Returns `null` outside of `<SafeAreaProvider>`.
99
-
100
70
  ### `useSafeAreaFrame(viewportWidth, viewportHeight)`
101
-
102
71
  ```ts
103
72
  function useSafeAreaFrame(
104
73
  viewportWidth: number,
105
74
  viewportHeight: number,
106
75
  ): Computed<{ x: number; y: number; width: number; height: number }>;
107
76
  ```
108
-
109
77
  Computed inner safe frame — `(x, y)` origin and `width`/`height` of the rect inside the insets. Useful for absolute-positioned overlays and modal bounds that need to know "the visible content rect", not just inset deltas.
110
-
111
78
  `viewportWidth`/`viewportHeight` are caller-supplied (typically a one-time read via `@sigx/lynx-device-info`); the safe-area module deliberately doesn't pull device-info as a transitive dependency.
112
-
113
79
  ### `useSafeAreaInsetsMT()`
114
-
115
80
  ```ts
116
81
  function useSafeAreaInsetsMT(): EdgeInsets;
117
82
  ```
118
-
119
83
  Synchronous read from inside a `'main thread'`-marked worklet. Reads `lynx.__globalProps` directly — there's no signal subscription, so callers re-evaluate per worklet invocation rather than reactively. For declarative MT-driven layout the recommended path is `<SafeAreaView>` (which composes `useSafeAreaSharedValues()` with `useAnimatedStyle`).
120
-
121
84
  ### Types
122
-
123
85
  ```ts
124
86
  interface EdgeInsets {
125
87
  top: number;
@@ -135,25 +97,17 @@ interface EdgeInsets {
135
97
  /** Navigation-bar height (Android gesture/3-button nav at bottom). */
136
98
  navigationBar: number;
137
99
  }
138
-
139
100
  const ZERO_INSETS: EdgeInsets;
140
101
  ```
141
-
142
102
  All values are in dp/pt (logical pixels), not raw pixels.
143
-
144
103
  ### Lower-level escape hatches
145
-
146
104
  ```ts
147
105
  import { readGlobalSafeArea, GLOBAL_PROPS_KEY } from '@sigx/lynx-safe-area';
148
106
  ```
149
-
150
107
  - `readGlobalSafeArea()` — synchronous one-shot read from `lynx.__globalProps`. Returns `EdgeInsets` (zeros if the publisher hasn't run yet). What `<SafeAreaProvider>` uses to seed initial values.
151
108
  - `GLOBAL_PROPS_KEY` — the key the native publisher writes under. Exported for tests/debugging.
152
-
153
109
  ## CSS variables
154
-
155
110
  The provider's host view exposes these on the element style — descendant selectors inherit them via the cascade:
156
-
157
111
  | Variable | Maps to |
158
112
  | ----------------------- | ---------------------------------------- |
159
113
  | `--sat` | `insets.top` (in px) |
@@ -161,11 +115,8 @@ The provider's host view exposes these on the element style — descendant selec
161
115
  | `--sab` | `insets.bottom` |
162
116
  | `--sal` | `insets.left` |
163
117
  | `--safe-area-keyboard` | `insets.keyboard` |
164
-
165
118
  Works uniformly across iOS and Android — upstream's `env(safe-area-inset-*)` is iOS-only, so this is what you reach for if you're using DaisyUI/Tailwind utilities like `pt-[var(--sat)]`.
166
-
167
119
  ## How it works
168
-
169
120
  ```
170
121
  ┌──────────────────────────────────────┐
171
122
  │ Native (iOS UIView / Android View) │
@@ -200,11 +151,5 @@ Works uniformly across iOS and Android — upstream's `env(safe-area-inset-*)` i
200
151
  │ useSafeAreaInsets() consumers │
201
152
  └──────────────────────────────────────┘
202
153
  ```
203
-
204
154
  Why `SharedValue`s for the four edges but a plain `signal` for keyboard/statusBar/navigationBar? The four edges drive layout (`<SafeAreaView>` wants to write padding from a worklet on every flush) and the SV bridge is the right tool for that. The extras are informational — keyboard already lives in `bottom` on iOS, statusBar/navigationBar are decorative — so the SV plumbing isn't worth the cost there.
205
-
206
155
  A custom `safeAreaChanged` event is used instead of upstream's `onGlobalPropsChanged` because the upstream event-name conventions have churned across Lynx releases and we want the contract in our hands.
207
-
208
- ## Reference app
209
-
210
- `examples/lynx-one/my-sigx-app/src/App.tsx` mounts `<SafeAreaProvider>` and a `<SafeAreaView>` for the page chrome — useful as a copy-paste reference and as the smoke-test target when porting the publisher to a new platform.
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,MAAM,EAEX,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAiBjD,MAAM,MAAM,qBAAqB,GAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,gBAAgB;;EA2F3B,CAAC;AAkDH,YAAY,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,MAAM,EAEX,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAwBjD,MAAM,MAAM,qBAAqB,GAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,gBAAgB;;EAiI3B,CAAC;AAiDH,YAAY,EAAE,WAAW,EAAE,CAAC"}
package/dist/provider.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
- import { component, defineProvide, computed, signal, onMounted, onUnmounted, useSharedValue, useMainThreadRef, runOnMainThread, } from '@sigx/lynx';
2
+ import { component, effect, defineProvide, computed, signal, onMounted, onUnmounted, useSharedValue, useMainThreadRef, runOnMainThread, } from '@sigx/lynx';
3
3
  import { useSafeAreaContext } from './injectable.js';
4
4
  import { readGlobalSafeArea } from './globals.js';
5
5
  /**
@@ -13,6 +13,9 @@ import { readGlobalSafeArea } from './globals.js';
13
13
  * churned across Lynx releases).
14
14
  */
15
15
  export const SAFE_AREA_EVENT = 'safeAreaChanged';
16
+ // Unique host id per provider instance so the runtime `setProperty` call can
17
+ // target this provider's own view.
18
+ let safeAreaIdSeq = 0;
16
19
  /**
17
20
  * Mount once at the root of an app. Responsibilities:
18
21
  *
@@ -85,8 +88,12 @@ export const SafeAreaProvider = component(({ props, slots }) => {
85
88
  // Hold the elRef purely so consumers can extend the provider's host view
86
89
  // via the published CSS variables. Not used internally for any MT writes.
87
90
  const elRef = useMainThreadRef(null);
91
+ // Host id for the runtime `setProperty` CSS-variable application (below).
92
+ const hostId = `safe-area-${++safeAreaIdSeq}`;
88
93
  let listener;
89
94
  let emitter;
95
+ let varsEffect;
96
+ let insetsGen = 0;
90
97
  onMounted(() => {
91
98
  // `lynx` is a closure-injected identifier (provided by
92
99
  // `@lynx-js/runtime-wrapper-webpack-plugin`'s `__init_card_bundle__`
@@ -95,6 +102,39 @@ export const SafeAreaProvider = component(({ props, slots }) => {
95
102
  const lynxObj = typeof lynx !== 'undefined'
96
103
  ? lynx
97
104
  : undefined;
105
+ // Publish insets as real, inheritable CSS custom properties via the runtime
106
+ // `setProperty` API. Lynx does NOT honor custom properties declared through
107
+ // the inline `style` attribute, so `class="pt-[var(--sat)]"` consumers rely
108
+ // on this. On cold start the host view isn't queryable from the background
109
+ // thread the instant this runs, so retry on a short timer until it resolves
110
+ // (`insetsGen` drops a superseded retry). Reactive on `insets.value`.
111
+ const pushInsets = () => {
112
+ const i = insets.value;
113
+ if (!lynxObj?.getElementById)
114
+ return;
115
+ const vars = {
116
+ '--sat': `${i.top}px`,
117
+ '--sar': `${i.right}px`,
118
+ '--sab': `${i.bottom}px`,
119
+ '--sal': `${i.left}px`,
120
+ '--safe-area-keyboard': `${i.keyboard}px`,
121
+ };
122
+ const gen = ++insetsGen;
123
+ let tries = 0;
124
+ const attempt = () => {
125
+ if (gen !== insetsGen)
126
+ return;
127
+ const el = lynxObj.getElementById(hostId);
128
+ if (el) {
129
+ el.setProperty(vars);
130
+ return;
131
+ }
132
+ if (tries++ < 30)
133
+ setTimeout(attempt, 16);
134
+ };
135
+ attempt();
136
+ };
137
+ varsEffect = effect(() => { pushInsets(); });
98
138
  emitter = lynxObj?.getJSModule?.('GlobalEventEmitter');
99
139
  if (!emitter)
100
140
  return;
@@ -112,8 +152,11 @@ export const SafeAreaProvider = component(({ props, slots }) => {
112
152
  onUnmounted(() => {
113
153
  if (emitter && listener)
114
154
  emitter.removeListener(SAFE_AREA_EVENT, listener);
155
+ varsEffect?.stop();
156
+ varsEffect = undefined;
157
+ ++insetsGen; // cancel any pending setProperty retry
115
158
  });
116
- return () => (_jsx("view", { class: props.class, "main-thread:ref": elRef, style: cssVarStyle(insets.value, props.style), children: slots.default?.() }));
159
+ return () => (_jsx("view", { id: hostId, class: props.class, "main-thread:ref": elRef, style: rootStyle(props.style), children: slots.default?.() }));
117
160
  });
118
161
  function normaliseInsets(raw, fallback) {
119
162
  if (!raw || typeof raw !== 'object')
@@ -132,22 +175,22 @@ function normaliseInsets(raw, fallback) {
132
175
  function numOr(v, fallback) {
133
176
  return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
134
177
  }
135
- function cssVarStyle(i, user) {
178
+ function rootStyle(user) {
136
179
  // Defaults make the provider fill the device viewport and act as a
137
180
  // flex-column ancestor. Without these, every Lynx app re-rolls inline
138
181
  // `style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}`
139
182
  // because `<view>` defaults to auto height and the lynx-tailwind
140
183
  // preset (as of 0.4.0) doesn't ship an `h-screen` rule. Consumers can
141
184
  // override any of these via `props.style`.
185
+ //
186
+ // The safe-area CSS variables (`--sat`/`--sar`/`--sab`/`--sal`/
187
+ // `--safe-area-keyboard`) are NOT set here: Lynx ignores custom properties
188
+ // declared via inline `style`. They're published via the runtime
189
+ // `setProperty` API in the provider's mount effect instead.
142
190
  const base = {
143
191
  height: '100vh',
144
192
  display: 'flex',
145
193
  flexDirection: 'column',
146
- '--sat': `${i.top}px`,
147
- '--sar': `${i.right}px`,
148
- '--sab': `${i.bottom}px`,
149
- '--sal': `${i.left}px`,
150
- '--safe-area-keyboard': `${i.keyboard}px`,
151
194
  };
152
195
  return user ? { ...base, ...user } : base;
153
196
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigx/lynx-safe-area",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Safe area insets (notch, home indicator, status bar, keyboard) for sigx-lynx",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,15 +31,15 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@sigx/reactivity": "^0.4.8",
34
- "@sigx/lynx-runtime-internal": "^0.4.2",
35
- "@sigx/lynx": "^0.4.2"
34
+ "@sigx/lynx": "^0.4.4",
35
+ "@sigx/lynx-runtime-internal": "^0.4.4"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@typescript/native-preview": "7.0.0-dev.20260521.1",
39
39
  "typescript": "^6.0.3",
40
- "@sigx/lynx-runtime-main": "^0.4.2",
41
- "@sigx/lynx-testing": "^0.4.2",
42
- "@sigx/lynx-plugin": "^0.4.2"
40
+ "@sigx/lynx-runtime-main": "^0.4.4",
41
+ "@sigx/lynx-testing": "^0.4.4",
42
+ "@sigx/lynx-plugin": "^0.4.4"
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
package/src/provider.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  component,
3
+ effect,
3
4
  defineProvide,
4
5
  computed,
5
6
  signal,
@@ -35,6 +36,9 @@ interface GlobalEventEmitterLike {
35
36
 
36
37
  interface LynxLike {
37
38
  getJSModule?: (name: string) => GlobalEventEmitterLike | undefined;
39
+ getElementById?: (
40
+ id: string,
41
+ ) => { setProperty(props: Record<string, string>): void } | null;
38
42
  }
39
43
 
40
44
  // Closure-injected identifier provided by
@@ -43,6 +47,10 @@ interface LynxLike {
43
47
  // have to depend on lynx-runtime-internal just for the ambient.
44
48
  declare const lynx: unknown | undefined;
45
49
 
50
+ // Unique host id per provider instance so the runtime `setProperty` call can
51
+ // target this provider's own view.
52
+ let safeAreaIdSeq = 0;
53
+
46
54
  export type SafeAreaProviderProps =
47
55
  & Define.Prop<'class', string, false>
48
56
  & Define.Prop<'style', Record<string, string | number>, false>
@@ -127,8 +135,13 @@ export const SafeAreaProvider = component<SafeAreaProviderProps>(({ props, slots
127
135
  // via the published CSS variables. Not used internally for any MT writes.
128
136
  const elRef = useMainThreadRef<MainThread.Element | null>(null);
129
137
 
138
+ // Host id for the runtime `setProperty` CSS-variable application (below).
139
+ const hostId = `safe-area-${++safeAreaIdSeq}`;
140
+
130
141
  let listener: ((...a: unknown[]) => void) | undefined;
131
142
  let emitter: GlobalEventEmitterLike | undefined;
143
+ let varsEffect: { stop: () => void } | undefined;
144
+ let insetsGen = 0;
132
145
 
133
146
  onMounted(() => {
134
147
  // `lynx` is a closure-injected identifier (provided by
@@ -138,6 +151,35 @@ export const SafeAreaProvider = component<SafeAreaProviderProps>(({ props, slots
138
151
  const lynxObj: LynxLike | undefined = typeof lynx !== 'undefined'
139
152
  ? (lynx as unknown as LynxLike)
140
153
  : undefined;
154
+
155
+ // Publish insets as real, inheritable CSS custom properties via the runtime
156
+ // `setProperty` API. Lynx does NOT honor custom properties declared through
157
+ // the inline `style` attribute, so `class="pt-[var(--sat)]"` consumers rely
158
+ // on this. On cold start the host view isn't queryable from the background
159
+ // thread the instant this runs, so retry on a short timer until it resolves
160
+ // (`insetsGen` drops a superseded retry). Reactive on `insets.value`.
161
+ const pushInsets = (): void => {
162
+ const i = insets.value;
163
+ if (!lynxObj?.getElementById) return;
164
+ const vars: Record<string, string> = {
165
+ '--sat': `${i.top}px`,
166
+ '--sar': `${i.right}px`,
167
+ '--sab': `${i.bottom}px`,
168
+ '--sal': `${i.left}px`,
169
+ '--safe-area-keyboard': `${i.keyboard}px`,
170
+ };
171
+ const gen = ++insetsGen;
172
+ let tries = 0;
173
+ const attempt = (): void => {
174
+ if (gen !== insetsGen) return;
175
+ const el = lynxObj!.getElementById!(hostId);
176
+ if (el) { el.setProperty(vars); return; }
177
+ if (tries++ < 30) setTimeout(attempt, 16);
178
+ };
179
+ attempt();
180
+ };
181
+ varsEffect = effect(() => { pushInsets(); });
182
+
141
183
  emitter = lynxObj?.getJSModule?.('GlobalEventEmitter');
142
184
  if (!emitter) return;
143
185
  listener = (raw: unknown) => {
@@ -154,13 +196,17 @@ export const SafeAreaProvider = component<SafeAreaProviderProps>(({ props, slots
154
196
 
155
197
  onUnmounted(() => {
156
198
  if (emitter && listener) emitter.removeListener(SAFE_AREA_EVENT, listener);
199
+ varsEffect?.stop();
200
+ varsEffect = undefined;
201
+ ++insetsGen; // cancel any pending setProperty retry
157
202
  });
158
203
 
159
204
  return () => (
160
205
  <view
206
+ id={hostId}
161
207
  class={props.class}
162
208
  main-thread:ref={elRef}
163
- style={cssVarStyle(insets.value, props.style)}
209
+ style={rootStyle(props.style)}
164
210
  >
165
211
  {slots.default?.()}
166
212
  </view>
@@ -191,8 +237,7 @@ function numOr(v: unknown, fallback: number): number {
191
237
  return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
192
238
  }
193
239
 
194
- function cssVarStyle(
195
- i: EdgeInsets,
240
+ function rootStyle(
196
241
  user: Record<string, string | number> | undefined,
197
242
  ): Record<string, string | number> {
198
243
  // Defaults make the provider fill the device viewport and act as a
@@ -201,15 +246,15 @@ function cssVarStyle(
201
246
  // because `<view>` defaults to auto height and the lynx-tailwind
202
247
  // preset (as of 0.4.0) doesn't ship an `h-screen` rule. Consumers can
203
248
  // override any of these via `props.style`.
249
+ //
250
+ // The safe-area CSS variables (`--sat`/`--sar`/`--sab`/`--sal`/
251
+ // `--safe-area-keyboard`) are NOT set here: Lynx ignores custom properties
252
+ // declared via inline `style`. They're published via the runtime
253
+ // `setProperty` API in the provider's mount effect instead.
204
254
  const base: Record<string, string | number> = {
205
255
  height: '100vh',
206
256
  display: 'flex',
207
257
  flexDirection: 'column',
208
- '--sat': `${i.top}px`,
209
- '--sar': `${i.right}px`,
210
- '--sab': `${i.bottom}px`,
211
- '--sal': `${i.left}px`,
212
- '--safe-area-keyboard': `${i.keyboard}px`,
213
258
  };
214
259
  return user ? { ...base, ...user } : base;
215
260
  }