@sigx/lynx-safe-area 0.1.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -15
- package/dist/globals.d.ts +1 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/injectable.d.ts +1 -1
- package/dist/injectable.d.ts.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/safe-area-view.d.ts +1 -1
- package/dist/safe-area-view.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/globals.ts +1 -1
- package/src/hooks.ts +3 -3
- package/src/index.ts +10 -10
- package/src/injectable.ts +1 -1
- package/src/provider.tsx +12 -3
- package/src/safe-area-view.tsx +22 -8
- /package/{sigx-module.json → signalx-module.json} +0 -0
package/README.md
CHANGED
|
@@ -10,20 +10,7 @@ Mirrors React Native's `react-native-safe-area-context` API where it makes sense
|
|
|
10
10
|
pnpm add @sigx/lynx-safe-area
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { defineLynxConfig } from '@sigx/lynx-cli/config';
|
|
17
|
-
|
|
18
|
-
export default defineLynxConfig({
|
|
19
|
-
modules: [
|
|
20
|
-
// ...
|
|
21
|
-
'@sigx/lynx-safe-area',
|
|
22
|
-
],
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`sigx prebuild` then 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.
|
|
13
|
+
`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.
|
|
27
14
|
|
|
28
15
|
## Quick start
|
|
29
16
|
|
|
@@ -35,7 +22,7 @@ import { SafeAreaProvider, SafeAreaView } from '@sigx/lynx-safe-area';
|
|
|
35
22
|
|
|
36
23
|
defineApp(() => () => (
|
|
37
24
|
<SafeAreaProvider>
|
|
38
|
-
<SafeAreaView edges={['top', 'bottom']} class="bg-base-100
|
|
25
|
+
<SafeAreaView edges={['top', 'bottom']} class="bg-base-100">
|
|
39
26
|
<PageContent />
|
|
40
27
|
</SafeAreaView>
|
|
41
28
|
</SafeAreaProvider>
|
|
@@ -44,6 +31,12 @@ defineApp(() => () => (
|
|
|
44
31
|
|
|
45
32
|
`<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.
|
|
46
33
|
|
|
34
|
+
**Sensible layout defaults** — `<SafeAreaProvider>` defaults its host
|
|
35
|
+
view to `height: 100vh` + `flex-direction: column`, and `<SafeAreaView>`
|
|
36
|
+
defaults to flex-fill long-form. Consumers don't need to add inline
|
|
37
|
+
`height: '100vh'` anchors or `flex-1` classes for the layout chain to
|
|
38
|
+
work. Pass `style={…}` to override.
|
|
39
|
+
|
|
47
40
|
## API
|
|
48
41
|
|
|
49
42
|
### `<SafeAreaProvider>`
|
package/dist/globals.d.ts
CHANGED
package/dist/globals.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,
|
|
1
|
+
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,SAAS,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAe/C"}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Computed, type PrimitiveSignal } from '@sigx/reactivity';
|
|
2
|
-
import { type EdgeInsets, type SafeAreaContextValue } from './types
|
|
2
|
+
import { type EdgeInsets, type SafeAreaContextValue } from './types';
|
|
3
3
|
type InsetsRead = PrimitiveSignal<EdgeInsets> | Computed<EdgeInsets>;
|
|
4
4
|
/**
|
|
5
5
|
* BG-side reactive read of current safe-area insets. Returns the live signal
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjF,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjF,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAElF,KAAK,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,CAI9C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,oBAAoB,CAAC,IAAI,CAAC,GAAG,IAAI,CAG3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,QAAQ,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAWnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAEhD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { SafeAreaProvider, SAFE_AREA_EVENT } from './provider
|
|
2
|
-
export type { SafeAreaProviderProps } from './provider
|
|
3
|
-
export { SafeAreaView } from './safe-area-view
|
|
4
|
-
export type { SafeAreaViewProps } from './safe-area-view
|
|
5
|
-
export { useSafeAreaInsets, useSafeAreaSharedValues, useSafeAreaFrame, useSafeAreaInsetsMT, } from './hooks
|
|
6
|
-
export { useSafeAreaContext } from './injectable
|
|
7
|
-
export { readGlobalSafeArea, GLOBAL_PROPS_KEY, } from './globals
|
|
8
|
-
export type { RawSafeAreaProps } from './globals
|
|
9
|
-
export { ZERO_INSETS } from './types
|
|
10
|
-
export type { EdgeInsets, Edge, SafeAreaMode, SafeAreaContextValue, } from './types
|
|
1
|
+
export { SafeAreaProvider, SAFE_AREA_EVENT } from './provider';
|
|
2
|
+
export type { SafeAreaProviderProps } from './provider';
|
|
3
|
+
export { SafeAreaView } from './safe-area-view';
|
|
4
|
+
export type { SafeAreaViewProps } from './safe-area-view';
|
|
5
|
+
export { useSafeAreaInsets, useSafeAreaSharedValues, useSafeAreaFrame, useSafeAreaInsetsMT, } from './hooks';
|
|
6
|
+
export { useSafeAreaContext } from './injectable';
|
|
7
|
+
export { readGlobalSafeArea, GLOBAL_PROPS_KEY, } from './globals';
|
|
8
|
+
export type { RawSafeAreaProps } from './globals';
|
|
9
|
+
export { ZERO_INSETS } from './types';
|
|
10
|
+
export type { EdgeInsets, Edge, SafeAreaMode, SafeAreaContextValue, } from './types';
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EACV,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -91,6 +91,9 @@ function b(e, t) {
|
|
|
91
91
|
}
|
|
92
92
|
function x(e, t) {
|
|
93
93
|
let n = {
|
|
94
|
+
height: "100vh",
|
|
95
|
+
display: "flex",
|
|
96
|
+
flexDirection: "column",
|
|
94
97
|
"--sat": `${e.top}px`,
|
|
95
98
|
"--sar": `${e.right}px`,
|
|
96
99
|
"--sab": `${e.bottom}px`,
|
|
@@ -140,12 +143,19 @@ var O = [
|
|
|
140
143
|
], k = e(({ props: e, slots: t }) => {
|
|
141
144
|
let n = S(), r = e.edges ?? O, i = e.mode ?? "padding";
|
|
142
145
|
return () => {
|
|
143
|
-
let a = n.value, o = {
|
|
146
|
+
let a = n.value, o = {
|
|
147
|
+
flexGrow: 1,
|
|
148
|
+
flexShrink: 1,
|
|
149
|
+
flexBasis: 0,
|
|
150
|
+
minHeight: 0,
|
|
151
|
+
display: "flex",
|
|
152
|
+
flexDirection: "column"
|
|
153
|
+
};
|
|
144
154
|
return r.includes("top") && (o[i === "padding" ? "paddingTop" : "marginTop"] = `${a.top}px`), r.includes("right") && (o[i === "padding" ? "paddingRight" : "marginRight"] = `${a.right}px`), r.includes("bottom") && (o[i === "padding" ? "paddingBottom" : "marginBottom"] = `${a.bottom}px`), r.includes("left") && (o[i === "padding" ? "paddingLeft" : "marginLeft"] = `${a.left}px`), /* @__PURE__ */ u("view", {
|
|
145
155
|
class: e.class,
|
|
146
156
|
style: e.style ? {
|
|
147
|
-
...
|
|
148
|
-
...
|
|
157
|
+
...o,
|
|
158
|
+
...e.style
|
|
149
159
|
} : o,
|
|
150
160
|
children: t.default?.()
|
|
151
161
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/injectable.ts","../src/types.ts","../src/globals.ts","../src/provider.tsx","../src/hooks.ts","../src/safe-area-view.tsx"],"sourcesContent":["import { defineInjectable } from '@sigx/lynx';\nimport type { SafeAreaContextValue } from './types.js';\n\n/**\n * The DI handle for the safe-area context.\n *\n * - Inside `<SafeAreaProvider>`'s setup we call `defineProvide(useSafeAreaContext, factory)`\n * to install a per-app instance.\n * - Anywhere downstream `useSafeAreaContext()` returns that instance, or\n * `null` if no provider is in scope. Hooks defined in `./hooks.ts` wrap\n * this with the null-check + signal subscription.\n *\n * The factory returns `null` at the global-singleton level so consumers\n * outside a `<SafeAreaProvider>` get a clear signal (vs. a phantom zero-\n * insets context that silently does nothing).\n */\nexport const useSafeAreaContext = defineInjectable<SafeAreaContextValue | null>(() => null);\n","/**\n * Per-edge inset values, in dp/pt (logical pixels). Top/right/bottom/left\n * follow CSS shorthand order. Keyboard, statusBar, navigationBar are\n * informational extras populated when the host platform exposes them — they\n * may be 0 if unknown.\n */\nexport interface EdgeInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n /** IME (soft keyboard) height when visible, 0 when hidden. */\n keyboard: number;\n /** Status-bar height (top system bar). Often equal to `top`, but on\n * notched devices the safe-area top includes the notch and the status\n * bar is the smaller status-only inset. */\n statusBar: number;\n /** Navigation-bar height (Android gesture/3-button nav at bottom). */\n navigationBar: number;\n}\n\n/**\n * The four standard CSS edges. Subset to control which sides\n * `<SafeAreaView>` applies inset padding/margin to.\n */\nexport type Edge = 'top' | 'right' | 'bottom' | 'left';\n\n/** Whether `<SafeAreaView>` applies its insets as `padding` or `margin`. */\nexport type SafeAreaMode = 'padding' | 'margin';\n\n/**\n * The injectable shape exposed by `<SafeAreaProvider>`. Components that need\n * insets reactively read `insets.value` (BG signal) or, for MT-driven\n * layouts, subscribe to per-edge `SharedValue`s.\n */\nexport interface SafeAreaContextValue {\n /** BG-side reactive insets. Re-renders the consumer on change. */\n readonly insets: import('@sigx/reactivity').PrimitiveSignal<EdgeInsets>;\n /** Per-edge SharedValues for MT-driven `useAnimatedStyle` bindings. */\n readonly sv: {\n top: import('@sigx/lynx').SharedValue<number>;\n right: import('@sigx/lynx').SharedValue<number>;\n bottom: import('@sigx/lynx').SharedValue<number>;\n left: import('@sigx/lynx').SharedValue<number>;\n };\n}\n\nexport const ZERO_INSETS: EdgeInsets = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n keyboard: 0,\n statusBar: 0,\n navigationBar: 0,\n};\n","import { type EdgeInsets, ZERO_INSETS } from './types.js';\n\n/**\n * The key under `lynx.__globalProps` where the native publisher writes the\n * inset map. Kept as a constant so iOS/Android publishers, the JS reader, and\n * tests all agree on a single string.\n */\nexport const GLOBAL_PROPS_KEY = 'safeArea';\n\n/**\n * Shape of the safe-area sub-object the native publishers write to\n * `lynx.__globalProps[GLOBAL_PROPS_KEY]`. Some fields may be absent on\n * platforms that don't expose them (e.g. Android pre-31 navigation-bar API);\n * the reader fills missing keys with 0.\n */\nexport interface RawSafeAreaProps {\n top?: number;\n right?: number;\n bottom?: number;\n left?: number;\n keyboard?: number;\n statusBar?: number;\n navigationBar?: number;\n}\n\ninterface LynxGlobalLike {\n __globalProps?: { [k: string]: unknown };\n}\n\n// Closure-injected identifier — see provider.tsx for context.\ndeclare const lynx: unknown | undefined;\n\n/**\n * Synchronously read the current safe-area insets from `lynx.__globalProps`.\n *\n * Returns `ZERO_INSETS` when the publisher hasn't populated yet, when the\n * package is bundled into a non-Lynx host (web preview, SSR), or when the\n * host runtime omits the global. All callers must be prepared for the\n * zero-fallback — it's the natural state during cold start before the native\n * publisher has fired its first `updateGlobalProps`.\n *\n * Safe to call from both the Background Thread (BG) and the Main Thread\n * (MT), since `lynx.__globalProps` is mirrored across both. Sync read on MT\n * is what gives us inset-aware first paint.\n *\n * The `lynx` symbol is a closure-injected identifier (provided by\n * `@lynx-js/runtime-wrapper-webpack-plugin`'s `__init_card_bundle__`\n * wrapper), NOT a property of `globalThis`. Access it as a bare identifier\n * with a `typeof` guard — same pattern used by `lynx-runtime/src/bg-bridge.ts`.\n */\nexport function readGlobalSafeArea(): EdgeInsets {\n const lynxObj: LynxGlobalLike | undefined = typeof lynx !== 'undefined'\n ? (lynx as unknown as LynxGlobalLike)\n : undefined;\n const raw = lynxObj?.__globalProps?.[GLOBAL_PROPS_KEY] as RawSafeAreaProps | undefined;\n if (!raw || typeof raw !== 'object') return ZERO_INSETS;\n return {\n top: numOr0(raw.top),\n right: numOr0(raw.right),\n bottom: numOr0(raw.bottom),\n left: numOr0(raw.left),\n keyboard: numOr0(raw.keyboard),\n statusBar: numOr0(raw.statusBar),\n navigationBar: numOr0(raw.navigationBar),\n };\n}\n\nfunction numOr0(v: unknown): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : 0;\n}\n","import {\n component,\n defineProvide,\n computed,\n signal,\n onMounted,\n onUnmounted,\n useSharedValue,\n useMainThreadRef,\n runOnMainThread,\n type Define,\n type MainThread,\n type SharedValue,\n} from '@sigx/lynx';\nimport { useSafeAreaContext } from './injectable.js';\nimport { readGlobalSafeArea } from './globals.js';\nimport type { EdgeInsets, SafeAreaContextValue } from './types.js';\n\n/**\n * The native publisher (iOS `SafeAreaPublisher.swift`, Android\n * `SafeAreaPublisher.kt`) emits this event via `GlobalEventEmitter` every\n * time it republishes insets. Payload mirrors the same `RawSafeAreaProps`\n * shape stored under `lynx.__globalProps[GLOBAL_PROPS_KEY]`.\n *\n * We use a custom event rather than upstream's `onGlobalPropsChanged` so\n * the contract stays in our hands (upstream's event-name conventions have\n * churned across Lynx releases).\n */\nexport const SAFE_AREA_EVENT = 'safeAreaChanged';\n\ninterface GlobalEventEmitterLike {\n addListener: (name: string, fn: (...a: unknown[]) => void) => void;\n removeListener: (name: string, fn: (...a: unknown[]) => void) => void;\n}\n\ninterface LynxLike {\n getJSModule?: (name: string) => GlobalEventEmitterLike | undefined;\n}\n\n// Closure-injected identifier provided by\n// `@lynx-js/runtime-wrapper-webpack-plugin`. Same pattern as\n// `lynx-runtime/src/shims.d.ts`. Declared locally so this package doesn't\n// have to depend on lynx-runtime-internal just for the ambient.\ndeclare const lynx: unknown | undefined;\n\nexport type SafeAreaProviderProps =\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\n/**\n * Mount once at the root of an app. Responsibilities:\n *\n * 1. **Seed insets synchronously** from `lynx.__globalProps[safeArea]`. The\n * native side populates this *before* the MT bundle evaluates, so the\n * seed is correct on first render — no flash of unsafe content.\n *\n * 2. **Provide a DI context** (`useSafeAreaContext`) holding:\n * - four per-edge `SharedValue<number>`s — the single source of truth,\n * writable on MT, observable from both threads.\n * - a derived BG `computed<EdgeInsets>` for re-render-driven consumers\n * (`useSafeAreaInsets()`).\n *\n * 3. **Subscribe to live updates** via `GlobalEventEmitter`. The native\n * publisher emits `'safeAreaChanged'` after each `updateGlobalProps`,\n * carrying the new inset map. We dispatch a `runOnMainThread` worklet\n * that writes the per-edge SVs on MT — the SharedValue diff/publish\n * bridge then propagates the new values back to the BG signal mirror,\n * which re-fires the `computed` and re-renders consumers.\n *\n * 4. **Apply CSS variables** (`--sat`, `--sar`, `--sab`, `--sal`,\n * `--safe-area-keyboard`) on the root `<view>` so utility-class\n * consumers can write `class=\"pt-[var(--sat)]\"` and have it work\n * uniformly across iOS and Android (upstream's\n * `env(safe-area-inset-*)` is iOS-only).\n */\nexport const SafeAreaProvider = component<SafeAreaProviderProps>(({ props, slots }) => {\n const initial = readGlobalSafeArea();\n\n const svTop = useSharedValue(initial.top);\n const svRight = useSharedValue(initial.right);\n const svBottom = useSharedValue(initial.bottom);\n const svLeft = useSharedValue(initial.left);\n\n // Reactive object signal for the non-SV extras (BG-only — keyboard,\n // statusBar, navigationBar don't drive MT-bound layout, so SV plumbing\n // isn't worth the cost). `signal({...})` returns a deeply reactive proxy;\n // access via `extras.keyboard` etc., replace via `extras.$set({...})`.\n const extras = signal<Extras>({\n keyboard: initial.keyboard,\n statusBar: initial.statusBar,\n navigationBar: initial.navigationBar,\n });\n\n // Single source of truth for BG consumers — derived reactively from the\n // four edge SVs (which live on MT) and the extras signal (which lives on\n // BG). Re-runs when MT publishes new SV values via the AvBridge OR when\n // the safeAreaChanged listener writes to `extras`.\n const insets = computed<EdgeInsets>(() => ({\n top: svTop.value,\n right: svRight.value,\n bottom: svBottom.value,\n left: svLeft.value,\n keyboard: extras.keyboard,\n statusBar: extras.statusBar,\n navigationBar: extras.navigationBar,\n }));\n\n const ctx: SafeAreaContextValue = {\n insets,\n sv: { top: svTop, right: svRight, bottom: svBottom, left: svLeft },\n };\n defineProvide(useSafeAreaContext, () => ctx);\n\n // Worklet that writes the four per-edge SVs on MT. Captured by `_c` at\n // build time — runOnMainThread ships the SV refs as `{_wvid, _initValue}`\n // placeholders that the MT runtime resolves to the live envelope.\n const writeOnMT = runOnMainThread((t: number, r: number, b: number, l: number) => {\n 'main thread';\n svTop.current.value = t;\n svRight.current.value = r;\n svBottom.current.value = b;\n svLeft.current.value = l;\n });\n\n // Hold the elRef purely so consumers can extend the provider's host view\n // via the published CSS variables. Not used internally for any MT writes.\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n let listener: ((...a: unknown[]) => void) | undefined;\n let emitter: GlobalEventEmitterLike | undefined;\n\n onMounted(() => {\n // `lynx` is a closure-injected identifier (provided by\n // `@lynx-js/runtime-wrapper-webpack-plugin`'s `__init_card_bundle__`\n // wrapper), NOT a property of `globalThis`. Access as a bare identifier\n // with `typeof` guard — same pattern as `lynx-runtime/src/bg-bridge.ts`.\n const lynxObj: LynxLike | undefined = typeof lynx !== 'undefined'\n ? (lynx as unknown as LynxLike)\n : undefined;\n emitter = lynxObj?.getJSModule?.('GlobalEventEmitter');\n if (!emitter) return;\n listener = (raw: unknown) => {\n const next = normaliseInsets(raw, insets.value);\n extras.$set({\n keyboard: next.keyboard,\n statusBar: next.statusBar,\n navigationBar: next.navigationBar,\n });\n void writeOnMT(next.top, next.right, next.bottom, next.left);\n };\n emitter.addListener(SAFE_AREA_EVENT, listener);\n });\n\n onUnmounted(() => {\n if (emitter && listener) emitter.removeListener(SAFE_AREA_EVENT, listener);\n });\n\n return () => (\n <view\n class={props.class}\n main-thread:ref={elRef}\n style={cssVarStyle(insets.value, props.style)}\n >\n {slots.default?.()}\n </view>\n );\n});\n\ninterface Extras {\n keyboard: number;\n statusBar: number;\n navigationBar: number;\n}\n\nfunction normaliseInsets(raw: unknown, fallback: EdgeInsets): EdgeInsets {\n if (!raw || typeof raw !== 'object') return fallback;\n const o = raw as Record<string, unknown>;\n return {\n top: numOr(o['top'], fallback.top),\n right: numOr(o['right'], fallback.right),\n bottom: numOr(o['bottom'], fallback.bottom),\n left: numOr(o['left'], fallback.left),\n keyboard: numOr(o['keyboard'], fallback.keyboard),\n statusBar: numOr(o['statusBar'], fallback.statusBar),\n navigationBar: numOr(o['navigationBar'], fallback.navigationBar),\n };\n}\n\nfunction numOr(v: unknown, fallback: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\nfunction cssVarStyle(\n i: EdgeInsets,\n user: Record<string, string | number> | undefined,\n): Record<string, string | number> {\n const base: Record<string, string | number> = {\n '--sat': `${i.top}px`,\n '--sar': `${i.right}px`,\n '--sab': `${i.bottom}px`,\n '--sal': `${i.left}px`,\n '--safe-area-keyboard': `${i.keyboard}px`,\n };\n return user ? { ...base, ...user } : base;\n}\n\n// re-export so users only need `@sigx/lynx-safe-area`\nexport type { SharedValue };\n","import { computed, type Computed, type PrimitiveSignal } from '@sigx/reactivity';\nimport { useSafeAreaContext } from './injectable.js';\nimport { readGlobalSafeArea } from './globals.js';\nimport { ZERO_INSETS, type EdgeInsets, type SafeAreaContextValue } from './types.js';\n\ntype InsetsRead = PrimitiveSignal<EdgeInsets> | Computed<EdgeInsets>;\n\n/**\n * BG-side reactive read of current safe-area insets. Returns the live signal\n * — components calling this re-render on inset change (rotation, keyboard,\n * split-view).\n *\n * If no `<SafeAreaProvider>` is in scope, returns a signal seeded with\n * `ZERO_INSETS` and warns in dev. (We don't throw because mounting an app\n * fragment for tests/storybook without a provider is convenient and the\n * zero fallback degrades gracefully.)\n *\n * @example\n * ```tsx\n * const insets = useSafeAreaInsets();\n * return () => <view style={{ paddingTop: insets.value.top }} />;\n * ```\n */\nexport function useSafeAreaInsets(): InsetsRead {\n const ctx = useSafeAreaContext();\n if (!ctx) return fallbackSignal();\n return ctx.insets;\n}\n\n/**\n * Per-edge SharedValues for MT-driven `useAnimatedStyle` bindings — the\n * recommended path for `<SafeAreaView>`-style layouts that need padding to\n * track insets without a BG re-render. See `safe-area-view.tsx` for the\n * canonical consumer.\n */\nexport function useSafeAreaSharedValues(): SafeAreaContextValue['sv'] | null {\n const ctx = useSafeAreaContext();\n return ctx?.sv ?? null;\n}\n\n/**\n * Computed signal of the inner safe frame (origin + size) in dp/pt.\n *\n * Useful for absolute-positioned overlays, modal bounds, and layout math\n * that needs to know \"the visible content rect\" rather than just the inset\n * deltas. The frame size is computed from the host viewport — you must pass\n * `viewportWidth`/`viewportHeight` (typically read once via\n * `@sigx/lynx-device-info`) since the safe-area module deliberately avoids\n * pulling that whole dependency.\n *\n * @example\n * ```tsx\n * const { screenWidth, screenHeight } = await DeviceInfo.getInfo();\n * const frame = useSafeAreaFrame(screenWidth, screenHeight);\n * return () => <view style={{\n * position: 'absolute',\n * top: frame.value.y, left: frame.value.x,\n * width: frame.value.width, height: frame.value.height,\n * }} />;\n * ```\n */\nexport function useSafeAreaFrame(\n viewportWidth: number,\n viewportHeight: number,\n): Computed<{ x: number; y: number; width: number; height: number }> {\n const insets = useSafeAreaInsets();\n return computed(() => {\n const i = insets.value;\n return {\n x: i.left,\n y: i.top,\n width: Math.max(0, viewportWidth - i.left - i.right),\n height: Math.max(0, viewportHeight - i.top - i.bottom - i.keyboard),\n };\n });\n}\n\n/**\n * **MT-thread** synchronous read of the current safe-area insets. For use\n * inside `'main thread'`-marked worklet bodies. Reads `lynx.__globalProps`\n * directly — there's no signal subscription, so callers re-evaluate per\n * worklet invocation rather than reactively.\n *\n * For declarative MT-driven layout (the common case), prefer\n * `<SafeAreaView edges={…}>`, which composes `useSafeAreaSharedValues()`\n * with `useAnimatedStyle` — that path is reactive and applies on flush.\n */\nexport function useSafeAreaInsetsMT(): EdgeInsets {\n return readGlobalSafeArea();\n}\n\nlet _fallback: Computed<EdgeInsets> | undefined;\nfunction fallbackSignal(): Computed<EdgeInsets> {\n if (!_fallback) {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.['NODE_ENV'];\n if (env !== 'production') {\n console.warn(\n '[sigx-safe-area] useSafeAreaInsets() called outside <SafeAreaProvider>. ' +\n 'Returning ZERO_INSETS. Wrap your app in <SafeAreaProvider> to receive ' +\n 'live device insets.',\n );\n }\n _fallback = computed(() => ZERO_INSETS);\n }\n return _fallback;\n}\n","import { component, type Define } from '@sigx/lynx';\nimport { useSafeAreaInsets } from './hooks.js';\nimport type { Edge, SafeAreaMode } from './types.js';\n\nexport type SafeAreaViewProps =\n & Define.Prop<'edges', Edge[], false>\n & Define.Prop<'mode', SafeAreaMode, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\nconst ALL_EDGES: Edge[] = ['top', 'right', 'bottom', 'left'];\n\n/**\n * Drop-in container that applies the current safe-area insets as padding\n * (default) or margin on the configured edges.\n *\n * Implementation: BG signal + inline style. Sigx auto-tracks `insets.value`\n * access in the render function, so the inset values land in the FIRST\n * layout pass and re-apply reactively on every `safeAreaChanged` event.\n *\n * The previous implementation used `useAnimatedStyle` to drive padding via\n * the MT bridge — but `setStyleProperties` writes that affect layout fire\n * AFTER the first layout pass, and child elements that have already laid\n * out (notably `<scroll-view>`, which captures its frame eagerly) don't\n * reflow. Inline style avoids that timing trap entirely.\n *\n * `edges` defaults to all four sides. Pass a subset (e.g. `['top']`) to\n * leave the unspecified sides unaffected.\n *\n * Must be a descendant of `<SafeAreaProvider>`. If no provider is in scope\n * (test/storybook), `useSafeAreaInsets()` returns `ZERO_INSETS` with a\n * dev-mode warning and SafeAreaView passes through unchanged.\n *\n * @example\n * ```tsx\n * <SafeAreaProvider>\n * <SafeAreaView edges={['top', 'bottom']} class=\"bg-base-100 flex-1\">\n * <PageContent />\n * </SafeAreaView>\n * </SafeAreaProvider>\n * ```\n */\nexport const SafeAreaView = component<SafeAreaViewProps>(({ props, slots }) => {\n const insets = useSafeAreaInsets();\n const edges = props.edges ?? ALL_EDGES;\n const mode = props.mode ?? 'padding';\n\n return () => {\n const i = insets.value;\n const insetStyle: Record<string, string | number> = {};\n if (edges.includes('top')) {\n insetStyle[mode === 'padding' ? 'paddingTop' : 'marginTop'] = `${i.top}px`;\n }\n if (edges.includes('right')) {\n insetStyle[mode === 'padding' ? 'paddingRight' : 'marginRight'] = `${i.right}px`;\n }\n if (edges.includes('bottom')) {\n insetStyle[mode === 'padding' ? 'paddingBottom' : 'marginBottom'] = `${i.bottom}px`;\n }\n if (edges.includes('left')) {\n insetStyle[mode === 'padding' ? 'paddingLeft' : 'marginLeft'] = `${i.left}px`;\n }\n return (\n <view\n class={props.class}\n style={props.style ? { ...props.style, ...insetStyle } : insetStyle}\n >\n {slots.default?.()}\n </view>\n );\n };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/injectable.ts","../src/types.ts","../src/globals.ts","../src/provider.tsx","../src/hooks.ts","../src/safe-area-view.tsx"],"sourcesContent":["import { defineInjectable } from '@sigx/lynx';\nimport type { SafeAreaContextValue } from './types';\n\n/**\n * The DI handle for the safe-area context.\n *\n * - Inside `<SafeAreaProvider>`'s setup we call `defineProvide(useSafeAreaContext, factory)`\n * to install a per-app instance.\n * - Anywhere downstream `useSafeAreaContext()` returns that instance, or\n * `null` if no provider is in scope. Hooks defined in `./hooks.ts` wrap\n * this with the null-check + signal subscription.\n *\n * The factory returns `null` at the global-singleton level so consumers\n * outside a `<SafeAreaProvider>` get a clear signal (vs. a phantom zero-\n * insets context that silently does nothing).\n */\nexport const useSafeAreaContext = defineInjectable<SafeAreaContextValue | null>(() => null);\n","/**\n * Per-edge inset values, in dp/pt (logical pixels). Top/right/bottom/left\n * follow CSS shorthand order. Keyboard, statusBar, navigationBar are\n * informational extras populated when the host platform exposes them — they\n * may be 0 if unknown.\n */\nexport interface EdgeInsets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n /** IME (soft keyboard) height when visible, 0 when hidden. */\n keyboard: number;\n /** Status-bar height (top system bar). Often equal to `top`, but on\n * notched devices the safe-area top includes the notch and the status\n * bar is the smaller status-only inset. */\n statusBar: number;\n /** Navigation-bar height (Android gesture/3-button nav at bottom). */\n navigationBar: number;\n}\n\n/**\n * The four standard CSS edges. Subset to control which sides\n * `<SafeAreaView>` applies inset padding/margin to.\n */\nexport type Edge = 'top' | 'right' | 'bottom' | 'left';\n\n/** Whether `<SafeAreaView>` applies its insets as `padding` or `margin`. */\nexport type SafeAreaMode = 'padding' | 'margin';\n\n/**\n * The injectable shape exposed by `<SafeAreaProvider>`. Components that need\n * insets reactively read `insets.value` (BG signal) or, for MT-driven\n * layouts, subscribe to per-edge `SharedValue`s.\n */\nexport interface SafeAreaContextValue {\n /** BG-side reactive insets. Re-renders the consumer on change. */\n readonly insets: import('@sigx/reactivity').PrimitiveSignal<EdgeInsets>;\n /** Per-edge SharedValues for MT-driven `useAnimatedStyle` bindings. */\n readonly sv: {\n top: import('@sigx/lynx').SharedValue<number>;\n right: import('@sigx/lynx').SharedValue<number>;\n bottom: import('@sigx/lynx').SharedValue<number>;\n left: import('@sigx/lynx').SharedValue<number>;\n };\n}\n\nexport const ZERO_INSETS: EdgeInsets = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n keyboard: 0,\n statusBar: 0,\n navigationBar: 0,\n};\n","import { type EdgeInsets, ZERO_INSETS } from './types';\n\n/**\n * The key under `lynx.__globalProps` where the native publisher writes the\n * inset map. Kept as a constant so iOS/Android publishers, the JS reader, and\n * tests all agree on a single string.\n */\nexport const GLOBAL_PROPS_KEY = 'safeArea';\n\n/**\n * Shape of the safe-area sub-object the native publishers write to\n * `lynx.__globalProps[GLOBAL_PROPS_KEY]`. Some fields may be absent on\n * platforms that don't expose them (e.g. Android pre-31 navigation-bar API);\n * the reader fills missing keys with 0.\n */\nexport interface RawSafeAreaProps {\n top?: number;\n right?: number;\n bottom?: number;\n left?: number;\n keyboard?: number;\n statusBar?: number;\n navigationBar?: number;\n}\n\ninterface LynxGlobalLike {\n __globalProps?: { [k: string]: unknown };\n}\n\n// Closure-injected identifier — see provider.tsx for context.\ndeclare const lynx: unknown | undefined;\n\n/**\n * Synchronously read the current safe-area insets from `lynx.__globalProps`.\n *\n * Returns `ZERO_INSETS` when the publisher hasn't populated yet, when the\n * package is bundled into a non-Lynx host (web preview, SSR), or when the\n * host runtime omits the global. All callers must be prepared for the\n * zero-fallback — it's the natural state during cold start before the native\n * publisher has fired its first `updateGlobalProps`.\n *\n * Safe to call from both the Background Thread (BG) and the Main Thread\n * (MT), since `lynx.__globalProps` is mirrored across both. Sync read on MT\n * is what gives us inset-aware first paint.\n *\n * The `lynx` symbol is a closure-injected identifier (provided by\n * `@lynx-js/runtime-wrapper-webpack-plugin`'s `__init_card_bundle__`\n * wrapper), NOT a property of `globalThis`. Access it as a bare identifier\n * with a `typeof` guard — same pattern used by `lynx-runtime/src/bg-bridge.ts`.\n */\nexport function readGlobalSafeArea(): EdgeInsets {\n const lynxObj: LynxGlobalLike | undefined = typeof lynx !== 'undefined'\n ? (lynx as unknown as LynxGlobalLike)\n : undefined;\n const raw = lynxObj?.__globalProps?.[GLOBAL_PROPS_KEY] as RawSafeAreaProps | undefined;\n if (!raw || typeof raw !== 'object') return ZERO_INSETS;\n return {\n top: numOr0(raw.top),\n right: numOr0(raw.right),\n bottom: numOr0(raw.bottom),\n left: numOr0(raw.left),\n keyboard: numOr0(raw.keyboard),\n statusBar: numOr0(raw.statusBar),\n navigationBar: numOr0(raw.navigationBar),\n };\n}\n\nfunction numOr0(v: unknown): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : 0;\n}\n","import {\n component,\n defineProvide,\n computed,\n signal,\n onMounted,\n onUnmounted,\n useSharedValue,\n useMainThreadRef,\n runOnMainThread,\n type Define,\n type MainThread,\n type SharedValue,\n} from '@sigx/lynx';\nimport { useSafeAreaContext } from './injectable';\nimport { readGlobalSafeArea } from './globals';\nimport type { EdgeInsets, SafeAreaContextValue } from './types';\n\n/**\n * The native publisher (iOS `SafeAreaPublisher.swift`, Android\n * `SafeAreaPublisher.kt`) emits this event via `GlobalEventEmitter` every\n * time it republishes insets. Payload mirrors the same `RawSafeAreaProps`\n * shape stored under `lynx.__globalProps[GLOBAL_PROPS_KEY]`.\n *\n * We use a custom event rather than upstream's `onGlobalPropsChanged` so\n * the contract stays in our hands (upstream's event-name conventions have\n * churned across Lynx releases).\n */\nexport const SAFE_AREA_EVENT = 'safeAreaChanged';\n\ninterface GlobalEventEmitterLike {\n addListener: (name: string, fn: (...a: unknown[]) => void) => void;\n removeListener: (name: string, fn: (...a: unknown[]) => void) => void;\n}\n\ninterface LynxLike {\n getJSModule?: (name: string) => GlobalEventEmitterLike | undefined;\n}\n\n// Closure-injected identifier provided by\n// `@lynx-js/runtime-wrapper-webpack-plugin`. Same pattern as\n// `lynx-runtime/src/shims.d.ts`. Declared locally so this package doesn't\n// have to depend on lynx-runtime-internal just for the ambient.\ndeclare const lynx: unknown | undefined;\n\nexport type SafeAreaProviderProps =\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\n/**\n * Mount once at the root of an app. Responsibilities:\n *\n * 1. **Seed insets synchronously** from `lynx.__globalProps[safeArea]`. The\n * native side populates this *before* the MT bundle evaluates, so the\n * seed is correct on first render — no flash of unsafe content.\n *\n * 2. **Provide a DI context** (`useSafeAreaContext`) holding:\n * - four per-edge `SharedValue<number>`s — the single source of truth,\n * writable on MT, observable from both threads.\n * - a derived BG `computed<EdgeInsets>` for re-render-driven consumers\n * (`useSafeAreaInsets()`).\n *\n * 3. **Subscribe to live updates** via `GlobalEventEmitter`. The native\n * publisher emits `'safeAreaChanged'` after each `updateGlobalProps`,\n * carrying the new inset map. We dispatch a `runOnMainThread` worklet\n * that writes the per-edge SVs on MT — the SharedValue diff/publish\n * bridge then propagates the new values back to the BG signal mirror,\n * which re-fires the `computed` and re-renders consumers.\n *\n * 4. **Apply CSS variables** (`--sat`, `--sar`, `--sab`, `--sal`,\n * `--safe-area-keyboard`) on the root `<view>` so utility-class\n * consumers can write `class=\"pt-[var(--sat)]\"` and have it work\n * uniformly across iOS and Android (upstream's\n * `env(safe-area-inset-*)` is iOS-only).\n */\nexport const SafeAreaProvider = component<SafeAreaProviderProps>(({ props, slots }) => {\n const initial = readGlobalSafeArea();\n\n const svTop = useSharedValue(initial.top);\n const svRight = useSharedValue(initial.right);\n const svBottom = useSharedValue(initial.bottom);\n const svLeft = useSharedValue(initial.left);\n\n // Reactive object signal for the non-SV extras (BG-only — keyboard,\n // statusBar, navigationBar don't drive MT-bound layout, so SV plumbing\n // isn't worth the cost). `signal({...})` returns a deeply reactive proxy;\n // access via `extras.keyboard` etc., replace via `extras.$set({...})`.\n const extras = signal<Extras>({\n keyboard: initial.keyboard,\n statusBar: initial.statusBar,\n navigationBar: initial.navigationBar,\n });\n\n // Single source of truth for BG consumers — derived reactively from the\n // four edge SVs (which live on MT) and the extras signal (which lives on\n // BG). Re-runs when MT publishes new SV values via the AvBridge OR when\n // the safeAreaChanged listener writes to `extras`.\n const insets = computed<EdgeInsets>(() => ({\n top: svTop.value,\n right: svRight.value,\n bottom: svBottom.value,\n left: svLeft.value,\n keyboard: extras.keyboard,\n statusBar: extras.statusBar,\n navigationBar: extras.navigationBar,\n }));\n\n const ctx: SafeAreaContextValue = {\n insets,\n sv: { top: svTop, right: svRight, bottom: svBottom, left: svLeft },\n };\n defineProvide(useSafeAreaContext, () => ctx);\n\n // Worklet that writes the four per-edge SVs on MT. Captured by `_c` at\n // build time — runOnMainThread ships the SV refs as `{_wvid, _initValue}`\n // placeholders that the MT runtime resolves to the live envelope.\n const writeOnMT = runOnMainThread((t: number, r: number, b: number, l: number) => {\n 'main thread';\n svTop.current.value = t;\n svRight.current.value = r;\n svBottom.current.value = b;\n svLeft.current.value = l;\n });\n\n // Hold the elRef purely so consumers can extend the provider's host view\n // via the published CSS variables. Not used internally for any MT writes.\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n let listener: ((...a: unknown[]) => void) | undefined;\n let emitter: GlobalEventEmitterLike | undefined;\n\n onMounted(() => {\n // `lynx` is a closure-injected identifier (provided by\n // `@lynx-js/runtime-wrapper-webpack-plugin`'s `__init_card_bundle__`\n // wrapper), NOT a property of `globalThis`. Access as a bare identifier\n // with `typeof` guard — same pattern as `lynx-runtime/src/bg-bridge.ts`.\n const lynxObj: LynxLike | undefined = typeof lynx !== 'undefined'\n ? (lynx as unknown as LynxLike)\n : undefined;\n emitter = lynxObj?.getJSModule?.('GlobalEventEmitter');\n if (!emitter) return;\n listener = (raw: unknown) => {\n const next = normaliseInsets(raw, insets.value);\n extras.$set({\n keyboard: next.keyboard,\n statusBar: next.statusBar,\n navigationBar: next.navigationBar,\n });\n void writeOnMT(next.top, next.right, next.bottom, next.left);\n };\n emitter.addListener(SAFE_AREA_EVENT, listener);\n });\n\n onUnmounted(() => {\n if (emitter && listener) emitter.removeListener(SAFE_AREA_EVENT, listener);\n });\n\n return () => (\n <view\n class={props.class}\n main-thread:ref={elRef}\n style={cssVarStyle(insets.value, props.style)}\n >\n {slots.default?.()}\n </view>\n );\n});\n\ninterface Extras {\n keyboard: number;\n statusBar: number;\n navigationBar: number;\n}\n\nfunction normaliseInsets(raw: unknown, fallback: EdgeInsets): EdgeInsets {\n if (!raw || typeof raw !== 'object') return fallback;\n const o = raw as Record<string, unknown>;\n return {\n top: numOr(o['top'], fallback.top),\n right: numOr(o['right'], fallback.right),\n bottom: numOr(o['bottom'], fallback.bottom),\n left: numOr(o['left'], fallback.left),\n keyboard: numOr(o['keyboard'], fallback.keyboard),\n statusBar: numOr(o['statusBar'], fallback.statusBar),\n navigationBar: numOr(o['navigationBar'], fallback.navigationBar),\n };\n}\n\nfunction numOr(v: unknown, fallback: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\nfunction cssVarStyle(\n i: EdgeInsets,\n user: Record<string, string | number> | undefined,\n): Record<string, string | number> {\n // Defaults make the provider fill the device viewport and act as a\n // flex-column ancestor. Without these, every Lynx app re-rolls inline\n // `style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}`\n // because `<view>` defaults to auto height and the lynx-tailwind\n // preset (as of 0.4.0) doesn't ship an `h-screen` rule. Consumers can\n // override any of these via `props.style`.\n const base: Record<string, string | number> = {\n height: '100vh',\n display: 'flex',\n flexDirection: 'column',\n '--sat': `${i.top}px`,\n '--sar': `${i.right}px`,\n '--sab': `${i.bottom}px`,\n '--sal': `${i.left}px`,\n '--safe-area-keyboard': `${i.keyboard}px`,\n };\n return user ? { ...base, ...user } : base;\n}\n\n// re-export so users only need `@sigx/lynx-safe-area`\nexport type { SharedValue };\n","import { computed, type Computed, type PrimitiveSignal } from '@sigx/reactivity';\nimport { useSafeAreaContext } from './injectable';\nimport { readGlobalSafeArea } from './globals';\nimport { ZERO_INSETS, type EdgeInsets, type SafeAreaContextValue } from './types';\n\ntype InsetsRead = PrimitiveSignal<EdgeInsets> | Computed<EdgeInsets>;\n\n/**\n * BG-side reactive read of current safe-area insets. Returns the live signal\n * — components calling this re-render on inset change (rotation, keyboard,\n * split-view).\n *\n * If no `<SafeAreaProvider>` is in scope, returns a signal seeded with\n * `ZERO_INSETS` and warns in dev. (We don't throw because mounting an app\n * fragment for tests/storybook without a provider is convenient and the\n * zero fallback degrades gracefully.)\n *\n * @example\n * ```tsx\n * const insets = useSafeAreaInsets();\n * return () => <view style={{ paddingTop: insets.value.top }} />;\n * ```\n */\nexport function useSafeAreaInsets(): InsetsRead {\n const ctx = useSafeAreaContext();\n if (!ctx) return fallbackSignal();\n return ctx.insets;\n}\n\n/**\n * Per-edge SharedValues for MT-driven `useAnimatedStyle` bindings — the\n * recommended path for `<SafeAreaView>`-style layouts that need padding to\n * track insets without a BG re-render. See `safe-area-view.tsx` for the\n * canonical consumer.\n */\nexport function useSafeAreaSharedValues(): SafeAreaContextValue['sv'] | null {\n const ctx = useSafeAreaContext();\n return ctx?.sv ?? null;\n}\n\n/**\n * Computed signal of the inner safe frame (origin + size) in dp/pt.\n *\n * Useful for absolute-positioned overlays, modal bounds, and layout math\n * that needs to know \"the visible content rect\" rather than just the inset\n * deltas. The frame size is computed from the host viewport — you must pass\n * `viewportWidth`/`viewportHeight` (typically read once via\n * `@sigx/lynx-device-info`) since the safe-area module deliberately avoids\n * pulling that whole dependency.\n *\n * @example\n * ```tsx\n * const { screenWidth, screenHeight } = await DeviceInfo.getInfo();\n * const frame = useSafeAreaFrame(screenWidth, screenHeight);\n * return () => <view style={{\n * position: 'absolute',\n * top: frame.value.y, left: frame.value.x,\n * width: frame.value.width, height: frame.value.height,\n * }} />;\n * ```\n */\nexport function useSafeAreaFrame(\n viewportWidth: number,\n viewportHeight: number,\n): Computed<{ x: number; y: number; width: number; height: number }> {\n const insets = useSafeAreaInsets();\n return computed(() => {\n const i = insets.value;\n return {\n x: i.left,\n y: i.top,\n width: Math.max(0, viewportWidth - i.left - i.right),\n height: Math.max(0, viewportHeight - i.top - i.bottom - i.keyboard),\n };\n });\n}\n\n/**\n * **MT-thread** synchronous read of the current safe-area insets. For use\n * inside `'main thread'`-marked worklet bodies. Reads `lynx.__globalProps`\n * directly — there's no signal subscription, so callers re-evaluate per\n * worklet invocation rather than reactively.\n *\n * For declarative MT-driven layout (the common case), prefer\n * `<SafeAreaView edges={…}>`, which composes `useSafeAreaSharedValues()`\n * with `useAnimatedStyle` — that path is reactive and applies on flush.\n */\nexport function useSafeAreaInsetsMT(): EdgeInsets {\n return readGlobalSafeArea();\n}\n\nlet _fallback: Computed<EdgeInsets> | undefined;\nfunction fallbackSignal(): Computed<EdgeInsets> {\n if (!_fallback) {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.['NODE_ENV'];\n if (env !== 'production') {\n console.warn(\n '[sigx-safe-area] useSafeAreaInsets() called outside <SafeAreaProvider>. ' +\n 'Returning ZERO_INSETS. Wrap your app in <SafeAreaProvider> to receive ' +\n 'live device insets.',\n );\n }\n _fallback = computed(() => ZERO_INSETS);\n }\n return _fallback;\n}\n","import { component, type Define } from '@sigx/lynx';\nimport { useSafeAreaInsets } from './hooks';\nimport type { Edge, SafeAreaMode } from './types';\n\nexport type SafeAreaViewProps =\n & Define.Prop<'edges', Edge[], false>\n & Define.Prop<'mode', SafeAreaMode, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\nconst ALL_EDGES: Edge[] = ['top', 'right', 'bottom', 'left'];\n\n/**\n * Drop-in container that applies the current safe-area insets as padding\n * (default) or margin on the configured edges.\n *\n * Implementation: BG signal + inline style. Sigx auto-tracks `insets.value`\n * access in the render function, so the inset values land in the FIRST\n * layout pass and re-apply reactively on every `safeAreaChanged` event.\n *\n * The previous implementation used `useAnimatedStyle` to drive padding via\n * the MT bridge — but `setStyleProperties` writes that affect layout fire\n * AFTER the first layout pass, and child elements that have already laid\n * out (notably `<scroll-view>`, which captures its frame eagerly) don't\n * reflow. Inline style avoids that timing trap entirely.\n *\n * `edges` defaults to all four sides. Pass a subset (e.g. `['top']`) to\n * leave the unspecified sides unaffected.\n *\n * Must be a descendant of `<SafeAreaProvider>`. If no provider is in scope\n * (test/storybook), `useSafeAreaInsets()` returns `ZERO_INSETS` with a\n * dev-mode warning and SafeAreaView passes through unchanged.\n *\n * @example\n * ```tsx\n * <SafeAreaProvider>\n * <SafeAreaView edges={['top', 'bottom']} class=\"bg-base-100 flex-1\">\n * <PageContent />\n * </SafeAreaView>\n * </SafeAreaProvider>\n * ```\n */\nexport const SafeAreaView = component<SafeAreaViewProps>(({ props, slots }) => {\n const insets = useSafeAreaInsets();\n const edges = props.edges ?? ALL_EDGES;\n const mode = props.mode ?? 'padding';\n\n return () => {\n const i = insets.value;\n // Default to filling the parent (flex-grow + flex-column). Lynx (like\n // React Native) does NOT treat `flex: 1` shorthand the way browsers\n // do — its `flexBasis` resolves to `'auto'`, which sizes to content\n // and collapses the chain. The long-form `flexBasis: 0` is the only\n // reliable way to \"fill remaining space.\" Bottom chrome like\n // `<NavTabBar />` from `@sigx/lynx-daisyui` won't park itself at the\n // screen edge without this.\n const baseStyle: Record<string, string | number> = {\n flexGrow: 1,\n flexShrink: 1,\n flexBasis: 0,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n };\n if (edges.includes('top')) {\n baseStyle[mode === 'padding' ? 'paddingTop' : 'marginTop'] = `${i.top}px`;\n }\n if (edges.includes('right')) {\n baseStyle[mode === 'padding' ? 'paddingRight' : 'marginRight'] = `${i.right}px`;\n }\n if (edges.includes('bottom')) {\n baseStyle[mode === 'padding' ? 'paddingBottom' : 'marginBottom'] = `${i.bottom}px`;\n }\n if (edges.includes('left')) {\n baseStyle[mode === 'padding' ? 'paddingLeft' : 'marginLeft'] = `${i.left}px`;\n }\n return (\n <view\n class={props.class}\n style={props.style ? { ...baseStyle, ...props.style } : baseStyle}\n >\n {slots.default?.()}\n </view>\n );\n };\n});\n"],"mappings":"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"}
|
package/dist/injectable.d.ts
CHANGED
package/dist/injectable.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injectable.d.ts","sourceRoot":"","sources":["../src/injectable.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"injectable.d.ts","sourceRoot":"","sources":["../src/injectable.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,8EAA4D,CAAC"}
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/safe-area-view.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Define } from '@sigx/lynx';
|
|
2
|
-
import type { Edge, SafeAreaMode } from './types
|
|
2
|
+
import type { Edge, SafeAreaMode } from './types';
|
|
3
3
|
export type SafeAreaViewProps = Define.Prop<'edges', Edge[], false> & Define.Prop<'mode', SafeAreaMode, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Slot<'default'>;
|
|
4
4
|
/**
|
|
5
5
|
* Drop-in container that applies the current safe-area insets as padding
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-area-view.d.ts","sourceRoot":"","sources":["../src/safe-area-view.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"safe-area-view.d.ts","sourceRoot":"","sources":["../src/safe-area-view.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAElD,MAAM,MAAM,iBAAiB,GACzB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,GACxC,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;AAI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,YAAY;;EA2CvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-safe-area",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
-
"./
|
|
13
|
+
"./signalx-module.json": "./signalx-module.json"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
17
|
"dist",
|
|
18
18
|
"ios",
|
|
19
19
|
"android",
|
|
20
|
-
"
|
|
20
|
+
"signalx-module.json"
|
|
21
21
|
],
|
|
22
22
|
"keywords": [
|
|
23
23
|
"sigx",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@sigx/reactivity": "^0.4.3",
|
|
34
|
-
"@sigx/lynx
|
|
35
|
-
"@sigx/lynx": "^0.
|
|
34
|
+
"@sigx/lynx": "^0.4.0",
|
|
35
|
+
"@sigx/lynx-runtime-internal": "^0.4.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^6.0.3",
|
|
39
39
|
"vite": "^8.0.12",
|
|
40
|
-
"@sigx/lynx-plugin": "^0.
|
|
41
|
-
"@sigx/lynx-
|
|
42
|
-
"@sigx/lynx-
|
|
40
|
+
"@sigx/lynx-plugin": "^0.4.0",
|
|
41
|
+
"@sigx/lynx-testing": "^0.4.0",
|
|
42
|
+
"@sigx/lynx-runtime-main": "^0.4.0"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
package/src/globals.ts
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed, type Computed, type PrimitiveSignal } from '@sigx/reactivity';
|
|
2
|
-
import { useSafeAreaContext } from './injectable
|
|
3
|
-
import { readGlobalSafeArea } from './globals
|
|
4
|
-
import { ZERO_INSETS, type EdgeInsets, type SafeAreaContextValue } from './types
|
|
2
|
+
import { useSafeAreaContext } from './injectable';
|
|
3
|
+
import { readGlobalSafeArea } from './globals';
|
|
4
|
+
import { ZERO_INSETS, type EdgeInsets, type SafeAreaContextValue } from './types';
|
|
5
5
|
|
|
6
6
|
type InsetsRead = PrimitiveSignal<EdgeInsets> | Computed<EdgeInsets>;
|
|
7
7
|
|
package/src/index.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
// Public API for @sigx/lynx-safe-area.
|
|
2
2
|
|
|
3
|
-
export { SafeAreaProvider, SAFE_AREA_EVENT } from './provider
|
|
4
|
-
export type { SafeAreaProviderProps } from './provider
|
|
3
|
+
export { SafeAreaProvider, SAFE_AREA_EVENT } from './provider';
|
|
4
|
+
export type { SafeAreaProviderProps } from './provider';
|
|
5
5
|
|
|
6
|
-
export { SafeAreaView } from './safe-area-view
|
|
7
|
-
export type { SafeAreaViewProps } from './safe-area-view
|
|
6
|
+
export { SafeAreaView } from './safe-area-view';
|
|
7
|
+
export type { SafeAreaViewProps } from './safe-area-view';
|
|
8
8
|
|
|
9
9
|
export {
|
|
10
10
|
useSafeAreaInsets,
|
|
11
11
|
useSafeAreaSharedValues,
|
|
12
12
|
useSafeAreaFrame,
|
|
13
13
|
useSafeAreaInsetsMT,
|
|
14
|
-
} from './hooks
|
|
14
|
+
} from './hooks';
|
|
15
15
|
|
|
16
|
-
export { useSafeAreaContext } from './injectable
|
|
16
|
+
export { useSafeAreaContext } from './injectable';
|
|
17
17
|
|
|
18
18
|
export {
|
|
19
19
|
readGlobalSafeArea,
|
|
20
20
|
GLOBAL_PROPS_KEY,
|
|
21
|
-
} from './globals
|
|
22
|
-
export type { RawSafeAreaProps } from './globals
|
|
21
|
+
} from './globals';
|
|
22
|
+
export type { RawSafeAreaProps } from './globals';
|
|
23
23
|
|
|
24
|
-
export { ZERO_INSETS } from './types
|
|
24
|
+
export { ZERO_INSETS } from './types';
|
|
25
25
|
export type {
|
|
26
26
|
EdgeInsets,
|
|
27
27
|
Edge,
|
|
28
28
|
SafeAreaMode,
|
|
29
29
|
SafeAreaContextValue,
|
|
30
|
-
} from './types
|
|
30
|
+
} from './types';
|
package/src/injectable.ts
CHANGED
package/src/provider.tsx
CHANGED
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
type MainThread,
|
|
13
13
|
type SharedValue,
|
|
14
14
|
} from '@sigx/lynx';
|
|
15
|
-
import { useSafeAreaContext } from './injectable
|
|
16
|
-
import { readGlobalSafeArea } from './globals
|
|
17
|
-
import type { EdgeInsets, SafeAreaContextValue } from './types
|
|
15
|
+
import { useSafeAreaContext } from './injectable';
|
|
16
|
+
import { readGlobalSafeArea } from './globals';
|
|
17
|
+
import type { EdgeInsets, SafeAreaContextValue } from './types';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The native publisher (iOS `SafeAreaPublisher.swift`, Android
|
|
@@ -195,7 +195,16 @@ function cssVarStyle(
|
|
|
195
195
|
i: EdgeInsets,
|
|
196
196
|
user: Record<string, string | number> | undefined,
|
|
197
197
|
): Record<string, string | number> {
|
|
198
|
+
// Defaults make the provider fill the device viewport and act as a
|
|
199
|
+
// flex-column ancestor. Without these, every Lynx app re-rolls inline
|
|
200
|
+
// `style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}`
|
|
201
|
+
// because `<view>` defaults to auto height and the lynx-tailwind
|
|
202
|
+
// preset (as of 0.4.0) doesn't ship an `h-screen` rule. Consumers can
|
|
203
|
+
// override any of these via `props.style`.
|
|
198
204
|
const base: Record<string, string | number> = {
|
|
205
|
+
height: '100vh',
|
|
206
|
+
display: 'flex',
|
|
207
|
+
flexDirection: 'column',
|
|
199
208
|
'--sat': `${i.top}px`,
|
|
200
209
|
'--sar': `${i.right}px`,
|
|
201
210
|
'--sab': `${i.bottom}px`,
|
package/src/safe-area-view.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { component, type Define } from '@sigx/lynx';
|
|
2
|
-
import { useSafeAreaInsets } from './hooks
|
|
3
|
-
import type { Edge, SafeAreaMode } from './types
|
|
2
|
+
import { useSafeAreaInsets } from './hooks';
|
|
3
|
+
import type { Edge, SafeAreaMode } from './types';
|
|
4
4
|
|
|
5
5
|
export type SafeAreaViewProps =
|
|
6
6
|
& Define.Prop<'edges', Edge[], false>
|
|
@@ -48,23 +48,37 @@ export const SafeAreaView = component<SafeAreaViewProps>(({ props, slots }) => {
|
|
|
48
48
|
|
|
49
49
|
return () => {
|
|
50
50
|
const i = insets.value;
|
|
51
|
-
|
|
51
|
+
// Default to filling the parent (flex-grow + flex-column). Lynx (like
|
|
52
|
+
// React Native) does NOT treat `flex: 1` shorthand the way browsers
|
|
53
|
+
// do — its `flexBasis` resolves to `'auto'`, which sizes to content
|
|
54
|
+
// and collapses the chain. The long-form `flexBasis: 0` is the only
|
|
55
|
+
// reliable way to "fill remaining space." Bottom chrome like
|
|
56
|
+
// `<NavTabBar />` from `@sigx/lynx-daisyui` won't park itself at the
|
|
57
|
+
// screen edge without this.
|
|
58
|
+
const baseStyle: Record<string, string | number> = {
|
|
59
|
+
flexGrow: 1,
|
|
60
|
+
flexShrink: 1,
|
|
61
|
+
flexBasis: 0,
|
|
62
|
+
minHeight: 0,
|
|
63
|
+
display: 'flex',
|
|
64
|
+
flexDirection: 'column',
|
|
65
|
+
};
|
|
52
66
|
if (edges.includes('top')) {
|
|
53
|
-
|
|
67
|
+
baseStyle[mode === 'padding' ? 'paddingTop' : 'marginTop'] = `${i.top}px`;
|
|
54
68
|
}
|
|
55
69
|
if (edges.includes('right')) {
|
|
56
|
-
|
|
70
|
+
baseStyle[mode === 'padding' ? 'paddingRight' : 'marginRight'] = `${i.right}px`;
|
|
57
71
|
}
|
|
58
72
|
if (edges.includes('bottom')) {
|
|
59
|
-
|
|
73
|
+
baseStyle[mode === 'padding' ? 'paddingBottom' : 'marginBottom'] = `${i.bottom}px`;
|
|
60
74
|
}
|
|
61
75
|
if (edges.includes('left')) {
|
|
62
|
-
|
|
76
|
+
baseStyle[mode === 'padding' ? 'paddingLeft' : 'marginLeft'] = `${i.left}px`;
|
|
63
77
|
}
|
|
64
78
|
return (
|
|
65
79
|
<view
|
|
66
80
|
class={props.class}
|
|
67
|
-
style={props.style ? { ...props.style
|
|
81
|
+
style={props.style ? { ...baseStyle, ...props.style } : baseStyle}
|
|
68
82
|
>
|
|
69
83
|
{slots.default?.()}
|
|
70
84
|
</view>
|
|
File without changes
|