@onekeyfe/react-native-native-list 3.0.117 → 3.0.119
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 +6 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +72 -22
- package/ios/NativeListCell.swift +97 -26
- package/ios/RNCNativeListView.swift +250 -23
- package/lib/module/NativeList.js +12 -8
- package/lib/module/NativeList.web.js +14 -0
- package/lib/module/index.js +1 -1
- package/lib/module/web/NativeListWebEngine.js +112 -18
- package/lib/typescript/src/NativeList.d.ts +2 -0
- package/lib/typescript/src/NativeList.web.d.ts +2 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/models.d.ts +1 -0
- package/package.json +3 -3
- package/src/NativeList.tsx +23 -15
- package/src/NativeList.web.tsx +38 -1
- package/src/index.ts +1 -1
- package/src/models.ts +1 -0
- package/src/web/NativeListWebEngine.ts +161 -28
|
@@ -628,11 +628,32 @@ function configureWebAvatar(image, source, uri) {
|
|
|
628
628
|
});
|
|
629
629
|
webAvatarCleanup.set(image, dispose);
|
|
630
630
|
}
|
|
631
|
+
const WEB_IMAGE_FADE_DELAY_MS = 100;
|
|
632
|
+
const WEB_IMAGE_FADE_DURATION_MS = 140;
|
|
633
|
+
function webImageNow(image) {
|
|
634
|
+
return image.ownerDocument.defaultView?.performance.now() ?? Date.now();
|
|
635
|
+
}
|
|
636
|
+
function revealWebImage(element, startedAt) {
|
|
637
|
+
element.getAnimations?.().forEach(animation => animation.cancel());
|
|
638
|
+
element.style.opacity = '1';
|
|
639
|
+
const view = element.ownerDocument.defaultView;
|
|
640
|
+
if (webImageNow(element) - startedAt < WEB_IMAGE_FADE_DELAY_MS || view?.matchMedia?.('(prefers-reduced-motion: reduce)').matches || typeof element.animate !== 'function') return;
|
|
641
|
+
element.animate([{
|
|
642
|
+
opacity: 0
|
|
643
|
+
}, {
|
|
644
|
+
opacity: 1
|
|
645
|
+
}], {
|
|
646
|
+
duration: WEB_IMAGE_FADE_DURATION_MS,
|
|
647
|
+
easing: 'ease-out'
|
|
648
|
+
});
|
|
649
|
+
}
|
|
631
650
|
function createImage(context, source, className) {
|
|
632
651
|
const avatarUri = canonicalNativeListAvatarUri(source.uri);
|
|
633
652
|
const uri = avatarUri ?? safeImageUri(source.uri);
|
|
634
653
|
if (!uri) return undefined;
|
|
635
654
|
const image = context.document.createElement('img');
|
|
655
|
+
const startedAt = webImageNow(image);
|
|
656
|
+
image.style.opacity = '0';
|
|
636
657
|
if (className) image.className = className;
|
|
637
658
|
// OneKey patch: consume recoverable errors before the visual's final fallback listener.
|
|
638
659
|
if (avatarUri) {
|
|
@@ -646,6 +667,13 @@ function createImage(context, source, className) {
|
|
|
646
667
|
image.loading = 'lazy';
|
|
647
668
|
image.decoding = 'async';
|
|
648
669
|
image.style.objectFit = source.contentFit === 'fill' ? 'fill' : source.contentFit ?? 'cover';
|
|
670
|
+
image.addEventListener('load', () => {
|
|
671
|
+
if (image.dataset.nativeListSelectorPaint !== 'true') revealWebImage(image, startedAt);
|
|
672
|
+
});
|
|
673
|
+
image.addEventListener('error', () => {
|
|
674
|
+
image.style.opacity = '0';
|
|
675
|
+
});
|
|
676
|
+
image.dataset.nativeListImageStartedAt = String(startedAt);
|
|
649
677
|
return image;
|
|
650
678
|
}
|
|
651
679
|
|
|
@@ -655,14 +683,18 @@ function paintSelectorImageBackground(image, frame, inset = 0) {
|
|
|
655
683
|
const paint = createElement(image.ownerDocument, 'span', 'ok-native-list-selector-image-background');
|
|
656
684
|
paint.style.cssText = 'position:absolute;pointer-events:none;border-radius:inherit;background-position:center;background-repeat:no-repeat';
|
|
657
685
|
paint.style.inset = String(inset) + 'px';
|
|
686
|
+
paint.style.opacity = '0';
|
|
658
687
|
paint.style.backgroundSize = image.style.objectFit === 'fill' ? '100% 100%' : image.style.objectFit === 'center' ? 'auto' : image.style.objectFit;
|
|
688
|
+
image.dataset.nativeListSelectorPaint = 'true';
|
|
659
689
|
image.style.opacity = '0';
|
|
660
690
|
const update = () => {
|
|
661
691
|
paint.style.backgroundImage = 'url(' + JSON.stringify(image.currentSrc || image.src) + ')';
|
|
692
|
+
revealWebImage(paint, Number(image.dataset.nativeListImageStartedAt) || webImageNow(image));
|
|
662
693
|
};
|
|
663
694
|
image.addEventListener('load', update);
|
|
664
695
|
image.addEventListener('error', () => {
|
|
665
696
|
paint.style.backgroundImage = 'none';
|
|
697
|
+
paint.style.opacity = '0';
|
|
666
698
|
});
|
|
667
699
|
frame.insertBefore(paint, image);
|
|
668
700
|
if (image.complete && image.naturalWidth > 0) update();
|
|
@@ -949,14 +981,28 @@ function createVisual(context, visual, selectorPresentation) {
|
|
|
949
981
|
frame.appendChild(fallback);
|
|
950
982
|
return frame;
|
|
951
983
|
}
|
|
952
|
-
|
|
984
|
+
|
|
985
|
+
// OneKey patch: NativeList images are usually local and should not flash a
|
|
986
|
+
// placeholder. Callers can opt in with image.loadingStrategy.
|
|
987
|
+
const visualBackground = 'backgroundColor' in visual && visual.backgroundColor ? visual.backgroundColor : 'transparent';
|
|
953
988
|
const source = visual.kind === 'image' ? visual.image : visual.image;
|
|
989
|
+
const showsSourcePlaceholder = !!source && source.loadingStrategy !== undefined && source.loadingStrategy !== 'none';
|
|
990
|
+
frame.style.background = showsSourcePlaceholder ? 'var(--nl-strong)' : visualBackground;
|
|
991
|
+
const fallbackIcon = 'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
|
|
992
|
+
const handlesSourceFallback = showsSourcePlaceholder && (fallbackIcon !== undefined || 'fallbackText' in visual);
|
|
954
993
|
const image = source ? createImage(context, source) : undefined;
|
|
955
994
|
if (image) {
|
|
956
995
|
image.className = 'ok-native-list-visual-main';
|
|
957
996
|
frame.appendChild(image);
|
|
997
|
+
if (showsSourcePlaceholder) {
|
|
998
|
+
const restoreBackground = () => {
|
|
999
|
+
frame.style.background = visualBackground;
|
|
1000
|
+
};
|
|
1001
|
+
image.addEventListener('load', restoreBackground);
|
|
1002
|
+
if (image.complete && image.naturalWidth > 0) restoreBackground();
|
|
1003
|
+
}
|
|
958
1004
|
if (selectorPresentation) paintSelectorImageBackground(image, frame);
|
|
959
|
-
} else {
|
|
1005
|
+
} else if (!source || showsSourcePlaceholder) {
|
|
960
1006
|
frame.appendChild(createElement(context.document, 'span', 'ok-native-list-visual-fallback', 'fallbackText' in visual ? visual.fallbackText ?? '' : ''));
|
|
961
1007
|
}
|
|
962
1008
|
if (visual.kind === 'token' && visual.networkImage) {
|
|
@@ -970,17 +1016,19 @@ function createVisual(context, visual, selectorPresentation) {
|
|
|
970
1016
|
frame.appendChild(corner);
|
|
971
1017
|
}
|
|
972
1018
|
// OneKey patch: image failure uses the same source-derived fallback as v1.
|
|
973
|
-
|
|
974
|
-
|
|
1019
|
+
const hasFallbackText = 'fallbackText' in visual;
|
|
1020
|
+
if ((fallbackIcon || hasFallbackText) && (!source || handlesSourceFallback)) {
|
|
975
1021
|
const showFallback = () => {
|
|
976
1022
|
if (image) {
|
|
977
1023
|
disposeWebImageRetries(image);
|
|
978
1024
|
image.remove();
|
|
979
1025
|
}
|
|
980
1026
|
frame.querySelector('.ok-native-list-visual-fallback:not(.ok-native-list-visual-corner)')?.remove();
|
|
981
|
-
const fallback = createElement(context.document, 'span', 'ok-native-list-visual-fallback');
|
|
982
|
-
|
|
983
|
-
|
|
1027
|
+
const fallback = createElement(context.document, 'span', 'ok-native-list-visual-fallback', fallbackIcon ? undefined : 'fallbackText' in visual ? visual.fallbackText ?? '' : '');
|
|
1028
|
+
if (fallbackIcon) {
|
|
1029
|
+
applySelectorIcon(fallback, fallbackIcon.name);
|
|
1030
|
+
if (fallbackIcon.tintColor) fallback.style.color = fallbackIcon.tintColor;
|
|
1031
|
+
}
|
|
984
1032
|
frame.prepend(fallback);
|
|
985
1033
|
};
|
|
986
1034
|
if (image) image.addEventListener('error', showFallback, {
|
|
@@ -1833,16 +1881,45 @@ function createMarketRow(context, row) {
|
|
|
1833
1881
|
titleLine.appendChild(element);
|
|
1834
1882
|
});
|
|
1835
1883
|
main.appendChild(titleLine);
|
|
1836
|
-
if (row.subtitle || row.subtitleSegments?.length) {
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1884
|
+
if (row.subtitlePrefix || row.subtitle || row.subtitleSegments?.length) {
|
|
1885
|
+
const subtitleLine = createElement(context.document, 'span', 'ok-native-list-market-subtitle-line');
|
|
1886
|
+
subtitleLine.style.display = 'flex';
|
|
1887
|
+
subtitleLine.style.alignItems = 'center';
|
|
1888
|
+
subtitleLine.style.minWidth = '0';
|
|
1889
|
+
subtitleLine.style.overflow = 'hidden';
|
|
1890
|
+
subtitleLine.style.gap = String(row.subtitlePrefix ? row.subtitlePrefix.gap ?? 4 : 0) + 'px';
|
|
1891
|
+
if (row.subtitlePrefix) {
|
|
1892
|
+
const prefix = createElement(context.document, 'span', 'ok-native-list-market-subtitle-prefix', row.subtitlePrefix.text);
|
|
1893
|
+
applyMarketTextStyle(prefix, row.subtitlePrefix.style, {
|
|
1894
|
+
fontSize: 12,
|
|
1895
|
+
lineHeight: 16,
|
|
1896
|
+
weight: 400,
|
|
1897
|
+
alignment: 'start'
|
|
1898
|
+
});
|
|
1899
|
+
prefix.style.display = 'block';
|
|
1900
|
+
prefix.style.flex = '1 1 auto';
|
|
1901
|
+
prefix.style.minWidth = '0';
|
|
1902
|
+
prefix.style.overflow = 'hidden';
|
|
1903
|
+
prefix.style.textOverflow = 'ellipsis';
|
|
1904
|
+
prefix.style.color = row.subtitlePrefix.style?.color ?? 'var(--nl-secondary)';
|
|
1905
|
+
if (row.subtitlePrefix.maxWidth !== undefined) {
|
|
1906
|
+
prefix.style.maxWidth = String(row.subtitlePrefix.maxWidth) + 'px';
|
|
1907
|
+
}
|
|
1908
|
+
subtitleLine.appendChild(prefix);
|
|
1909
|
+
}
|
|
1910
|
+
if (row.subtitle || row.subtitleSegments?.length) {
|
|
1911
|
+
const subtitle = createElement(context.document, 'span', 'ok-native-list-market-subtitle', row.subtitle);
|
|
1912
|
+
applyMarketTextStyle(subtitle, style?.subtitle, {
|
|
1913
|
+
fontSize: 14,
|
|
1914
|
+
lineHeight: 20,
|
|
1915
|
+
weight: 400,
|
|
1916
|
+
alignment: 'start'
|
|
1917
|
+
});
|
|
1918
|
+
applyValueSegments(subtitle, row.subtitleSegments, style?.subtitle?.fontSize ?? 14, style?.subtitle?.lineHeight ?? 20, marketFontWeight(style?.subtitle?.fontWeight, 400));
|
|
1919
|
+
subtitle.style.flex = row.subtitlePrefix ? '0 0 auto' : '1 1 auto';
|
|
1920
|
+
subtitleLine.appendChild(subtitle);
|
|
1921
|
+
}
|
|
1922
|
+
main.appendChild(subtitleLine);
|
|
1846
1923
|
}
|
|
1847
1924
|
body.appendChild(main);
|
|
1848
1925
|
const trailing = createElement(context.document, 'span', 'ok-native-list-market-trailing');
|
|
@@ -2493,8 +2570,25 @@ export class NativeListWebEngine {
|
|
|
2493
2570
|
sectionIndexMetrics(viewportHeight) {
|
|
2494
2571
|
const availableHeight = Math.max(0, viewportHeight - SECTION_INDEX_EDGE_PADDING * 2);
|
|
2495
2572
|
const trackHeight = Math.min(availableHeight, SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length);
|
|
2573
|
+
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
2574
|
+
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
2575
|
+
return {
|
|
2576
|
+
originY: centeredOriginY,
|
|
2577
|
+
trackHeight
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
const view = this.document.defaultView;
|
|
2581
|
+
if (!view) return {
|
|
2582
|
+
originY: centeredOriginY,
|
|
2583
|
+
trackHeight
|
|
2584
|
+
};
|
|
2585
|
+
const visualViewport = view.visualViewport;
|
|
2586
|
+
const windowCenterY = visualViewport ? visualViewport.offsetTop + visualViewport.height / 2 : view.innerHeight / 2;
|
|
2587
|
+
const railTop = this.viewportFrame.getBoundingClientRect().top + (Number.parseFloat(this.indexRail.style.top) || 0);
|
|
2588
|
+
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
2589
|
+
const maxOriginY = Math.max(minOriginY, viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight);
|
|
2496
2590
|
return {
|
|
2497
|
-
originY: (
|
|
2591
|
+
originY: Math.min(maxOriginY, Math.max(minOriginY, windowCenterY - railTop - trackHeight / 2)),
|
|
2498
2592
|
trackHeight
|
|
2499
2593
|
};
|
|
2500
2594
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { ImageSource } from './models';
|
|
2
3
|
import type { NativeListProps } from './NativeList.types';
|
|
3
4
|
export type { NativeListProps, NativeListRef } from './NativeList.types';
|
|
4
5
|
export type { ActionAnchorState, ScrollAlignment, ScrollPositionOptions, ScrollToEndParams, ScrollToIndexFailedInfo, ScrollToIndexParams, ScrollToItemParams, ScrollToKeyParams, ScrollToLocationParams, ScrollToOffsetParams, } from './NativeList.types';
|
|
6
|
+
export declare function preloadNativeListAvatarImages(sources: readonly ImageSource[]): Promise<boolean>;
|
|
5
7
|
export declare const NativeList: React.ForwardRefExoticComponent<NativeListProps & React.RefAttributes<Readonly<{
|
|
6
8
|
applySnapshot(snapshot: import("./models").NativeListSnapshot): void;
|
|
7
9
|
applyPatches(patches: readonly import("./models").RowPatch[]): void;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { NativeListProps } from './NativeList.types';
|
|
3
|
-
import type { NativeListSnapshot, RowPatch } from './models';
|
|
3
|
+
import type { ImageSource, NativeListSnapshot, RowPatch } from './models';
|
|
4
4
|
export type { NativeListProps, NativeListRef } from './NativeList.types';
|
|
5
5
|
export type { ActionAnchorState, ScrollAlignment, ScrollPositionOptions, ScrollToEndParams, ScrollToIndexFailedInfo, ScrollToIndexParams, ScrollToItemParams, ScrollToKeyParams, ScrollToLocationParams, ScrollToOffsetParams, } from './NativeList.types';
|
|
6
|
+
export declare function preloadNativeListAvatarImages(sources: readonly ImageSource[]): Promise<boolean>;
|
|
6
7
|
export declare const NativeList: React.ForwardRefExoticComponent<NativeListProps & React.RefAttributes<Readonly<{
|
|
7
8
|
applySnapshot(snapshot: NativeListSnapshot): void;
|
|
8
9
|
applyPatches(patches: readonly RowPatch[]): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NativeList } from './NativeList';
|
|
1
|
+
export { NativeList, preloadNativeListAvatarImages } from './NativeList';
|
|
2
2
|
export type { ActionAnchorState, NativeListProps, NativeListRef, ScrollAlignment, ScrollPositionOptions, ScrollToEndParams, ScrollToIndexFailedInfo, ScrollToIndexParams, ScrollToItemParams, ScrollToKeyParams, ScrollToLocationParams, ScrollToOffsetParams, } from './NativeList';
|
|
3
3
|
export type * from './models';
|
|
4
4
|
export { applyRowPatches, serializePatches, serializeSnapshot, validatePatches, validateSnapshot, } from './validation';
|
|
@@ -19,6 +19,7 @@ export type ImageSource = Readonly<{
|
|
|
19
19
|
autoplay?: boolean;
|
|
20
20
|
optimizeTos?: boolean;
|
|
21
21
|
overscan?: number;
|
|
22
|
+
/** Defaults to `none` in NativeList; use `static` or `skeleton` to opt into loading UI. */
|
|
22
23
|
loadingStrategy?: ImageLoadingStrategy;
|
|
23
24
|
fallbackUri?: string;
|
|
24
25
|
retryTimes?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-list",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.119",
|
|
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",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"typescript": "^5.9.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@onekeyfe/react-native-image": "3.0.
|
|
87
|
-
"@onekeyfe/react-native-native-logger": "3.0.
|
|
86
|
+
"@onekeyfe/react-native-image": "3.0.119",
|
|
87
|
+
"@onekeyfe/react-native-native-logger": "3.0.119",
|
|
88
88
|
"react": "*",
|
|
89
89
|
"react-native": "*",
|
|
90
90
|
"react-native-nitro-modules": "0.37.0"
|
package/src/NativeList.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from './NativeList.nitro';
|
|
21
21
|
import type {
|
|
22
22
|
ActionAnchorInvalidatedEvent,
|
|
23
|
+
ImageSource,
|
|
23
24
|
RowActionEvent,
|
|
24
25
|
SelectionDeltaEvent,
|
|
25
26
|
ReorderEvent,
|
|
@@ -64,6 +65,27 @@ const NativeListHost = getHostComponent<
|
|
|
64
65
|
NativeListMethods
|
|
65
66
|
>('NativeList', () => NativeListConfig);
|
|
66
67
|
|
|
68
|
+
export function preloadNativeListAvatarImages(
|
|
69
|
+
sources: readonly ImageSource[]
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
return OneKeyImageCache.preload(
|
|
72
|
+
sources.map((source) => ({
|
|
73
|
+
uri: source.uri,
|
|
74
|
+
headers: source.headers,
|
|
75
|
+
resizeWidth: source.width,
|
|
76
|
+
resizeHeight: source.height,
|
|
77
|
+
optimizeTos: source.optimizeTos !== false,
|
|
78
|
+
overscan: source.overscan,
|
|
79
|
+
cachePolicy:
|
|
80
|
+
source.cachePolicy === 'memory'
|
|
81
|
+
? OneKeyImageCachePolicy.MEMORY
|
|
82
|
+
: source.cachePolicy === 'disk'
|
|
83
|
+
? OneKeyImageCachePolicy.DISK
|
|
84
|
+
: OneKeyImageCachePolicy.MEMORY_DISK,
|
|
85
|
+
}))
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
function parsePayload<T>(payloadJson: string): T {
|
|
68
90
|
return JSON.parse(payloadJson) as T;
|
|
69
91
|
}
|
|
@@ -151,21 +173,7 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
151
173
|
useEffect(() => {
|
|
152
174
|
avatarLifecycle.current = 'mounted';
|
|
153
175
|
const queue = new NativeAvatarPrefetchQueue((source) =>
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
uri: source.uri,
|
|
157
|
-
headers: source.headers,
|
|
158
|
-
resizeWidth: source.width,
|
|
159
|
-
resizeHeight: source.height,
|
|
160
|
-
optimizeTos: false,
|
|
161
|
-
cachePolicy:
|
|
162
|
-
source.cachePolicy === 'memory'
|
|
163
|
-
? OneKeyImageCachePolicy.MEMORY
|
|
164
|
-
: source.cachePolicy === 'disk'
|
|
165
|
-
? OneKeyImageCachePolicy.DISK
|
|
166
|
-
: OneKeyImageCachePolicy.MEMORY_DISK,
|
|
167
|
-
},
|
|
168
|
-
])
|
|
176
|
+
preloadNativeListAvatarImages([source])
|
|
169
177
|
);
|
|
170
178
|
avatarQueueRef.current = queue;
|
|
171
179
|
updateAvatarPrefetchRef.current();
|
package/src/NativeList.web.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import React, {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { View } from 'react-native';
|
|
11
11
|
import type { NativeListProps, NativeListRef } from './NativeList.types';
|
|
12
|
-
import type { NativeListSnapshot, RowPatch } from './models';
|
|
12
|
+
import type { ImageSource, NativeListSnapshot, RowPatch } from './models';
|
|
13
13
|
import {
|
|
14
14
|
normalizeIndexScroll,
|
|
15
15
|
normalizeKeyScroll,
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
NativeListWebEngine,
|
|
25
25
|
type NativeListWebCallbacks,
|
|
26
26
|
} from './web/NativeListWebEngine';
|
|
27
|
+
import {
|
|
28
|
+
acquireNativeListAvatar,
|
|
29
|
+
canonicalNativeListAvatarUri,
|
|
30
|
+
} from './web/NativeListWebAvatarCache';
|
|
27
31
|
|
|
28
32
|
export type { NativeListProps, NativeListRef } from './NativeList.types';
|
|
29
33
|
export type {
|
|
@@ -39,6 +43,39 @@ export type {
|
|
|
39
43
|
ScrollToOffsetParams,
|
|
40
44
|
} from './NativeList.types';
|
|
41
45
|
|
|
46
|
+
export function preloadNativeListAvatarImages(
|
|
47
|
+
sources: readonly ImageSource[]
|
|
48
|
+
): Promise<boolean> {
|
|
49
|
+
if (typeof document === 'undefined') return Promise.resolve(false);
|
|
50
|
+
const uris = [
|
|
51
|
+
...new Set(
|
|
52
|
+
sources
|
|
53
|
+
.map((source) => canonicalNativeListAvatarUri(source.uri))
|
|
54
|
+
.filter((uri): uri is string => uri !== undefined)
|
|
55
|
+
),
|
|
56
|
+
];
|
|
57
|
+
if (!uris.length) return Promise.resolve(false);
|
|
58
|
+
return Promise.all(
|
|
59
|
+
uris.map(
|
|
60
|
+
(uri) =>
|
|
61
|
+
new Promise<boolean>((resolve) => {
|
|
62
|
+
let release: (() => void) | undefined;
|
|
63
|
+
const settle = (success: boolean) => {
|
|
64
|
+
resolve(success);
|
|
65
|
+
queueMicrotask(() => release?.());
|
|
66
|
+
};
|
|
67
|
+
release = acquireNativeListAvatar(
|
|
68
|
+
document,
|
|
69
|
+
uri,
|
|
70
|
+
() => settle(true),
|
|
71
|
+
() => settle(false),
|
|
72
|
+
0
|
|
73
|
+
);
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
).then((results) => results.every(Boolean));
|
|
77
|
+
}
|
|
78
|
+
|
|
42
79
|
export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
43
80
|
function NativeList(
|
|
44
81
|
{
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type ImageSource = Readonly<{
|
|
|
21
21
|
autoplay?: boolean;
|
|
22
22
|
optimizeTos?: boolean;
|
|
23
23
|
overscan?: number;
|
|
24
|
+
/** Defaults to `none` in NativeList; use `static` or `skeleton` to opt into loading UI. */
|
|
24
25
|
loadingStrategy?: ImageLoadingStrategy;
|
|
25
26
|
// OneKey patch: fallbackUri is Web-only; retryTimes opts all platforms into terminal retries.
|
|
26
27
|
fallbackUri?: string;
|
|
@@ -1087,6 +1087,30 @@ function configureWebAvatar(
|
|
|
1087
1087
|
webAvatarCleanup.set(image, dispose);
|
|
1088
1088
|
}
|
|
1089
1089
|
|
|
1090
|
+
const WEB_IMAGE_FADE_DELAY_MS = 100;
|
|
1091
|
+
const WEB_IMAGE_FADE_DURATION_MS = 140;
|
|
1092
|
+
|
|
1093
|
+
function webImageNow(image: HTMLImageElement): number {
|
|
1094
|
+
return image.ownerDocument.defaultView?.performance.now() ?? Date.now();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function revealWebImage(element: HTMLElement, startedAt: number) {
|
|
1098
|
+
element.getAnimations?.().forEach((animation) => animation.cancel());
|
|
1099
|
+
element.style.opacity = '1';
|
|
1100
|
+
const view = element.ownerDocument.defaultView;
|
|
1101
|
+
if (
|
|
1102
|
+
webImageNow(element as HTMLImageElement) - startedAt <
|
|
1103
|
+
WEB_IMAGE_FADE_DELAY_MS ||
|
|
1104
|
+
view?.matchMedia?.('(prefers-reduced-motion: reduce)').matches ||
|
|
1105
|
+
typeof element.animate !== 'function'
|
|
1106
|
+
)
|
|
1107
|
+
return;
|
|
1108
|
+
element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
1109
|
+
duration: WEB_IMAGE_FADE_DURATION_MS,
|
|
1110
|
+
easing: 'ease-out',
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1090
1114
|
function createImage(
|
|
1091
1115
|
context: RenderContext,
|
|
1092
1116
|
source: ImageSource,
|
|
@@ -1096,6 +1120,8 @@ function createImage(
|
|
|
1096
1120
|
const uri = avatarUri ?? safeImageUri(source.uri);
|
|
1097
1121
|
if (!uri) return undefined;
|
|
1098
1122
|
const image = context.document.createElement('img');
|
|
1123
|
+
const startedAt = webImageNow(image);
|
|
1124
|
+
image.style.opacity = '0';
|
|
1099
1125
|
if (className) image.className = className;
|
|
1100
1126
|
// OneKey patch: consume recoverable errors before the visual's final fallback listener.
|
|
1101
1127
|
if (avatarUri) {
|
|
@@ -1110,6 +1136,14 @@ function createImage(
|
|
|
1110
1136
|
image.decoding = 'async';
|
|
1111
1137
|
image.style.objectFit =
|
|
1112
1138
|
source.contentFit === 'fill' ? 'fill' : source.contentFit ?? 'cover';
|
|
1139
|
+
image.addEventListener('load', () => {
|
|
1140
|
+
if (image.dataset.nativeListSelectorPaint !== 'true')
|
|
1141
|
+
revealWebImage(image, startedAt);
|
|
1142
|
+
});
|
|
1143
|
+
image.addEventListener('error', () => {
|
|
1144
|
+
image.style.opacity = '0';
|
|
1145
|
+
});
|
|
1146
|
+
image.dataset.nativeListImageStartedAt = String(startedAt);
|
|
1113
1147
|
return image;
|
|
1114
1148
|
}
|
|
1115
1149
|
|
|
@@ -1128,20 +1162,27 @@ function paintSelectorImageBackground(
|
|
|
1128
1162
|
paint.style.cssText =
|
|
1129
1163
|
'position:absolute;pointer-events:none;border-radius:inherit;background-position:center;background-repeat:no-repeat';
|
|
1130
1164
|
paint.style.inset = String(inset) + 'px';
|
|
1165
|
+
paint.style.opacity = '0';
|
|
1131
1166
|
paint.style.backgroundSize =
|
|
1132
1167
|
image.style.objectFit === 'fill'
|
|
1133
1168
|
? '100% 100%'
|
|
1134
1169
|
: image.style.objectFit === 'center'
|
|
1135
1170
|
? 'auto'
|
|
1136
1171
|
: image.style.objectFit;
|
|
1172
|
+
image.dataset.nativeListSelectorPaint = 'true';
|
|
1137
1173
|
image.style.opacity = '0';
|
|
1138
1174
|
const update = () => {
|
|
1139
1175
|
paint.style.backgroundImage =
|
|
1140
1176
|
'url(' + JSON.stringify(image.currentSrc || image.src) + ')';
|
|
1177
|
+
revealWebImage(
|
|
1178
|
+
paint,
|
|
1179
|
+
Number(image.dataset.nativeListImageStartedAt) || webImageNow(image)
|
|
1180
|
+
);
|
|
1141
1181
|
};
|
|
1142
1182
|
image.addEventListener('load', update);
|
|
1143
1183
|
image.addEventListener('error', () => {
|
|
1144
1184
|
paint.style.backgroundImage = 'none';
|
|
1185
|
+
paint.style.opacity = '0';
|
|
1145
1186
|
});
|
|
1146
1187
|
frame.insertBefore(paint, image);
|
|
1147
1188
|
if (image.complete && image.naturalWidth > 0) update();
|
|
@@ -1516,15 +1557,38 @@ function createVisual(
|
|
|
1516
1557
|
return frame;
|
|
1517
1558
|
}
|
|
1518
1559
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1560
|
+
// OneKey patch: NativeList images are usually local and should not flash a
|
|
1561
|
+
// placeholder. Callers can opt in with image.loadingStrategy.
|
|
1562
|
+
const visualBackground =
|
|
1563
|
+
'backgroundColor' in visual && visual.backgroundColor
|
|
1564
|
+
? visual.backgroundColor
|
|
1565
|
+
: 'transparent';
|
|
1521
1566
|
const source = visual.kind === 'image' ? visual.image : visual.image;
|
|
1567
|
+
const showsSourcePlaceholder =
|
|
1568
|
+
!!source &&
|
|
1569
|
+
source.loadingStrategy !== undefined &&
|
|
1570
|
+
source.loadingStrategy !== 'none';
|
|
1571
|
+
frame.style.background = showsSourcePlaceholder
|
|
1572
|
+
? 'var(--nl-strong)'
|
|
1573
|
+
: visualBackground;
|
|
1574
|
+
const fallbackIcon =
|
|
1575
|
+
'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
|
|
1576
|
+
const handlesSourceFallback =
|
|
1577
|
+
showsSourcePlaceholder &&
|
|
1578
|
+
(fallbackIcon !== undefined || 'fallbackText' in visual);
|
|
1522
1579
|
const image = source ? createImage(context, source) : undefined;
|
|
1523
1580
|
if (image) {
|
|
1524
1581
|
image.className = 'ok-native-list-visual-main';
|
|
1525
1582
|
frame.appendChild(image);
|
|
1583
|
+
if (showsSourcePlaceholder) {
|
|
1584
|
+
const restoreBackground = () => {
|
|
1585
|
+
frame.style.background = visualBackground;
|
|
1586
|
+
};
|
|
1587
|
+
image.addEventListener('load', restoreBackground);
|
|
1588
|
+
if (image.complete && image.naturalWidth > 0) restoreBackground();
|
|
1589
|
+
}
|
|
1526
1590
|
if (selectorPresentation) paintSelectorImageBackground(image, frame);
|
|
1527
|
-
} else {
|
|
1591
|
+
} else if (!source || showsSourcePlaceholder) {
|
|
1528
1592
|
frame.appendChild(
|
|
1529
1593
|
createElement(
|
|
1530
1594
|
context.document,
|
|
@@ -1556,8 +1620,8 @@ function createVisual(
|
|
|
1556
1620
|
frame.appendChild(corner);
|
|
1557
1621
|
}
|
|
1558
1622
|
// OneKey patch: image failure uses the same source-derived fallback as v1.
|
|
1559
|
-
|
|
1560
|
-
|
|
1623
|
+
const hasFallbackText = 'fallbackText' in visual;
|
|
1624
|
+
if ((fallbackIcon || hasFallbackText) && (!source || handlesSourceFallback)) {
|
|
1561
1625
|
const showFallback = () => {
|
|
1562
1626
|
if (image) {
|
|
1563
1627
|
disposeWebImageRetries(image);
|
|
@@ -1571,10 +1635,18 @@ function createVisual(
|
|
|
1571
1635
|
const fallback = createElement(
|
|
1572
1636
|
context.document,
|
|
1573
1637
|
'span',
|
|
1574
|
-
'ok-native-list-visual-fallback'
|
|
1638
|
+
'ok-native-list-visual-fallback',
|
|
1639
|
+
fallbackIcon
|
|
1640
|
+
? undefined
|
|
1641
|
+
: 'fallbackText' in visual
|
|
1642
|
+
? visual.fallbackText ?? ''
|
|
1643
|
+
: ''
|
|
1575
1644
|
);
|
|
1576
|
-
|
|
1577
|
-
|
|
1645
|
+
if (fallbackIcon) {
|
|
1646
|
+
applySelectorIcon(fallback, fallbackIcon.name);
|
|
1647
|
+
if (fallbackIcon.tintColor)
|
|
1648
|
+
fallback.style.color = fallbackIcon.tintColor;
|
|
1649
|
+
}
|
|
1578
1650
|
frame.prepend(fallback);
|
|
1579
1651
|
};
|
|
1580
1652
|
if (image) image.addEventListener('error', showFallback, { once: true });
|
|
@@ -3254,27 +3326,67 @@ function createMarketRow(context: RenderContext, row: MarketRow): HTMLElement {
|
|
|
3254
3326
|
titleLine.appendChild(element);
|
|
3255
3327
|
});
|
|
3256
3328
|
main.appendChild(titleLine);
|
|
3257
|
-
if (row.subtitle || row.subtitleSegments?.length) {
|
|
3258
|
-
const
|
|
3329
|
+
if (row.subtitlePrefix || row.subtitle || row.subtitleSegments?.length) {
|
|
3330
|
+
const subtitleLine = createElement(
|
|
3259
3331
|
context.document,
|
|
3260
3332
|
'span',
|
|
3261
|
-
'ok-native-list-market-subtitle'
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3333
|
+
'ok-native-list-market-subtitle-line'
|
|
3334
|
+
);
|
|
3335
|
+
subtitleLine.style.display = 'flex';
|
|
3336
|
+
subtitleLine.style.alignItems = 'center';
|
|
3337
|
+
subtitleLine.style.minWidth = '0';
|
|
3338
|
+
subtitleLine.style.overflow = 'hidden';
|
|
3339
|
+
subtitleLine.style.gap =
|
|
3340
|
+
String(row.subtitlePrefix ? row.subtitlePrefix.gap ?? 4 : 0) + 'px';
|
|
3341
|
+
if (row.subtitlePrefix) {
|
|
3342
|
+
const prefix = createElement(
|
|
3343
|
+
context.document,
|
|
3344
|
+
'span',
|
|
3345
|
+
'ok-native-list-market-subtitle-prefix',
|
|
3346
|
+
row.subtitlePrefix.text
|
|
3347
|
+
);
|
|
3348
|
+
applyMarketTextStyle(prefix, row.subtitlePrefix.style, {
|
|
3349
|
+
fontSize: 12,
|
|
3350
|
+
lineHeight: 16,
|
|
3351
|
+
weight: 400,
|
|
3352
|
+
alignment: 'start',
|
|
3353
|
+
});
|
|
3354
|
+
prefix.style.display = 'block';
|
|
3355
|
+
prefix.style.flex = '1 1 auto';
|
|
3356
|
+
prefix.style.minWidth = '0';
|
|
3357
|
+
prefix.style.overflow = 'hidden';
|
|
3358
|
+
prefix.style.textOverflow = 'ellipsis';
|
|
3359
|
+
prefix.style.color =
|
|
3360
|
+
row.subtitlePrefix.style?.color ?? 'var(--nl-secondary)';
|
|
3361
|
+
if (row.subtitlePrefix.maxWidth !== undefined) {
|
|
3362
|
+
prefix.style.maxWidth = String(row.subtitlePrefix.maxWidth) + 'px';
|
|
3363
|
+
}
|
|
3364
|
+
subtitleLine.appendChild(prefix);
|
|
3365
|
+
}
|
|
3366
|
+
if (row.subtitle || row.subtitleSegments?.length) {
|
|
3367
|
+
const subtitle = createElement(
|
|
3368
|
+
context.document,
|
|
3369
|
+
'span',
|
|
3370
|
+
'ok-native-list-market-subtitle',
|
|
3371
|
+
row.subtitle
|
|
3372
|
+
);
|
|
3373
|
+
applyMarketTextStyle(subtitle, style?.subtitle, {
|
|
3374
|
+
fontSize: 14,
|
|
3375
|
+
lineHeight: 20,
|
|
3376
|
+
weight: 400,
|
|
3377
|
+
alignment: 'start',
|
|
3378
|
+
});
|
|
3379
|
+
applyValueSegments(
|
|
3380
|
+
subtitle,
|
|
3381
|
+
row.subtitleSegments,
|
|
3382
|
+
style?.subtitle?.fontSize ?? 14,
|
|
3383
|
+
style?.subtitle?.lineHeight ?? 20,
|
|
3384
|
+
marketFontWeight(style?.subtitle?.fontWeight, 400)
|
|
3385
|
+
);
|
|
3386
|
+
subtitle.style.flex = row.subtitlePrefix ? '0 0 auto' : '1 1 auto';
|
|
3387
|
+
subtitleLine.appendChild(subtitle);
|
|
3388
|
+
}
|
|
3389
|
+
main.appendChild(subtitleLine);
|
|
3278
3390
|
}
|
|
3279
3391
|
body.appendChild(main);
|
|
3280
3392
|
const trailing = createElement(
|
|
@@ -4221,8 +4333,29 @@ export class NativeListWebEngine {
|
|
|
4221
4333
|
availableHeight,
|
|
4222
4334
|
SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length
|
|
4223
4335
|
);
|
|
4336
|
+
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
4337
|
+
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
4338
|
+
return { originY: centeredOriginY, trackHeight };
|
|
4339
|
+
}
|
|
4340
|
+
const view = this.document.defaultView;
|
|
4341
|
+
if (!view) return { originY: centeredOriginY, trackHeight };
|
|
4342
|
+
const visualViewport = view.visualViewport;
|
|
4343
|
+
const windowCenterY = visualViewport
|
|
4344
|
+
? visualViewport.offsetTop + visualViewport.height / 2
|
|
4345
|
+
: view.innerHeight / 2;
|
|
4346
|
+
const railTop =
|
|
4347
|
+
this.viewportFrame.getBoundingClientRect().top +
|
|
4348
|
+
(Number.parseFloat(this.indexRail.style.top) || 0);
|
|
4349
|
+
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
4350
|
+
const maxOriginY = Math.max(
|
|
4351
|
+
minOriginY,
|
|
4352
|
+
viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight
|
|
4353
|
+
);
|
|
4224
4354
|
return {
|
|
4225
|
-
originY: (
|
|
4355
|
+
originY: Math.min(
|
|
4356
|
+
maxOriginY,
|
|
4357
|
+
Math.max(minOriginY, windowCenterY - railTop - trackHeight / 2)
|
|
4358
|
+
),
|
|
4226
4359
|
trackHeight,
|
|
4227
4360
|
};
|
|
4228
4361
|
}
|