Skip to content

feat: add htmlDir and safeAreaInsetBottom props, and collapse hidden WebView on Android - #134

Open
carloseustaquio wants to merge 2 commits into
ketch-com:mainfrom
carloseustaquio:fix/hidden-webview-rtl-and-insets
Open

feat: add htmlDir and safeAreaInsetBottom props, and collapse hidden WebView on Android#134
carloseustaquio wants to merge 2 commits into
ketch-com:mainfrom
carloseustaquio:fix/hidden-webview-rtl-and-insets

Conversation

@carloseustaquio

@carloseustaquio carloseustaquio commented Jul 30, 2026

Copy link
Copy Markdown

Description of this change

We run this SDK in a production React Native app serving both LTR and RTL locales. We have been carrying variants of the changes below as a patch-package patch against 0.21.2 and would like to upstream them so we (and others) can drop the patch. Existing behavior is unchanged unless the new opt-in props are used, apart from the two small rendering fixes described in 1 and 4.

1. Fix: hidden WebView blocks all touches on Android

The SDK keeps the WebView container mounted at all times so the consent logic keeps running, and relies on opacity: 0 + pointerEvents: 'none' while no experience is shown. On Android that is not enough: the native android.webkit.WebView handles touches itself and does not honour a React-level parent pointerEvents: 'none', so the invisible full-screen WebView swallows every tap and the app underneath becomes unresponsive.

The hidden style now also collapses the container to 0x0 (width: 0, height: 0, overflow: 'hidden'). The WebView stays mounted — the JS engine, injectJavaScript and onMessage all keep working — but it has no area to receive touches. When an experience is shown, styles.shown expands it back to full screen as before.

2. Feature: RTL enablement via a new htmlDir prop

htmlDir?: 'ltr' | 'rtl' | 'auto';

The value is applied as the dir attribute on the WebView document root (<html dir="...">). Today the internal bootstrap HTML hardcodes <html> with no dir, so there is no way for direction-sensitive styling inside the experiences to ever activate, regardless of the device or app locale.

Setting dir="rtl" on the root:

  • enables the browser's natural right-to-left bidi layout inside the experience,
  • activates Tailwind's RTL support — the rtl:/ltr: variants compile to &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *), so they key off exactly this attribute — and
  • makes [dir='rtl']-scoped rules in a cssOverride take effect.

Usage:

<KetchServiceProvider
  organizationCode="..."
  propertyCode="..."
  identities={{ ... }}
  htmlDir={isRtlLocale ? 'rtl' : undefined}
>

Note for maintainers: the experiences currently use physical utilities (e.g. ketch-text-left, ketch-ml-auto), which pin the layout to LTR even in an RTL document. We would love to see the web experience components adopt Tailwind's rtl:/ltr: variants or logical properties/utilities (ms-*/me-*, text-start/text-end) for full native mirroring — this prop provides the document-level dir signal those rely on. We deliberately did not bundle an RTL override stylesheet in this SDK, since the mirroring belongs in the components themselves. In the interim, consumers can layer small [dir='rtl']-scoped tweaks via cssOverride (as we do in production today).

3. Feature: safeAreaInsetBottom prop for edge-to-edge Android apps

The current Android bottom padding heuristic, (screen.height - window.height) / 2, returns 0 in edge-to-edge apps (where screen == window, the default on modern React Native / Android 15), which leaves the bottom of the experience rendered underneath the gesture/navigation bar. The new optional prop lets the host app pass the real inset, with the heuristic kept as the fallback:

import { initialWindowMetrics } from 'react-native-safe-area-context';

<KetchServiceProvider safeAreaInsetBottom={initialWindowMetrics?.insets.bottom} ... >

4. Fix: reset default body margin in the internal WebView document

The internal HTML document had no margin: 0 on body, so the browser default 8px margin offset the experience content.

Why is this change being made?

  • Chore (non-functional changes)
  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How was this tested? How can the reviewer verify your testing?

  • Locally ran the same steps as CI in package/: npm run lint, npm run typecheck and npm run prepare (bob build) all pass, plus jest (11 tests, including new coverage for injectCssIntoHtml and injectDirIntoHtml in __tests__/assets.test.ts).
  • The equivalent changes have been verified in our production app (as a patch-package patch of the published package): taps reach the app while the experience is hidden on Android, the dir="rtl" document root correctly activates our [dir='rtl']-scoped styles on RTL locales, and the experience no longer renders under the Android gesture bar in edge-to-edge mode.

Related issues

None filed — happy to open separate issues for the Android touch/inset bugs if you prefer to track them individually.

Checklist

  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have evaluated the security impact of this change, and OWASP Secure Coding Practices have been observed. htmlDir only accepts the three literal values.
  • I have informed stakeholders of my changes.

…WebView on Android

- Collapse the always-mounted hidden WebView container to 0x0 so it cannot
  swallow touches on Android while no experience is shown
- Add htmlDir prop: sets dir on the WebView document root and, for 'rtl',
  injects a bundled RTL stylesheet that mirrors the Ketch experiences
- Add safeAreaInsetBottom prop so hosts can pass the real Android bottom
  inset in edge-to-edge apps where the Dimensions heuristic returns 0
- Reset the default body margin of the internal WebView document

Co-authored-by: Cursor <cursoragent@cursor.com>
@carloseustaquio
carloseustaquio requested review from a team as code owners July 30, 2026 15:31
…t in the web experiences

htmlDir now only sets the dir attribute on the WebView document root.
That attribute is what activates Tailwind's rtl:/ltr: variants (which
compile to [dir="rtl"]/[dir="ltr"] ancestor selectors), so the mirroring
styles themselves belong in the Ketch web experience components rather
than shipped as an override stylesheet in this SDK.

Co-authored-by: Cursor <cursoragent@cursor.com>
@carloseustaquio carloseustaquio changed the title feat: add RTL support, safeAreaInsetBottom prop, and collapse hidden WebView on Android feat: add htmlDir and safeAreaInsetBottom props, and collapse hidden WebView on Android Jul 30, 2026
@jboileau99

Copy link
Copy Markdown
Collaborator

@carloseustaquio thank you for these contributions! We've completed some internal testing and all looks good.

@jboileau99

Copy link
Copy Markdown
Collaborator

@carloseustaquio if you're able to resubmit these changes with verified commits we'll be all set to merge. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants