Commit 291b8cf
fix(forms): floating form offset clamping + data-klaviyo-device device info (#443)
* fix(forms): clamp floating form size to honor offsets
Bring Android floating-form offset handling in line with iOS. Offsets
(plus safe-area insets) are treated as the margin from the screen edge
and take priority over the requested form size: if form dimensions plus
margins would exceed the screen, the form now shrinks to fit. If it
already fits, margins anchor it away from the edge without shrinking.
- Introduce calculateLayoutParams() that folds safe-area + user offsets
into available width AND height, clamps requested dims via min(), and
returns gravity-relative x/y consistent with Android's WindowManager
model.
- Respect left/right offsets for horizontally-centered positions (TOP,
BOTTOM, CENTER) and top/bottom offsets for CENTER by shifting toward
the smaller margin (matches iOS calculateFrame semantics).
- FULLSCREEN continues to ignore offsets and fill the screen.
- Update calculateFormBottomGap() to account for the asymmetric-margin
shift on CENTER and for the clamped form height, keeping the
keyboard-shift overlap math correct.
- Remove the now-dead FormPosition.isHorizontallyCentered() helper.
- Expand FloatingFormWindowTest coverage for clamping, asymmetric
centering, safe-area + offset interaction, and the FULLSCREEN
short-circuit.
* fix(forms): clamp form bottom gap to zero
Forms taller than the available screen area (or CENTER with heavily
asymmetric margins) could produce a negative calculateFormBottomGap
value, which in turn over-shifted the flyout when the keyboard opened.
Floor the gap at zero so keyboard-shift math stays well-defined.
* feat(forms): expose device info to onsite JS via data-klaviyo-device
Adds a `data-klaviyo-device` attribute on the in-app forms template head
that publishes screen dimensions, safe-area insets, orientation, and
device pixel ratio using CSSOM conventions. This lets onsite-in-app
compute flexible-form dimensions at HTML parse time without querying
potentially-stale `window.screen.*` before view-hierarchy attachment.
The attribute is re-published whenever insets change (via the existing
`setOnApplyWindowInsetsListener`) and on orientation changes (via the
presentation manager's `ConfigurationChanged` handler), so onsite stays
in sync with the device state.
* fix(forms): harden device-info push thread-safety and test coverage
* feat(forms): rename layout offsets key and add addSafeAreaInsetsToOffsets flag
Rename the formWillAppear wire key `margin` to `offsets` to match the
Android internal naming. Read `offsets` first and fall back to `margin`
for backward compatibility with older onsite payloads, emitting a
one-time verbose deprecation log the first time the fallback is hit.
Add a new optional `addSafeAreaInsetsToOffsets` flag (default true) that
lets onsite opt out of the SDK's safe-area handling. When false, the SDK
treats offsets as absolute distances from the screen edge and skips safe
area entirely for both position math and available-space clamping, so
onsite is fully responsible for any safe-area inset it wants to apply.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forms): defer device-info push to next resumed activity to avoid rotation lag
ConfigurationChanged fires before the old activity is destroyed, so
Display.rotation/rootWindowInsets still reflect the prior orientation
when read from Registry.lifecycleMonitor.currentActivity at that moment.
Pushing device info then left `data-klaviyo-device` one rotation behind
on every subsequent rotation.
Replace the immediate pushDeviceInfo() call in onConfigurationChanged
with a one-shot ActivityObserver that waits for the next Resumed event
(when the new activity's Display metrics are fresh), with a safety
timeout to unregister if the new activity never resumes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forms): tighten device-info push timeout from 5s to 1s
Rotation-to-Resumed is typically sub-300ms. 5s was overspecified and meant
stale attribute data could linger for 5s in the pathological destroyed-
without-replacement path. 1s comfortably exceeds real recreation time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(forms): clarify device-info observer skips clearTimers intentionally
BugBot flagged that clearTimers() does not reset deviceInfoPushObserver /
deviceInfoPushTimeout. That is deliberate — the data-klaviyo-device
attribute should stay fresh on the preloaded webview whether or not a
form is presenting. dismiss() does not destroy the webview, and
pushDeviceInfo already guards against a null webview.
Also remove the diagnostic console-log snippet from the HTML template.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(forms): prefer Activity.display on API 30+; silent pushDeviceInfo null branch
- DeviceInfo.kt: replace the blanket @Suppress(DEPRECATION) on
windowManager.defaultDisplay with a proper version split. Uses the
non-deprecated Activity.display on API 30+ (also activity-scoped for
multi-display correctness) and falls back to the deprecated API only
on API 23-29 where there's no alternative.
- KlaviyoWebViewClient.kt: switch pushDeviceInfo to block body and drop
the verbose log on the null-webview branch. Null webview is an
expected idle state (preload / post-destroy / between register &
unregister), not an error — logging on every rotation while idle is
noise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(forms): drop JS escape layer; inject device info via JSON.stringify
Instead of embedding the JSON payload inside a single-quoted JS string
literal and escaping single quotes / backslashes, embed the raw JSON as
a JS object expression and wrap with JSON.stringify at runtime. JSON is
a subset of JS, so the engine parses the object literal natively and
then stringifies it back for the attribute value.
Removes the jsEscape extension and its two tests — no longer needed.
The initial template-replacement path is unchanged (HTML-attribute
context was never affected by JS escaping).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(forms): drop legacy margin wire-key fallback now that onsite is migrated
Onsite has fully migrated to the 'offsets' wire key in production — no
remaining payloads will emit the legacy 'margin' key. Remove the
fallback, the one-shot AtomicBoolean deprecation log, and the test-only
reset helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(forms): rename marginX locals to offsetX for naming consistency
Local variables in FloatingFormWindow.calculateLayoutParams still used
the old margin terminology. Renamed to offset to match the wire key and
the Offsets data class. Pure rename — no behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(forms): source device-info bounds from currentWindowMetrics on API 30+
Align DeviceInfoProvider with FloatingFormWindow — both now read window
dimensions from Activity.windowManager.currentWindowMetrics.bounds on
API 30+, falling back to resources.displayMetrics on older APIs. Kills
a minor inconsistency where device-info injection and offset math could
in theory report different 'screen' sizes in multi-window scenarios.
Empirically the two paths produce the same values on modern Android
phones in both full-screen and split-screen; this change is about using
the canonically-correct API rather than fixing an observed regression.
DeviceInfo.from() signature changes: takes screenWidthPx/Height/density
primitives instead of a full DisplayMetrics object. Pure-function unit
tests simplify accordingly (DisplayMetrics helper no longer needed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 1aa33a4 commit 291b8cf
12 files changed
Lines changed: 1154 additions & 307 deletions
File tree
- sdk/forms/src
- main
- assets
- java/com/klaviyo/forms
- bridge
- presentation
- webview
- test/java/com/klaviyo/forms
- bridge
- presentation
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
Lines changed: 202 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
Lines changed: 13 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | 19 | | |
30 | 20 | | |
31 | 21 | | |
| |||
147 | 137 | | |
148 | 138 | | |
149 | 139 | | |
150 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
151 | 148 | | |
152 | 149 | | |
153 | 150 | | |
| |||
162 | 159 | | |
163 | 160 | | |
164 | 161 | | |
165 | | - | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
166 | 165 | | |
167 | 166 | | |
168 | 167 | | |
169 | 168 | | |
170 | 169 | | |
171 | | - | |
| 170 | + | |
| 171 | + | |
172 | 172 | | |
173 | 173 | | |
174 | 174 | | |
| |||
0 commit comments