Conversation
…ange Core enables edge-to-edge once, in onActivityCreated, with the auto system bar style. The activity survives a dark-mode switch (uiMode is a handled configuration change), so the status and navigation bar icons keep the appearance chosen for the previous theme: dark icons over the app's dark canvas after switching to night mode. Add `refreshEdgeToEdge`, which re-applies the activity's styling with any colors or handler registered through the existing setters, and call it from the Android application when the system appearance changes.
Five defects in how androidOverflowEdge distributes window insets, all reproduced on a real Android 16 device (SM-A536E, SDK 36): - `ignore` was missing from the edge map, so a stacked value like `ignore,bottom` silently degraded to plain `bottom`. - parseEdges returned null for any value resolving to none (`none,none`), and null means "leave the view alone" - so the property became a no-op that stranded the view on whatever edges it had before. - A view switched back to `ignore` never gave back the padding it had baked in: the listener stays installed but passes insets straight through from then on, so nothing undid the earlier pass. - setPadding re-added only edgeInsets.bottom, while the inset pass had applied max(navigation bar, ime). Any setPadding while the keyboard was open collapsed the keyboard gap. - Removing one androidOverflowInset subscriber tore down the native listener for all of them, and disposeNativeView left insetListenerIsSet true, so a recycled native view never got the listener back and the event went quiet after navigating away and returning. Also drops the shortcut that returned WindowInsetsCompat.CONSUMED when a pass reported no system bars: it skipped the padding reset and never notified `dont-apply` listeners when the bars went away. parseEdges moves to its own internal module so it can be unit tested without the whole view stack, and org.nativescript.widgets.d.ts picks up OverflowEdgeIgnore, getEdgeInsets, getImeInsets and setInsetListener.
Seven pages under pages/ete, reachable from the ete/hub entry on the main
page. Each one is readable from a screenshot alone: a shared colour legend
means the ring you see around the content IS the padding the view applied,
so a wrong inset shows up as a wrong-sized band rather than a number
someone has to go looking for.
- edges walks every androidOverflowEdge value, including the stacked
`none,none` and `ignore,bottom` regressions
- nested dont-apply parent whose JS handler rewrites and consumes the
insets, with a child that pads by what survived
- ime keyboard insets, and setPadding while the keyboard is open
- lifecycle the ignore reset sequence, and whether the inset event still
fires after the native view has been recycled
- modals fullscreen vs floating modal windows
- bars statusBarStyle, bar colours, and live system theme switches
- scroll the everyday pattern: list scrolling under the navigation bar
Pages that let the probe overflow keep their controls out of the
navigation bar's reach - a bottom-pinned button there is not tappable.
The bar icon appearance is chosen from the theme when the style is applied, and the activity survives a dark mode switch, so without this the bars keep the old theme's icons until the app restarts. Confirmed on an Android 16 device before merging: a live switch to light mode left the navigation bar dark-on-light-icons, while a fresh start in light mode got it right.
Insets came from Type.systemBars() alone, which leaves the display cutout out. In portrait the two coincide and nobody notices, but rotate the device and the camera moves to an edge that has no bar - so `none`, which reads as "keep my content clear of the system UI", let content slide under it. `cutout` appends to any existing value (`none,cutout`, `top,cutout`) and folds displayCutout() into the insets being distributed. It is a modifier rather than an edge: the bit is masked off before anything asks which edges were requested, so it composes with the whole vocabulary instead of multiplying it. Whatever is consumed of the cutout is withheld from children too, so a nested view that also asked for it does not apply it twice. Left opt-in rather than made the default: folding the cutout in everywhere would silently add padding to landscape layouts that have already worked around this. Measured on an Android 16 device in landscape, where systemBars is 0,84,0,42 and the cutout is 88,0,0,0: `none` pads 0,84,0,42 and `none,cutout` pads 88,84,0,42.
…ange refreshEdgeToEdge re-styles the activity's window, but a fullscreen modal has its own dialog window styled separately when it opens. Nothing re-applied that one, so a modal open across a dark-mode switch kept the bar icons of whichever theme was active when it was shown - white icons on a light navigation bar, and the reverse switching back. The dialog fragment now watches its own configuration changes and re-applies when the night mode bit actually moves, so the cost is one comparison per configuration change rather than a restyle on every rotation. Verified on an Android 16 device with a fullscreen modal open across a switch in both directions.
…adding Adds the css env() function with the css-env-1 safe-area-inset-* and safe-area-max-inset-* variables on iOS and Android, resolvable from stylesheets, inline styles and JS/TS writes, and re-evaluated when the insets change. - SafeArea (@nativescript/core/safe-area) owns the window insets. - overflowSafeArea / overflow-safe-area hands edges to the author. - A padding that names its own edge's inset takes that edge over: the framework stops insetting it on the view, and descendants see it as consumed. - env(ns-safe-area-inset-*) and View.getRemainingSafeAreaInsets report the inset still left at a view. - safe-area-padding: padding plus the remaining inset on each edge.
|
View your CI Pipeline Execution ↗ for commit 5e8a564
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
|
I will say I dont think this is the right implémentation for safe area. |
|
There is a JS API you can use import { SafeArea } from '@nativescript/core';
console.log(SafeArea.insets.bottom); // e.g. 34 (dip)
SafeArea.on('insetsChanged', ({ insets }) => {
// push into a store, recompute, etc.
}); |
|
@triniwiz did not see it, missed it! |
| view._batchUpdate(() => { | ||
| for (const [p, v] of converter(value)) { | ||
| this[p.name] = v; | ||
| // Invalid at computed-value time unsets every longhand. |
There was a problem hiding this comment.
@triniwiz I'm worried with this hardcoded padding handling.
I'd rather for ShorthandProperty class not to be tied to padding or any other property like this and have a general api that "also" let us handle padding instead.
There was a problem hiding this comment.
I only added this for sugar it can be removed
PR Checklist
Stacked on #11434 (Android edge-to-edge stabilization), which this builds on. The base is that branch so the diff shows only this change; retarget to
mainonce #11434 lands.What is the current behavior?
env()is not understood anywhere.padding-bottom: env(safe-area-inset-bottom), the most common line of safe-area CSS on the web, reachesLength.parseand the declaration is dropped. Safe-area layout is done withiosOverflowSafeArea/iosIgnoreSafeAreaon iOS,androidOverflowEdgeon Android, or computed in anandroidOverflowInsethandler. None of these are in the cascade, and when the framework's inset and the author's padding both apply, the edge is padded twice.calc()andvar()written from JS/TS (view.style.width = 'calc(50% - 10dip)') also reach the converter unevaluated.What is the new behavior?
Implements NativeScript/rfcs#55.
env()follows css-env-1: name, indices, fallback, invalid at computed-value time, and substitution beforevar()so an unread fallback cannot invalidate a declaration. It works in stylesheets, inline styles, shorthands and JS/TS writes, and values re-evaluate when the insets change.Variables:
safe-area-inset-*andsafe-area-max-inset-*: window scoped, as in the spec.ns-safe-area-inset-*: what is still left at the view after its ancestors, for nested pages and frames.registerCssEnvironmentVariablelets plugins add their own.SafeArea(@nativescript/core/safe-area) owns the window insets in dip, with aninsetsChangedevent. iOS reads the key window and refreshes fromviewSafeAreaInsetsDidChange; Android reads the rootWindowInsetsCompat(system bars and cutout, no IME) through a non-consuming listener on the decor view.overflowSafeArea/overflow-safe-area(auto | none | all | <edge>+) is the cross-platform switch that hands edges to the author, the NativeScript form ofviewport-fit=cover.auto, the default, keeps each platform's current behavior.androidOverflowEdgewins where both are set, with a trace warning.Smart padding: a
padding-*whose value names its own edge's inset takes that edge over. The framework stops insetting it on that view (iOS zeroes it ingetSafeAreaInsets; Android consumes it in theLayoutBaseflags without padding), and descendants see it as consumed. The footer above needs nooverflow-safe-area. Only the edge's own variable counts, and a local write overrides the cascade per edge.safe-area-paddingtakes the same 1 to 4 values aspaddingand adds the remaining inset to each edge:Opt-in consumption tracking (
SafeArea.trackConsumption, off by default) letsns-safe-area-inset-*also use UIKit's own per-view propagation on iOS, which sees the layouts that inset without declaring it.Verification
env()grammar and substitution, theSafeAreastore, the edge parser, the consumption chain, smart padding, and the shorthand (css, js,var(), invalid values, re-evaluation, and a regression test for a re-evaluation loop).tsc --noEmitonpackages/coreis clean.ns-safe-area-inset-bottomunder an insetting host adds 0.getSafeAreaInsets. The page's host is a GridLayout, which already drops the bottom inset and doubles its own bottom padding on iOS: a plain34padding also gives a 68dip gap. That is pre-existing, listed as unresolved in the RFC, and not addressed here.Behavior changes
No existing layout moves unless it opts in:
env()was a dropped declaration, andoverflowSafeAreadefaults toauto. The one change on an existing path is that a JS/TS style write containingcalc(,var(orenv(is now evaluated instead of reaching the converter as raw text.