@onekeyfe/react-native-native-list 6.7.0-alpha.398 → 6.7.0-alpha.400

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.
@@ -11,7 +11,7 @@ import { measureRailWidth } from "./templates/RailRowRenderer.js";
11
11
  import { rowRenderer, rowRendererKey, recycleRowBody } from "./templates/RowRendererRegistry.js";
12
12
  import { checkboxStateForKeys, checkboxStateForSection, isRowPressEnabled, isSelectableRow, reduceSelection, selectionStateFromSnapshot } from "../selection.js";
13
13
  import { calculateAlignedScrollOffset, resolveLocationIndex, scrollFailure, validateOffset } from "../scrolling.js";
14
- import { applyRowPatches, containNativeListInput, emptyNativeListSnapshot, validatePatches, validateSnapshot } from "../validation.js";
14
+ import { applyValidatedRowPatches, containNativeListInput, emptyNativeListSnapshot, validateSnapshot } from "../validation.js";
15
15
  import { avatarPrefetchWindow } from "../avatarPrefetch.js";
16
16
  import { acquireNativeListAvatar, canonicalNativeListAvatarUri } from "./NativeListWebAvatarCache.js";
17
17
  const SECTION_INDEX_CONTENT_INSET = 16;
@@ -2007,20 +2007,14 @@ export class NativeListWebEngine {
2007
2007
  return this.snapshot;
2008
2008
  }
2009
2009
  applyPatches(patches) {
2010
- const accepted = containNativeListInput(() => {
2011
- const normalized = validatePatches(patches);
2012
- return {
2013
- patches: normalized,
2014
- snapshot: applyRowPatches(this.snapshot, normalized)
2015
- };
2016
- });
2010
+ const accepted = containNativeListInput(() => applyValidatedRowPatches(this.snapshot, patches));
2017
2011
  if (!accepted || accepted.patches.length === 0) return;
2018
2012
  patches = accepted.patches;
2019
2013
  const next = accepted.snapshot;
2020
2014
  const selection = new Set(this.selectedKeys);
2021
2015
  patches.forEach(patch => {
2022
2016
  const selected = 'selected' in patch.changes ? patch.changes.selected : undefined;
2023
- if (selected === true) selection.add(patch.key);else if (selected === false) selection.delete(patch.key);
2017
+ if (selected === true) selection.add(patch.key);else if (selected === false || selected === null) selection.delete(patch.key);
2024
2018
  });
2025
2019
  const tableValuesOnly = patches.every(patch => {
2026
2020
  const index = this.rows.findIndex(row => row.key === patch.key);
@@ -100,7 +100,7 @@ function createIdentityRow(document, primitives, row) {
100
100
  });
101
101
  column.insertBefore(segments, titleElement.nextSibling);
102
102
  }
103
- if (row.titleIcon || row.secondaryTitleIcon) {
103
+ if (row.titleIcon || row.secondaryTitleIcon || row.titleImage) {
104
104
  const badgeLine = titleElement.querySelector(':scope > .ok-native-list-badges');
105
105
  const titleText = createElement(document, 'span', 'ok-native-list-identity-title-text');
106
106
  while (titleElement.firstChild && titleElement.firstChild !== badgeLine) titleText.appendChild(titleElement.firstChild);
@@ -116,6 +116,38 @@ function createIdentityRow(document, primitives, row) {
116
116
  if (descriptor.accessibilityLabel) icon.setAttribute('aria-label', descriptor.accessibilityLabel);else icon.setAttribute('aria-hidden', 'true');
117
117
  return icon;
118
118
  });
119
+ if (row.titleImage) {
120
+ const descriptor = row.titleImage;
121
+ const holder = createElement(document, 'span', 'ok-native-list-identity-title-image');
122
+ Object.assign(holder.style, {
123
+ flex: `0 0 ${descriptor.image.width}px`,
124
+ width: `${descriptor.image.width}px`,
125
+ height: `${descriptor.image.height}px`,
126
+ borderRadius: '50%',
127
+ overflow: 'hidden'
128
+ });
129
+ const image = primitives.thumbnail(descriptor.image);
130
+ if (image) {
131
+ Object.assign(image.style, {
132
+ width: '100%',
133
+ height: '100%',
134
+ borderRadius: 'inherit',
135
+ objectFit: descriptor.image.contentFit === 'center' ? 'none' : descriptor.image.contentFit ?? 'cover'
136
+ });
137
+ image.setAttribute('alt', '');
138
+ holder.appendChild(image);
139
+ }
140
+ if (descriptor.hoverActionKey) {
141
+ setData(holder, 'nativeListHoverAction', descriptor.hoverActionKey);
142
+ setData(holder, 'nativeListIconHelp', true);
143
+ markActionAnchorSource(holder, 'leadingAction');
144
+ }
145
+ if (descriptor.accessibilityLabel) {
146
+ holder.setAttribute('role', 'img');
147
+ holder.setAttribute('aria-label', descriptor.accessibilityLabel);
148
+ } else holder.setAttribute('aria-hidden', 'true');
149
+ icons.push(holder);
150
+ }
119
151
  titleText.after(...icons);
120
152
  titleElement.style.display = 'flex';
121
153
  titleElement.style.alignItems = 'center';
@@ -144,7 +176,7 @@ function createIdentityRow(document, primitives, row) {
144
176
  const label = createElement(document, 'span', 'ok-native-list-identity-title-suffix', suffix.text);
145
177
  label.style.flex = '0 1 auto';
146
178
  label.style.minWidth = '0';
147
- label.style.marginInlineStart = row.titleIcon || row.secondaryTitleIcon ? '4px' : '8px';
179
+ label.style.marginInlineStart = row.titleIcon || row.secondaryTitleIcon || row.titleImage ? '4px' : '8px';
148
180
  label.style.maxWidth = suffix.maxWidth === undefined ? '' : `${suffix.maxWidth}px`;
149
181
  label.style.overflow = 'hidden';
150
182
  label.style.textOverflow = 'ellipsis';
@@ -157,7 +189,7 @@ function createIdentityRow(document, primitives, row) {
157
189
  titleElement.insertBefore(label, badges);
158
190
  });
159
191
  }
160
- if (row.badges?.length && !row.titleIcon && !row.secondaryTitleIcon) {
192
+ if (row.badges?.length && !row.titleIcon && !row.secondaryTitleIcon && !row.titleImage) {
161
193
  const titleText = titleElement.querySelector(':scope > [data-nl-slot="title"]');
162
194
  const badges = titleElement.querySelector(':scope > .ok-native-list-badges');
163
195
  titleElement.style.display = 'flex';
@@ -267,8 +299,22 @@ function bind(body, row, primitives) {
267
299
  const v = views.get(body);
268
300
  v.primitives = primitives;
269
301
  let used = false;
302
+ let usedTitleImage = false;
270
303
  const next = createIdentityRow(body.ownerDocument, {
271
304
  ...primitives,
305
+ thumbnail: source => {
306
+ usedTitleImage = true;
307
+ const key = JSON.stringify([row.key, source]);
308
+ if (v.titleImage?.key !== key) {
309
+ if (v.titleImage) primitives.dispose(v.titleImage.node);
310
+ const node = primitives.thumbnail(source);
311
+ v.titleImage = node ? {
312
+ key,
313
+ node
314
+ } : undefined;
315
+ }
316
+ return v.titleImage?.node;
317
+ },
272
318
  visual: (source, presentation) => {
273
319
  used = true;
274
320
  const key = JSON.stringify([row.key, source, presentation]);
@@ -293,6 +339,10 @@ function bind(body, row, primitives) {
293
339
  primitives.dispose(v.visual.node);
294
340
  v.visual = undefined;
295
341
  }
342
+ if (!usedTitleImage && v.titleImage) {
343
+ primitives.dispose(v.titleImage.node);
344
+ v.titleImage = undefined;
345
+ }
296
346
  body.replaceChildren(...Array.from(next.childNodes));
297
347
  body.className = next.className;
298
348
  body.style.cssText = next.style.cssText;
@@ -357,7 +407,11 @@ function create(document, row, primitives) {
357
407
  function recycle(body) {
358
408
  const v = views.get(body);
359
409
  if (v?.visual) v.primitives.dispose(v.visual.node);
360
- if (v) v.visual = undefined;
410
+ if (v?.titleImage) v.primitives.dispose(v.titleImage.node);
411
+ if (v) {
412
+ v.visual = undefined;
413
+ v.titleImage = undefined;
414
+ }
361
415
  body.replaceChildren();
362
416
  }
363
417
  export const identityRowRenderer = {
@@ -428,6 +428,13 @@ export type IdentityRow = RowBase & Readonly<{
428
428
  }>;
429
429
  /** Web-only additional help icon after titleIcon. */
430
430
  secondaryTitleIcon?: IdentityRow['titleIcon'];
431
+ /** Circular image after title icons; image dimensions must be square, 1...40. */
432
+ titleImage?: Readonly<{
433
+ image: ImageSource;
434
+ accessibilityLabel?: string;
435
+ /** Web-only help anchor; the consumer owns tooltip content. */
436
+ hoverActionKey?: string;
437
+ }>;
431
438
  subtitle?: string;
432
439
  tertiary?: string;
433
440
  tertiaryTone?: 'secondary' | 'info';
@@ -864,57 +871,61 @@ export type NativeListSnapshot = Readonly<{
864
871
  listStyle?: NativeListListStyle;
865
872
  }>;
866
873
  type CommonPatchFields = 'revision' | 'disabled' | 'selected' | 'separator' | 'height' | 'heightRounding' | 'opacity' | 'pressDisabled' | 'accessibilityLabel' | 'style';
874
+ /** Null removes optional fields; undefined leaves the existing value unchanged. */
875
+ export type PatchClearableChanges<Row, Keys extends keyof Row> = Readonly<{
876
+ [K in Keys]?: undefined extends Row[K] ? Row[K] | null : Row[K];
877
+ }>;
867
878
  export type RowPatch = Readonly<{
868
879
  type: 'identity';
869
880
  key: string;
870
- changes: Partial<Pick<IdentityRow, CommonPatchFields | 'title' | 'titleSuffixes' | 'titleIcon' | 'secondaryTitleIcon' | 'subtitle' | 'subtitleLines' | 'contentHeight' | 'longPressActionKey' | 'tertiary' | 'tertiaryTone' | 'badges' | 'trailing' | 'leading' | 'leadingAction' | 'titleMatch' | 'titleActionKey' | 'titleActionOnHover' | 'subtitleSegments'>>;
881
+ changes: PatchClearableChanges<IdentityRow, CommonPatchFields | 'title' | 'titleSuffixes' | 'titleIcon' | 'secondaryTitleIcon' | 'titleImage' | 'subtitle' | 'subtitleLines' | 'contentHeight' | 'longPressActionKey' | 'tertiary' | 'tertiaryTone' | 'badges' | 'trailing' | 'leading' | 'leadingAction' | 'titleMatch' | 'titleActionKey' | 'titleActionOnHover' | 'subtitleSegments'>;
871
882
  }> | Readonly<{
872
883
  type: 'rail';
873
884
  key: string;
874
- changes: Partial<Pick<RailRow, CommonPatchFields | 'title' | 'status' | 'badge' | 'visual'>>;
885
+ changes: PatchClearableChanges<RailRow, CommonPatchFields | 'title' | 'status' | 'badge' | 'visual'>;
875
886
  }> | Readonly<{
876
887
  type: 'activity';
877
888
  key: string;
878
- changes: Partial<Pick<ActivityRow, CommonPatchFields | 'title' | 'description' | 'status' | 'amounts' | 'badges' | 'presentation' | 'fee' | 'descriptionActionKey' | 'primaryAmount' | 'secondaryAmount' | 'leading' | 'hideLeading' | 'secondaryLeading' | 'footerActions'>>;
889
+ changes: PatchClearableChanges<ActivityRow, CommonPatchFields | 'title' | 'description' | 'status' | 'amounts' | 'badges' | 'presentation' | 'fee' | 'descriptionActionKey' | 'primaryAmount' | 'secondaryAmount' | 'leading' | 'hideLeading' | 'secondaryLeading' | 'footerActions'>;
879
890
  }> | Readonly<{
880
891
  type: 'message';
881
892
  key: string;
882
- changes: Partial<Pick<MessageRow, CommonPatchFields | 'unread' | 'title' | 'body' | 'bodyLines' | 'presentation' | 'time' | 'thumbnail'>>;
893
+ changes: PatchClearableChanges<MessageRow, CommonPatchFields | 'unread' | 'title' | 'body' | 'bodyLines' | 'presentation' | 'time' | 'thumbnail'>;
883
894
  }> | Readonly<{
884
895
  type: 'dataRow';
885
896
  key: string;
886
- changes: Partial<Pick<DataRow, CommonPatchFields | 'leading' | 'columns' | 'trailing' | 'checkbox' | 'index' | 'badge' | 'badges' | 'favorite' | 'favoriteActive'>>;
897
+ changes: PatchClearableChanges<DataRow, CommonPatchFields | 'leading' | 'columns' | 'trailing' | 'checkbox' | 'index' | 'badge' | 'badges' | 'favorite' | 'favoriteActive'>;
887
898
  }> | Readonly<{
888
899
  type: 'market';
889
900
  key: string;
890
- changes: Partial<Pick<MarketRow, CommonPatchFields | 'leadingAction' | 'leading' | 'title' | 'subtitle' | 'subtitleSegments' | 'price' | 'priceSegments' | 'change' | 'badges' | 'pressActionKey' | 'pressInActionKey' | 'longPressActionKey' | 'diagnostics' | 'webTable' | 'style'>>;
901
+ changes: PatchClearableChanges<MarketRow, CommonPatchFields | 'leadingAction' | 'leading' | 'title' | 'subtitle' | 'subtitleSegments' | 'price' | 'priceSegments' | 'change' | 'badges' | 'pressActionKey' | 'pressInActionKey' | 'longPressActionKey' | 'diagnostics' | 'webTable' | 'style'>;
891
902
  }> | Readonly<{
892
903
  type: 'mediaTile';
893
904
  key: string;
894
- changes: Partial<Pick<MediaTileRow, CommonPatchFields | 'image' | 'imageState' | 'media' | 'networkImage' | 'title' | 'subtitle' | 'badge' | 'closeActionKey'>>;
905
+ changes: PatchClearableChanges<MediaTileRow, CommonPatchFields | 'image' | 'imageState' | 'media' | 'networkImage' | 'title' | 'subtitle' | 'badge' | 'closeActionKey'>;
895
906
  }> | Readonly<{
896
907
  type: 'metricCard';
897
908
  key: string;
898
- changes: Partial<Pick<MetricCardRow, CommonPatchFields | 'variant' | 'title' | 'value' | 'subtitle' | 'trend' | 'trendTone' | 'visual' | 'badge' | 'metrics' | 'progress'>>;
909
+ changes: PatchClearableChanges<MetricCardRow, CommonPatchFields | 'variant' | 'title' | 'value' | 'subtitle' | 'trend' | 'trendTone' | 'visual' | 'badge' | 'metrics' | 'progress'>;
899
910
  }> | Readonly<{
900
911
  type: 'sectionHeader';
901
912
  key: string;
902
- changes: Partial<Pick<SectionHeaderRow, CommonPatchFields | 'variant' | 'titleLoading' | 'title' | 'subtitle' | 'value' | 'valueHelp' | 'leadingBadge' | 'valueActionKey' | 'titleActionKey' | 'titleActionOnHover' | 'titleIcon' | 'valueIcon' | 'checkbox'>>;
913
+ changes: PatchClearableChanges<SectionHeaderRow, CommonPatchFields | 'variant' | 'titleLoading' | 'title' | 'subtitle' | 'value' | 'valueHelp' | 'leadingBadge' | 'valueActionKey' | 'titleActionKey' | 'titleActionOnHover' | 'titleIcon' | 'valueIcon' | 'checkbox'>;
903
914
  }> | Readonly<{
904
915
  type: 'action';
905
916
  key: string;
906
- changes: Partial<Pick<ActionRow, CommonPatchFields | 'title' | 'tone' | 'icon' | 'checkbox' | 'trailing'>>;
917
+ changes: PatchClearableChanges<ActionRow, CommonPatchFields | 'title' | 'tone' | 'icon' | 'checkbox' | 'trailing'>;
907
918
  }> | Readonly<{
908
919
  type: 'buttonGroup';
909
920
  key: string;
910
- changes: Partial<Pick<ButtonGroupRow, CommonPatchFields | 'buttons'>>;
921
+ changes: PatchClearableChanges<ButtonGroupRow, CommonPatchFields | 'buttons'>;
911
922
  }> | Readonly<{
912
923
  type: 'system';
913
924
  key: string;
914
925
  changes: Readonly<{
915
- revision?: number;
916
- disabled?: boolean;
917
- message?: string;
926
+ revision?: number | null;
927
+ disabled?: boolean | null;
928
+ message?: string | null;
918
929
  }>;
919
930
  }>;
920
931
  export type NativeListActionSource = 'row' | 'marketBadge' | 'leadingAction' | 'trailingAccessory' | 'footerAction' | 'buttonGroup' | 'description' | 'mediaClose';
@@ -10,13 +10,18 @@ export declare function ownValidatedNativeSnapshot(snapshot: NativeListSnapshot)
10
10
  export declare function validatePatches(patches: readonly RowPatch[]): readonly RowPatch[];
11
11
  /** Plan value-only patches; callers own valid immutable snapshots and commit state. */
12
12
  export declare function planNativeListPatches(previous: NativeListSnapshot, next: NativeListSnapshot): readonly RowPatch[] | undefined;
13
- export declare function applyRowPatches(snapshot: NativeListSnapshot, patches: readonly RowPatch[]): NativeListSnapshot;
14
- /** Native wrapper only: preserve full public validation for unsupported patches. */
13
+ /** One canonical batch owns both accepted rows and the native wire payload. */
14
+ export declare function applyValidatedRowPatches(snapshot: NativeListSnapshot, patches: readonly RowPatch[]): {
15
+ patches: readonly RowPatch[];
16
+ snapshot: NativeListSnapshot;
17
+ json: string;
18
+ };
15
19
  export declare function applyOwnedNativePatches(snapshot: NativeListSnapshot, patches: readonly RowPatch[]): {
16
20
  patches: readonly RowPatch[];
17
21
  snapshot: NativeListSnapshot;
18
22
  json: string;
19
23
  };
24
+ export declare function applyRowPatches(snapshot: NativeListSnapshot, patches: readonly RowPatch[]): NativeListSnapshot;
20
25
  export declare function serializeSnapshot(snapshot: NativeListSnapshot): string;
21
26
  export declare function serializePatches(patches: readonly RowPatch[]): string;
22
27
  //# sourceMappingURL=validation.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-list",
3
- "version": "6.7.0-alpha.398",
3
+ "version": "6.7.0-alpha.400",
4
4
  "description": "Template-driven native RecyclerView and UICollectionView for React Native",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",
@@ -86,8 +86,8 @@
86
86
  "typescript": "^5.9.2"
87
87
  },
88
88
  "peerDependencies": {
89
- "@onekeyfe/react-native-image": "6.7.0-alpha.398",
90
- "@onekeyfe/react-native-native-logger": "6.7.0-alpha.398",
89
+ "@onekeyfe/react-native-image": "6.7.0-alpha.400",
90
+ "@onekeyfe/react-native-native-logger": "6.7.0-alpha.400",
91
91
  "react": "*",
92
92
  "react-native": "*",
93
93
  "react-native-nitro-modules": "0.37.0"
@@ -25,7 +25,6 @@ import {
25
25
  applyRowPatches,
26
26
  containNativeListInput,
27
27
  emptyNativeListSnapshot,
28
- validatePatches,
29
28
  validateSnapshot,
30
29
  } from './validation';
31
30
  import {
@@ -454,20 +453,17 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
454
453
  synchronizeSlots(next);
455
454
  },
456
455
  applyPatches(patches) {
457
- const accepted = containNativeListInput(() => validatePatches(patches));
458
- if (accepted && accepted.length > 0) {
459
- const engine = engineRef.current;
460
- if (engine) {
461
- engine.applyPatches(accepted);
462
- snapshotRef.current = engine.currentSnapshot();
463
- } else {
464
- const next = containNativeListInput(() =>
465
- applyRowPatches(snapshotRef.current, accepted)
466
- );
467
- if (next) snapshotRef.current = next;
468
- }
469
- acceptedSnapshotRef.current = snapshotRef.current;
456
+ const engine = engineRef.current;
457
+ if (engine) {
458
+ engine.applyPatches(patches);
459
+ snapshotRef.current = engine.currentSnapshot();
460
+ } else {
461
+ const next = containNativeListInput(() =>
462
+ applyRowPatches(snapshotRef.current, patches)
463
+ );
464
+ if (next) snapshotRef.current = next;
470
465
  }
466
+ acceptedSnapshotRef.current = snapshotRef.current;
471
467
  },
472
468
  reconcileSelection(selectedKeys) {
473
469
  engineRef.current?.reconcileSelection(selectedKeys);