@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
@@ -47,6 +47,7 @@ function bindLegacy(
47
47
  }
48
48
  const visualKey = JSON.stringify([
49
49
  row.key,
50
+ row.hideLeading,
50
51
  row.leading,
51
52
  row.secondaryLeading,
52
53
  ]);
@@ -55,7 +56,7 @@ function bindLegacy(
55
56
  primitives.dispose(node);
56
57
  node.remove();
57
58
  });
58
- v.visuals = [row.leading, row.secondaryLeading]
59
+ v.visuals = (row.hideLeading ? [] : [row.leading, row.secondaryLeading])
59
60
  .map((source) => primitives.visual(source))
60
61
  .filter((node): node is HTMLElement => !!node);
61
62
  v.visuals.forEach((node) => body.insertBefore(node, v.column));
@@ -389,11 +390,12 @@ function bindHistory(
389
390
  Object.assign(v.identity.style, {
390
391
  display: 'flex',
391
392
  alignItems: 'center',
392
- gap: '12px',
393
+ gap: row.hideLeading ? '0' : '12px',
393
394
  minWidth: '0',
394
395
  flex: '1 1 0',
395
396
  });
396
397
  Object.assign(v.visual.style, {
398
+ display: row.hideLeading ? 'none' : '',
397
399
  position: 'relative',
398
400
  width: '40px',
399
401
  height: '40px',
@@ -401,6 +403,7 @@ function bindHistory(
401
403
  });
402
404
  const visualKey = JSON.stringify([
403
405
  row.key,
406
+ row.hideLeading,
404
407
  row.leading,
405
408
  row.secondaryLeading,
406
409
  ]);
@@ -410,7 +413,9 @@ function bindHistory(
410
413
  );
411
414
  v.visual.replaceChildren();
412
415
  v.visuals = [];
413
- const sources = [row.leading, row.secondaryLeading].filter(Boolean);
416
+ const sources = row.hideLeading
417
+ ? []
418
+ : [row.leading, row.secondaryLeading].filter(Boolean);
414
419
  sources.forEach((source) => {
415
420
  const visual = primitives.visual(source);
416
421
  if (!visual) return;
@@ -439,7 +444,9 @@ function bindHistory(
439
444
  });
440
445
  v.visualKey = visualKey;
441
446
  }
442
- const sources = [row.leading, row.secondaryLeading].filter(Boolean);
447
+ const sources = row.hideLeading
448
+ ? []
449
+ : [row.leading, row.secondaryLeading].filter(Boolean);
443
450
  v.visuals.forEach((retained, index) => {
444
451
  const { node: visual, style, background, loaded } = retained;
445
452
  const source = sources[index];
@@ -773,8 +780,9 @@ function bindHistory(
773
780
  20,
774
781
  400
775
782
  );
776
- v.actions.style.cssText =
777
- 'display:flex;gap:12px;padding-left:52px;padding-bottom:4px;';
783
+ v.actions.style.cssText = `display:flex;gap:12px;padding-left:${
784
+ row.hideLeading ? 0 : 52
785
+ }px;padding-bottom:4px;`;
778
786
  v.actions.style.display = footer ? 'flex' : 'none';
779
787
  const actionKeys = new Set<string>();
780
788
  for (const [index, action] of (row.footerActions ?? []).entries()) {
@@ -0,0 +1,58 @@
1
+ import type { ButtonGroupRow } from '../../models';
2
+ import { markActionAnchorSource } from './RowElements';
3
+ import type { RowPrimitives } from './RowVisual';
4
+
5
+ function bind(body: HTMLElement, row: ButtonGroupRow) {
6
+ body.replaceChildren();
7
+ body.className = 'ok-native-list-row ok-native-list-button-group';
8
+ body.removeAttribute('data-nl-container-background');
9
+ body.removeAttribute('data-nl-container-border');
10
+ row.buttons.forEach((descriptor, index) => {
11
+ const button = body.ownerDocument.createElement('button');
12
+ button.type = 'button';
13
+ button.className = 'ok-native-list-block-button';
14
+ button.dataset.nativeListAction = descriptor.actionKey;
15
+ button.dataset.nlButtonKey = descriptor.key;
16
+ button.dataset.nlSlot = `button-${index}`;
17
+ if (descriptor.testID) button.dataset.testid = descriptor.testID;
18
+ button.setAttribute(
19
+ 'aria-label',
20
+ descriptor.accessibilityLabel ?? descriptor.label
21
+ );
22
+ button.disabled = Boolean(
23
+ row.disabled || descriptor.disabled || descriptor.loading
24
+ );
25
+ button.setAttribute('aria-busy', descriptor.loading ? 'true' : 'false');
26
+ markActionAnchorSource(button, 'buttonGroup', index);
27
+ const label = body.ownerDocument.createElement('span');
28
+ label.textContent = descriptor.label;
29
+ if (descriptor.loading) {
30
+ const spinner = body.ownerDocument.createElement('span');
31
+ spinner.className = 'ok-native-list-spinner';
32
+ spinner.setAttribute('role', 'progressbar');
33
+ button.appendChild(spinner);
34
+ }
35
+ button.appendChild(label);
36
+ body.appendChild(button);
37
+ });
38
+ }
39
+
40
+ function create(
41
+ document: Document,
42
+ row: ButtonGroupRow,
43
+ _primitives: RowPrimitives
44
+ ) {
45
+ const body = document.createElement('div');
46
+ body.dataset.nlRenderer = 'buttonGroup';
47
+ bind(body, row);
48
+ return body;
49
+ }
50
+
51
+ export const buttonGroupRowRenderer = {
52
+ key: 'buttonGroup' as const,
53
+ create,
54
+ bind,
55
+ recycle: (body: HTMLElement) => body.replaceChildren(),
56
+ measure: (_row: ButtonGroupRow, _width: number) => 64,
57
+ measureRendered: (body: HTMLElement) => body.scrollHeight,
58
+ };
@@ -1,10 +1,17 @@
1
1
  import type { DataRow, TextTone } from '../../models';
2
- import { createElement, tagSlot } from './RowElements';
2
+ import {
3
+ createElement,
4
+ markActionAnchorSource,
5
+ setData,
6
+ tagSlot,
7
+ } from './RowElements';
8
+ import { applyValueSegments, fontWeight } from './RowText';
3
9
  import { RowVisualStyle, type RowPrimitives } from './RowVisual';
4
10
  type ColumnViews = {
5
11
  body: HTMLElement;
6
12
  primaryLine: HTMLElement;
7
13
  primary: HTMLElement;
14
+ titleIcons: HTMLElement;
8
15
  badges: HTMLElement;
9
16
  secondaryLine: HTMLElement;
10
17
  secondary: HTMLElement[];
@@ -13,6 +20,7 @@ type Views = {
13
20
  favorite: HTMLElement;
14
21
  index: HTMLElement;
15
22
  controls: HTMLElement;
23
+ trailing: HTMLElement;
16
24
  columns: ColumnViews[];
17
25
  visual?: HTMLElement;
18
26
  visualStyle?: RowVisualStyle;
@@ -43,6 +51,11 @@ function bind(body: HTMLElement, row: DataRow, primitives: RowPrimitives) {
43
51
  if (row.checkbox)
44
52
  v.controls.appendChild(primitives.accessory(row.key, row.checkbox, 0));
45
53
  v.controls.style.display = 'contents';
54
+ v.trailing.replaceChildren();
55
+ row.trailing?.forEach((accessory, index) =>
56
+ v.trailing.appendChild(primitives.accessory(row.key, accessory, index))
57
+ );
58
+ v.trailing.hidden = !row.trailing?.length;
46
59
  v.index.style.cssText = '';
47
60
  v.index.hidden = row.index === undefined;
48
61
  v.index.textContent = String(row.index ?? '');
@@ -88,7 +101,41 @@ function bind(body: HTMLElement, row: DataRow, primitives: RowPrimitives) {
88
101
  cell.primaryLine.style.gap = `${style.titleBadgeGap}px`;
89
102
  cell.primary.style.cssText = '';
90
103
  cell.primary.textContent = column.text;
104
+ applyValueSegments(
105
+ cell.primary,
106
+ column.textSegments,
107
+ style?.columns?.fontSize ?? 14,
108
+ style?.columns?.lineHeight ?? 20,
109
+ fontWeight(style?.columns?.fontWeight, 500)
110
+ );
91
111
  if (style?.columns) primitives.textStyle(cell.primary, style.columns);
112
+ cell.titleIcons.replaceChildren();
113
+ if (column.titleIcon || column.secondaryTitleIcon) {
114
+ cell.primary.style.minWidth = '0';
115
+ cell.primary.style.overflow = 'hidden';
116
+ cell.primary.style.textOverflow = 'ellipsis';
117
+ cell.primary.style.whiteSpace = 'nowrap';
118
+ [column.titleIcon, column.secondaryTitleIcon]
119
+ .filter((icon): icon is NonNullable<typeof icon> => icon !== undefined)
120
+ .forEach((descriptor) => {
121
+ const icon = primitives.iconAction(
122
+ descriptor.name,
123
+ undefined,
124
+ undefined,
125
+ descriptor.tintColor
126
+ );
127
+ icon.className = 'ok-native-list-identity-title-icon';
128
+ if (descriptor.hoverActionKey) {
129
+ setData(icon, 'nativeListHoverAction', descriptor.hoverActionKey);
130
+ setData(icon, 'nativeListIconHelp', true);
131
+ markActionAnchorSource(icon, 'leadingAction');
132
+ }
133
+ if (descriptor.accessibilityLabel)
134
+ icon.setAttribute('aria-label', descriptor.accessibilityLabel);
135
+ else icon.setAttribute('aria-hidden', 'true');
136
+ cell.titleIcons.appendChild(icon);
137
+ });
138
+ }
92
139
  cell.badges.replaceChildren();
93
140
  cell.badges.style.display = 'contents';
94
141
  if (column.key === 'asset')
@@ -107,25 +154,34 @@ function bind(body: HTMLElement, row: DataRow, primitives: RowPrimitives) {
107
154
  });
108
155
  // Legacy Web layout: secondary leading text sits inline before the primary
109
156
  // text; secondary text is a direct line below it, toned by secondaryTone.
110
- cell.secondaryLine.style.display = column.secondaryText
111
- ? 'contents'
112
- : 'none';
113
- [column.secondaryLeadingText, column.secondaryText].forEach(
114
- (text, slot) => {
115
- const label = cell.secondary[slot]!;
116
- label.hidden = !text;
117
- if (!text) label.remove();
118
- else if (slot === 0) cell.primaryLine.insertBefore(label, cell.primary);
119
- else cell.secondaryLine.appendChild(label);
120
- label.style.cssText = '';
121
- label.textContent = text ?? '';
122
- if (slot === 1)
123
- label.style.color = tone(column.secondaryTone, 'secondary');
124
- if (style?.columnSecondary)
125
- primitives.textStyle(label, style.columnSecondary);
157
+ const secondaryText = column.secondaryTextSegments?.length
158
+ ? column.secondaryTextSegments.map((segment) => segment.text).join('')
159
+ : column.secondaryText;
160
+ cell.secondaryLine.style.display = secondaryText ? 'contents' : 'none';
161
+ [column.secondaryLeadingText, secondaryText].forEach((text, slot) => {
162
+ const label = cell.secondary[slot]!;
163
+ label.hidden = !text;
164
+ if (!text) label.remove();
165
+ else if (slot === 0) cell.primaryLine.insertBefore(label, cell.primary);
166
+ else cell.secondaryLine.appendChild(label);
167
+ label.style.cssText = '';
168
+ label.textContent = text ?? '';
169
+ if (slot === 1) {
170
+ applyValueSegments(
171
+ label,
172
+ column.secondaryTextSegments,
173
+ style?.columnSecondary?.fontSize ?? 12,
174
+ style?.columnSecondary?.lineHeight ?? 16,
175
+ fontWeight(style?.columnSecondary?.fontWeight, 400)
176
+ );
126
177
  }
127
- );
178
+ if (slot === 1)
179
+ label.style.color = tone(column.secondaryTone, 'secondary');
180
+ if (style?.columnSecondary)
181
+ primitives.textStyle(label, style.columnSecondary);
182
+ });
128
183
  });
184
+ body.appendChild(v.trailing);
129
185
  }
130
186
  function create(document: Document, row: DataRow, primitives: RowPrimitives) {
131
187
  const body = createElement(
@@ -140,6 +196,11 @@ function create(document: Document, row: DataRow, primitives: RowPrimitives) {
140
196
  );
141
197
  const favorite = createElement(document, 'span', 'ok-native-list-favorite');
142
198
  const controls = createElement(document, 'span');
199
+ const trailing = createElement(
200
+ document,
201
+ 'span',
202
+ 'ok-native-list-accessories'
203
+ );
143
204
  body.append(controls, index, favorite);
144
205
  const columns = Array.from({ length: 4 }, () => {
145
206
  const cell = createElement(document, 'span', 'ok-native-list-data-cell');
@@ -149,8 +210,10 @@ function create(document: Document, row: DataRow, primitives: RowPrimitives) {
149
210
  'ok-native-list-data-primary'
150
211
  );
151
212
  const primary = tagSlot(createElement(document, 'span'), 'columns');
213
+ const titleIcons = createElement(document, 'span');
214
+ titleIcons.style.display = 'contents';
152
215
  const badges = createElement(document, 'span');
153
- primaryLine.append(primary, badges);
216
+ primaryLine.append(primary, titleIcons, badges);
154
217
  const secondaryLine = createElement(
155
218
  document,
156
219
  'span',
@@ -169,15 +232,18 @@ function create(document: Document, row: DataRow, primitives: RowPrimitives) {
169
232
  body: cell,
170
233
  primaryLine,
171
234
  primary,
235
+ titleIcons,
172
236
  badges,
173
237
  secondaryLine,
174
238
  secondary,
175
239
  };
176
240
  });
241
+ body.appendChild(trailing);
177
242
  views.set(body, {
178
243
  favorite,
179
244
  index,
180
245
  controls,
246
+ trailing,
181
247
  columns,
182
248
  visualKey: '',
183
249
  primitives,
@@ -196,6 +262,7 @@ function recycle(body: HTMLElement) {
196
262
  v.visualStyle = undefined;
197
263
  v.visualKey = '';
198
264
  v.controls.replaceChildren();
265
+ v.trailing.replaceChildren();
199
266
  }
200
267
  export const dataRowRenderer = {
201
268
  key: 'dataRow' as const,
@@ -204,7 +271,13 @@ export const dataRowRenderer = {
204
271
  recycle,
205
272
  // Legacy presets: table rows without secondary text are 8 units shorter.
206
273
  measure: (row: DataRow, _width: number, layout?: string) => {
207
- const secondary = row.columns.some((column) => column.secondaryText);
274
+ const secondary = row.columns.some((column) =>
275
+ column.secondaryTextSegments?.length
276
+ ? column.secondaryTextSegments.some(
277
+ (segment) => segment.text.length > 0
278
+ )
279
+ : !!column.secondaryText
280
+ );
208
281
  return (secondary ? 60 : 56) + (layout === 'table' && !secondary ? -8 : 0);
209
282
  },
210
283
  measureRendered: (_body: HTMLElement, _row: DataRow): number | undefined =>
@@ -226,6 +226,117 @@ function createIdentityRow(
226
226
  });
227
227
  column.insertBefore(segments, titleElement.nextSibling);
228
228
  }
229
+ if (row.titleIcon || row.secondaryTitleIcon) {
230
+ const badgeLine = titleElement.querySelector<HTMLElement>(
231
+ ':scope > .ok-native-list-badges'
232
+ );
233
+ const titleText = createElement(
234
+ document,
235
+ 'span',
236
+ 'ok-native-list-identity-title-text'
237
+ );
238
+ while (titleElement.firstChild && titleElement.firstChild !== badgeLine)
239
+ titleText.appendChild(titleElement.firstChild);
240
+ titleElement.prepend(titleText);
241
+ const icons = [row.titleIcon, row.secondaryTitleIcon]
242
+ .filter((icon): icon is NonNullable<typeof icon> => icon !== undefined)
243
+ .map((descriptor) => {
244
+ const icon = primitives.iconAction(
245
+ descriptor.name,
246
+ undefined,
247
+ undefined,
248
+ descriptor.tintColor
249
+ );
250
+ icon.className = 'ok-native-list-identity-title-icon';
251
+ if (descriptor.hoverActionKey) {
252
+ setData(icon, 'nativeListHoverAction', descriptor.hoverActionKey);
253
+ setData(icon, 'nativeListIconHelp', true);
254
+ markActionAnchorSource(icon, 'leadingAction');
255
+ }
256
+ if (descriptor.accessibilityLabel)
257
+ icon.setAttribute('aria-label', descriptor.accessibilityLabel);
258
+ else icon.setAttribute('aria-hidden', 'true');
259
+ return icon;
260
+ });
261
+ titleText.after(...icons);
262
+ titleElement.style.display = 'flex';
263
+ titleElement.style.alignItems = 'center';
264
+ titleElement.style.gap = '4px';
265
+ }
266
+ if (row.titleSuffixes?.length) {
267
+ const badges = titleElement.querySelector(
268
+ ':scope > .ok-native-list-badges'
269
+ );
270
+ let titleText = titleElement.querySelector<HTMLElement>(
271
+ ':scope > .ok-native-list-identity-title-text, :scope > [data-nl-slot="title"]'
272
+ );
273
+ if (!titleText) {
274
+ titleText = tagSlot(
275
+ createElement(document, 'span', 'ok-native-list-identity-title-text'),
276
+ 'title'
277
+ );
278
+ while (titleElement.firstChild && titleElement.firstChild !== badges)
279
+ titleText.appendChild(titleElement.firstChild);
280
+ titleElement.prepend(titleText);
281
+ titleElement.removeAttribute('data-nl-slot');
282
+ } else if (titleElement.hasAttribute('data-nl-slot')) {
283
+ titleElement.removeAttribute('data-nl-slot');
284
+ tagSlot(titleText, 'title');
285
+ }
286
+ titleElement.style.display = 'flex';
287
+ titleElement.style.alignItems = 'center';
288
+ titleText.style.flex = '0 1 auto';
289
+ titleText.style.minWidth = 'min(80px, 100%)';
290
+ titleText.style.overflow = 'hidden';
291
+ titleText.style.textOverflow = 'ellipsis';
292
+ titleText.style.whiteSpace = 'nowrap';
293
+ row.titleSuffixes.forEach((suffix) => {
294
+ const label = createElement(
295
+ document,
296
+ 'span',
297
+ 'ok-native-list-identity-title-suffix',
298
+ suffix.text
299
+ );
300
+ label.style.flex = '0 1 auto';
301
+ label.style.minWidth = '0';
302
+ label.style.marginInlineStart =
303
+ row.titleIcon || row.secondaryTitleIcon ? '4px' : '8px';
304
+ label.style.maxWidth =
305
+ suffix.maxWidth === undefined ? '' : `${suffix.maxWidth}px`;
306
+ label.style.overflow = 'hidden';
307
+ label.style.textOverflow = 'ellipsis';
308
+ label.style.whiteSpace = 'nowrap';
309
+ label.style.fontSize = '12px';
310
+ label.style.lineHeight = '16px';
311
+ label.style.fontWeight = '400';
312
+ label.style.fontFamily = 'var(--nl-font-regular)';
313
+ label.style.color =
314
+ suffix.tone === 'disabled'
315
+ ? 'var(--nl-disabled)'
316
+ : 'var(--nl-secondary)';
317
+ titleElement.insertBefore(label, badges);
318
+ });
319
+ }
320
+ if (row.badges?.length && !row.titleIcon && !row.secondaryTitleIcon) {
321
+ const titleText = titleElement.querySelector<HTMLElement>(
322
+ ':scope > [data-nl-slot="title"]'
323
+ );
324
+ const badges = titleElement.querySelector<HTMLElement>(
325
+ ':scope > .ok-native-list-badges'
326
+ );
327
+ titleElement.style.display = 'flex';
328
+ titleElement.style.alignItems = 'center';
329
+ if (titleText) {
330
+ titleText.style.flex = '0 1 auto';
331
+ titleText.style.minWidth = row.titleSuffixes?.length
332
+ ? 'min(80px, 100%)'
333
+ : '0';
334
+ }
335
+ if (badges) {
336
+ badges.style.flex = '0 1 auto';
337
+ badges.style.minWidth = '0';
338
+ }
339
+ }
229
340
  if (presentation === 'walletSidebar' && row.badges?.length) {
230
341
  const badges = createElement(
231
342
  document,
@@ -242,7 +353,13 @@ function createIdentityRow(
242
353
  if (lines !== undefined)
243
354
  column
244
355
  .querySelectorAll<HTMLElement>(`[data-nl-slot="${key}"]`)
245
- .forEach((text) => applyTextLayout(text, { lines }));
356
+ .forEach((text) =>
357
+ applyTextLayout(
358
+ text,
359
+ lines === 0 ? {} : { lines },
360
+ lines === 0 ? 0 : undefined
361
+ )
362
+ );
246
363
  }
247
364
  }
248
365
  body.appendChild(column);
@@ -272,6 +389,29 @@ function applyIdentityStyle(body: HTMLElement, row: IdentityRow) {
272
389
  const leading = body.querySelector<HTMLElement>(
273
390
  ':scope > .ok-native-list-visual, :scope > .ok-native-list-stacked'
274
391
  );
392
+ if (row.style?.leadingContentMaxWidthRatio !== undefined && !vertical) {
393
+ const column = body.querySelector<HTMLElement>(
394
+ ':scope > .ok-native-list-flex'
395
+ );
396
+ const trailing = body.querySelector<HTMLElement>(
397
+ ':scope > .ok-native-list-accessories'
398
+ );
399
+ const visualWidth = leading
400
+ ? row.style.image?.width ??
401
+ (row.presentation === 'accountSelector' ||
402
+ row.presentation === 'networkSelector'
403
+ ? 32
404
+ : 40)
405
+ : 0;
406
+ const gap = leading ? row.style.leadingGap ?? 12 : 0;
407
+ if (column) {
408
+ column.style.flex = '0 1 auto';
409
+ column.style.maxWidth = `calc(${
410
+ row.style.leadingContentMaxWidthRatio * 100
411
+ }% - ${visualWidth + gap}px)`;
412
+ }
413
+ if (trailing) trailing.style.marginInlineStart = 'auto';
414
+ }
275
415
  const defaultGap =
276
416
  body.style.gap ||
277
417
  (row.type === 'identity' && row.presentation === 'walletSidebar'
@@ -343,7 +483,7 @@ function applyIdentityStyle(body: HTMLElement, row: IdentityRow) {
343
483
  const images = leading.matches('img')
344
484
  ? [leading]
345
485
  : leading.querySelectorAll<HTMLElement>(
346
- ':scope > img, :scope > .ok-native-list-visual-fallback'
486
+ ':scope > img:not(.ok-native-list-visual-corner), :scope > .ok-native-list-visual-fallback:not(.ok-native-list-visual-corner)'
347
487
  );
348
488
  images.forEach((bitmap) => {
349
489
  if (bitmap !== leading) {
@@ -522,7 +662,12 @@ export const identityRowRenderer = {
522
662
  ? 60
523
663
  : 56,
524
664
  measureRendered: (
525
- _body: HTMLElement,
526
- _row: IdentityRow
527
- ): number | undefined => undefined,
665
+ body: HTMLElement,
666
+ row: IdentityRow
667
+ ): number | undefined => {
668
+ if (row.contentHeight !== 'auto' || hasExplicitRowHeight(row))
669
+ return undefined;
670
+ const height = body.getBoundingClientRect().height;
671
+ return height > 0 ? height : undefined;
672
+ },
528
673
  };
@@ -4,10 +4,8 @@ import type { MessageRow, NativeListTextStyle } from '../../models';
4
4
 
5
5
  type Text = Readonly<{
6
6
  value: string;
7
- style: Required<
8
- Pick<NativeListTextStyle, 'fontSize' | 'lineHeight' | 'lines'>
9
- > &
10
- NativeListTextStyle;
7
+ style: Required<Pick<NativeListTextStyle, 'fontSize' | 'lineHeight'>> &
8
+ Omit<NativeListTextStyle, 'lines'> & { lines: 0 | 1 | 2 | 3 };
11
9
  }>;
12
10
  export function resolveMessageRow(row: MessageRow) {
13
11
  const text = (
@@ -15,7 +13,7 @@ export function resolveMessageRow(row: MessageRow) {
15
13
  style: NativeListTextStyle | undefined,
16
14
  fontSize: number,
17
15
  lineHeight: number,
18
- lines: 1 | 2 | 3,
16
+ lines: 0 | 1 | 2 | 3,
19
17
  color: string,
20
18
  fontWeight: NativeListTextStyle['fontWeight'] = 'regular'
21
19
  ): Text => ({
@@ -117,6 +115,9 @@ export function bindMessageRow(
117
115
  body.style.cssText = '';
118
116
  body.removeAttribute('data-nl-container-background');
119
117
  body.removeAttribute('data-nl-container-border');
118
+ if (row.presentation === 'inlineWarning')
119
+ body.setAttribute('data-nl-inline-warning', 'true');
120
+ else body.removeAttribute('data-nl-inline-warning');
120
121
  body.style.padding = `${resolved.verticalPadding}px ${resolved.horizontalPadding}px`;
121
122
  body.style.height = resolved.automaticHeight ? 'auto' : '100%';
122
123
  views.column.style.cssText = '';
@@ -128,7 +129,19 @@ export function bindMessageRow(
128
129
  ] as const) {
129
130
  view.style.cssText = '';
130
131
  view.textContent = text.value;
131
- primitives.textStyle(view, text.style);
132
+ const { lines, ...textStyle } = text.style;
133
+ primitives.textStyle(
134
+ view,
135
+ lines === 0 ? textStyle : { ...textStyle, lines }
136
+ );
137
+ if (lines === 0) {
138
+ view.style.whiteSpace = 'pre-wrap';
139
+ view.style.overflow = 'visible';
140
+ view.style.textOverflow = 'clip';
141
+ view.style.maxHeight = 'none';
142
+ view.style.removeProperty('-webkit-line-clamp');
143
+ view.style.removeProperty('-webkit-box-orient');
144
+ }
132
145
  }
133
146
  views.unread.style.cssText = '';
134
147
  if (row.body) {
@@ -258,7 +271,10 @@ export function measureMessageRow(
258
271
  ),
259
272
  0
260
273
  );
261
- return Math.min(text.style.lines, lines) * text.style.lineHeight;
274
+ return (
275
+ (text.style.lines === 0 ? lines : Math.min(text.style.lines, lines)) *
276
+ text.style.lineHeight
277
+ );
262
278
  };
263
279
  const timeWidth = Math.min(
264
280
  availableWidth / 3,
@@ -88,6 +88,8 @@ export function applyRowContainerStyle(body: HTMLElement, row: RowModel): void {
88
88
  }
89
89
  if (container.cornerRadius !== undefined)
90
90
  set(body, 'border-radius', `${container.cornerRadius}px`);
91
+ if (row.type === 'message' && row.presentation === 'inlineWarning')
92
+ set(body, 'border-radius', '0px');
91
93
  if (container.borderWidth !== undefined) {
92
94
  // Paint above member backgrounds without changing the content box or hit targets.
93
95
  body.setAttribute('data-nl-container-border', 'true');
@@ -16,8 +16,15 @@ import type { SystemRow } from '../../models';
16
16
  import { systemRowRenderer } from './SystemRowRenderer';
17
17
  import type { ActionRow } from '../../models';
18
18
  import { actionRowRenderer } from './ActionRowRenderer';
19
+ import { buttonGroupRowRenderer } from './ButtonGroupRowRenderer';
19
20
  import { mediaTileRowRenderer } from './MediaTileRowRenderer';
20
- import type { MessageRow, RailRow, MediaTileRow, RowModel } from '../../models';
21
+ import type {
22
+ ButtonGroupRow,
23
+ MessageRow,
24
+ RailRow,
25
+ MediaTileRow,
26
+ RowModel,
27
+ } from '../../models';
21
28
  import { messageRowRenderer } from './MessageRowRenderer';
22
29
  import type { RowPrimitives } from './RowVisual';
23
30
  import { railRowRenderer } from './RailRowRenderer';
@@ -71,6 +78,8 @@ const factories = {
71
78
  registration(activityRowRenderer, row as ActivityRow),
72
79
  system: (row: RowModel) => registration(systemRowRenderer, row as SystemRow),
73
80
  action: (row: RowModel) => registration(actionRowRenderer, row as ActionRow),
81
+ buttonGroup: (row: RowModel) =>
82
+ registration(buttonGroupRowRenderer, row as ButtonGroupRow),
74
83
  message: (row: RowModel) =>
75
84
  registration(messageRowRenderer, row as MessageRow),
76
85
  rail: (row: RowModel) => registration(railRowRenderer, row as RailRow),
@@ -91,6 +100,7 @@ const renderers = {
91
100
  activity: activityRowRenderer,
92
101
  system: systemRowRenderer,
93
102
  action: actionRowRenderer,
103
+ buttonGroup: buttonGroupRowRenderer,
94
104
  message: messageRowRenderer,
95
105
  rail: railRowRenderer,
96
106
  mediaTile: mediaTileRowRenderer,