@onekeyfe/react-native-native-list 6.7.0-alpha.375 → 6.7.0-alpha.382

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.
Files changed (116) hide show
  1. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +14 -1
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +240 -11
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +7 -4
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +6 -3
  5. package/android/src/main/java/com/onekey/nativelist/NativeListButtonGroupRenderer.kt +102 -0
  6. package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +12 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +87 -7
  8. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +177 -1
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +43 -14
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +25 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +33 -2
  12. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +22 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +2 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +2 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +3 -2
  16. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +69 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +55 -1
  18. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1912 -107
  19. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListPressPolicyTest.kt +1 -0
  20. package/docs/ROW_TEMPLATES.md +60 -9
  21. package/docs/SPEC.md +450 -5
  22. package/docs/STYLE_SPEC.md +6 -0
  23. package/ios/HybridNativeList.swift +21 -2
  24. package/ios/NativeListAccessoryStack.swift +227 -39
  25. package/ios/NativeListActivityDetailsView.swift +18 -8
  26. package/ios/NativeListActivityRenderer.swift +8 -2
  27. package/ios/NativeListButtonGroupRenderer.swift +89 -0
  28. package/ios/NativeListDataRowRenderer.swift +155 -27
  29. package/ios/NativeListDesignAssets.swift +3 -0
  30. package/ios/NativeListIdentityRenderer.swift +166 -30
  31. package/ios/NativeListLeadingVisual.swift +49 -14
  32. package/ios/NativeListMessageRenderer.swift +34 -1
  33. package/ios/NativeListModels.swift +42 -2
  34. package/ios/NativeListRendererRegistry.swift +1 -0
  35. package/ios/NativeListResolvedText.swift +2 -1
  36. package/ios/NativeListRowHost.swift +14 -0
  37. package/ios/NativeListSectionHeaderRenderer.swift +88 -0
  38. package/ios/RNCNativeListView.swift +1013 -55
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +15 -0
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +4 -0
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +15 -0
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +3 -0
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +15 -0
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +3 -0
  45. package/ios/tests/NativeListActivityPresentationTests.swift +56 -0
  46. package/ios/tests/NativeListLifecycleTests.swift +2720 -3
  47. package/lib/module/NativeList.js +175 -28
  48. package/lib/module/NativeList.web.js +129 -30
  49. package/lib/module/index.js +1 -1
  50. package/lib/module/models.js +1 -0
  51. package/lib/module/selection.js +1 -1
  52. package/lib/module/validation.js +421 -8
  53. package/lib/module/web/NativeListWebEngine.js +507 -19
  54. package/lib/module/web/templates/ActivityRowRenderer.js +8 -7
  55. package/lib/module/web/templates/ButtonGroupRowRenderer.js +47 -0
  56. package/lib/module/web/templates/DataRowRenderer.js +40 -5
  57. package/lib/module/web/templates/IdentityRowRenderer.js +90 -4
  58. package/lib/module/web/templates/MessageRowRenderer.js +18 -2
  59. package/lib/module/web/templates/RowContainerStyle.js +1 -0
  60. package/lib/module/web/templates/RowRendererRegistry.js +3 -0
  61. package/lib/module/web/templates/SectionHeaderRowRenderer.js +36 -2
  62. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  63. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  64. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  65. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  66. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  67. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  68. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  69. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  70. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  71. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  72. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  73. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  74. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  75. package/lib/typescript/src/NativeList.d.ts +1 -1
  76. package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
  77. package/lib/typescript/src/NativeList.types.d.ts +18 -0
  78. package/lib/typescript/src/index.d.ts +2 -2
  79. package/lib/typescript/src/models.d.ts +105 -9
  80. package/lib/typescript/src/validation.d.ts +12 -0
  81. package/lib/typescript/src/web/NativeListWebEngine.d.ts +32 -0
  82. package/lib/typescript/src/web/templates/ButtonGroupRowRenderer.d.ts +14 -0
  83. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +1 -1
  84. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +9 -3
  85. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
  86. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
  87. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
  89. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
  90. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
  91. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
  92. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
  93. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
  94. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
  95. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
  96. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
  97. package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
  98. package/package.json +3 -3
  99. package/react-native-native-list.podspec +1 -0
  100. package/src/NativeList.nitro.ts +2 -0
  101. package/src/NativeList.tsx +313 -44
  102. package/src/NativeList.types.ts +22 -0
  103. package/src/NativeList.web.tsx +204 -48
  104. package/src/index.ts +2 -0
  105. package/src/models.ts +117 -3
  106. package/src/selection.ts +1 -0
  107. package/src/validation.ts +852 -6
  108. package/src/web/NativeListWebEngine.ts +803 -20
  109. package/src/web/templates/ActivityRowRenderer.ts +14 -6
  110. package/src/web/templates/ButtonGroupRowRenderer.ts +58 -0
  111. package/src/web/templates/DataRowRenderer.ts +93 -20
  112. package/src/web/templates/IdentityRowRenderer.ts +150 -5
  113. package/src/web/templates/MessageRowRenderer.ts +23 -7
  114. package/src/web/templates/RowContainerStyle.ts +2 -0
  115. package/src/web/templates/RowRendererRegistry.ts +11 -1
  116. package/src/web/templates/SectionHeaderRowRenderer.ts +47 -4
package/docs/SPEC.md CHANGED
@@ -26,6 +26,232 @@ prevention; there is no automatic fitting or shrinking pass.
26
26
 
27
27
  ## Public API and defaults
28
28
 
29
+ ### Recipient identity and inline warning rows
30
+
31
+ Status: implemented in local source for the Select Address and Swap selectors;
32
+ native device and Web runtime verification remain pending. These fields travel in snapshot/patch JSON
33
+ and do not change the Nitro ABI.
34
+
35
+ An `identity` row may opt into `subtitleLines: 0`, meaning the complete subtitle
36
+ wraps at its measured text-column width without a line cap. Existing omitted,
37
+ one-line and two-line behavior stays unchanged. An identity row with
38
+ `contentHeight: 'auto'` measures its actual title, subtitle, leading visual,
39
+ badges and trailing accessories and grows to fit; this is opt-in and does not
40
+ alter selector or wallet-sidebar presets. An explicit `style.container.height`
41
+ or `row.height` still wins over content measurement, in that order. Content
42
+ height changes with row width, text, style or configured font and may not retain
43
+ a stale prior width's measurement. The existing 4096 UTF-16-code-unit bound per
44
+ text field still applies; zero is not an unlimited data-size allowance.
45
+
46
+ An `identity` row may set `longPressActionKey` to a nonempty action key. A valid
47
+ touch long press on an enabled row emits one `onRowAction` with its current row key,
48
+ action key, `row` anchor source and window press point. An accessibility long-click
49
+ without a physical touch retains the row anchor without a press point. It suppresses the
50
+ ordinary press for that gesture. Motion cancellation, row rebind/recycle,
51
+ disabled state and disposal cancel pending recognition. Trailing accessory
52
+ actions remain independent. Omission preserves existing tap behavior.
53
+
54
+ A `message` row may opt into `bodyLines: 0`; its complete body wraps within its
55
+ existing text area and determines natural height unless an explicit row or
56
+ container height overrides it. Existing omitted and one-to-three-line behavior
57
+ stays unchanged. `style.body.lines`, when supplied, retains its existing
58
+ one-to-three-line precedence. `presentation: 'inlineWarning'` changes only the
59
+ message container border: requested `style.container.borderWidth` and
60
+ `borderColor` draw top and bottom edges at full row width, with no side edges or
61
+ corner radius. Omitted border width draws no border; omitted presentation keeps
62
+ the existing all-edge container style. The caller supplies warning text, icon,
63
+ font, padding, colors and list-width layout; the SDK does not infer risk status.
64
+ All optional fields must clear on rebind/recycle and patch replacement. The
65
+ existing 4096-code-unit body limit applies.
66
+
67
+ An image-backed `LeadingVisual` may set `fallbackTextColor` to a hex
68
+ `#RRGGBB` or `#RRGGBBAA` color. It colors only the visible initials/fallback
69
+ text, including a failed image's fallback state; omission retains the existing
70
+ platform default. It does not alter the visual background, bitmap, icon, or
71
+ overlay colors. Validation rejects malformed colors. Rebind and recycle must
72
+ clear the previous text color so dark/light theme updates cannot leak between
73
+ rows. Consumers own the semantic theme choice (for example inverse text over
74
+ inverse background).
75
+
76
+ iOS measures opt-in identity and unlimited message content before collection
77
+ placement. Android binds text with unlimited native lines and allows its row
78
+ view to measure that content. Web uses wrapping DOM text and measured row
79
+ height. All platforms retain existing explicit-height precedence and existing
80
+ default rows. Focused source tests must cover validation, patching, old-to-new
81
+ reuse, action isolation and height changing with width/text; real iOS, Android
82
+ and Web acceptance must cover narrow addresses, dynamic type/fonts, long press
83
+ versus tap, keyboard interaction, and the horizontal warning border. Source or
84
+ build success alone is not runtime evidence.
85
+
86
+ ### DeFi position row controls
87
+
88
+ Status: implemented in local source, unreleased. Focused validation and Web
89
+ DOM tests pass; native source compilation passes, while target-app compilation
90
+ and device acceptance remain separate gates.
91
+ `buttonGroup` is a vertical data row with `RowBase` identity and a nonempty
92
+ `buttons` array. Each button has a unique nonempty `key`, localized nonempty
93
+ `label`, nonempty `actionKey`, and optional `disabled`, `loading`, `testID` and
94
+ `accessibilityLabel`. It renders the existing DeFi medium block info-outline
95
+ controls: equal-width buttons, 10-point gap, 12-point vertical padding and
96
+ 12-point radius. Every declared action remains visible; the renderer must not
97
+ truncate the array. Loading retains the button's width, disables that button,
98
+ and displays an activity indicator. A disabled row disables every button.
99
+ Activation emits one existing row action with the button's `actionKey` and
100
+ its own source/slot, without activating the row. A full rebind or recycle
101
+ removes prior buttons, loading indicators, labels, test IDs and handlers.
102
+ The SDK does not infer which DeFi actions are allowed or perform transactions.
103
+
104
+ `sectionHeader` may include `leadingBadge` (the existing badge shape),
105
+ with an optional `NativeListTheme.successBackground` used only for its
106
+ `success` tone. Omitting that theme color keeps the old badge default.
107
+ `style.valuePlacement: 'afterTitle'`, and
108
+ `style.titleDecoration: 'none' | 'dashed'`. Missing fields retain the current
109
+ heading layout and decoration. `valueHelp` and a `valuePair` trailing
110
+ accessory's `primaryHelp` share
111
+ `{ actionKey, presentation: 'tooltip' | 'popover', underline?: 'dashed' }`.
112
+ Presentation and underline are independent: the former selects trigger
113
+ semantics, the latter only draws a dashed underline. `tooltip` on Web opens on
114
+ mouse/pen entry and eligible focus, closes on departure/blur, and mouse/pen
115
+ press closes it until departure; touch toggles. It has no native touch target
116
+ or event. `popover` on all platforms toggles only on click/press and does not
117
+ open from hover. These help targets do not activate their row. Presentation
118
+ change, departure, scroll, rebind, replacement by another anchor and disposal
119
+ invalidate the old anchor.
120
+ On Web, the value-pair primary help target is keyboard focusable; focus opens
121
+ tooltip help and Enter/Space activates popover help without pressing the row.
122
+ The consuming app owns Tooltip/Popover content. Missing help fields keep
123
+ the original noninteractive text. All additions use snapshot JSON and do not
124
+ change the Nitro ABI.
125
+
126
+ `valuePair.primaryFontWeight?: 'medium'` applies only to the primary text
127
+ line. The secondary line keeps its existing regular weight. Omitting the field
128
+ preserves each platform's existing value-pair typography; full rebind and
129
+ recycle remove any previous primary-only override. The field travels in the
130
+ existing snapshot JSON and does not change the Nitro ABI.
131
+
132
+ Validation rejects empty or duplicate button keys, empty labels/action keys,
133
+ invalid booleans and help descriptors, and unknown style fields. The list's
134
+ existing row, text and snapshot bounds apply; no
135
+ silent action truncation is permitted. Conformance includes Web DOM events,
136
+ iOS/Android renderer bind/reuse, TypeScript validation, native builds, and
137
+ later real UI checks for help anchors, loading, disabled actions and geometry.
138
+
139
+ ### Segmented value-pair accessory
140
+
141
+ Status: implemented in local source, unreleased. Focused validation and Web DOM
142
+ tests pass; production iOS target compilation passes. Native instrumentation,
143
+ full App builds and device acceptance remain separate gates. A `valuePair`
144
+ trailing accessory retains its required `primary` and `secondary` fallback
145
+ strings. It may also supply optional readonly `primaryTextSegments` and
146
+ `secondaryTextSegments` arrays of existing `ValueTextSegment` objects. Missing
147
+ or empty arrays display the corresponding fallback; nonempty arrays display
148
+ their text runs. Callers provide matching fallback text, but validation does
149
+ not enforce that match.
150
+
151
+ Each array has at most 64 segments, each with string `text` of at most 4096
152
+ characters and optional `style: 'subscript'`. Total text per array is at most
153
+ 4096 characters. Full snapshots and explicit patch replacements reject null,
154
+ malformed arrays, unknown segment keys and other styles. An omitted patch field
155
+ leaves the existing accessory unchanged; replacement without segments clears
156
+ old runs. Subscript retains the existing 60-percent smaller-font convention
157
+ after semantic text styling. Both lines retain their default size, tone, line
158
+ height, right alignment and accessibility text. These fields add no action or
159
+ hit target and travel in existing snapshot JSON without a Nitro ABI change.
160
+
161
+ ### Identity row risk-management controls
162
+
163
+ Status: implemented in source for local consumers, unreleased. Focused validation,
164
+ Web DOM tests, native builds and device acceptance are tracked separately below.
165
+ The optional `identity.titleIcon` is a static, non-actionable icon after the
166
+ title: a 20-point glyph with a 4-point gap, optional tint and accessibility
167
+ label. Its optional Web-only `hoverActionKey` emits an anchored help action
168
+ from the icon alone on mouse/pen hover or touch press; the consumer owns the
169
+ tooltip. Mouse/pen press closes the anchor until pointer leave. Touch press
170
+ toggles the icon anchor. The icon adds no tab stop or separate click action;
171
+ row activation remains unchanged. Departure, scroll, full rebind and destroy
172
+ invalidate the anchor. Omission removes the icon in a full snapshot; omission
173
+ in a patch leaves the existing value unchanged. An explicit replacement icon
174
+ without `hoverActionKey` clears the old hover action. Removing the icon
175
+ requires a full snapshot.
176
+
177
+ On Web, `identity.secondaryTitleIcon` accepts the same descriptor and appears
178
+ after `titleIcon`. Both icons retain independent hover/touch anchors at their
179
+ own glyph rectangles; entering the other invalidates the previous anchor.
180
+ Native renderers ignore this optional Web-only field. Full rebinding to one
181
+ or no icons clears the removed glyph and anchor; explicit icon replacement
182
+ without `hoverActionKey` clears that icon's old hover action. Neither icon
183
+ creates a tab stop or separate click action.
184
+ On Web, clicking either help icon (including its SVG) does not activate the row;
185
+ clicking elsewhere on the row keeps the normal row action.
186
+
187
+ `identity.style.leadingContentMaxWidthRatio`, when given,
188
+ must be finite and within `(0, 1]`; it caps the combined leading visual, gap
189
+ and title content relative to the row's available inner width. Omission keeps
190
+ the existing unrestricted layout. Long titles truncate inside the cap, and
191
+ unused width remains available to trailing controls. The ratio is layout-only,
192
+ never a reuse identity or row-height input.
193
+
194
+ `trailing` accepts an optional `button` accessory with required nonempty
195
+ `label` and `actionKey`, optional `disabled`, `testID`,
196
+ `accessibilityLabel`, and optional hex colors `textColor`,
197
+ `backgroundColor`, `pressedBackgroundColor`, `hoverBackgroundColor`, and
198
+ `focusRingColor`. This single variant has the existing small secondary button
199
+ geometry: medium 14/20 text, 10-point horizontal and 4-point vertical
200
+ padding, a one-point transparent border and a pill radius. Omitted colors use
201
+ the theme's corresponding text and strong-background colors. The button does
202
+ not carry arbitrary React content, a size or variant parameter, or loading
203
+ state. Row or accessory disabled state prevents its action and dims the control
204
+ to 0.4 opacity.
205
+
206
+ Activation emits one existing row-action event with the current row key,
207
+ `trailingAccessory` source and zero-based descriptor slot, without also activating the row.
208
+ The host's existing binding epoch guards recycled controls. Full rebinding
209
+ clears omitted title icons and old button text, visual states, test IDs,
210
+ accessibility values and callbacks, including button-to-icon-to-absent reuse.
211
+ Patches replace fields they explicitly change and leave omitted fields intact.
212
+ iOS and Android use native controls; Web uses a semantic `button` with native
213
+ keyboard activation and disabled semantics. The new fields travel inside the
214
+ existing snapshot JSON and do not alter the Nitro bridge ABI. Consumers own
215
+ localized labels, state updates and any tooltip outside the data row.
216
+
217
+ Acceptance requires snapshot and patch validation, Web action/isolation and
218
+ reuse checks, native renderer tests on production classes, native builds and
219
+ device checks for title placement, cap/truncation, colors, press and recycle.
220
+ No source-only check counts as native runtime evidence.
221
+
222
+ `theme.rowHoverBackground` is an optional Web-only hex color for an enabled
223
+ row's hover background. Omission keeps the existing pressed-background hover
224
+ color. Pressed/active rows continue to use `rowPressedBackground`; native
225
+ renderers ignore the new field. The existing `loadingStrategy: 'skeleton'`
226
+ on a Web leading image displays an animated skeleton while loading, then
227
+ clears it on success, failure, rebind or recycle; other strategies keep their
228
+ existing appearance.
229
+
230
+ Identity leading visuals consume the existing `borderColor` as a one-point/dp
231
+ solid bitmap border when supplied without `borderStyle: 'dashed'`; omission or
232
+ a dashed style clears the solid border on every bind.
233
+ The image is inset by that border width. An overlay may optionally set
234
+ `fontWeight` to one of the existing text weight names
235
+ (`regular | medium | semibold | bold`). This changes only overlay text;
236
+ omission retains the current platform default. Setting and clearing the field
237
+ must survive patching and recycled-view rebinding on all three platforms.
238
+ The existing `leading.fallbackIcon` may optionally set a positive `size` for
239
+ its glyph and a hex `backgroundColor` for the visual's no-image and image-load-
240
+ failure states. A successfully loaded bitmap continues to use
241
+ `leading.backgroundColor`. Omitted fallback fields preserve current rendering.
242
+ Image success, failure and full rebinding must restore the matching background
243
+ and glyph size without carrying values from the previous visual.
244
+ An image overlay may optionally declare `fallbackIcon` with a required glyph
245
+ name and optional tint. It shows only after that overlay image fails, while
246
+ loading retains the overlay's empty background and success shows the bitmap.
247
+ The existing icon accessory accepts only one optional `buttonStyle` value,
248
+ `tertiary`, for the source icon button's 38-point press frame, 24-point glyph,
249
+ six-point padding, one-point border, seven-point visual overhang and pressed
250
+ background. Omission preserves every existing icon accessory layout and action.
251
+ The non-action `RefreshCcwOutline` accessory keeps the source's 16-point glyph
252
+ and frame, with no independent press target. Rebinding it to an actionable
253
+ icon restores the existing icon size and event behavior.
254
+
29
255
  ### iOS 26 fixed top scroll-edge overlay
30
256
 
31
257
  Standalone `NativeList` accepts optional
@@ -192,6 +418,16 @@ section index titles retain their separate development-only
192
418
  no-surrounding-whitespace assertion.
193
419
  The 256-code-unit key limit is a development assertion, not a production limit.
194
420
 
421
+ Each iOS and Android host retains an index from accepted logical row keys to
422
+ row positions. An accepted complete snapshot reuses that index only when its
423
+ generation and ordered `(key, type)` sequence match the prior accepted
424
+ snapshot; otherwise it replaces the index. Reorder commits rebuild it and
425
+ disposal clears it. Patch batches reuse the index and never include retiring
426
+ presentation rows in it. They validate the whole batch against the current
427
+ accepted row models before changing config or presentation; invalid batches
428
+ leave both unchanged. iOS updates only changed accepted entries in its
429
+ presentation lookup after a valid patch, preserving any retiring rows.
430
+
195
431
  ### Validation environments and rejected input
196
432
 
197
433
  Development builds (`__DEV__ === true`) run the complete snapshot and patch
@@ -208,6 +444,34 @@ arguments; this protects the runtime's argument limit.
208
444
  The component and Web engine contain input validation failures in both modes:
209
445
  initial invalid input renders an empty list; rejected later snapshots or patch
210
446
  batches preserve the last accepted state and are not dispatched to native.
447
+ On iOS and Android, the React wrapper captures caller data before validation
448
+ and owns an immutable accepted snapshot. Later caller mutation cannot rewrite
449
+ that accepted state. Imperative selection keys are captured on entry. Eligible
450
+ value-only `identity` and `dataRow` patches validate their complete changed
451
+ rows; other patches and snapshots without this private ownership use the
452
+ existing complete-snapshot validation path. This does not change exported
453
+ validators or Web behavior.
454
+ An `undefined` value in an eligible field stays on the changed-row path only
455
+ when that field was already `undefined`; clearing a present value uses the
456
+ complete-snapshot path.
457
+
458
+ `planNativeListPatches(previous, next)` is an explicit, pure public helper for
459
+ callers that own two valid, immutable snapshots. It returns a readonly empty
460
+ batch for equal snapshots, value-only `identity` or `dataRow` row patches when
461
+ only eligible values change, and `undefined` when the caller must deliver a
462
+ complete snapshot. It never dispatches, accepts or validates an update, and
463
+ does not deep-copy, freeze or cache caller input. Returned patches may refer
464
+ to `next`'s immutable values; the caller retains baseline and commit ownership.
465
+ The component does not automatically invoke this planner for prop snapshots. Any
466
+ change outside `rows`, including generation, selection, layout, list style, empty state or
467
+ footer, requires a complete snapshot. Row keys, order and types must match.
468
+ `identity` may patch `subtitle`, `subtitleSegments` and `trailing`; `dataRow`
469
+ may patch non-asset columns and `trailing` while preserving the first asset
470
+ column. Changes to other row fields or to the presence of optional patch
471
+ fields require a complete snapshot. Comparison is by JSON value, ignores
472
+ object property order and properties whose value is `undefined`, and keeps
473
+ array order significant.
474
+
211
475
  Rejection emits a diagnostic without including row keys, text or image URLs.
212
476
  Explicit calls to the exported validation functions still throw on rejected
213
477
  input, so development tooling can inspect the full contract failure. Rejection
@@ -721,6 +985,49 @@ leaked audio/player resources. Static API checks do not prove these cases.
721
985
 
722
986
  ## Native container slots (source implementation, runtime verification pending)
723
987
 
988
+ ### Optional scrolling sidecar (source implemented; native runtime verification pending)
989
+
990
+ `listSidecar` and `sidecarLayout: { width, gap, maxContentWidth }` are an
991
+ optional pair for a vertical linear list. Omission preserves the existing
992
+ single-column layout. All three values are finite non-negative logical pixels;
993
+ `width` and `maxContentWidth` must be positive and `width + gap` must be less
994
+ than `maxContentWidth`. The list remains the sole scroll owner. At viewport
995
+ width `V`, the content band is centered with width `min(V, maxContentWidth)`;
996
+ the sidecar occupies the logical end `width`, separated from the logical start
997
+ data/slot column by `gap`. In RTL the sidecar is physically left and the list
998
+ is physically right. Header, rows, empty slot and footer use the start column. The sidecar
999
+ starts at the content top, scrolls on the same native frame as rows, and may
1000
+ contain interactive React content. Its root is retained until Fabric unmount;
1001
+ it does not become a row, affect row keys, indices, selection or prefetch. The
1002
+ left RecyclerView/UICollectionView still lays out only viewport-near rows.
1003
+
1004
+ The scrolling extent is at least the greater of the left column including its
1005
+ footer and the sidecar's measured bottom. A taller sidecar extends the tail
1006
+ after the left footer; it must not push that footer below its normal position.
1007
+ `scrollToEnd`, refresh and structural transition offset clamping use this
1008
+ combined extent. A sidecar height or breakpoint change preserves an in-range
1009
+ offset; an out-of-range offset is clamped by the existing shrink correction.
1010
+ Rapid row transitions retain their existing one-shot id and anchor semantics.
1011
+ Native sidecar position follows the native scroll offset directly, without a
1012
+ per-frame JS bridge. A drag beginning over interactive sidecar content must
1013
+ still scroll the same list; taps retain the content's own actions.
1014
+ On iOS, React container slots are clipped below the enclosing Pager's fixed overlay and sticky region using its existing owner-scoped native viewport callback. Scrolling, rotation, owner removal, and recycling update or release those masks without JS frames. On iOS the sidecar host is a narrow UICollectionView subview in content
1015
+ coordinates. On Android the narrow overlay follows the RecyclerView's measured
1016
+ logical offset; after a vertical touch slop it cancels the React press and
1017
+ forwards the original touch stream to the list's SwipeRefreshLayout. The
1018
+ Android tail spacer derives from explicit/measured row and slot extents, not
1019
+ RecyclerView's variable-height scrollbar estimate.
1020
+
1021
+ Web uses the same measured two-column geometry inside its current owned or
1022
+ explicit external scroll host, with DOM virtualization limited to the left
1023
+ rows; the sidecar is a single React slot. Callers already placing a Web sidebar
1024
+ as a sibling may omit the new pair there. Horizontal/grid configurations with
1025
+ a sidecar are rejected. Acceptance requires iOS and Android wide Home scrolling,
1026
+ right-column drag and action, short/empty assets, collapse animation while
1027
+ scrolled, refresh, resize across the breakpoint without list remount, and
1028
+ thousands of rows remaining bounded to the native recycler viewport. Source,
1029
+ type and unit checks alone do not establish device acceptance.
1030
+
724
1031
  `listHeader`, `listEmpty` and `listFooter` accept React nodes on iOS and Android
725
1032
  vertical lists only. They are optional and default to absent. They are common
726
1033
  container capabilities, not row templates, and MUST NOT be used for per-item
@@ -747,9 +1054,10 @@ width React lays it out at. Android's narrow-screen row scale and section-index
747
1054
  apply to data rows only. Slot React trees own their internal layout and
748
1055
  business actions; NativeList owns their scroll placement and lifecycle.
749
1056
 
750
- The wrapper mounts exactly three non-collapsible slot roots and reports measured
751
- heights through `containerSlotHeightsJson` in header/empty/footer order. Native
752
- validates three finite, non-negative heights. Slot updates MUST NOT alter data-row
1057
+ The wrapper mounts four non-collapsible slot roots and reports measured
1058
+ heights through `containerSlotHeightsJson` in header/empty/footer/sidecar order.
1059
+ The sidecar root has zero reserved height when absent. Native validates four
1060
+ finite, non-negative heights. Slot updates MUST NOT alter data-row
753
1061
  keys or public row indices. No per-scroll-frame JS position updates are used.
754
1062
  iOS mounts each slot root inside a list-owned host view in UICollectionView and
755
1063
  reserves native layout space. The host owns the slot's vertical position and clips to
@@ -760,6 +1068,102 @@ data adapter; adapter binding indices stay local while layout-manager positions
760
1068
  are explicitly translated. Slots span all grid columns, do not select/reorder,
761
1069
  and do not emit row actions, visible-row indices, or image-prefetch work.
762
1070
 
1071
+ ### Disclosure slots and settlement (source implemented; limited runtime verification)
1072
+
1073
+ Native callers may opt in with `snapshot.disclosure: { expanded: boolean,
1074
+ expandedTrailingInset?: number }`, `listExpandedContent`, and
1075
+ `listStructureToggle`. `expanded` is the accepted target state independent of
1076
+ row count, the expanded slot's measured height, and the one-shot structural
1077
+ transition command. `expandedTrailingInset` defaults to zero and must be a
1078
+ finite, non-negative number of logical pixels. Without `disclosure`, the
1079
+ existing four-slot and structural-transition behavior is unchanged. On Web,
1080
+ `C` and `T` occupy the existing single React footer band after the list rows,
1081
+ followed by the expanded trailing inset and the caller's footer `F`. `C` is
1082
+ mounted on first expansion and may remain mounted for that scope after collapse.
1083
+ Without an accepted `disclosure`, supplied `C` and `T` are hidden and inert.
1084
+ At the collapsed endpoint `C` has zero layout height and is hidden from pointer,
1085
+ keyboard, and accessibility interaction; `T` remains mounted and interactive
1086
+ through rapid reversals. Web does not use the native settlement callback to
1087
+ release `C`. The Web engine continues to measure that one footer band and to
1088
+ own its existing structural-tail animation and external scroll-host correction;
1089
+ its per-frame presentation need not match the native clip geometry. Desktop
1090
+ and extension retain their existing structural presentation.
1091
+
1092
+ The native slot order is 0 header, 1 empty, 2 existing footer `F`, 3 sidecar,
1093
+ 4 expanded content `C`, and 5 stable structure toggle `T`; the optional iOS
1094
+ top overlay follows at index 6. The native wrapper reports six measured slot
1095
+ heights in that order. This slot-index ABI requires the package's JavaScript
1096
+ wrapper and native binary to be integrated from the same version; the new
1097
+ native host does not reinterpret an old four-slot wrapper's index 4 as an
1098
+ overlay. The disclosure layout in the left list column is data
1099
+ rows `R`, then `C`, then `T`, then a native trailing inset `I`, then the one
1100
+ existing `F`. `F` is never duplicated as retiring presentation. `T` retains
1101
+ one React instance and a stable measured height while its text changes, and
1102
+ remains interactive during a transition so a tap can reverse it. `C` is
1103
+ measured after mounting for expansion. On collapse the caller retains that
1104
+ same React instance until the newest collapsed settlement callback, after
1105
+ which it may unmount it. Its measured root keeps its natural height; native
1106
+ clipping, not a React height animation, controls the visible portion. `I` is
1107
+ `expandedTrailingInset` at the expanded endpoint and zero at the collapsed
1108
+ endpoint. The native display-link/animator updates the visible `R` suffix,
1109
+ `C`, `T` position, `I`, `F` position, scroll offset, and sidecar-aware extent
1110
+ on one UI-thread timeline. No per-frame JavaScript command is required.
1111
+ The wrapper may mount `C` and `T` before delivering a new structural snapshot;
1112
+ the previous accepted snapshot continues to own the list during this short
1113
+ preparation. For that new command only, the wrapper waits for the current
1114
+ instances of both slot roots to report their first layout, including a genuine
1115
+ zero-height layout, then delivers the complete snapshot once. A numeric zero
1116
+ height without a layout event is not proof that a newly mounted `C` is ready.
1117
+ A newer pending id replaces an older one; no stale prepared command may be
1118
+ delivered later. Initial snapshots and same-id content updates do not wait.
1119
+ Native applies the measured slot heights and the complete pending snapshot in
1120
+ one ordered main-thread update, with heights first, regardless of which Nitro
1121
+ property setter ran first in that React commit. It must not reveal `C` or emit
1122
+ a settlement callback between those two values.
1123
+
1124
+ All retiring or newly revealed row suffix is inert and excluded from
1125
+ accessibility, selection, row actions, visible-range, and end-reached events.
1126
+ The whole `C` slot is inert and accessibility-hidden while preparing,
1127
+ animating, or settled collapsed; it becomes interactive and accessible only
1128
+ after the expanded endpoint settles. The visible part of `T` remains
1129
+ hit-testable throughout so a tap can reverse the transition. Native
1130
+ must not infer the target from product-specific row counts or toggle text.
1131
+ An initially expanded snapshot and a same-transition-id content or price
1132
+ update use `disclosure.expanded` without starting or re-acknowledging a
1133
+ structural transition. An initial collapsed snapshot may omit or mount an
1134
+ empty `C` root; later collapse keeps its mounted instance through settlement.
1135
+
1136
+ For each newly consumed `structuralTransition.id` with `disclosure`, native
1137
+ emits `onStructuralTransitionSettled` once for the latest non-superseded id
1138
+ when a listener is registered. Without a listener, native still settles the
1139
+ command and releases its pending transition state, but does not serialize or
1140
+ cross the callback boundary. Adding or replacing a listener does not replay a
1141
+ settled id; removing it stops delivery for subsequent settlements. The caller
1142
+ may keep `C` mounted after settlement; native still controls its visibility,
1143
+ interaction, and accessibility. The callback carries JSON
1144
+ `{ transitionId: string, status: 'completed' | 'interrupted',
1145
+ settledExpanded: boolean }`. `settledExpanded` describes the accepted target
1146
+ after native geometry and scroll ownership have reached that state, never a
1147
+ guess from measured `C` height. Natural animation completion, reduced motion,
1148
+ zero duration, and a new id with unchanged or empty row keys that settles
1149
+ directly report `completed`. Drag, explicit scrolling, resize, or unsupported
1150
+ geometry atomically commit the accepted target and report `interrupted` after
1151
+ temporary presentation and scroll ownership have been released. A newer id
1152
+ replaces the previous command from current presentation geometry: the older
1153
+ id must not complete late or force its former target before the newer one.
1154
+ The caller guards the callback by its latest id and current list/account/network
1155
+ owner; a matching numeric id from a prior owner must not unmount current `C`.
1156
+ An accepted row patch while a direct disclosure commit is pending MUST preserve
1157
+ that command's settlement ownership. If the patch supersedes the pending list
1158
+ submission, its committed callback settles the current non-superseded id after
1159
+ layout and any pending collapsed-toggle positioning, exactly once. It MUST NOT
1160
+ leave callers waiting for a discarded list callback.
1161
+ An already settled id
1162
+ does not re-emit on a same-id patch. Disposal drops the callback with its
1163
+ owner. An unsupported or immediately settled new id must still acknowledge
1164
+ when a listener is registered; otherwise a caller could retain `C`
1165
+ indefinitely after collapse.
1166
+
763
1167
  On Android, mounting/unmounting a slot or changing its actual rounded native
764
1168
  height MUST request one coalesced RecyclerView measurement/layout repair. The
765
1169
  repair runs after the current layout completes and only with a positive viewport
@@ -770,7 +1174,8 @@ request another repair. This leaves Fabric-owned slot content and row indices
770
1174
  unchanged; source checks alone do not establish first-layout visibility.
771
1175
 
772
1176
  Fabric mount/unmount ownership remains explicit even when RecyclerView detaches a
773
- slot holder. The native manager retains at most three roots until Fabric unmount;
1177
+ slot holder. The new native wrapper retains six scrolling roots until Fabric unmount
1178
+ (plus the independent optional fixed top overlay);
774
1179
  recycling detaches slot content without disposing its React tree. Unmount removes
775
1180
  that exact view. Native list disposal MUST NOT dispatch pending row/slot events.
776
1181
  The generated Nitro 0.37 leaf managers require deterministic child-forwarding
@@ -819,8 +1224,28 @@ applied inset and remains valid while that inset is retained across detach.
819
1224
 
820
1225
  ## Native rich activity presentation
821
1226
 
1227
+ Status: Source implemented for `ActivityRow.hideLeading`. Web DOM tests cover
1228
+ both Activity paths, Android production renderer instrumentation covers
1229
+ same-view rebinds, and the iOS NativeListModule arm64 Simulator target builds.
1230
+ iOS runtime acceptance remains pending. Optional `hideLeading: true` suppresses
1231
+ the activity row's main leading visual, its accessibility content, and the entire visual width and
1232
+ following gap. The title, description, status, amounts, fee, and footer actions
1233
+ retain their normal semantics; footer actions align with the title region rather
1234
+ than a reserved visual slot. `leading` remains required in the row model and is
1235
+ still validated. Omitted or `false` preserves the existing layout and image
1236
+ behavior. The field must be a boolean in snapshots and patches; an omitted patch
1237
+ field preserves the current value. A full snapshot or patch changing
1238
+ `hideLeading` rebinds the visual and layout in both legacy and rich Activity
1239
+ presentations, including key reuse. Hidden visuals do not start image requests;
1240
+ rebinding to `false` may load the current image. Recycling and late image
1241
+ callbacks must not restore a hidden visual or leave hidden layout/accessibility
1242
+ state on a later visible row. This contract applies on iOS, Android, and Web.
1243
+ The Android instrumentation uses icon visuals to verify layout, action alignment
1244
+ and amount preservation. Late image completion is guarded by the existing
1245
+ image-slot recycle epoch; a hide-during-image-load device case remains unverified.
1246
+
822
1247
  Status: Source implemented; native runtime acceptance pending. Optional `amounts`
823
- supersedes legacy `primaryAmount`/`secondaryAmount` only on native. It accepts at
1248
+ supersedes legacy `primaryAmount`/`secondaryAmount` in rich Activity rows. It accepts at
824
1249
  most 32 keyed lines, each with formatted text, optional leading visual, tone,
825
1250
  secondary text and subscript text segments. Segments are caller-formatted and
826
1251
  never interpreted as numbers by NativeList. Omission preserves the legacy tree.
@@ -1298,6 +1723,26 @@ Presentation wrapper changes move the existing nodes and preserve asynchronous
1298
1723
  loading/error visibility; they do not recreate or reload an unchanged source.
1299
1724
  Changing the source set retains the existing rebuild/disposal behavior.
1300
1725
 
1726
+ ## Opt-in structural snapshot transition (Home assets)
1727
+
1728
+ `NativeListSnapshot.structuralTransition?: { id: string; durationMs?: number; anchorKey?: string }` is an update command carried by a validated complete snapshot. It applies only after the initial mount, when `id` differs from the last consumed transition id and the row-key sequence changes while both the previous and next layouts are vertical, single-column `linear` or `table`. Grid, sectioned, and horizontal source or target layouts apply the snapshot without this animation. The default duration is 220 ms; explicit values must be finite and in the 0–2000 ms range. Reusing an id on price, balance, selection, or theme snapshots is inert. `applyPatches` never starts a structural transition. A newer id supersedes an in-flight transition from its current visual geometry; host disposal cancels it. Reduced-motion settings disable animation while preserving final rows and scroll position.
1729
+
1730
+ The host captures the first visible row whose key survives the update and keeps its viewport offset where feasible. If none survives, an off-screen `anchorKey` supplies a fallback target. The host never teleports the fallback into view before animation. A collapse that makes the prior absolute offset unreachable must ease the final clamp over the same transition instead of jumping. The React footer slot's old/new height difference is compensated inside that same transition so its button and following content remain continuous while the slot changes. The header/footer slots remain in the one list scroll owner. Web external-scroll-host mode must suppress conflicting browser scroll anchoring during the transition. A failed or unsupported animation still applies the final snapshot and anchor atomically. Row geometry, footer position, and scroll offset share one platform timeline; Pager observes native offset changes as list-owned scroll commands. iOS and Android hosts drive interpolation on the native UI thread without JavaScript bridge frames; Web uses one animation frame timeline for height and offset.
1731
+
1732
+ Native prefix-tail refinement status: Android source and its production-class native regressions are implemented. The prior iOS presentation-layer implementation exercised Home row, footer, sidecar, and scroll geometry, but manual testing found long display-link gaps. The iOS single-tree replacement below is source implemented with limited iOS simulator consuming-app verification; actual presented FPS remains unverified. Only a vertical, single-column `linear` or `table` update whose old and new keys share a nonempty unchanged ordered prefix with unchanged retained-row geometry may use this path. The accepted snapshot becomes current immediately. On iOS the one collection view presents full-natural-height row cells: expansion installs the final row snapshot once before the first animation frame, while collapse retains the old suffix only as presentation until completion. Its flow layout clips the suffix at one animated boundary and reports the animated left extent, combined with the right sidecar maximum, as the scrollable content height. A partially visible boundary cell is masked without changing its measured height. Every suffix row is inert and hidden from accessibility throughout the transition, including newly accepted expansion rows and retiring collapse rows; none dispatch actions, receive selection, or enter visible-range and end-reached events until final commit. No second row-host tree is created, and expansion completion does not reinstall the same row snapshot. If UIKit cannot safely maintain the clipped row window, the host commits the final layout atomically rather than exposing blank or interactive retired content. Android continues to use its bounded inert native presentation layer, with at most 128 row hosts covering the viewport and one viewport of overscan on either side.
1733
+
1734
+ The same native display-link/animator tick changes suffix extent, footer position, clipping, and owner offset. A footer height change during the transition remains on that clock: both native hosts may temporarily use a signed tail extent to offset the footer's new naturally measured height, while the native root keeps its normal measurement; the current extent and owner offset become the start of the remaining segment, which still ends at the original deadline. On iOS a negative extent clips only the footer's overlapped top and excludes that region from hit testing, while the visible footer button remains interactive; cancellation or completion restores the unadjusted extent and full hit region. Expansion preserves the starting offset. Collapse chooses the nearest final offset that fully exposes the collapsed footer below sticky chrome when it fits; a footer taller than the viewport starts below sticky chrome. The final scroll extent is the maximum of the left content and the right sidecar. A newer transition id starts from the current presentation extent and offset and invalidates the old completion when the retained prefix boundary is unchanged. If that boundary changes, the host commits the latest snapshot atomically instead of carrying the old spacer into a different row position. User drag, an explicit scroll command, owner change, resize, reduced motion, or disposal releases temporary presentation state without a stale completion taking scroll ownership. Price patches update the accepted snapshot and any still presented matching rows without restarting the clock. Unsupported shapes retain the existing structural behavior. This refinement introduces no public field or command.
1735
+
1736
+ Web/Desktop/extension prefix-preserving tail refinement status: source implemented; Web and Desktop consuming-app preview routes exercised with the local candidate Web overlay. Browser extension was outside that preview scope, and no FPS or paint-duration claim has been verified. The complete accepted snapshot becomes current immediately, while removed suffix rows may remain only in an inert, accessibility-hidden presentation window until the existing 220 ms transition completes. That window follows the same scroll owner and mounts only viewport-near old rows as the owner moves; each row keeps its natural height while the tail is clipped by the interpolated list extent. Inserted suffix rows use their final layout immediately and are revealed by the growing extent. The shared animation frame updates list height, React footer compensation, and programmatic scroll together. Its final scroll range uses the actual parent page extent, including a taller sidecar. For an off-screen fallback, Web uses the footer's complete visible interval where it fits, including the sticky inset, and otherwise aligns the footer start below the sticky region; the retained row is preferred only when the footer is absent. Price patches update the accepted snapshot and visible retained rows without restarting the transition. A newer structure command, unsupported shape, user scroll takeover, reduced motion, resize, or disposal releases presentation nodes and animation ownership without a stale completion writing an older offset. Other structural changes retain the existing behavior. This refinement adds no public field or native behavior; focused Web DOM tests passed, while browser extension and broader runtime performance remain unverified.
1737
+
1738
+ `DataRow.trailing?: readonly TrailingAccessory[]` reuses the existing accessory action contract, including `rowKey`, `actionKey`, disabled state, and accessibility/test labels. At most two accessories are accepted. Desktop Home can render its independent price/balance columns with `DataRow.columns` and a Swap button in `trailing`. Existing rows without `trailing` retain their current layout. On iOS, trailing controls reserve their intrinsic width and the weighted columns fill the remaining row width. Changing between linear and table layout rebinds retained cells even when their row payload is unchanged.
1739
+
1740
+ `DataColumn.textSegments` and `secondaryTextSegments` reuse `ValueTextSegment` (`text`, optional `style: 'subscript'`) for formatted prices, balances, and values in each independent table column. Plain `text` and `secondaryText` remain the spoken/fallback strings. Missing segments preserve existing rendering.
1741
+
1742
+ `DataColumn.titleIcon` and `secondaryTitleIcon` reuse `IdentityRow.titleIcon` after the primary column text. They preserve native-asset and receipt-token symbols in the desktop asset column. Web `hoverActionKey` uses the same anchored help action as Identity rows; absent icons leave the existing column layout unchanged.
1743
+
1744
+ `IdentityRow.titleSuffixes?: readonly { text: string; tone: 'secondary' | 'disabled'; maxWidth?: number }[]` supports up to three plain 12/16 labels on the title line. Each suffix truncates independently at its optional maximum width. This preserves Recent recipient EVM, network, and relative-time metadata without using background badges; rows without suffixes keep their present title layout.
1745
+
1301
1746
  ### Web slot-list scroll ownership
1302
1747
 
1303
1748
  When container slots create an owned Web scroll host without `webScrollHost`,
@@ -153,6 +153,7 @@ resolved colors; `style.color` does not resolve application color-token names.
153
153
  | `bgApp` | `background` | List ground |
154
154
  | `bg` | `rowBackground` | Row ground |
155
155
  | `bgActive` | `rowSelectedBackground`, `rowPressedBackground` | Selected / pressed row |
156
+ | `bgHover` | `rowHoverBackground` (optional Web) | Hovered row; omission uses pressed color |
156
157
  | `bgSubdued` | `subduedBackground` | Card and group ground |
157
158
  | `bgStrong` | `strongBackground` | Chip / badge ground |
158
159
  | `bgInverse` | `inverseBackground` | Inverted ground |
@@ -269,6 +270,11 @@ Each cell describes the same public contract on **Web + iOS + Android**.
269
270
  | `action` | title, value | padding, leadingGap, trailingGap, image |
270
271
  | `system` | title, message, actionText | padding, lineGap |
271
272
 
273
+ `identity.style.leadingContentMaxWidthRatio` optionally caps the leading
274
+ visual, gap and title content to a finite fraction in `(0, 1]` of the row's
275
+ inner width. Omission retains the existing width behavior. This lets a
276
+ same-row trailing text button keep its own space while long titles truncate.
277
+
272
278
  Market additionally exposes `titleBadgeLayout: inline`, `contentTrailingGap`,
273
279
  `subtitleTrailingPadding`, `changeWidth`, `changeHeight`, `changeCornerRadius`, preserving
274
280
  its existing template-specific style surface on every platform.
@@ -27,17 +27,22 @@ final class HybridNativeList: HybridNativeListSpec {
27
27
  }
28
28
 
29
29
  var containerSlotHeightsJson: String = "[]" {
30
+ didSet { if containerSlotHeightsJson != oldValue { slotHeightsChanged = true } }
31
+ }
32
+
33
+ var sidecarLayoutJson: String = "" {
30
34
  didSet {
31
- guard containerSlotHeightsJson != oldValue else { return }
35
+ guard sidecarLayoutJson != oldValue else { return }
32
36
  runOnMain { [weak self] in
33
37
  guard let self else { return }
34
- self.hostView?.setContainerSlotHeightsJson(self.containerSlotHeightsJson)
38
+ self.hostView?.setSidecarLayoutJson(self.sidecarLayoutJson)
35
39
  }
36
40
  }
37
41
  }
38
42
 
39
43
  private var fontsChanged = false
40
44
  private var snapshotChanged = false
45
+ private var slotHeightsChanged = false
41
46
 
42
47
  var fontFamiliesJson: String = "{}" {
43
48
  didSet { if fontFamiliesJson != oldValue { fontsChanged = true } }
@@ -75,11 +80,16 @@ final class HybridNativeList: HybridNativeListSpec {
75
80
 
76
81
  func afterUpdate() {
77
82
  let fonts = fontsChanged ? fontFamiliesJson : nil
83
+ let slotHeights = slotHeightsChanged ? containerSlotHeightsJson : nil
78
84
  let snapshot = snapshotChanged ? snapshotJson : nil
79
85
  fontsChanged = false
86
+ slotHeightsChanged = false
80
87
  snapshotChanged = false
81
88
  runOnMain { [weak self] in
82
89
  if let fonts { self?.hostView?.setFontFamiliesJson(fonts) }
90
+ if let slotHeights {
91
+ self?.hostView?.setContainerSlotHeightsJson(slotHeights, followedBySnapshot: snapshot != nil)
92
+ }
83
93
  if let snapshot { self?.hostView?.applySnapshotJson(snapshot) }
84
94
  }
85
95
  }
@@ -109,6 +119,14 @@ final class HybridNativeList: HybridNativeListSpec {
109
119
  var onSelectionDelta: ((_ payloadJson: String) -> Void)?
110
120
  var onReorder: ((_ payloadJson: String) -> Void)?
111
121
  var onEndReached: ((_ payloadJson: String) -> Void)?
122
+ var onStructuralTransitionSettled: ((_ payloadJson: String) -> Void)? {
123
+ didSet {
124
+ runOnMain { [weak self] in
125
+ guard let self else { return }
126
+ self.hostView?.onStructuralTransitionSettled = self.onStructuralTransitionSettled
127
+ }
128
+ }
129
+ }
112
130
  var onVisibleRangeChanged: ((_ payloadJson: String) -> Void)? {
113
131
  didSet {
114
132
  runOnMain { [weak self] in
@@ -222,6 +240,7 @@ final class HybridNativeList: HybridNativeListSpec {
222
240
  onSelectionDelta = nil
223
241
  onReorder = nil
224
242
  onEndReached = nil
243
+ onStructuralTransitionSettled = nil
225
244
  onVisibleRangeChanged = nil
226
245
  onScrollPositionThresholdChange = nil
227
246
  hostView.dispose()