@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 3.0.157-alpha.251
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 +21 -5
- package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
- package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
- package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
- package/docs/DESIGN.md +518 -9
- package/docs/MIGRATION_BASELINE.md +117 -0
- package/docs/ROW_TEMPLATES.md +359 -0
- package/docs/SPEC.md +358 -0
- package/docs/STYLE_SPEC.md +978 -0
- package/docs/images/list-capabilities.svg +39 -0
- package/docs/images/row-action.svg +17 -0
- package/docs/images/row-activity.svg +24 -0
- package/docs/images/row-data.svg +24 -0
- package/docs/images/row-identity.svg +25 -0
- package/docs/images/row-market.svg +23 -0
- package/docs/images/row-media-tile.svg +26 -0
- package/docs/images/row-message.svg +20 -0
- package/docs/images/row-metric-card.svg +33 -0
- package/docs/images/row-rail.svg +18 -0
- package/docs/images/row-section-header.svg +19 -0
- package/docs/images/row-style-regions.svg +30 -0
- package/docs/images/row-system.svg +30 -0
- package/docs/images/row-wallet-group.svg +26 -0
- package/ios/NativeListAccessoryStack.swift +680 -0
- package/ios/NativeListActionRenderer.swift +108 -0
- package/ios/NativeListActivityRenderer.swift +179 -0
- package/ios/NativeListDataRowRenderer.swift +444 -0
- package/ios/NativeListIdentityRenderer.swift +685 -0
- package/ios/NativeListImageSlot.swift +140 -0
- package/ios/NativeListLeadingVisual.swift +372 -0
- package/ios/NativeListMarketRenderer.swift +698 -0
- package/ios/NativeListMediaTileRenderer.swift +184 -0
- package/ios/NativeListMessageRenderer.swift +168 -0
- package/ios/NativeListMetricCardRenderer.swift +420 -0
- package/ios/NativeListModels.swift +32 -0
- package/ios/NativeListRailRenderer.swift +152 -0
- package/ios/NativeListRendererCell.swift +311 -0
- package/ios/NativeListRendererRegistry.swift +50 -0
- package/ios/NativeListResolvedText.swift +122 -0
- package/ios/NativeListRowHost.swift +63 -0
- package/ios/NativeListSectionHeaderRenderer.swift +708 -0
- package/ios/NativeListSystemRenderer.swift +568 -0
- package/ios/NativeListTextLabel.swift +42 -0
- package/ios/NativeListTextStyles.swift +236 -0
- package/ios/NativeListWalletGroupRenderer.swift +255 -0
- package/ios/RNCNativeListView.swift +111 -180
- package/lib/module/models.js +54 -0
- package/lib/module/validation.js +324 -26
- package/lib/module/web/NativeListWebEngine.js +148 -936
- package/lib/module/web/templates/ActionRowRenderer.js +97 -0
- package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
- package/lib/module/web/templates/DataRowRenderer.js +145 -0
- package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
- package/lib/module/web/templates/MarketRowRenderer.js +310 -0
- package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
- package/lib/module/web/templates/MessageRowRenderer.js +165 -0
- package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
- package/lib/module/web/templates/RailRowRenderer.js +98 -0
- package/lib/module/web/templates/RowContainerStyle.js +100 -0
- package/lib/module/web/templates/RowElements.js +93 -0
- package/lib/module/web/templates/RowRendererRegistry.js +70 -0
- package/lib/module/web/templates/RowText.js +121 -0
- package/lib/module/web/templates/RowVisual.js +43 -0
- package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
- package/lib/module/web/templates/SystemRowRenderer.js +161 -0
- package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
- package/lib/typescript/src/models.d.ts +166 -13
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
- package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
- package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
- package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
- package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
- package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
- package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
- package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
- package/package.json +3 -3
- package/src/models.ts +281 -19
- package/src/validation.ts +501 -46
- package/src/web/NativeListWebEngine.ts +361 -1932
- package/src/web/templates/ActionRowRenderer.ts +123 -0
- package/src/web/templates/ActivityRowRenderer.ts +159 -0
- package/src/web/templates/DataRowRenderer.ts +212 -0
- package/src/web/templates/IdentityRowRenderer.ts +527 -0
- package/src/web/templates/MarketRowRenderer.ts +467 -0
- package/src/web/templates/MediaTileRowRenderer.ts +182 -0
- package/src/web/templates/MessageRowRenderer.ts +306 -0
- package/src/web/templates/MetricCardRowRenderer.ts +337 -0
- package/src/web/templates/RailRowRenderer.ts +136 -0
- package/src/web/templates/RowContainerStyle.ts +131 -0
- package/src/web/templates/RowElements.ts +160 -0
- package/src/web/templates/RowRendererRegistry.ts +111 -0
- package/src/web/templates/RowText.ts +181 -0
- package/src/web/templates/RowVisual.ts +115 -0
- package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
- package/src/web/templates/SystemRowRenderer.ts +282 -0
- package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
- package/ios/NativeListCell.swift +0 -4300
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
# NativeList Style Spec
|
|
2
|
+
|
|
3
|
+
Status: **shared style contract implemented on Web, iOS and Android through the
|
|
4
|
+
registered template renderers**. Consolidated on 2026-09-22 from PRs #107, #109,
|
|
5
|
+
#110, #111 and #112, on main `feac35eb6`; the renderer migration and its dated
|
|
6
|
+
per-stage runtime acceptance are recorded in [SPEC.md](SPEC.md) and
|
|
7
|
+
[DESIGN.md](DESIGN.md). The full §9 matrix (pixel parity, localized/RTL text and
|
|
8
|
+
font scaling on every template) has not been executed as one pass.
|
|
9
|
+
Use symbol names as source anchors. Tables of legacy defaults below are an
|
|
10
|
+
inventory, not evidence of three-platform visual acceptance.
|
|
11
|
+
|
|
12
|
+
This document is the shared design vocabulary for `@onekeyfe/react-native-native-list`.
|
|
13
|
+
iOS, Android, and Web each own a separate renderer, and nothing in the build forces
|
|
14
|
+
them to agree. This spec is therefore **authoritative by review, not by codegen**.
|
|
15
|
+
It records what a value is called, what it should be, and what each platform
|
|
16
|
+
currently does — so a divergence is visible in review instead of on a device.
|
|
17
|
+
|
|
18
|
+
See [DESIGN.md](DESIGN.md) for the architecture and ownership model.
|
|
19
|
+
Start with the [illustrated template catalog](ROW_TEMPLATES.md) for each template's
|
|
20
|
+
purpose, fields, variants, fixed structure and allowed local styling.
|
|
21
|
+
|
|
22
|
+
## 1. Why this exists
|
|
23
|
+
|
|
24
|
+
The current `RowModel` union declares **12** row types, including `walletGroup`
|
|
25
|
+
and the structural `sectionHeader`. Presentation variants are not additional row
|
|
26
|
+
types. Every
|
|
27
|
+
template hard-codes its own typography and geometry in three places, and the three
|
|
28
|
+
vocabularies have drifted:
|
|
29
|
+
|
|
30
|
+
- The package's theme keys and the application's design tokens are **two different
|
|
31
|
+
names for the same colors**. Every consumer writes a mapping table by hand.
|
|
32
|
+
- The application has a named type scale (`$bodyMd`, `$headingSm`, …). The list has
|
|
33
|
+
ten-plus bare font sizes and references the scale nowhere. "One step smaller" has
|
|
34
|
+
no shared meaning.
|
|
35
|
+
- The same concept resolves to different numbers per platform (see §6).
|
|
36
|
+
|
|
37
|
+
This branch extends the original Market style API to the other row types. A
|
|
38
|
+
template defines structure; a local style parameter customizes a named part of
|
|
39
|
+
that structure. Header/footer placement, sections, scrolling and the indexed bar
|
|
40
|
+
belong to the list container, independently of the content template.
|
|
41
|
+
|
|
42
|
+
### 1.1 Ownership contract
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
| Owner | Responsibility | Must not control |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| List container | Viewport, header/footer placement, sections, scroll position, refresh/load more, indexed bar and list chrome | The business meaning of a content row |
|
|
49
|
+
| Layout | Orientation, row allocation, grid spans/table columns, content padding and item spacing | Typography of a particular row field |
|
|
50
|
+
| Row template + variant | Slot order, hierarchy, required/optional fields, compression and bounded line counts | Whether the list has a header, footer, section index or scroll API |
|
|
51
|
+
| `row.style` | Allowlisted typography, colors, local metrics and explicit container height | Arbitrary children, flex direction, positioning, reparenting or list-level geometry |
|
|
52
|
+
|
|
53
|
+
**Normative requirement:** replacing an `identity` row with a `market` or
|
|
54
|
+
`dataRow` in the same valid container must not remove that container's common
|
|
55
|
+
capabilities. Compatibility conditions may depend on layout/orientation (for
|
|
56
|
+
example the indexed bar requires vertical sections), not a particular business
|
|
57
|
+
row template. This requirement does not claim every current renderer satisfies it.
|
|
58
|
+
Current API limits and implementation gaps are explicit in §5 and §6.5.
|
|
59
|
+
|
|
60
|
+
### 1.2 Row template and row style are different contracts
|
|
61
|
+
|
|
62
|
+
- **Row template** (`row.type` plus its declared `variant`/`presentation`) defines
|
|
63
|
+
the content model and structure: slot order, hierarchy, optional parts, and
|
|
64
|
+
interactions. `identity`, `market` and `dataRow` are templates. Changing a
|
|
65
|
+
template is a model change, not a style change.
|
|
66
|
+
- **Row style** (`row.style`) customizes existing semantic fields and local
|
|
67
|
+
metrics within that template. For example, `identity.style.title.fontSize`
|
|
68
|
+
changes title typography; `image.width` changes the primary visual slot.
|
|
69
|
+
A style cannot add a field, reorder slots or change a column's weight.
|
|
70
|
+
- **List capabilities** belong to the container. Header/footer placement,
|
|
71
|
+
sections, scrolling, indexed bar, selection and list chrome do not belong to
|
|
72
|
+
either a content template or its row style.
|
|
73
|
+
|
|
74
|
+
The configurable property names, valid values, semantic targets and precedence
|
|
75
|
+
are the same on Web, iOS and Android. A template exposes only parameters for parts
|
|
76
|
+
it owns. Unsupported keys are rejected by the shared validator before
|
|
77
|
+
serialization; optional or variant-specific parts that are absent are not created
|
|
78
|
+
by styling them. See the complete applicability matrix in §4.1.
|
|
79
|
+
|
|
80
|
+
The 12 templates each have a definition and schematic in ROW_TEMPLATES.md.
|
|
81
|
+
Overflow fitting remains the integrating developer's responsibility: choose
|
|
82
|
+
fitting text/image/padding values and row heights. `style.container.height` can
|
|
83
|
+
define a new row height; omission preserves the model/template measurement rules.
|
|
84
|
+
Existing measured templates account for explicit text/image/padding metrics.
|
|
85
|
+
Automatic overflow fitting, combination-fit rejection and automatic font shrinking
|
|
86
|
+
remain outside this contract.
|
|
87
|
+
The five source PRs are closed; implementation continues in the consolidated PR.
|
|
88
|
+
|
|
89
|
+
## 2. How this spec is enforced
|
|
90
|
+
|
|
91
|
+
The three renderers cannot constrain each other, but they all read the same
|
|
92
|
+
`snapshotJson`. That boundary is the single enforcement point.
|
|
93
|
+
|
|
94
|
+
| Layer | Enforced by | Covers |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| Token names and values | `src/validation.ts` resolves tokens to numbers before serialization | §3 |
|
|
97
|
+
| Which keys a template accepts | `validateSnapshot` rejects keys not declared for that row type | §4 |
|
|
98
|
+
| Numeric bounds | `validateSnapshot` throws on out-of-range values | §3, §4 |
|
|
99
|
+
| Per-platform default values | **This document + PR review** | §4, §5 |
|
|
100
|
+
| Known divergences | **This document + PR review** | §6 |
|
|
101
|
+
|
|
102
|
+
A style token is resolved on the JavaScript side. `{ token: '$bodyLg' }` becomes
|
|
103
|
+
`{ fontSize: 16, lineHeight: 24, fontWeight: 'regular' }` before it crosses the
|
|
104
|
+
bridge — and an explicit `fontSize` alongside the token wins. Native renderers
|
|
105
|
+
never learn the token vocabulary and therefore cannot drift from it. Raw numeric
|
|
106
|
+
overrides stay available for pixel-parity work. Resolution is idempotent, and a
|
|
107
|
+
snapshot with nothing to resolve is returned unchanged, so the common path keeps
|
|
108
|
+
object identity.
|
|
109
|
+
|
|
110
|
+
### Status
|
|
111
|
+
|
|
112
|
+
| Layer | State |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| Per-template key validation, token resolution, patch support | Shared TypeScript/runtime contract; identical for all three renderers |
|
|
115
|
+
| Text roles and local box/image parameters | Implemented for the applicable template slots in §4.1; absent optional slots remain absent |
|
|
116
|
+
| `dataRow` column styles | Independent primary/secondary text on all platforms, in linear and table layouts |
|
|
117
|
+
| Sticky header styles | Android uses the same row view/binder as normal headers; iOS and Web also reuse their row renderers |
|
|
118
|
+
| `listStyle.separator` / `groupCornerRadius` | Initial/update propagation and actual-group scope aligned |
|
|
119
|
+
| Row heights and overflow | `style.container.height` overrides `row.height`; omission retains template measurement, including existing style-dependent sizing; caller owns content fitting |
|
|
120
|
+
| Legacy baseline dimensions | Inventory in §4/§6; explicit style values use logical units independently of Android's legacy list scale |
|
|
121
|
+
| Rendered acceptance | Per-stage renderer runtime cases are recorded in SPEC.md/DESIGN.md; source and unit checks alone do not establish the full §9 pixel/interaction matrix |
|
|
122
|
+
|
|
123
|
+
Each renderer maps its own model fields to owned views. There is no global
|
|
124
|
+
cross-template style-slot map or shared legacy text-view pool. §4 remains the
|
|
125
|
+
contract for the Swift, Kotlin and Web implementations; regression tests check
|
|
126
|
+
semantic isolation and style set/clear behavior.
|
|
127
|
+
|
|
128
|
+
The example application's **Native List Row Style** page has plain/styled pairs
|
|
129
|
+
for all 12 types, plus list chrome and an explicit-height example. Its wallet
|
|
130
|
+
group styles the parent member while leaving a child unstyled to expose leakage.
|
|
131
|
+
Migration/runtime acceptance is recorded in DESIGN.md; the checklist in §9 still
|
|
132
|
+
applies to future changes. The catalog
|
|
133
|
+
illustrations are structural diagrams, not screenshots or proof of rendering parity.
|
|
134
|
+
|
|
135
|
+
## 3. T1 — Design tokens
|
|
136
|
+
|
|
137
|
+
### 3.1 Color tokens
|
|
138
|
+
|
|
139
|
+
This table maps application token names to `NativeListTheme` keys by meaning.
|
|
140
|
+
It does not add runtime aliases: consumers still pass the actual theme keys and
|
|
141
|
+
resolved colors; `style.color` does not resolve application color-token names.
|
|
142
|
+
|
|
143
|
+
| Application token | `NativeListTheme` key | Role |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `text` | `primaryText` | Primary label |
|
|
146
|
+
| `textSubdued` | `secondaryText` | Secondary label |
|
|
147
|
+
| `textDisabled` | `disabledText` | Disabled / timestamp |
|
|
148
|
+
| `textInverse` | `inverseText` | Text on inverted ground |
|
|
149
|
+
| `textSuccess` | `positive` | Positive amount |
|
|
150
|
+
| `textCritical` | `negative` | Negative amount |
|
|
151
|
+
| `textCaution` | `caution` | Warning text |
|
|
152
|
+
| `textInfo` | `info` | Informational text, search highlight |
|
|
153
|
+
| `bgApp` | `background` | List ground |
|
|
154
|
+
| `bg` | `rowBackground` | Row ground |
|
|
155
|
+
| `bgActive` | `rowSelectedBackground`, `rowPressedBackground` | Selected / pressed row |
|
|
156
|
+
| `bgSubdued` | `subduedBackground` | Card and group ground |
|
|
157
|
+
| `bgStrong` | `strongBackground` | Chip / badge ground |
|
|
158
|
+
| `bgInverse` | `inverseBackground` | Inverted ground |
|
|
159
|
+
| `bgCriticalSubdued` | `criticalBackground` | Failed-state chip |
|
|
160
|
+
| `bgCautionSubdued` | `cautionBackground` | Caution chip |
|
|
161
|
+
| `borderSubdued` | `separator` | Separator, group border |
|
|
162
|
+
| `icon` | `icon` | Icon default |
|
|
163
|
+
| `iconSubdued` | `iconSubdued` | Secondary icon |
|
|
164
|
+
| `iconActive` | `accent` | Accent — **name mismatch, see §6** |
|
|
165
|
+
|
|
166
|
+
### 3.2 Typography scale
|
|
167
|
+
|
|
168
|
+
Sizes below are the application's scale. The "used by" column records which list
|
|
169
|
+
slots already sit on that step; slots that do not are listed in §4.
|
|
170
|
+
|
|
171
|
+
| Token | Size / line height | Weight | Used by |
|
|
172
|
+
| --- | --- | --- | --- |
|
|
173
|
+
| `$headingXl` | 24 / 32 | semibold | `metricCard.value` (size `large`) |
|
|
174
|
+
| `$headingLg` | 20 / 28 | semibold | — |
|
|
175
|
+
| `$headingMd` | 18 / 24 | semibold | `sectionHeader` gallery, `metricCard.value` |
|
|
176
|
+
| `$headingSm` | 16 / 24 | medium | `identity.title`, `market.title`, `market.price` |
|
|
177
|
+
| `$headingXs` | 14 / 20 | semibold | `message.title`, `sectionHeader` default |
|
|
178
|
+
| `$bodyLg` | 16 / 24 | regular | `action.title`, `sectionHeader` summary |
|
|
179
|
+
| `$bodyMd` | 14 / 20 | regular | `identity.subtitle`, `message.body`, `market.change` |
|
|
180
|
+
| `$bodySm` | 12 / 16 | regular | `rail.title`, `market.subtitle`, `identity.badge` |
|
|
181
|
+
| `$bodyXs` | 11 / 16 | regular | `metricCard` labels, `market` badges |
|
|
182
|
+
|
|
183
|
+
Weight names map to the bundled Roobert faces: `regular`, `medium`, `semibold`,
|
|
184
|
+
`bold`. No other family is available; `fontFamily` is not part of the style surface.
|
|
185
|
+
|
|
186
|
+
### 3.3 Spacing, radius, bounds
|
|
187
|
+
|
|
188
|
+
| Kind | Allowed values | Bound |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| Padding / gap | finite number | `0…64` |
|
|
191
|
+
| `lineGap` (title to subtitle) | finite number | `0…16` |
|
|
192
|
+
| Font size | finite number | `8…48` |
|
|
193
|
+
| Line height | finite number | `8…64` |
|
|
194
|
+
| Corner radius | finite number | `0…80` |
|
|
195
|
+
| Image edge | finite number | `1…160` |
|
|
196
|
+
|
|
197
|
+
These are the validator's numeric bounds, not a guarantee that every combination
|
|
198
|
+
fits a row. Fractional values are accepted; native rounding can differ. See §7.1
|
|
199
|
+
for the stronger layout-safety contract and its current enforcement limits.
|
|
200
|
+
|
|
201
|
+
Radius steps in use: `4` (chip), `8` (rail, small visual), `10` (visual `rounded`),
|
|
202
|
+
`12` (row, card), `16` (media tile), `20` (wallet sidebar row on iOS only — §6).
|
|
203
|
+
|
|
204
|
+
## 4. T2 — Template style surface
|
|
205
|
+
|
|
206
|
+
`style.X` modifies the rendering of the **model field named `X`**, regardless of
|
|
207
|
+
which physical view carries it. Each registered template renderer owns its views
|
|
208
|
+
and maps the key to the view rendering that field. The removed legacy native
|
|
209
|
+
views shared slots (for example legacy `metricCard` rendered its *value* through
|
|
210
|
+
the title label and its *title* through the subtitle label), which is why style
|
|
211
|
+
keys were never named after views. `market` follows the same rule with
|
|
212
|
+
`style.price` / `style.change`.
|
|
213
|
+
|
|
214
|
+
On Web the element rendering a field carries `data-nl-slot="<field>"`, so the
|
|
215
|
+
style pass resolves a slot by name rather than by CSS class — on its own,
|
|
216
|
+
`.ok-native-list-secondary` is the identity subtitle, the rail status, a metric
|
|
217
|
+
label, a data column's secondary text, and a system message. iOS and Android express
|
|
218
|
+
the same semantic mapping in native code. Singular style roles such as `badge`
|
|
219
|
+
and `value` can refer to repeated badges or values in `trailing`; their precise
|
|
220
|
+
meaning is defined in the [catalog](ROW_TEMPLATES.md).
|
|
221
|
+
|
|
222
|
+
### 4.1 Shared configurable properties
|
|
223
|
+
|
|
224
|
+
Every text role listed below accepts the same `NativeListTextStyle`:
|
|
225
|
+
`token`, `fontSize`, `fontWeight`, `color`, `lineHeight`, `lines`, `truncate`,
|
|
226
|
+
`alignment`, `verticalAlignment`, `offsetY`.
|
|
227
|
+
Weights are `regular | medium | semibold | bold`; line counts are `1 | 2 | 3`;
|
|
228
|
+
`start | center | end` alignment follows layout direction on all three
|
|
229
|
+
platforms (`end` is the left edge in RTL). Colors must be
|
|
230
|
+
`#RRGGBB` or `#RRGGBBAA`, the common native/Web color format. Omitted properties
|
|
231
|
+
preserve the template's values. Explicit properties override token values,
|
|
232
|
+
presentation defaults and rich-text runs only for that property.
|
|
233
|
+
|
|
234
|
+
Every supported image role accepts `width`, `height`, `shape`, `cornerRadius`,
|
|
235
|
+
`contentFit`. Shape is `circle | rounded | square`; explicit corner radius wins.
|
|
236
|
+
Fit is `cover | contain | fill | center`. These parameters target the primary
|
|
237
|
+
visual, not network badges, corner decorations, message thumbnails or nested
|
|
238
|
+
metric images. A media tile keeps its square default when only width is set;
|
|
239
|
+
an explicit image height overrides that aspect. Image/text/padding metrics can
|
|
240
|
+
participate in an existing intrinsic/measured template, such as Market or message.
|
|
241
|
+
They do not override an explicit row height. Use `style.container.height` to
|
|
242
|
+
define a new outer height for any template.
|
|
243
|
+
|
|
244
|
+
In the matrix, **padding** means both `horizontalPadding` and `verticalPadding`.
|
|
245
|
+
Each cell describes the same public contract on **Web + iOS + Android**.
|
|
246
|
+
|
|
247
|
+
| Template | Semantic text roles | Local box/image properties |
|
|
248
|
+
| --- | --- | --- |
|
|
249
|
+
| `identity` | title, subtitle, tertiary, badge, value, valueSecondary | padding, leadingGap, lineGap, titleBadgeGap, trailingGap, image |
|
|
250
|
+
| `walletGroup` | None; members own their identity styles | padding |
|
|
251
|
+
| `rail` | title, badge, status | padding, leadingGap, titleBadgeGap, trailingGap, image |
|
|
252
|
+
| `activity` | title, description, status, primaryAmount, secondaryAmount | padding, leadingGap, lineGap, trailingGap, image |
|
|
253
|
+
| `message` | title, body, time | padding, leadingGap, lineGap, image |
|
|
254
|
+
| `dataRow` | columns, columnSecondary, index | padding, leadingGap, lineGap, titleBadgeGap, image |
|
|
255
|
+
| `market` | title, subtitle, price, change | padding, leadingGap, lineGap, titleBadgeGap, trailingGap, image; Market-specific properties below |
|
|
256
|
+
| `mediaTile` | title, subtitle, badge | padding, leadingGap, lineGap, image |
|
|
257
|
+
| `metricCard` | title, value, subtitle, trend | padding, leadingGap, lineGap, image |
|
|
258
|
+
| `sectionHeader` | title, subtitle, value | padding, lineGap, trailingGap |
|
|
259
|
+
| `action` | title, value | padding, leadingGap, trailingGap, image |
|
|
260
|
+
| `system` | title, message, actionText | padding, lineGap |
|
|
261
|
+
|
|
262
|
+
Market additionally exposes `titleBadgeLayout: inline`, `contentTrailingGap`,
|
|
263
|
+
`subtitleTrailingPadding`, `changeWidth`, `changeHeight`, `changeCornerRadius`, preserving
|
|
264
|
+
its existing template-specific style surface on every platform.
|
|
265
|
+
|
|
266
|
+
Gap meanings are structural: `leadingGap` separates the primary visual from
|
|
267
|
+
content; `lineGap` separates content text rows (primary/secondary per data column);
|
|
268
|
+
`titleBadgeGap` separates a title and its badges (vertical for walletSidebar);
|
|
269
|
+
`trailingGap` separates trailing values/controls (title/badge to status for rail).
|
|
270
|
+
`walletGroup` padding applies only to the group; it never cascades into members.
|
|
271
|
+
Composite metric-card `lineGap` separates heading/metric blocks and dividers; it
|
|
272
|
+
does not change the typography or inner label/value gap of each metric.
|
|
273
|
+
|
|
274
|
+
Variant applicability is also shared: composite metric-card `title` styles its
|
|
275
|
+
heading, while standard-card `value`/`subtitle`/`trend` do not style nested metrics;
|
|
276
|
+
`system.title` applies to warning, `actionText` to the native retry button. Web
|
|
277
|
+
(legacy) draws no retry button for any presentation, so `style.actionText` has
|
|
278
|
+
no Web target and is ignored there; iOS/Android style their button. A missing leading visual,
|
|
279
|
+
badge, subtitle or trailing value remains absent. New nested-metric/thumbnail
|
|
280
|
+
style roles require a separately declared cross-platform contract.
|
|
281
|
+
|
|
282
|
+
All numeric overrides use logical units (CSS px / iOS pt / Android dp), including
|
|
283
|
+
explicit typography. Legacy Android scaling may affect omitted defaults, but must
|
|
284
|
+
not rescale an explicit style value. Physical-pixel/font-rasterization differences
|
|
285
|
+
remain platform-specific. See §3.3 for bounds and §7 for fitting responsibility.
|
|
286
|
+
|
|
287
|
+
### 4.1.1 Row container (all 12 templates)
|
|
288
|
+
|
|
289
|
+

|
|
290
|
+
|
|
291
|
+
`style.container` addresses the outer row surface, including `walletGroup` and
|
|
292
|
+
rows used as headers/footers. It never overwrites member styles; opacity naturally composites the entire
|
|
293
|
+
container including descendants.
|
|
294
|
+
|
|
295
|
+
| Property | Values / default when supplied | Meaning |
|
|
296
|
+
| --- | --- | --- |
|
|
297
|
+
| `height` | `0…4096` logical units | Explicit row allocation; takes precedence over `row.height` and template measurement |
|
|
298
|
+
| `backgroundColor` | `#RRGGBB` / `#RRGGBBAA` | Resting row background |
|
|
299
|
+
| `opacity` | `0…1` | Opacity of the whole row, multiplied by disabled dimming |
|
|
300
|
+
| `cornerRadius` | `0…80` logical units | All four outer row corners |
|
|
301
|
+
| `borderWidth` | `0…8` logical units | Inward border; no effect on content allocation |
|
|
302
|
+
| `borderColor` | `#RRGGBB` / `#RRGGBBAA` | Border color; transparent if width is supplied alone |
|
|
303
|
+
| `contentVerticalAlignment` | `top / center / bottom` | Align the row's content within its padded height; preserve slot order and orientation |
|
|
304
|
+
|
|
305
|
+
Omission preserves the template's existing value. Explicit container background
|
|
306
|
+
and opacity take precedence over legacy `row.backgroundColor` / `row.opacity`.
|
|
307
|
+
Existing top-level fields remain accepted; new integrations use `style.container`.
|
|
308
|
+
An explicit background is the resting surface (including selection); existing
|
|
309
|
+
pressed feedback remains visible. Radius and border remain during press/selection.
|
|
310
|
+
Explicit radius overrides grouped corner geometry for this row only; it does not
|
|
311
|
+
change `listStyle.groupCornerRadius`. `backgroundFullWidth` remains a layout/model
|
|
312
|
+
option and uses the resolved resting background. A border never changes padding.
|
|
313
|
+
|
|
314
|
+
`horizontalPadding` / `verticalPadding` remain on `row.style`; there is no second
|
|
315
|
+
padding entry under `container`. Outer margin, width, positioning, visibility,
|
|
316
|
+
flex direction, column weights and independent hit-area changes are not container
|
|
317
|
+
style properties. `height` changes the row frame and its normal row hit area; it
|
|
318
|
+
does not rearrange slots or resize their contents.
|
|
319
|
+
Shadows, gradients and per-corner/per-edge styling are outside this version.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
// Inside an identity row; the style itself can allocate a new row height.
|
|
323
|
+
style: {
|
|
324
|
+
container: {
|
|
325
|
+
height: 112, backgroundColor: '#EDF6FF', cornerRadius: 12,
|
|
326
|
+
borderWidth: 1, borderColor: '#8DB7E4', contentVerticalAlignment: 'center',
|
|
327
|
+
},
|
|
328
|
+
horizontalPadding: 16, verticalPadding: 12, leadingGap: 10, lineGap: 4,
|
|
329
|
+
title: { lines: 1, truncate: 'tail', alignment: 'start' },
|
|
330
|
+
subtitle: { lines: 3, truncate: 'clip', lineHeight: 18, offsetY: -1 },
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 4.1.2 Text wrapping, truncation and alignment
|
|
335
|
+
|
|
336
|
+
| Property | Shared meaning |
|
|
337
|
+
| --- | --- |
|
|
338
|
+
| `lines` | Maximum 1, 2 or 3 lines, not a forced reservation of that many lines |
|
|
339
|
+
| `truncate` | `tail` (ellipsis at the end of the last visible line) or `clip` (no ellipsis); default `tail` when line/truncation controls are supplied |
|
|
340
|
+
| `lineHeight` | Line box height inside one text field, distinct from `lineGap` between fields |
|
|
341
|
+
| `alignment` | Horizontal `start / center / end` inside the text region, respecting direction |
|
|
342
|
+
| `verticalAlignment` | `top / center / bottom` for the complete text block inside its allocated text region; no effect without spare height |
|
|
343
|
+
| `offsetY` | `-8…8` logical units of optical translation; does not change measurement, sibling spacing or hit areas |
|
|
344
|
+
|
|
345
|
+
Single-line text does not wrap; explicit CR/LF line breaks become spaces (CRLF is
|
|
346
|
+
one break). Multi-line text wraps naturally, preserves explicit breaks, and
|
|
347
|
+
truncates after the last permitted line. Rich runs share one line budget. Styling
|
|
348
|
+
one field must not alter adjacent badges, values or member rows. Middle/head
|
|
349
|
+
truncation is not exposed in this version; it needs a separate cross-platform
|
|
350
|
+
contract rather than platform-specific fallback.
|
|
351
|
+
|
|
352
|
+
Every existing declared text role accepts the same range `1…3`; each template's
|
|
353
|
+
omitted defaults remain documented in the inventory. Optional absent fields stay
|
|
354
|
+
absent. `style.title.lines` overrides legacy `titleLines`,
|
|
355
|
+
`style.subtitle.lines` overrides `subtitleLines`, and `style.body.lines` overrides
|
|
356
|
+
`message.bodyLines`. New callers should use styles for all three. Removing a
|
|
357
|
+
style restores the model/default value. `patch.changes.style` replaces the whole
|
|
358
|
+
style object; `{}` clears it, including container and text overrides.
|
|
359
|
+
|
|
360
|
+
Height precedence is `style.container.height` → `row.height` → existing
|
|
361
|
+
template/layout measurement. A style height is an exact logical height, not a
|
|
362
|
+
minimum. It applies to every template, normal rows, grid/horizontal layouts,
|
|
363
|
+
wallet members, sticky headers and fixed footers. Clearing the whole style or
|
|
364
|
+
omitting `container.height` restores the original model height or measurement;
|
|
365
|
+
the original `row.height` is never rewritten. Wallet-group compact reorder keeps
|
|
366
|
+
its temporary drag height and restores the styled height afterward. Android
|
|
367
|
+
uses unscaled logical dp, with `heightRounding` when supplied (nearest otherwise).
|
|
368
|
+
|
|
369
|
+
An explicit height is not automatically expanded. With neither explicit height,
|
|
370
|
+
styles may change an existing measured/intrinsic row height. Existing intrinsic/measured
|
|
371
|
+
paths must account for explicit text metrics within their declared measurement
|
|
372
|
+
model. Font shaping and exact wrap points can vary across platform fonts; the
|
|
373
|
+
property meanings and maximum line counts must agree.
|
|
374
|
+
|
|
375
|
+
`container.contentVerticalAlignment` moves the existing content arrangement as a
|
|
376
|
+
whole within the row; text `verticalAlignment` moves only that field's glyph block.
|
|
377
|
+
They must not be substituted for each other. For horizontal rows, the container
|
|
378
|
+
alignment aligns existing immediate content groups on the vertical axis.
|
|
379
|
+
|
|
380
|
+
### 4.1.3 Spacing and admission rules
|
|
381
|
+
|
|
382
|
+
Spacing is always between named semantic regions, never arbitrary descendants:
|
|
383
|
+
`leadingGap` is visual-to-content, `titleBadgeGap` is title-to-badge, and
|
|
384
|
+
`trailingGap` is between the template's trailing items (rail: title/badge-to-status).
|
|
385
|
+
`lineGap` is between text fields; the existing composite metric variant explicitly
|
|
386
|
+
uses it between its heading/metric blocks. New templates must identify both
|
|
387
|
+
endpoints in their diagram and must not invent a different meaning for these keys.
|
|
388
|
+
List `layout.itemSpacing` remains the gap between rows.
|
|
389
|
+
|
|
390
|
+
Before adding a template or style property, update the catalog with its diagram,
|
|
391
|
+
semantic fields, defaults, applicable variants, units/ranges, precedence and
|
|
392
|
+
absent-field behavior. Reuse this shared text/image/container vocabulary. A new
|
|
393
|
+
property requires Web/iOS/Android implementations, validation for snapshot and
|
|
394
|
+
patch paths, and focused checks for replacement/reset, recycling, rich text,
|
|
395
|
+
member isolation, sticky headers and fixed footers where applicable. Source/build
|
|
396
|
+
checks and rendered acceptance must be reported separately. No renderer may
|
|
397
|
+
silently reinterpret an unsupported value.
|
|
398
|
+
|
|
399
|
+
### 4.2 Existing defaults (inventory, not a pixel parity claim)
|
|
400
|
+
|
|
401
|
+
Legend: **=** inventoried defaults agree; **≠** legacy divergence, see §6.
|
|
402
|
+
|
|
403
|
+
### identity
|
|
404
|
+
|
|
405
|
+
| Key | Default | iOS | Android | Web |
|
|
406
|
+
| --- | --- | --- | --- | --- |
|
|
407
|
+
| `title` | `$headingSm` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / 20px / 600 **≠** |
|
|
408
|
+
| `subtitle` | `$bodyMd` | 14 regular / 20 | sp(14) regular / dp(20) | 14px / 20px **=** |
|
|
409
|
+
| `tertiary` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px / 20px **=** |
|
|
410
|
+
| `subtitle` lines | model `subtitleLines` | default 1 | default **2** | default 1 **≠** |
|
|
411
|
+
| `badge` | `$bodySm` | 12 medium / 16, pad 8/2, r4 | sp(12) / dp(16), pad 8/2, r4 | 11px / 18px, pad 0 5, r5 **≠** |
|
|
412
|
+
| `value` | `$headingSm` | 16 medium | sp(16) medium / dp(24) | 14px / 20px / 500 **≠** |
|
|
413
|
+
| `valueSecondary` | `$bodyMd` | 14 regular | sp(14) regular / dp(20) | 14px **=** |
|
|
414
|
+
| box | pad 12/8, gap 12 | 12 / -12 / 8 / -8, spacing 12 | dp(12), dp(8) | `padding:8px 12px`, `gap:12px` **=** |
|
|
415
|
+
| `image` | 40 | 40×40 (32 for `network`) | 40 (32 for `network`) | 40px (32px for account) **=** |
|
|
416
|
+
|
|
417
|
+
### identity · presentation `walletSidebar`
|
|
418
|
+
|
|
419
|
+
| Key | Default | iOS | Android | Web |
|
|
420
|
+
| --- | --- | --- | --- | --- |
|
|
421
|
+
| `title` | `$bodySm` | 12 regular / 16, centered | sp(12) / dp(16), centered | 12px / 16px / 400 **=** |
|
|
422
|
+
| `badge` | `$bodyXs` | 11 / 14, pad 6/2, r4 | sp(11) / dp(14), pad 6/2, r4 | 11px / 14px, pad 2 6, r4 **=** |
|
|
423
|
+
| `image` | 40 | 40×40, fallback 28 | 40, fallback sp(28) | 40px **=** |
|
|
424
|
+
| box | pad 4 | 4 / -4 / 4 / -4 | dp(4) × 4 | `padding:4px 8px` **≠** |
|
|
425
|
+
| row radius | 12 | 20 | 12 | 12 **≠** |
|
|
426
|
+
|
|
427
|
+
### identity · presentation `accountSelector`
|
|
428
|
+
|
|
429
|
+
| Key | Default | iOS | Android | Web |
|
|
430
|
+
| --- | --- | --- | --- | --- |
|
|
431
|
+
| `title` | `$bodyLg` | 16 regular | sp(16) regular | 16px / 20px / 400 **=** |
|
|
432
|
+
| `subtitle` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px / 20px / 400 **=** |
|
|
433
|
+
| `image` | 32 | 32×32 | 32 | 32px **=** |
|
|
434
|
+
| trailing icon | 38 | 38, margin −7 | dp(38), margin −dp(7) | 38px, margin −7px **=** |
|
|
435
|
+
|
|
436
|
+
### identity · presentation `networkSelector`
|
|
437
|
+
|
|
438
|
+
| Key | Default | iOS | Android | Web |
|
|
439
|
+
| --- | --- | --- | --- | --- |
|
|
440
|
+
| `title` | `$headingSm` | 16 medium / 24 | sp(16) / dp(24) | 16px / 24px / 500 **=** |
|
|
441
|
+
| `image` | 32 | 32×32, fallback 19/27 semibold | 32, fallback sp(19)/dp(27) | 32px, fallback 19/27/600 **=** |
|
|
442
|
+
| checkbox | 20, r4, border 2 | 20×20, r4, border 2 | dp(20), r4f, stroke dp(2) | 20px, r4, border 2px **=** |
|
|
443
|
+
|
|
444
|
+
### walletGroup
|
|
445
|
+
|
|
446
|
+
| Key | Default | iOS | Android | Web |
|
|
447
|
+
| --- | --- | --- | --- | --- |
|
|
448
|
+
| member gap | 12 | stack spacing 12 | topMargin dp(12) | `gap:12px` **=** |
|
|
449
|
+
| container radius | 12 | 20 | dp(12) | 12px **≠** |
|
|
450
|
+
| container border | 1, `borderSubdued`, drawn beneath members | 1 | dp(1) | 1px **=** |
|
|
451
|
+
| compact drag height | 68 | 68 | max(68, parent + 2 × inset) | 68 **≠** |
|
|
452
|
+
|
|
453
|
+
### rail
|
|
454
|
+
|
|
455
|
+
| Key | Default | iOS | Android | Web |
|
|
456
|
+
| --- | --- | --- | --- | --- |
|
|
457
|
+
| `title` | `$bodySm` | 12 medium / 16 | sp(12) / dp(16) | 12px / 500 **=** |
|
|
458
|
+
| `badge` | `$bodySm` | 12 medium / 16 | sp(12) medium / dp(16) | — |
|
|
459
|
+
| `status` | `$bodySm` | 12 regular / 16 | sp(12) | — |
|
|
460
|
+
| box | pad 4, gap 6 | 4 / -4, spacing 6 | dp(4), spacing 6 | `padding:4px;gap:6px` **=** |
|
|
461
|
+
| `image` | 20 | 20×20 | 20 | 20px **=** |
|
|
462
|
+
| row radius | 8 | 8 | 8 | 8px **=** |
|
|
463
|
+
|
|
464
|
+
### activity
|
|
465
|
+
|
|
466
|
+
| Key | Default | iOS | Android | Web |
|
|
467
|
+
| --- | --- | --- | --- | --- |
|
|
468
|
+
| `title` | `$headingSm` | lineHeight 24 | dp(24) | 16px / 20px **≠** |
|
|
469
|
+
| `description` | `$bodyMd` | lineHeight 20, 2 lines | dp(20), 2 lines | 14px / 20px **=** |
|
|
470
|
+
| `primaryAmount` | `$headingSm` | 16 medium / 24, tabular | sp(16) medium / dp(24) | 16px / 20px **≠** |
|
|
471
|
+
| `secondaryAmount` | `$bodyMd` | 14 regular / 20, tabular | sp(14) / dp(20) | 14px **=** |
|
|
472
|
+
| footer action | `$bodyMd` | 12 medium, r8, pad 4/8 | sp(14) semibold, r8f, pad 8/4 | 12px / 16px / 600 **≠** |
|
|
473
|
+
|
|
474
|
+
### message
|
|
475
|
+
|
|
476
|
+
| Key | Default | iOS | Android | Web |
|
|
477
|
+
| --- | --- | --- | --- | --- |
|
|
478
|
+
| `title` | `$headingXs` | 14 semibold / 20, 2 lines | sp(14) semibold / dp(20) | 15px / 20px / 600 **≠** |
|
|
479
|
+
| `body` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 13px / 18px **≠** |
|
|
480
|
+
| `time` | `$bodySm` | 12 / 16, `textDisabled` | sp(12) / dp(16), `disabledText` | 11px / 16px **≠** |
|
|
481
|
+
| `image` | 28 | 28×28 | 28 | 40px **≠** |
|
|
482
|
+
| thumbnail | 64, r6 | 64×64, r6 | dp(64), r6f | 64px, r10 **≠** |
|
|
483
|
+
| box | pad 12/16 | 16 / -16 | dp(12), dp(16) | `padding:8px 12px` **≠** |
|
|
484
|
+
|
|
485
|
+
### dataRow
|
|
486
|
+
|
|
487
|
+
| Key | Default | iOS | Android | Web |
|
|
488
|
+
| --- | --- | --- | --- | --- |
|
|
489
|
+
| `columns` (linear) | `$bodyMd` | 14 medium / 20 | sp(16) medium / dp(20) | 16px / 500 **≠** |
|
|
490
|
+
| `columns` (table) | `$bodyMd` | 14 medium / 20 | sp(14) medium / dp(20) | 16px / 500 **≠** |
|
|
491
|
+
| column secondary | `$bodySm` | 12 regular / 16 | sp(12) / dp(16) | — |
|
|
492
|
+
| `index` | `$bodySm` | — | dp(32) slot | 13px, 28px slot **≠** |
|
|
493
|
+
| favorite icon | 20 | 20×20 | dp(20) | 24px / 22px glyph **≠** |
|
|
494
|
+
| box | pad 12/6 | table 20/10 | table dp(20)/dp(10) | `padding:6px 12px` **=** |
|
|
495
|
+
|
|
496
|
+
### market
|
|
497
|
+
|
|
498
|
+
Already fully data-driven through `MarketRowStyle`. The values below are the
|
|
499
|
+
defaults applied when `style` is absent.
|
|
500
|
+
|
|
501
|
+
| Key | Default | All platforms |
|
|
502
|
+
| --- | --- | --- |
|
|
503
|
+
| `title` | `$headingSm` | 16 / 24 / medium |
|
|
504
|
+
| `subtitle` | `$bodyMd` | 14 / 20 / regular |
|
|
505
|
+
| `price` | `$headingSm` | 16 / 24 / medium |
|
|
506
|
+
| `change` | `$bodyMd` | 14 / 20 / medium, block 80×32 r8 |
|
|
507
|
+
| box | pad 20/12, gap 14 | perp 16, gap 8 |
|
|
508
|
+
| `image` | 32 (stock 40) | circle |
|
|
509
|
+
|
|
510
|
+
### mediaTile
|
|
511
|
+
|
|
512
|
+
| Key | Default | iOS | Android | Web |
|
|
513
|
+
| --- | --- | --- | --- | --- |
|
|
514
|
+
| `title` | `$headingSm` | 16 medium / 24 | sp(16) / dp(24) | 16px / 500 **=** |
|
|
515
|
+
| `subtitle` | `$bodySm` | 12 regular / 16 | sp(12) / dp(16) | 12px **=** |
|
|
516
|
+
| `badge` | `$bodyMd` | 14 medium, h24, r10 | sp(14), h dp(24), r10f | — |
|
|
517
|
+
| image radius | 10 | 10 | dp(10) | 10px **=** |
|
|
518
|
+
| tile radius | 16 | 16 | 16 | 16px **=** |
|
|
519
|
+
| network badge | 14 | 14×14, r7 | dp(14), circle | 14px, r50% **=** |
|
|
520
|
+
|
|
521
|
+
### metricCard
|
|
522
|
+
|
|
523
|
+
`title` is the small label; `value` is the large number. Style keys follow these
|
|
524
|
+
model fields, whatever the renderer's internal view names are.
|
|
525
|
+
|
|
526
|
+
| Key | Default | iOS | Android | Web |
|
|
527
|
+
| --- | --- | --- | --- | --- |
|
|
528
|
+
| `title` | `$bodyXs` | 11 regular, `textDisabled` | sp(11), `disabledText` | 14px **≠** |
|
|
529
|
+
| `value` | `$headingMd` | 18 semibold (24 when `size: large`) | sp(18) / sp(24) semibold | 22px / 28px / 700 **≠** |
|
|
530
|
+
| `trend` | `$bodySm` | 12, tone colored | sp(12), tone colored | — |
|
|
531
|
+
| metric label | `$bodyXs` | 11 regular / 14 | sp(11) / dp(14) | — |
|
|
532
|
+
| metric value | `$bodyMd` | 14 medium / 20 | sp(14) medium / dp(20) | 18px / 600 **≠** |
|
|
533
|
+
| card padding | 14 | 14 | dp(14) | 12px **≠** |
|
|
534
|
+
| card radius | 12 | 12 | 12 | 12px **=** |
|
|
535
|
+
| progress bar | 4, r2 | 4, r2 | dp(4), r dp(2) | 4px, r2 **=** |
|
|
536
|
+
|
|
537
|
+
### sectionHeader
|
|
538
|
+
|
|
539
|
+
| Variant | Default | iOS | Android | Web |
|
|
540
|
+
| --- | --- | --- | --- | --- |
|
|
541
|
+
| default | `$headingXs` | 14 semibold / 20 | sp(14) semibold / dp(20) | 13px / 18px / 700 **≠** |
|
|
542
|
+
| `summary` | `$bodyLg` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / 500 **=** |
|
|
543
|
+
| `gallery` | `$headingMd` | 18 semibold / 24 | sp(18) semibold / dp(24) | 16px **≠** |
|
|
544
|
+
| `history` | `$bodySm` | 12 semibold / 16, tracking 0.8 | sp(12) semibold / dp(16), tracking 0.8 | 12px / 16px **=** |
|
|
545
|
+
| table layout | `$bodyXs` | 11 regular / 14 | sp(11) regular / dp(14) | — |
|
|
546
|
+
| `value` | `$bodyLg` | 16 / 24 | sp(16) medium / dp(24) | 14px / 20px / 500 **≠** |
|
|
547
|
+
|
|
548
|
+
### action
|
|
549
|
+
|
|
550
|
+
| Key | Default | iOS | Android | Web |
|
|
551
|
+
| --- | --- | --- | --- | --- |
|
|
552
|
+
| `title` | `$bodyLg` | 16 / 24 | sp(16) / dp(24) | 15px / 600 **≠** |
|
|
553
|
+
| `title` (accountSelector) | `$bodyLg` | 16 medium / regular | medium / regular | 16px / 24px / 400 **=** |
|
|
554
|
+
| icon slot | 40 (32 accountSelector) | 40 / 32, glyph 24 | 40 / 32, glyph dp(24) | 40px / 32px **=** |
|
|
555
|
+
| box | pad 16 | — | — | `padding:0 16px` **=** |
|
|
556
|
+
|
|
557
|
+
### system
|
|
558
|
+
|
|
559
|
+
| Variant | Default | iOS | Android | Web |
|
|
560
|
+
| --- | --- | --- | --- | --- |
|
|
561
|
+
| `loading` skeleton | — | marks 32 / 80×16 / 60×12 / 80×18 | same | same **=** |
|
|
562
|
+
| `loading` spinner | 20 | 20, market 32 | dp(20), market 32 | 20px, market 32px **=** |
|
|
563
|
+
| `retry` | `$bodyMd` | 14 medium / 20, r14 | sp(14) / dp(20), r16f | 12px **≠** |
|
|
564
|
+
| `noMatch` / `end` | `$bodyMd` | 14 regular / 20 | sp(14) / dp(20) | 14px **=** |
|
|
565
|
+
| `warning` | `$bodyMd` | 14 medium + regular / 20 | sp(14) / dp(20) | 14px / 20px **=** |
|
|
566
|
+
| `spacer` | — | `height` only | `height` only | `height` only **=** |
|
|
567
|
+
|
|
568
|
+
### Carrier coverage
|
|
569
|
+
|
|
570
|
+
Rows in `snapshot.rows`, `snapshot.fixedFooter`, and `snapshot.emptyState` reuse
|
|
571
|
+
the row binders. The two standalone descriptors currently accept only `action`
|
|
572
|
+
or `system` in the public TypeScript API. `sectionHeader` is a structural row in
|
|
573
|
+
`rows`. Reusing a binder does not transfer ownership of footer positioning,
|
|
574
|
+
empty-state placement or header pinning to the row template. Android's pinned
|
|
575
|
+
header reuses the ordinary header row view and callbacks (§6.3).
|
|
576
|
+
|
|
577
|
+
## 5. T3 — List chrome
|
|
578
|
+
|
|
579
|
+
Chrome is not part of a row. This branch has `snapshot.listStyle` for separator
|
|
580
|
+
inset/color and grouped-card radius. Common capabilities remain in their existing
|
|
581
|
+
container APIs; being public does not require placing everything in `listStyle`.
|
|
582
|
+
|
|
583
|
+
### 5.1 Common capabilities and current API
|
|
584
|
+
|
|
585
|
+
| Capability | Existing entry point | Contract and current limit |
|
|
586
|
+
| --- | --- | --- |
|
|
587
|
+
| List header | Leading structural/summary rows; `CollapsiblePagerView.header` and `stickyHeader` for composed React content | Header placement belongs to the container. There is currently **no generic public `listHeader`/`ListHeaderComponent` prop**; arbitrary React children inside NativeList are not part of the current template API |
|
|
588
|
+
| List footer | A trailing `action`/`system` row scrolls with content; `snapshot.fixedFooter` stays outside the scrolling rows | Available independently of the content row type. `fixedFooter` currently accepts only `ActionRow \| SystemRow`; row style controls its content, not its fixed placement |
|
|
589
|
+
| Sections / sticky headers | `layout.kind: 'sectioned'`, `sectionHeader.sectionKey`, member `sectionKey`, `layout.stickyHeaders`, header `sticky` | A section can contain different content templates. `sectionHeader` describes the heading; the container owns membership, positioning and pinning. Complex headers share the normal row renderer when pinned; summary headers remain non-sticky on all three platforms (§6.3) |
|
|
590
|
+
| Scroll and restoration | `NativeListRef.scrollToKey/Index/Item/Offset/End/Location`, `initialScrollKey/Index` and view position/offset props | Shared viewport behavior, independent of row template. Style must not silently change measured offsets or invalidate stable row keys |
|
|
591
|
+
| Indexed bar | `capabilities.sectionIndex`, `sectionHeader.indexTitle`; Web has `webSectionIndexContainerRef` | Requires vertical `sectioned` layout; entries follow indexed header order and target header keys. No dependency on `identity`, `market`, or another content template |
|
|
592
|
+
| Refresh / pagination | `capabilities.pullToRefresh/refreshing/loadMore/endReachedThreshold`, `onRefresh`, `onEndReached`, `setRefreshing` | Container gestures/state; indicator implementation may differ by platform |
|
|
593
|
+
| Empty state | `snapshot.emptyState` (`ActionRow \| SystemRow`) | Container decides when to display it, independently of the regular content templates |
|
|
594
|
+
| Selection / reorder | `snapshot.selection`, `capabilities.reorderable`, selection/reorder events | Common orchestration; each row's available controls/draggable support still depend on its declared model |
|
|
595
|
+
| Separator / grouped cards | `listStyle`, row `separator`, `groupId` and `groupPosition` | Styling scope is the list; group membership is data. A custom group radius must not change unrelated template cards |
|
|
596
|
+
|
|
597
|
+
The header/footer slots in the diagram are ownership boundaries, not newly
|
|
598
|
+
available props. Do not introduce `marketHeader`, `identityFooter`, or per-template
|
|
599
|
+
scroll/index configuration. A future public header or broader footer carrier must
|
|
600
|
+
extend the container contract once, with the same placement rules for all content.
|
|
601
|
+
|
|
602
|
+
`activity.footerActions` means buttons **inside one activity row**. It is unrelated
|
|
603
|
+
to the list footer. Similarly, `walletGroup` is a composite wallet template, not
|
|
604
|
+
the list's general section mechanism.
|
|
605
|
+
|
|
606
|
+
### 5.2 Chrome surface
|
|
607
|
+
|
|
608
|
+
| Item | Data-driven today | iOS | Android | Web |
|
|
609
|
+
| --- | --- | --- | --- | --- |
|
|
610
|
+
| Content padding | yes (`layout.contentPadding*`) | `contentInset` | `setPadding` | `paddingValues` |
|
|
611
|
+
| Item spacing | yes (`layout.itemSpacing`) | flow layout spacing | `ItemSpacingDecoration` | layout gap |
|
|
612
|
+
| Separator | **yes** — `listStyle.separator.{inset,color}` | 1/scale, inset 60/12 | 1px, inset dp(60)/dp(12) | `1px`, class rule, **no inset** |
|
|
613
|
+
| Group card radius | **yes** — `listStyle.groupCornerRadius` | 20 / 12 | dp(12) | 12px |
|
|
614
|
+
| Section index rail | partial (`capabilities.sectionIndex`) | static constants, label 10 | `SECTION_INDEX_*_DP`, raw density | `SECTION_INDEX_*` + CSS |
|
|
615
|
+
| Section index preview | no | 48×48 r14, 22 semibold | preview w/h/margin constants | 48px r14, 22px |
|
|
616
|
+
| Pull to refresh | partial (`capabilities.pullToRefresh`) | `UIRefreshControl` | `SwipeRefreshLayout` | custom pill, 12px |
|
|
617
|
+
| Reorder preview | no | — | — | r12, shadow `0 4px 24px` |
|
|
618
|
+
| Reorder count badge | no | 24 h, r12, 12 semibold | dp(24), r dp(12), sp(12) semibold | 24px, r12, 12/22/600 |
|
|
619
|
+
|
|
620
|
+
The section index rail is three separate constant sets for one control; Android's
|
|
621
|
+
`sectionIndexDp()` deliberately never follows the source-scale switch.
|
|
622
|
+
|
|
623
|
+
`listStyle` carries only what all three platforms can honour. The rest of this table
|
|
624
|
+
is deliberately excluded rather than declared and half-implemented:
|
|
625
|
+
|
|
626
|
+
- **Pull to refresh** is a system control on both native platforms (`UIRefreshControl`,
|
|
627
|
+
`SwipeRefreshLayout`); only Web draws its own indicator.
|
|
628
|
+
- **Section index rail and preview** belong to `capabilities.sectionIndex`, which is
|
|
629
|
+
where their geometry should go if it is ever exposed, and are three independent
|
|
630
|
+
constant sets today.
|
|
631
|
+
- **Reorder preview and count badge** are drawn with platform-specific primitives —
|
|
632
|
+
Android paints the badge on `Canvas` inside `dispatchDraw`, Web uses a CSS overlay,
|
|
633
|
+
and iOS has neither.
|
|
634
|
+
- **Content padding and item spacing** are already `layout.contentPadding*` and
|
|
635
|
+
`layout.itemSpacing`; duplicating them here would give one value two homes.
|
|
636
|
+
|
|
637
|
+
Validated values: `separator.inset` is `0…64` logical units; `separator.color`
|
|
638
|
+
must be `#RRGGBB` or `#RRGGBBAA`, the same format as every row style color;
|
|
639
|
+
`groupCornerRadius` is `0…40` logical units. Any other `listStyle` or separator
|
|
640
|
+
key is rejected.
|
|
641
|
+
|
|
642
|
+
Every `listStyle` value is absent by default, and each platform keeps its own number
|
|
643
|
+
as the fallback, so an untouched list renders exactly as before. On Web that required
|
|
644
|
+
the inset separator to keep the transparent `border-bottom` for layout and paint the
|
|
645
|
+
visible line with a logical-inset overlay, rather than moving the row by a pixel.
|
|
646
|
+
|
|
647
|
+
## 6. T4 — Known divergences
|
|
648
|
+
|
|
649
|
+
Registered, **not** fixed by this document. Changing any of these requires a PR
|
|
650
|
+
that cites this table and carries device verification.
|
|
651
|
+
|
|
652
|
+
### 6.1 Row heights
|
|
653
|
+
|
|
654
|
+
Row height is computed from data by three independent implementations —
|
|
655
|
+
the iOS registered renderer measurement, Android registered row measurement, and
|
|
656
|
+
Web registered estimate/DOM measurement. Preserved defaults still differ:
|
|
657
|
+
|
|
658
|
+
| Template | iOS | Android | Web |
|
|
659
|
+
| --- | --- | --- | --- |
|
|
660
|
+
| `rail` | 40 | 28 | 40 |
|
|
661
|
+
| `activity` + footer actions | 100 | 104 | 100 |
|
|
662
|
+
| `dataRow` linear, no secondary text | 56 | 56 | 56 |
|
|
663
|
+
| `dataRow` linear + secondary text | 60 | **64** | 60 |
|
|
664
|
+
| `dataRow` table, no secondary text | 48 (56 − 8) | **52** (60 − 8) | 48 (56 − 8) |
|
|
665
|
+
| `dataRow` table + secondary text | 60 | 60 | 60 |
|
|
666
|
+
| `sectionHeader` `summary` | 68 | 80 | 68 |
|
|
667
|
+
| `sectionHeader` value + checkbox | 56 | 40 | 56 |
|
|
668
|
+
| `message`, `mediaTile`, composite `metricCard` | computed / 244 / 160+ | 0 (wrap content) | computed / 244 / 161 |
|
|
669
|
+
|
|
670
|
+
DataRow provenance (pre-migration engines at `52849b86f`): iOS
|
|
671
|
+
`RNCNativeListView.rowHeight()` used base 56, or 60 with secondary text, then
|
|
672
|
+
−8 in table layout without secondary text; legacy Web applied the same rule.
|
|
673
|
+
Android `NativeListRowView` used base 60 for every table row (−8 without
|
|
674
|
+
secondary text) and 64/56 for linear rows with/without secondary text. The
|
|
675
|
+
registered renderers return these values directly; `size` and explicit heights
|
|
676
|
+
apply as for other templates.
|
|
677
|
+
|
|
678
|
+
Message now uses its resolved text/spacing/image metrics for intrinsic
|
|
679
|
+
measurement; iOS measures the actual allocated column width and Web corrects
|
|
680
|
+
its conservative estimate from the rendered DOM. This explicitly fixes the old
|
|
681
|
+
Message estimator-only fallback differences. Explicit/model heights keep their
|
|
682
|
+
precedence. Other legacy mismatches remain: iOS `bindSystem()` uses 52/120 for a
|
|
683
|
+
market retry row where `rowHeight()` uses 44, and 88 for `noMatch` where
|
|
684
|
+
`rowHeight()` uses 44.
|
|
685
|
+
|
|
686
|
+
These values may have been tuned per platform on real devices. They are recorded
|
|
687
|
+
here and left alone.
|
|
688
|
+
|
|
689
|
+
### 6.2 Typography and geometry
|
|
690
|
+
|
|
691
|
+
| Concept | iOS | Android | Web |
|
|
692
|
+
| --- | --- | --- | --- |
|
|
693
|
+
| `identity.title` | 16 medium / 24 | sp(16) medium / dp(24) | 16px / **20px** / **600** |
|
|
694
|
+
| `identity.badge` | 12 / 16, pad 8/2, r4 | sp(12) / dp(16), pad 8/2, r4 | **11 / 18, pad 0 5, r5** |
|
|
695
|
+
| `message.body` | 14 / 20 | sp(14) / dp(20) | **13 / 18** |
|
|
696
|
+
| `message` leading | 28 | 28 | **40** |
|
|
697
|
+
| `sectionHeader` default | 14 semibold / 20 | sp(14) semibold / dp(20) | **13 / 18 / 700** |
|
|
698
|
+
| `action.title` | 16 / 24 | sp(16) / dp(24) | **15 / 600** |
|
|
699
|
+
| `metricCard.value` | 18 semibold | sp(18) semibold | **22 / 28 / 700** |
|
|
700
|
+
| visual `rounded` radius | `min(10, h/4)` | `min(10, size/4)`; 8 for accountSelector | **10px**; 8 `!important` for account action; 8 for market |
|
|
701
|
+
| `walletSidebar` row radius | **20** | 12 | 12 |
|
|
702
|
+
| `identity` subtitle lines (no `subtitleLines`) | 1 | **2** | 1 (CSS single line) |
|
|
703
|
+
| pressed row corners | 12 all corners (rail 8, mediaTile 16 resting, wallet sidebar **20**), ignoring `groupPosition` | 12 all corners (rail 8, mediaTile 16 resting); no wallet-sidebar exception | background only; resting group/template radius kept **≠** |
|
|
704
|
+
| `walletGroup` row `backgroundColor` | ignored (`style.container.backgroundColor` or theme `subduedBackground`) | ignored (same) | applied inline **≠** |
|
|
705
|
+
| `walletGroup` `row.height` | ignored (members / `style.container.height`) | ignored (same) | honored **≠** |
|
|
706
|
+
| System retry | literal "Retry" button (non-Market); `actionText` button (Market) | same as iOS | no button for any presentation; message only, row press retries **≠** |
|
|
707
|
+
| separator default inset | 60 identity / 12 | dp(60) identity / dp(12) | **0** |
|
|
708
|
+
| separator thickness | `1 / scale` (hairline) | `1f` raw px | `1px` |
|
|
709
|
+
|
|
710
|
+
Separator inset and colour are now settable through `listStyle.separator` (§5); the
|
|
711
|
+
defaults above are what a list gets when it does not set them. Thickness is not
|
|
712
|
+
exposed — a hairline is correct on iOS and a whole pixel is correct elsewhere.
|
|
713
|
+
|
|
714
|
+
### 6.3 Behavioral
|
|
715
|
+
|
|
716
|
+
- **Android pinned headers reuse normal row rendering.** The old Canvas-only
|
|
717
|
+
`StickySectionHeaderDecoration` is replaced by an overlay registered `NativeListSectionHeaderRowView`.
|
|
718
|
+
Typography, local metrics, values, checkbox/action callbacks and theme now follow
|
|
719
|
+
the same binding path. The overlay pushes away for the next sticky header and
|
|
720
|
+
forwards drag gestures to the RecyclerView. Summary and `sticky: false` headers
|
|
721
|
+
are excluded consistently. Device touch/accessibility acceptance remains required.
|
|
722
|
+
- **Source scale is list-wide on Android.**
|
|
723
|
+
`usesSelectorSourceScale` is computed with `items.any { … }`, so one selector row
|
|
724
|
+
switches the metric system (the sub-400dp 0.9 factor) for the entire list.
|
|
725
|
+
Only the legacy `row.height` (a WalletGroup's parent `height`) triggers it;
|
|
726
|
+
`style.container.height` switches its own row to selector explicit-height
|
|
727
|
+
geometry (as on iOS and Web) but never this list-wide metric system (§7 rule 3).
|
|
728
|
+
`NativeListTableColumnView`, the market skeleton, and the section index never
|
|
729
|
+
follow it. Explicit non-Market row styles now use density directly, so this
|
|
730
|
+
legacy baseline policy cannot scale an explicitly supplied style value.
|
|
731
|
+
Under source scaling, accountSelector/networkSelector/walletSidebar rows apply
|
|
732
|
+
the legacy selector typography (whole-pixel unscaled size, fractional advances,
|
|
733
|
+
original line box) to every text in the row, including trailing accessory
|
|
734
|
+
text and the action leading fallback text; a `fontSize` or `lineHeight` set in
|
|
735
|
+
that slot's style is applied as given. A walletSidebar leading visual under
|
|
736
|
+
source scaling draws a 1dp dashed border (else 2dp); its text-only overlays
|
|
737
|
+
are 16dp pills with 12sp text on a 16dp line, and other text overlays use 10sp.
|
|
738
|
+
- **`accent` is not an accent.** The consumer maps the application's `iconActive`
|
|
739
|
+
onto the theme key `accent`. The name should be retired in favour of an alias.
|
|
740
|
+
|
|
741
|
+
### 6.4 Load-bearing oddities (do not "clean up")
|
|
742
|
+
|
|
743
|
+
- `.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}`
|
|
744
|
+
overrides an **inline** radius written by `createVisual`. Removing `!important`
|
|
745
|
+
regresses the account action row to 10px.
|
|
746
|
+
- `.ok-native-list-market-change{color:#fff;background:#8d8d8d}` are literals, not
|
|
747
|
+
tokens, because `--nl-inverse-text` defaults to `#fcfcfc` and `--nl-secondary` to
|
|
748
|
+
`#6b7280`. Swapping them changes untinted rendering.
|
|
749
|
+
|
|
750
|
+
### 6.5 Consolidated gap audit
|
|
751
|
+
|
|
752
|
+
| Former gap | Resolution in source | Remaining verification |
|
|
753
|
+
| --- | --- | --- |
|
|
754
|
+
| Non-Market box/image parameters accepted without effect | Per-template allowlists and native/Web geometry/image mappings; properties for nonexistent template parts rejected | Native image loading, decoration clipping and variant geometry |
|
|
755
|
+
| Native table and secondary-column styling | Separate primary/secondary labels in linear and table layouts; styles target only their semantic text | Rendered alignment and reuse in both native layouts |
|
|
756
|
+
| Missing semantic text targets | Activity status, rich identity subtitles, wallet badges, retry action and composite heading routed to actual fields | Device variant coverage |
|
|
757
|
+
| Selector defaults overwrite styles | Explicit style applied after defaults; summary/selection updates restore and reapply styles | Rapid selection/summary updates on device |
|
|
758
|
+
| Native reuse leaks | Restore the actual bound text and local geometry before rebinding; preserve unspecified rich-text attributes | Styled → unstyled → another template during scrolling |
|
|
759
|
+
| Android sticky header uses a separate partial renderer | Normal row view renders pinned text, values and controls | Pin/push-off, checkbox/action taps, scrolling and accessibility |
|
|
760
|
+
| List chrome propagation and group scope | Initial/update paths receive listStyle; custom radius only applies to actual groups | Initial and chrome-only snapshot interactions |
|
|
761
|
+
| Parent style cascades to wallet members | Group exposes container appearance and padding; members bind their own styles | Independent group/member styling during reorder |
|
|
762
|
+
| Row container surface | Shared `style.container`, explicit height and appearance precedence, disabled opacity, border/radius and content alignment | Press/selection/reorder and nested members on device |
|
|
763
|
+
| Text line controls | Shared 1–3 lines, tail/clip, vertical alignment and optical offset; measured text paths updated | Long/CJK/RTL/rich text and fixed-height clipping on device |
|
|
764
|
+
|
|
765
|
+
### 6.6 Follow-up source audit (2026-09-22)
|
|
766
|
+
|
|
767
|
+
| Difference found | Repair |
|
|
768
|
+
| --- | --- |
|
|
769
|
+
| Web clip height guessed before CSS was mounted; iOS clip disabled natural wrapping | CSS line-height-relative clipping and iOS wrapping without ellipsis |
|
|
770
|
+
| Web ignored legacy identity line limits and Market badge metrics | Apply declared fields with style precedence and restore defaults on clear |
|
|
771
|
+
| Market rich subscripts retained their old size despite explicit font size | Superseded 2026-09-24: legacy (all platforms) sized a subscript at `ceil(0.6 × field fontSize)`. Web, iOS and Android restore that ratio for an explicit `fontSize` (native keeps the field line height, Web a `fontSize` line box, each as in legacy) |
|
|
772
|
+
| Web Market center fit used an invalid CSS value; change color used reversed precedence | Map center to object-fit none; explicit style color wins |
|
|
773
|
+
| Android line height excluded first/last text lines | Apply an exact line-height span including first/last font metrics |
|
|
774
|
+
| Legacy native centering overrode container alignment | Apply legacy positioning only when container alignment is omitted |
|
|
775
|
+
| Media leading gap changed when container alignment was supplied | Keep image-to-metadata spacing independent from vertical alignment |
|
|
776
|
+
| Opaque group members covered Web/Android explicit borders | Draw the border above member content without changing padding or hit targets |
|
|
777
|
+
| Web fixed footer was laid out beside the viewport | Stack viewport and footer vertically; styled footer height participates in allocation |
|
|
778
|
+
| Message timestamp line limits were absent from measured height | Include the timestamp text style in the existing message measurement |
|
|
779
|
+
| Runtime Market badge validation accepted text properties absent from its public type | Restrict badge style to its five declared metrics; it is not a row text role |
|
|
780
|
+
|
|
781
|
+
Market badge metrics remain `fontSize`, `fontWeight`, `lineHeight`, `height` and
|
|
782
|
+
`horizontalPadding`; badge colors stay on its existing model fields. This does
|
|
783
|
+
not extend the row-style vocabulary to arbitrary nested parts.
|
|
784
|
+
|
|
785
|
+
Common headers/footers already exist through structural rows, `fixedFooter` and
|
|
786
|
+
pager composition (§5.1). This work does not add an arbitrary React header slot.
|
|
787
|
+
Layout fitting is caller-owned, not an unresolved component feature. Legacy
|
|
788
|
+
unstyled defaults in §6 remain distinct from the unified configurable surface;
|
|
789
|
+
this change is not a wholesale redesign of those defaults.
|
|
790
|
+
|
|
791
|
+
## 7. Isolation rules
|
|
792
|
+
|
|
793
|
+
Every template has its own renderer reuse family; there is no shared legacy
|
|
794
|
+
pool. A styled row must not affect another row, whether it reuses the same host
|
|
795
|
+
or not. Four rules:
|
|
796
|
+
|
|
797
|
+
1. **Style types are per template.** `IdentityRowStyle` carries only identity's
|
|
798
|
+
fields; `MetricCardRowStyle` only metricCard's. A key that the row type does not
|
|
799
|
+
declare fails `validateSnapshot`/`validatePatches` instead of being ignored.
|
|
800
|
+
This strictness is new with the style surface: extra keys used to be ignored.
|
|
801
|
+
2. **Every property gets an explicit default.** Restore font, line height, line
|
|
802
|
+
count, alignment/gravity, padding, color and all other changed state before
|
|
803
|
+
binding the next row. Native style passes save the actual bound text defaults
|
|
804
|
+
for restoration (§6.5). An omitted
|
|
805
|
+
value must use that template's default, never a previous row's value. The
|
|
806
|
+
shared container pass clears every property it wrote on the next bind,
|
|
807
|
+
including `align-self`/`flex-shrink` on template children. Image loading
|
|
808
|
+
visibility is image lifecycle state, not a template default: restoring
|
|
809
|
+
defaults must not hide an image that already loaded.
|
|
810
|
+
3. **No list-wide side effects.** Any style-dependent decision is made per row.
|
|
811
|
+
`usesSelectorSourceScale` (§6.3) is the counter-example to avoid.
|
|
812
|
+
4. **Bounded and fail-soft.** Validate input in JavaScript before serialization;
|
|
813
|
+
native readers must safely handle absent fields. A per-field default does not
|
|
814
|
+
prove that the whole combination fits. Invalid style must not affect another
|
|
815
|
+
row or crash the list. §7.1 states the required layout boundary.
|
|
816
|
+
|
|
817
|
+
### 7.1 Template structure and integration layout responsibility
|
|
818
|
+
|
|
819
|
+
The component preserves template structure and style isolation. Developers
|
|
820
|
+
integrating the list are responsible for choosing sizes and content that fit;
|
|
821
|
+
overflow measurement, automatic size correction and combination-fit rejection
|
|
822
|
+
are explicitly out of scope. The following rules distinguish those responsibilities:
|
|
823
|
+
|
|
824
|
+
1. **Keep the skeleton fixed.** A style may change a declared semantic field's
|
|
825
|
+
typography/color and supported local padding/gaps/image metrics. It cannot
|
|
826
|
+
change slot order, orientation, column count/weight, structural spans, section
|
|
827
|
+
membership, control placement or parent constraints. Those belong to the
|
|
828
|
+
template, its explicit variant, or the container layout.
|
|
829
|
+
2. **Resolve row height consistently.** `style.container.height` defines the row
|
|
830
|
+
allocation before legacy `row.height`; without either, retain the template
|
|
831
|
+
measurement rules, including existing style-dependent text/image/padding
|
|
832
|
+
sizing. A new template documents whether its default height is a preset or
|
|
833
|
+
measured. Integrators can set a fitting style height without changing the
|
|
834
|
+
row model. Do not add automatic content fitting or font shrinking.
|
|
835
|
+
3. **Integrator: fit content within that frame.** After local padding, each visual/control
|
|
836
|
+
and text line box must fit. For a simple two-line identity row, a necessary
|
|
837
|
+
check is `2 * verticalPadding + max(visualHeight, titleLineHeight + lineGap +
|
|
838
|
+
subtitleLineHeight, trailingHeight) <= resolvedRowHeight`. More lines and template
|
|
839
|
+
subrows add their own budget. This formula is an example, not a general row
|
|
840
|
+
measurement algorithm; it does not cover platform font scaling or rounding.
|
|
841
|
+
4. **Integrator: verify compression and interaction.** Text may truncate/wrap only inside
|
|
842
|
+
its declared slot and line limit. It must not push a checkbox, menu, price or
|
|
843
|
+
action outside the row, overlap a neighbor, or steal another control's hit
|
|
844
|
+
target. Do not shrink hit areas to make an oversized style fit. `start`/`end`
|
|
845
|
+
follow layout direction; color changes must preserve readable contrast.
|
|
846
|
+
5. **Reject unsupported structure.** No arbitrary React/RN/CSS style bag is part
|
|
847
|
+
of this contract. `position`, `transform`, `flexDirection`, negative margins,
|
|
848
|
+
freeform children and view-slot names are not supported style parameters.
|
|
849
|
+
Public types and nested runtime allowlists constrain this surface. These
|
|
850
|
+
checks deliberately do not validate whether a combination fits the row.
|
|
851
|
+
6. **Retain local scope through every path.** Snapshot, patch, style removal,
|
|
852
|
+
scrolling reuse, footer binding and sticky rendering must agree. A parent
|
|
853
|
+
`walletGroup.style` must not silently become the child members' text style;
|
|
854
|
+
members use their own `IdentityRow.style`.
|
|
855
|
+
|
|
856
|
+
Example: `height: 48` with two 24-point text lines and 12-point top/bottom padding
|
|
857
|
+
needs at least 72 points before considering other content. Each number is legal
|
|
858
|
+
individually, but that combination is **not layout-safe**. Current validation
|
|
859
|
+
accepts such combinations; callers must choose a fitting explicit height and
|
|
860
|
+
verify the result. Do not describe numeric bounds as a clipping-prevention feature.
|
|
861
|
+
|
|
862
|
+
## 8. Where style is applied
|
|
863
|
+
|
|
864
|
+
The style pass belongs after template and presentation defaults. All 12 row types
|
|
865
|
+
retain their own binders; adding a local style must not replace their structure.
|
|
866
|
+
|
|
867
|
+
| Platform | Owner | Order and reset |
|
|
868
|
+
| --- | --- | --- |
|
|
869
|
+
| iOS | Registered template renderer and `NativeListRendererCell` | Renderer restores/resolves current template defaults, then applies semantic text/image/spacing styles; host owns common container appearance and action epochs |
|
|
870
|
+
| Android | Registered template renderer and `NativeListRendererRowView` | Renderer applies current defaults and local styles; shared host owns allocation, container appearance and separators |
|
|
871
|
+
| Web | Registered template `bind()` plus `RowContainerStyle` | Template resets its own nodes and applies local styles; engine applies common row chrome. WalletGroup delegates each member to Identity before applying its group container |
|
|
872
|
+
|
|
873
|
+
All twelve templates have dedicated renderer families. Bind must restore omitted
|
|
874
|
+
style fields even without a prior recycle call; same-key template changes replace
|
|
875
|
+
an incompatible host/body. No global restoration/slot map remains. A renderer
|
|
876
|
+
may retain a bounded, template-local typography baseline where its platform
|
|
877
|
+
needs it; it must never obtain defaults from another template's previous binding.
|
|
878
|
+
|
|
879
|
+
Measurement uses the current data/theme/style inputs. Web renderers report
|
|
880
|
+
rendered intrinsic heights through the registry; the container has no separate
|
|
881
|
+
warning-view measurement fallback. The System renderer reports an automatic-height
|
|
882
|
+
warning's rendered border-box height (padding, both borders, wrapped text), as
|
|
883
|
+
the legacy engine did; `row.height`/`style.container.height` skip it. Image primitives retain unchanged effective
|
|
884
|
+
requests and invalidate callbacks on source/slot replacement or recycle.
|
|
885
|
+
|
|
886
|
+
Selector tabular typography belongs to Identity, SectionHeader and Action.
|
|
887
|
+
WalletGroup supplies its own compact preview through the internal renderer
|
|
888
|
+
contract; the container does not search for a parent view inside its DOM.
|
|
889
|
+
Native containers likewise call the row-host lifecycle instead of casting to
|
|
890
|
+
concrete template views. Shared action hit-testing, checkbox/selection routing
|
|
891
|
+
and image leases remain common list capabilities.
|
|
892
|
+
|
|
893
|
+
The six-stage migration and dated runtime results are recorded in
|
|
894
|
+
[DESIGN.md](DESIGN.md#incremental-renderer-migration) and SPEC.md. New templates
|
|
895
|
+
must implement the same binding/reset/measurement lifecycle and register a
|
|
896
|
+
family on all three platforms; there is no legacy fallback or public plugin API.
|
|
897
|
+
|
|
898
|
+
## 9. Review checklist
|
|
899
|
+
|
|
900
|
+
For any PR that touches list typography or geometry:
|
|
901
|
+
|
|
902
|
+
- [ ] Does the change introduce a new bare font size or radius? If so, which token
|
|
903
|
+
in §3 should it be, or does §3 need a new step?
|
|
904
|
+
- [ ] Is the value applied identically on all three platforms? If not, is the
|
|
905
|
+
divergence recorded in §6?
|
|
906
|
+
- [ ] Does a new style key modify a **model field** name (§4), not a view name?
|
|
907
|
+
- [ ] Does the style pass write an explicit default for every property it can set
|
|
908
|
+
(§7 rule 2)?
|
|
909
|
+
- [ ] Is any decision derived from the whole row list rather than one row
|
|
910
|
+
(§7 rule 3)?
|
|
911
|
+
- [ ] For a section header: does the shared pinned row retain styles and interaction
|
|
912
|
+
(§6.3)?
|
|
913
|
+
- [ ] Does each style-only update preserve the allocated row frame and slot order?
|
|
914
|
+
Has the integrator verified fitting content and control hit areas (§7.1)?
|
|
915
|
+
- [ ] Can a content template be replaced while retaining header/footer, sections,
|
|
916
|
+
scrolling and the indexed bar (§5.1)?
|
|
917
|
+
|
|
918
|
+
### Acceptance matrix
|
|
919
|
+
|
|
920
|
+
Run these checks on iOS, Android and Web before calling the spec implemented.
|
|
921
|
+
Source review, Swift parsing and JS unit tests do not replace native builds and
|
|
922
|
+
rendered interaction checks.
|
|
923
|
+
|
|
924
|
+
| Area | Required cases | Pass condition |
|
|
925
|
+
| --- | --- | --- |
|
|
926
|
+
| Template inventory | All 12 types in the catalog; selector presentations; composite metric cards; linear/table data rows; system variants | Named fields receive the intended style, unsupported fields fail explicitly |
|
|
927
|
+
| Baseline | Style omitted, style toggled off, existing Market consumers | Platform's baseline rendering and row geometry preserved |
|
|
928
|
+
| Integration layout | Integrator-selected values at narrow widths, with long/localized text, RTL and supported font scaling | The integrator verifies overflow and hit areas; the component preserves the template structure and allocated frame |
|
|
929
|
+
| Reuse and updates | Styled → unstyled → another template; snapshot and patch; rapid scrolling | No leaked typography, alignment, colors or metrics |
|
|
930
|
+
| Public composition | Mixed `identity`/`market`/`dataRow` sections, summary header, fixed footer, indexed bar | Swapping content templates preserves common capabilities and stable scroll targets |
|
|
931
|
+
| Scroll / chrome | Initial scroll, scroll to key/index/location/end, resize, refresh, pagination, initial/chrome-only snapshots | Correct target/offset and consistent separator/group style without row-size changes |
|
|
932
|
+
| Independent scopes | Two list instances, grouped and non-grouped rows, nested wallet members | No style or index-host effect outside the intended list/group/row |
|
|
933
|
+
|
|
934
|
+
Next acceptance work should execute the device cases in §6.5 and record
|
|
935
|
+
platform-specific build and interaction evidence. The shared document is
|
|
936
|
+
the contract; no shared cross-language renderer or code generator is required.
|
|
937
|
+
|
|
938
|
+
### Verification — 2026-09-24 (Web legacy-default audit)
|
|
939
|
+
|
|
940
|
+
- Package typecheck passes; 180 package tests in five suites pass, including
|
|
941
|
+
(review round 3) Market subscript runs keeping the legacy 60% size under an
|
|
942
|
+
explicit `fontSize`, System warning layout at its rendered DOM height,
|
|
943
|
+
System/MetricCard text-layout styles (`lines: 1` text rewrite, `offsetY`
|
|
944
|
+
wrapper) clearing to a fresh unstyled render, walletSidebar `leadingGap`
|
|
945
|
+
cleared from reused visuals (standalone and WalletGroup members),
|
|
946
|
+
regressions for loaded MetricCard images across content/style rebinding,
|
|
947
|
+
WalletGroup vertical padding replacing the legacy inset in measured and
|
|
948
|
+
rendered height, message-only retry rows (Market included) firing on click
|
|
949
|
+
and Enter, member-only hover and member-press blocking for WalletGroup,
|
|
950
|
+
renderer values equal to the previous container write surviving a rebind,
|
|
951
|
+
container alignment cleared
|
|
952
|
+
from reused Activity/DataRow children, legacy table DataRow height (48) and
|
|
953
|
+
linear column structure,
|
|
954
|
+
selector geometry from `style.container.height`, attached/detached text
|
|
955
|
+
styling parity, `listStyle.separator.color` validation and the app Market
|
|
956
|
+
builder's style keys.
|
|
957
|
+
- These are jsdom/unit checks of the Web renderers and validation; they are not
|
|
958
|
+
rendered browser or native device acceptance.
|
|
959
|
+
|
|
960
|
+
### Verification — 2026-09-22 (historical, before the renderer migration)
|
|
961
|
+
|
|
962
|
+
- Package TypeScript and JavaScript: 109 tests in five suites pass. Added cases
|
|
963
|
+
cover per-template keys/colors, rich text and badge isolation, independent data
|
|
964
|
+
columns, media dimensions, local gaps, status/retry targets and value accessories.
|
|
965
|
+
- Package lint: zero errors, 21 existing warnings. Example and documented
|
|
966
|
+
composition TypeScript pass; the example covers 12 templates with 42 rows.
|
|
967
|
+
- Android: `:onekeyfe_react-native-native-list:compileDebugKotlin` and
|
|
968
|
+
`:onekeyfe_react-native-native-list:testDebugUnitTest` pass (seven tests in three
|
|
969
|
+
suites), using the example Gradle project with `--configure-on-demand`.
|
|
970
|
+
- iOS: the modified models/assets/cell typecheck with the real simulator UIKit
|
|
971
|
+
SDK and a test double matching the public OneKeyImage view interface. This does
|
|
972
|
+
not verify the image implementation, linking or a full application build.
|
|
973
|
+
- An isolated iOS simulator check could not start: CoreSimulator was denied
|
|
974
|
+
permission to create the external-drive device data (Cocoa 513 / EPERM).
|
|
975
|
+
No native rendered interaction acceptance is claimed. Execute the matrix above
|
|
976
|
+
before treating the refactor as device-accepted.
|
|
977
|
+
|
|
978
|
+
Overflow fitting is the integrating developer's responsibility.
|