@onekeyfe/react-native-native-list 3.0.105
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 +342 -0
- package/android/CMakeLists.txt +20 -0
- package/android/build.gradle +105 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +125 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +165 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +147 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListPackage.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +2939 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1794 -0
- package/common/fonts/Roobert-Bold.ttf +0 -0
- package/common/fonts/Roobert-Medium.ttf +0 -0
- package/common/fonts/Roobert-Regular.ttf +0 -0
- package/common/fonts/Roobert-SemiBold.ttf +0 -0
- package/docs/DESIGN.md +129 -0
- package/ios/HybridNativeList.swift +131 -0
- package/ios/NativeListCell.swift +2858 -0
- package/ios/NativeListDesignAssets.swift +110 -0
- package/ios/NativeListModels.swift +215 -0
- package/ios/RNCNativeListView.swift +1737 -0
- package/ios/Resources/NativeListIcons.xcassets/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +1 -0
- package/lib/module/NativeList.js +183 -0
- package/lib/module/NativeList.nitro.js +4 -0
- package/lib/module/NativeList.types.js +4 -0
- package/lib/module/NativeList.web.js +167 -0
- package/lib/module/index.js +6 -0
- package/lib/module/models.js +2 -0
- package/lib/module/package.json +1 -0
- package/lib/module/scrolling.js +121 -0
- package/lib/module/selection.js +85 -0
- package/lib/module/validation.js +562 -0
- package/lib/module/web/NativeListWebEngine.js +2238 -0
- package/lib/nitrogen/generated/.gitattributes +1 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
- package/lib/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
- package/lib/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
- package/lib/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
- package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
- package/lib/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
- package/lib/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
- package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
- package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
- package/lib/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
- package/lib/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeList.d.ts +18 -0
- package/lib/typescript/src/NativeList.nitro.d.ts +29 -0
- package/lib/typescript/src/NativeList.types.d.ts +87 -0
- package/lib/typescript/src/NativeList.web.d.ts +19 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/models.d.ts +456 -0
- package/lib/typescript/src/scrolling.d.ts +32 -0
- package/lib/typescript/src/selection.d.ts +27 -0
- package/lib/typescript/src/validation.d.ts +7 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +203 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
- package/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
- package/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
- package/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
- package/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
- package/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
- package/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
- package/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
- package/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
- package/package.json +126 -0
- package/react-native-native-list.podspec +38 -0
- package/react-native.config.js +19 -0
- package/src/NativeList.nitro.ts +48 -0
- package/src/NativeList.tsx +298 -0
- package/src/NativeList.types.ts +125 -0
- package/src/NativeList.web.tsx +258 -0
- package/src/index.ts +35 -0
- package/src/models.ts +604 -0
- package/src/scrolling.ts +189 -0
- package/src/selection.ts +159 -0
- package/src/validation.ts +877 -0
- package/src/web/NativeListWebEngine.ts +3767 -0
|
@@ -0,0 +1,2238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { checkboxStateForKeys, checkboxStateForSection, isSelectableRow, reduceSelection, selectionStateFromSnapshot } from "../selection.js";
|
|
4
|
+
import { calculateAlignedScrollOffset, resolveLocationIndex, scrollFailure, validateOffset } from "../scrolling.js";
|
|
5
|
+
import { applyRowPatches, validateSnapshot } from "../validation.js";
|
|
6
|
+
const SECTION_INDEX_GUTTER = 44;
|
|
7
|
+
const DEFAULT_VIEWPORT_WIDTH = 320;
|
|
8
|
+
const DEFAULT_VIEWPORT_HEIGHT = 640;
|
|
9
|
+
const OVERSCAN_VIEWPORTS = 1;
|
|
10
|
+
export const WEB_REORDER_ANIMATION = {
|
|
11
|
+
outOfWayDurationMs: 200,
|
|
12
|
+
outOfWayTimingFunction: 'cubic-bezier(0.2, 0, 0, 1)',
|
|
13
|
+
dropDurationMs: 80,
|
|
14
|
+
dropTimingFunction: 'ease'
|
|
15
|
+
};
|
|
16
|
+
const REORDER_TOUCH_LONG_PRESS_MS = 120;
|
|
17
|
+
const REORDER_MOUSE_MOVE_THRESHOLD_PX = 5;
|
|
18
|
+
const REORDER_EDGE_RATIO = 0.25;
|
|
19
|
+
const REORDER_MAX_SPEED_ZONE_RATIO = 0.05;
|
|
20
|
+
const REORDER_MAX_SCROLL_PX = 28;
|
|
21
|
+
const REORDER_SCROLL_ACCELERATE_AT_MS = 360;
|
|
22
|
+
const REORDER_SCROLL_DAMPENING_MS = 1_200;
|
|
23
|
+
const REORDER_DROP_TRANSITION_MS = WEB_REORDER_ANIMATION.dropDurationMs;
|
|
24
|
+
const WALLET_REORDER_COMPACT_HEIGHT = 68;
|
|
25
|
+
let webNativeListInstanceCounter = 0;
|
|
26
|
+
const defaultTheme = {
|
|
27
|
+
background: '#F7F7F7',
|
|
28
|
+
rowBackground: '#FFFFFF',
|
|
29
|
+
rowSelectedBackground: '#EAF2FF',
|
|
30
|
+
rowPressedBackground: '#E8E8E8',
|
|
31
|
+
subduedBackground: '#F9F9F9',
|
|
32
|
+
strongBackground: '#0000000F',
|
|
33
|
+
primaryText: '#111111',
|
|
34
|
+
secondaryText: '#6B7280',
|
|
35
|
+
disabledText: '#8D8D8D',
|
|
36
|
+
icon: '#111111',
|
|
37
|
+
iconSubdued: '#8D8D8D',
|
|
38
|
+
separator: '#E5E7EB',
|
|
39
|
+
accent: '#2F6BFF',
|
|
40
|
+
positive: '#15803D',
|
|
41
|
+
negative: '#DC2626',
|
|
42
|
+
criticalBackground: '#FEECEC',
|
|
43
|
+
inverseBackground: '#202020',
|
|
44
|
+
inverseText: '#FCFCFC',
|
|
45
|
+
info: '#0D74CE'
|
|
46
|
+
};
|
|
47
|
+
export function webActionAnchorPayload(token, rect, source, generation, layoutDirection, slot) {
|
|
48
|
+
return {
|
|
49
|
+
token,
|
|
50
|
+
windowRect: {
|
|
51
|
+
x: rect.left,
|
|
52
|
+
y: rect.top,
|
|
53
|
+
width: rect.width,
|
|
54
|
+
height: rect.height
|
|
55
|
+
},
|
|
56
|
+
source,
|
|
57
|
+
slot,
|
|
58
|
+
generation,
|
|
59
|
+
layoutDirection
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function isWebRowReorderable(snapshot, row) {
|
|
63
|
+
if (!snapshot.capabilities?.reorderable || row.disabled) return false;
|
|
64
|
+
if (row.type === 'rail' && row.draggable) return true;
|
|
65
|
+
if (row.type === 'identity' && row.draggable) return true;
|
|
66
|
+
if (row.type === 'walletGroup' && row.draggable) return true;
|
|
67
|
+
return (row.type === 'identity' || row.type === 'action') && Boolean(row.trailing?.some(accessory => accessory.kind === 'drag'));
|
|
68
|
+
}
|
|
69
|
+
export function moveWebReorderRow(rows, fromIndex, toIndex) {
|
|
70
|
+
if (fromIndex === toIndex || fromIndex < 0 || fromIndex >= rows.length || toIndex < 0 || toIndex >= rows.length) {
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
73
|
+
const next = [...rows];
|
|
74
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
75
|
+
if (!moved) return rows;
|
|
76
|
+
next.splice(toIndex, 0, moved);
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
export function cancelWebReorderRows(originalRows) {
|
|
80
|
+
return originalRows;
|
|
81
|
+
}
|
|
82
|
+
export function webReorderEventForRows(originalRows, finalRows, key) {
|
|
83
|
+
const fromIndex = originalRows.findIndex(row => row.key === key);
|
|
84
|
+
const toIndex = finalRows.findIndex(row => row.key === key);
|
|
85
|
+
if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return undefined;
|
|
86
|
+
return {
|
|
87
|
+
key,
|
|
88
|
+
fromIndex,
|
|
89
|
+
toIndex,
|
|
90
|
+
beforeKey: finalRows[toIndex - 1]?.key,
|
|
91
|
+
afterKey: finalRows[toIndex + 1]?.key
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function webReorderAutoScrollVelocity(point, start, end, elapsedMs) {
|
|
95
|
+
const viewportLength = Math.max(1, end - start);
|
|
96
|
+
const edge = viewportLength * REORDER_EDGE_RATIO;
|
|
97
|
+
const maxZone = viewportLength * REORDER_MAX_SPEED_ZONE_RATIO;
|
|
98
|
+
const speedForDistance = distance => {
|
|
99
|
+
if (distance > edge) return 0;
|
|
100
|
+
const proposed = distance <= maxZone ? REORDER_MAX_SCROLL_PX : distance === edge ? 1 : Math.ceil(REORDER_MAX_SCROLL_PX * Math.pow((edge - distance) / Math.max(1, edge - maxZone), 2));
|
|
101
|
+
if (elapsedMs < REORDER_SCROLL_ACCELERATE_AT_MS) return 1;
|
|
102
|
+
if (elapsedMs >= REORDER_SCROLL_DAMPENING_MS) return proposed;
|
|
103
|
+
const timeProgress = (elapsedMs - REORDER_SCROLL_ACCELERATE_AT_MS) / (REORDER_SCROLL_DAMPENING_MS - REORDER_SCROLL_ACCELERATE_AT_MS);
|
|
104
|
+
return Math.ceil(proposed * timeProgress * timeProgress);
|
|
105
|
+
};
|
|
106
|
+
if (point <= start + edge) return -speedForDistance(point - start);
|
|
107
|
+
if (point >= end - edge) return speedForDistance(end - point);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
export function hasExceededWebReorderMouseThreshold(deltaX, deltaY) {
|
|
111
|
+
return Math.abs(deltaX) >= REORDER_MOUSE_MOVE_THRESHOLD_PX || Math.abs(deltaY) >= REORDER_MOUSE_MOVE_THRESHOLD_PX;
|
|
112
|
+
}
|
|
113
|
+
function effectiveRows(snapshot) {
|
|
114
|
+
if (snapshot.rows.length > 0) return snapshot.rows;
|
|
115
|
+
return snapshot.emptyState ? [snapshot.emptyState] : [];
|
|
116
|
+
}
|
|
117
|
+
function resolvedTheme(snapshot) {
|
|
118
|
+
return {
|
|
119
|
+
...defaultTheme,
|
|
120
|
+
...snapshot.theme
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function paddingValues(snapshot) {
|
|
124
|
+
const fallback = snapshot.layout.contentPadding ?? 0;
|
|
125
|
+
return {
|
|
126
|
+
horizontal: snapshot.layout.contentPaddingHorizontal ?? fallback,
|
|
127
|
+
top: snapshot.layout.contentPaddingTop ?? fallback,
|
|
128
|
+
bottom: snapshot.layout.contentPaddingBottom ?? fallback
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function isStructuralRow(row) {
|
|
132
|
+
return row.type === 'sectionHeader' || row.type === 'system' || row.type === 'action';
|
|
133
|
+
}
|
|
134
|
+
function sizeModifier(row) {
|
|
135
|
+
if (row.type === 'sectionHeader' && (row.variant === 'summary' || row.variant === 'gallery')) return 0;
|
|
136
|
+
if (row.size === 'small') return -8;
|
|
137
|
+
if (row.size === 'large') return 12;
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
function approximateMessageHeight(row, availableWidth) {
|
|
141
|
+
if (row.type !== 'message') return 0;
|
|
142
|
+
const leadingWidth = row.leading ? 52 : 0;
|
|
143
|
+
const thumbnailWidth = row.thumbnail ? 88 : 0;
|
|
144
|
+
const charactersPerLine = Math.max(18, Math.floor((availableWidth - leadingWidth - thumbnailWidth - 40) / 7));
|
|
145
|
+
const titleLines = Math.min(2, Math.max(1, Math.ceil(row.title.length / charactersPerLine)));
|
|
146
|
+
const bodyLines = Math.min(row.bodyLines ?? 3, Math.max(1, Math.ceil(row.body.length / charactersPerLine)));
|
|
147
|
+
return 32 + titleLines * 20 + bodyLines * 20 + 22;
|
|
148
|
+
}
|
|
149
|
+
export function estimateWebRowHeight(row, snapshot, availableWidth) {
|
|
150
|
+
if (row.type === 'system' && row.variant === 'spacer') return row.height;
|
|
151
|
+
if (row.type === 'walletGroup') return (row.children.length + 1) * 68 + row.children.length * 12;
|
|
152
|
+
if (row.type === 'identity' && row.presentation === 'walletSidebar') return 68;
|
|
153
|
+
if (row.type === 'identity' && row.presentation === 'networkSelector') return 47;
|
|
154
|
+
if (row.type === 'identity' && row.presentation === 'accountSelector') return 58;
|
|
155
|
+
let base;
|
|
156
|
+
switch (row.type) {
|
|
157
|
+
case 'rail':
|
|
158
|
+
base = 40;
|
|
159
|
+
break;
|
|
160
|
+
case 'activity':
|
|
161
|
+
base = row.footerActions?.length ? 100 : 60;
|
|
162
|
+
break;
|
|
163
|
+
case 'message':
|
|
164
|
+
base = approximateMessageHeight(row, availableWidth);
|
|
165
|
+
break;
|
|
166
|
+
case 'mediaTile':
|
|
167
|
+
base = 244;
|
|
168
|
+
break;
|
|
169
|
+
case 'metricCard':
|
|
170
|
+
base = row.variant === 'activity' ? 161 : row.variant === 'performance' ? 178 : 132;
|
|
171
|
+
break;
|
|
172
|
+
case 'sectionHeader':
|
|
173
|
+
{
|
|
174
|
+
const isHistory = row.variant === 'history' || row.key.startsWith('history-') || row.sectionKey.startsWith('history-');
|
|
175
|
+
base = snapshot.layout.kind === 'table' ? 28 : row.presentation === 'networkSelector' ? 47 : isHistory ? 16 : row.variant === 'summary' ? 68 : row.variant === 'gallery' ? 32 : row.checkbox ? 56 : snapshot.layout.kind === 'linear' ? 30 : 36;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case 'system':
|
|
179
|
+
base = row.variant === 'noMatch' || row.variant === 'end' ? 36 : row.variant === 'retry' ? 44 : 56;
|
|
180
|
+
break;
|
|
181
|
+
case 'action':
|
|
182
|
+
base = row.presentation === 'accountSelector' ? 48 : row.icon ? 60 : 44;
|
|
183
|
+
break;
|
|
184
|
+
case 'dataRow':
|
|
185
|
+
base = row.columns.some(column => column.secondaryText) ? 60 : 56;
|
|
186
|
+
break;
|
|
187
|
+
case 'identity':
|
|
188
|
+
base = row.tertiary ? 72 : row.subtitle ? 60 : 56;
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
base = 56;
|
|
192
|
+
}
|
|
193
|
+
const tableAdjustment = snapshot.layout.kind === 'table' && row.type === 'dataRow' && !row.columns.some(column => column.secondaryText) ? -8 : 0;
|
|
194
|
+
return Math.max(0, base + sizeModifier(row) + tableAdjustment);
|
|
195
|
+
}
|
|
196
|
+
function estimateHorizontalWidth(row) {
|
|
197
|
+
if (row.type === 'mediaTile') return 200;
|
|
198
|
+
if (row.type !== 'rail') return 280;
|
|
199
|
+
const badgeLength = row.badge?.text.length ?? 0;
|
|
200
|
+
const statusLength = row.status && row.status !== 'none' ? row.status.length : 0;
|
|
201
|
+
return Math.min(288, Math.max(72, 50 + (row.title.length + badgeLength + statusLength) * 7));
|
|
202
|
+
}
|
|
203
|
+
function sectionIndexEnabled(snapshot) {
|
|
204
|
+
return Boolean(snapshot.capabilities?.sectionIndex?.enabled && snapshot.layout.kind === 'sectioned' && snapshot.layout.orientation !== 'horizontal' && snapshot.rows.some(row => row.type === 'sectionHeader' && row.indexTitle));
|
|
205
|
+
}
|
|
206
|
+
export function computeWebListLayout(snapshot, viewportWidth, viewportHeight, compactRowKey) {
|
|
207
|
+
const rows = effectiveRows(snapshot);
|
|
208
|
+
const horizontal = snapshot.layout.orientation === 'horizontal';
|
|
209
|
+
const spacing = snapshot.layout.itemSpacing ?? 0;
|
|
210
|
+
const padding = paddingValues(snapshot);
|
|
211
|
+
const indexGutter = sectionIndexEnabled(snapshot) ? SECTION_INDEX_GUTTER : 0;
|
|
212
|
+
const width = Math.max(1, viewportWidth || DEFAULT_VIEWPORT_WIDTH);
|
|
213
|
+
const height = Math.max(1, viewportHeight || DEFAULT_VIEWPORT_HEIGHT);
|
|
214
|
+
const availableWidth = Math.max(1, width - padding.horizontal * 2 - indexGutter);
|
|
215
|
+
const availableHeight = Math.max(1, height - padding.top - padding.bottom);
|
|
216
|
+
const items = [];
|
|
217
|
+
if (horizontal) {
|
|
218
|
+
let x = padding.horizontal;
|
|
219
|
+
rows.forEach((row, index) => {
|
|
220
|
+
const itemWidth = estimateHorizontalWidth(row);
|
|
221
|
+
const rowHeight = row.type === 'walletGroup' && row.key === compactRowKey ? WALLET_REORDER_COMPACT_HEIGHT : estimateWebRowHeight(row, snapshot, availableWidth);
|
|
222
|
+
items.push({
|
|
223
|
+
index,
|
|
224
|
+
key: row.key,
|
|
225
|
+
x,
|
|
226
|
+
y: padding.top,
|
|
227
|
+
width: itemWidth,
|
|
228
|
+
height: row.type === 'rail' ? Math.min(rowHeight, availableHeight) : availableHeight
|
|
229
|
+
});
|
|
230
|
+
x += itemWidth + spacing;
|
|
231
|
+
});
|
|
232
|
+
const contentWidth = rows.length > 0 ? x - spacing + padding.horizontal : padding.horizontal * 2;
|
|
233
|
+
return {
|
|
234
|
+
items,
|
|
235
|
+
horizontal: true,
|
|
236
|
+
contentWidth: Math.max(width, contentWidth),
|
|
237
|
+
contentHeight: height
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
if (snapshot.layout.kind !== 'grid') {
|
|
241
|
+
let y = padding.top;
|
|
242
|
+
rows.forEach((row, index) => {
|
|
243
|
+
const itemHeight = row.type === 'walletGroup' && row.key === compactRowKey ? WALLET_REORDER_COMPACT_HEIGHT : estimateWebRowHeight(row, snapshot, availableWidth);
|
|
244
|
+
items.push({
|
|
245
|
+
index,
|
|
246
|
+
key: row.key,
|
|
247
|
+
x: padding.horizontal,
|
|
248
|
+
y,
|
|
249
|
+
width: availableWidth,
|
|
250
|
+
height: itemHeight
|
|
251
|
+
});
|
|
252
|
+
y += itemHeight + spacing;
|
|
253
|
+
});
|
|
254
|
+
const contentHeight = rows.length > 0 ? y - spacing + padding.bottom : padding.top + padding.bottom;
|
|
255
|
+
return {
|
|
256
|
+
items,
|
|
257
|
+
horizontal: false,
|
|
258
|
+
contentWidth: width,
|
|
259
|
+
contentHeight: Math.max(height, contentHeight)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const columns = snapshot.layout.gridColumns ?? 2;
|
|
263
|
+
const itemWidth = Math.floor((availableWidth - spacing * (columns - 1)) / columns);
|
|
264
|
+
let y = padding.top;
|
|
265
|
+
let column = 0;
|
|
266
|
+
let bandStart = y;
|
|
267
|
+
let bandHeight = 0;
|
|
268
|
+
const flushBand = () => {
|
|
269
|
+
if (column === 0) return;
|
|
270
|
+
y = bandStart + bandHeight + spacing;
|
|
271
|
+
column = 0;
|
|
272
|
+
bandHeight = 0;
|
|
273
|
+
bandStart = y;
|
|
274
|
+
};
|
|
275
|
+
rows.forEach((row, index) => {
|
|
276
|
+
if (isStructuralRow(row)) {
|
|
277
|
+
flushBand();
|
|
278
|
+
const itemHeight = estimateWebRowHeight(row, snapshot, availableWidth);
|
|
279
|
+
items.push({
|
|
280
|
+
index,
|
|
281
|
+
key: row.key,
|
|
282
|
+
x: padding.horizontal,
|
|
283
|
+
y,
|
|
284
|
+
width: availableWidth,
|
|
285
|
+
height: itemHeight
|
|
286
|
+
});
|
|
287
|
+
y += itemHeight + spacing;
|
|
288
|
+
bandStart = y;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const itemHeight = row.type === 'mediaTile' ? itemWidth + 48 : estimateWebRowHeight(row, snapshot, itemWidth);
|
|
292
|
+
items.push({
|
|
293
|
+
index,
|
|
294
|
+
key: row.key,
|
|
295
|
+
x: padding.horizontal + column * (itemWidth + spacing),
|
|
296
|
+
y: bandStart,
|
|
297
|
+
width: itemWidth,
|
|
298
|
+
height: itemHeight
|
|
299
|
+
});
|
|
300
|
+
bandHeight = Math.max(bandHeight, itemHeight);
|
|
301
|
+
column += 1;
|
|
302
|
+
if (column === columns) flushBand();
|
|
303
|
+
});
|
|
304
|
+
flushBand();
|
|
305
|
+
const contentHeight = rows.length > 0 ? Math.max(padding.top, y - spacing) + padding.bottom : padding.top + padding.bottom;
|
|
306
|
+
return {
|
|
307
|
+
items,
|
|
308
|
+
horizontal: false,
|
|
309
|
+
contentWidth: width,
|
|
310
|
+
contentHeight: Math.max(height, contentHeight)
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
export function webWalletGroupReorderBadge(row) {
|
|
314
|
+
return row.type === 'walletGroup' && row.children.length > 0 ? '+' + String(row.children.length) : undefined;
|
|
315
|
+
}
|
|
316
|
+
function itemStart(item, horizontal) {
|
|
317
|
+
return horizontal ? item.x : item.y;
|
|
318
|
+
}
|
|
319
|
+
function itemEnd(item, horizontal) {
|
|
320
|
+
return itemStart(item, horizontal) + (horizontal ? item.width : item.height);
|
|
321
|
+
}
|
|
322
|
+
export function visibleWebLayoutItems(layout, offset, viewportLength, overscan = 0) {
|
|
323
|
+
if (layout.items.length === 0) return [];
|
|
324
|
+
const start = Math.max(0, offset - overscan);
|
|
325
|
+
const end = offset + viewportLength + overscan;
|
|
326
|
+
let low = 0;
|
|
327
|
+
let high = layout.items.length;
|
|
328
|
+
while (low < high) {
|
|
329
|
+
const middle = Math.floor((low + high) / 2);
|
|
330
|
+
if (itemStart(layout.items[middle], layout.horizontal) < start) low = middle + 1;else high = middle;
|
|
331
|
+
}
|
|
332
|
+
// Grid items in a band share a start coordinate but can have different
|
|
333
|
+
// heights. Include the preceding band when any of its items overlaps the
|
|
334
|
+
// viewport; every earlier band is guaranteed to have ended before it.
|
|
335
|
+
if (low > 0) {
|
|
336
|
+
const previousStart = itemStart(layout.items[low - 1], layout.horizontal);
|
|
337
|
+
let bandStart = low - 1;
|
|
338
|
+
while (bandStart > 0 && itemStart(layout.items[bandStart - 1], layout.horizontal) === previousStart) bandStart -= 1;
|
|
339
|
+
if (layout.items.slice(bandStart, low).some(item => itemEnd(item, layout.horizontal) >= start)) low = bandStart;
|
|
340
|
+
}
|
|
341
|
+
const visible = [];
|
|
342
|
+
for (let index = low; index < layout.items.length; index += 1) {
|
|
343
|
+
const item = layout.items[index];
|
|
344
|
+
if (itemStart(item, layout.horizontal) > end) break;
|
|
345
|
+
if (itemEnd(item, layout.horizontal) >= start) visible.push(item);
|
|
346
|
+
}
|
|
347
|
+
return visible;
|
|
348
|
+
}
|
|
349
|
+
export function webLayoutItemsForMount(layout, offset, viewportLength, virtualizationEnabled, overscan = 0) {
|
|
350
|
+
return virtualizationEnabled ? visibleWebLayoutItems(layout, offset, viewportLength, overscan) : layout.items;
|
|
351
|
+
}
|
|
352
|
+
function rowWithoutSelectionState(row) {
|
|
353
|
+
const copy = {
|
|
354
|
+
...row
|
|
355
|
+
};
|
|
356
|
+
delete copy.selected;
|
|
357
|
+
if (row.type === 'sectionHeader' || row.type === 'action' || row.type === 'dataRow') {
|
|
358
|
+
if (row.checkbox) copy.checkbox = {
|
|
359
|
+
...row.checkbox,
|
|
360
|
+
state: undefined
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (row.type === 'identity' || row.type === 'action') {
|
|
364
|
+
copy.trailing = row.trailing?.map(accessory => accessory.kind === 'checkbox' ? {
|
|
365
|
+
...accessory,
|
|
366
|
+
state: undefined
|
|
367
|
+
} : accessory);
|
|
368
|
+
}
|
|
369
|
+
return copy;
|
|
370
|
+
}
|
|
371
|
+
export function webRowRenderSignature(row) {
|
|
372
|
+
return JSON.stringify(rowWithoutSelectionState(row));
|
|
373
|
+
}
|
|
374
|
+
export const WEB_LIST_CSS = `
|
|
375
|
+
.ok-native-list-root{--nl-bg:#f7f7f7;--nl-row:#fff;--nl-selected:#eaf2ff;--nl-pressed:#e8e8e8;--nl-subdued:#f9f9f9;--nl-strong:#0000000f;--nl-primary:#111;--nl-secondary:#6b7280;--nl-disabled:#8d8d8d;--nl-icon:#111;--nl-icon-subdued:#8d8d8d;--nl-separator:#e5e7eb;--nl-accent:#2f6bff;--nl-positive:#15803d;--nl-negative:#dc2626;--nl-critical:#feecec;--nl-inverse:#202020;--nl-inverse-text:#fcfcfc;--nl-info:#0d74ce;position:absolute;inset:0;display:flex;min-width:0;min-height:0;overflow:hidden;background:var(--nl-bg);color:var(--nl-primary);font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-synthesis:none}
|
|
376
|
+
.ok-native-list-viewport-frame{position:relative;flex:1;min-width:0;min-height:0;overflow:hidden}
|
|
377
|
+
.ok-native-list-viewport{position:absolute;inset:0;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;scrollbar-gutter:stable}
|
|
378
|
+
.ok-native-list-content{position:relative;min-width:100%;min-height:100%}
|
|
379
|
+
.ok-native-list-item{position:absolute;box-sizing:border-box;contain:layout paint style;outline:none}
|
|
380
|
+
.ok-native-list-row{width:100%;height:100%;box-sizing:border-box;display:flex;align-items:center;gap:12px;overflow:hidden;background:var(--nl-row);color:var(--nl-primary);cursor:pointer;user-select:none;-webkit-user-select:none}
|
|
381
|
+
.ok-native-list-item[data-table-alternate="true"]>.ok-native-list-row{background:var(--nl-bg)}
|
|
382
|
+
.ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
|
|
383
|
+
.ok-native-list-item[data-native-list-disabled="true"]>.ok-native-list-row{opacity:.5;cursor:default}
|
|
384
|
+
.ok-native-list-item:not([data-native-list-disabled="true"]):hover>.ok-native-list-row{background:var(--nl-pressed)}
|
|
385
|
+
.ok-native-list-item:not([data-native-list-disabled="true"]):not([data-native-list-selected="true"]):hover>.ok-native-list-wallet-row{background:var(--nl-strong)}
|
|
386
|
+
.ok-native-list-item:not([data-native-list-disabled="true"]):not([data-native-list-selected="true"]):active>.ok-native-list-wallet-row{background:var(--nl-pressed)}
|
|
387
|
+
.ok-native-list-item[data-native-list-selected="true"]:hover>.ok-native-list-wallet-row{background:var(--nl-selected)}
|
|
388
|
+
.ok-native-list-wallet-group{display:flex;width:100%;height:100%;box-sizing:border-box;flex-direction:column;gap:12px;overflow:hidden;border:1px solid var(--nl-separator);border-radius:12px;background:var(--nl-subdued);user-select:none;-webkit-user-select:none}
|
|
389
|
+
.ok-native-list-wallet-member{flex:0 0 68px;height:68px;overflow:hidden;border-radius:12px;background:transparent;cursor:pointer}
|
|
390
|
+
.ok-native-list-wallet-member>.ok-native-list-wallet-row{height:100%;background:transparent}
|
|
391
|
+
.ok-native-list-wallet-member[data-native-list-selected="true"]>.ok-native-list-wallet-row{background:var(--nl-selected)}
|
|
392
|
+
.ok-native-list-wallet-member:not([data-native-list-selected="true"]):hover>.ok-native-list-wallet-row{background:var(--nl-strong)}
|
|
393
|
+
.ok-native-list-wallet-member:not([data-native-list-selected="true"]):active>.ok-native-list-wallet-row{background:var(--nl-pressed)}
|
|
394
|
+
.ok-native-list-item:focus-visible>.ok-native-list-row{outline:2px solid var(--nl-accent);outline-offset:-2px}
|
|
395
|
+
.ok-native-list-item[data-native-list-reorderable="true"]>.ok-native-list-row{cursor:grab}
|
|
396
|
+
.ok-native-list-root[data-native-list-dragging="true"] .ok-native-list-row{cursor:grabbing}
|
|
397
|
+
.ok-native-list-item[data-native-list-animate-reorder="true"]{transition:transform ${WEB_REORDER_ANIMATION.outOfWayDurationMs}ms ${WEB_REORDER_ANIMATION.outOfWayTimingFunction},height ${WEB_REORDER_ANIMATION.outOfWayDurationMs}ms ${WEB_REORDER_ANIMATION.outOfWayTimingFunction}}
|
|
398
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row{overflow:hidden;border-radius:12px;background:var(--nl-row)}
|
|
399
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-row>*{visibility:hidden}
|
|
400
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group{border-color:transparent;background:var(--nl-row)}
|
|
401
|
+
.ok-native-list-item[data-native-list-dragging="true"]>.ok-native-list-wallet-group>*{visibility:hidden}
|
|
402
|
+
.ok-native-list-reorder-preview{position:fixed;left:0;top:0;z-index:100001;pointer-events:none;overflow:hidden;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.12);transform-origin:center;will-change:transform;font-family:Roobert,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
403
|
+
.ok-native-list-reorder-preview>.ok-native-list-row{background:var(--nl-row);cursor:grabbing}
|
|
404
|
+
.ok-native-list-reorder-preview[data-native-list-selected="true"]>.ok-native-list-row{background:var(--nl-selected)}
|
|
405
|
+
.ok-native-list-reorder-count{position:absolute;right:4px;bottom:4px;display:flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:24px;height:24px;padding:0 6px;border:1px solid var(--nl-row);border-radius:12px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;line-height:22px;font-weight:600}
|
|
406
|
+
.ok-native-list-item[data-separator="true"]>.ok-native-list-row{border-bottom:1px solid var(--nl-separator)}
|
|
407
|
+
.ok-native-list-item[data-group-position="first"]>.ok-native-list-row{border-radius:12px 12px 0 0}
|
|
408
|
+
.ok-native-list-item[data-group-position="last"]>.ok-native-list-row{border-radius:0 0 12px 12px}
|
|
409
|
+
.ok-native-list-item[data-group-position="single"]>.ok-native-list-row{border-radius:12px}
|
|
410
|
+
.ok-native-list-standard{padding:8px 12px}.ok-native-list-network-row{padding:0 12px}.ok-native-list-wallet-row{padding:4px 8px;flex-direction:column;justify-content:center;gap:4px}.ok-native-list-account-row{padding:4px 12px;gap:8px}
|
|
411
|
+
.ok-native-list-account-row .ok-native-list-visual,.ok-native-list-account-action-row .ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-account-row .ok-native-list-visual>img,.ok-native-list-account-row .ok-native-list-visual-main,.ok-native-list-account-action-row .ok-native-list-visual>img,.ok-native-list-account-action-row .ok-native-list-visual-main{width:32px;height:32px}.ok-native-list-account-row .ok-native-list-visual>.ok-native-list-visual-corner{width:20px;height:20px;padding:2px}.ok-native-list-account-action-row .ok-native-list-visual{border-radius:8px!important}.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-secondary);font-size:12px;line-height:16px;font-weight:400}.ok-native-list-item[data-native-list-selected="true"]>.ok-native-list-wallet-row .ok-native-list-title{color:var(--nl-primary)}.ok-native-list-account-row .ok-native-list-title{font-size:16px;line-height:20px;font-weight:400}.ok-native-list-account-row .ok-native-list-secondary{font-size:14px;line-height:20px;font-weight:400}
|
|
412
|
+
.ok-native-list-flex{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-title{font-size:15px;line-height:20px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-secondary{font-size:13px;line-height:18px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-tertiary{color:var(--nl-secondary)}.ok-native-list-info{color:var(--nl-info)}
|
|
413
|
+
.ok-native-list-identity-row .ok-native-list-title,.ok-native-list-identity-row .ok-native-list-amounts>span:first-child{font-size:16px;line-height:20px}.ok-native-list-identity-row .ok-native-list-secondary{font-size:14px;line-height:20px}
|
|
414
|
+
.ok-native-list-value{font-size:14px;line-height:20px;font-weight:500;white-space:nowrap}.ok-native-list-time{font-size:11px;line-height:16px;color:var(--nl-secondary);align-self:flex-start}.ok-native-list-amounts{display:flex;flex-direction:column;align-items:flex-end;min-width:0}.ok-native-list-actions{display:flex;gap:16px;margin-top:6px}
|
|
415
|
+
.ok-native-list-action-button{appearance:none;border:0;background:transparent;padding:2px;color:var(--nl-accent);font:600 12px/16px inherit;cursor:pointer}.ok-native-list-action-button[data-tone="danger"]{color:var(--nl-negative)}.ok-native-list-action-button:disabled{opacity:.5;cursor:default}
|
|
416
|
+
.ok-native-list-visual{position:relative;flex:0 0 40px;width:40px;height:40px;border-radius:20px;overflow:visible;background:var(--nl-strong)}.ok-native-list-visual>img,.ok-native-list-visual-main{display:block;width:40px;height:40px;border-radius:inherit;object-fit:cover}.ok-native-list-visual-fallback{display:flex;width:100%;height:100%;align-items:center;justify-content:center;border-radius:inherit;color:var(--nl-primary);font-size:15px;font-weight:600;overflow:hidden}.ok-native-list-visual>.ok-native-list-visual-corner{position:absolute;right:-4px;bottom:-4px;width:18px;height:18px;padding:2px;border-radius:50%;background:var(--nl-row);object-fit:cover}.ok-native-list-stacked{display:flex;align-items:center;width:48px;flex-basis:48px}.ok-native-list-stacked img{width:28px;height:28px;border:2px solid var(--nl-row);border-radius:50%;object-fit:cover;margin-left:-10px}.ok-native-list-stacked img:first-child{margin-left:0}
|
|
417
|
+
.ok-native-list-accessories{display:flex;align-items:center;gap:10px;min-width:0}.ok-native-list-accessory{font-size:14px;color:var(--nl-primary);white-space:nowrap}.ok-native-list-accessory-secondary{color:var(--nl-secondary)}.ok-native-list-icon-button{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border:0;background:transparent;color:var(--nl-icon);font-size:20px;line-height:1;font-weight:500;font-family:inherit;cursor:pointer}.ok-native-list-checkbox{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:20px;height:20px;border:2px solid color-mix(in srgb,var(--nl-primary) 19%,transparent);border-radius:5px;background:var(--nl-row);cursor:pointer}.ok-native-list-checkbox[data-state="checked"],.ok-native-list-checkbox[data-state="indeterminate"]{border-color:transparent;background:var(--nl-primary)}.ok-native-list-checkbox[data-state="checked"]::after{content:"";width:6px;height:10px;margin-top:-2px;border-right:2px solid var(--nl-row);border-bottom:2px solid var(--nl-row);transform:rotate(45deg)}.ok-native-list-checkbox[data-state="indeterminate"]::after{content:"";width:8px;height:2px;border-radius:1px;background:var(--nl-row)}.ok-native-list-spinner{width:16px;height:16px;border:2px solid var(--nl-separator);border-top-color:var(--nl-primary);border-radius:50%;animation:ok-native-list-spin .8s linear infinite}@keyframes ok-native-list-spin{to{transform:rotate(360deg)}}
|
|
418
|
+
.ok-native-list-badge{display:inline-flex;align-items:center;max-width:100%;height:18px;padding:0 5px;border-radius:5px;background:color-mix(in srgb,var(--nl-accent) 12%,transparent);color:var(--nl-accent);font-size:11px;line-height:18px;white-space:nowrap}.ok-native-list-badge[data-tone="danger"]{background:var(--nl-critical);color:var(--nl-negative)}.ok-native-list-badges{display:inline-flex;gap:4px;margin-left:6px;vertical-align:middle}
|
|
419
|
+
.ok-native-list-section{padding:0 8px;background:var(--nl-bg);gap:8px}.ok-native-list-section[data-variant="summary"]{padding:0 20px}.ok-native-list-section[data-variant="gallery"]{padding:0 12px}.ok-native-list-section[data-variant="history"]{padding:0 8px}.ok-native-list-section-title{font-size:13px;line-height:18px;font-weight:700;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-section[data-variant="summary"] .ok-native-list-section-title{font-size:16px;font-weight:500;color:var(--nl-primary)}.ok-native-list-section[data-variant="gallery"] .ok-native-list-section-title{font-size:16px;color:var(--nl-primary)}.ok-native-list-section[data-variant="history"] .ok-native-list-section-title{font-size:12px;line-height:16px}.ok-native-list-section-value{margin-left:auto}
|
|
420
|
+
.ok-native-list-action-row{justify-content:space-between;padding:0 16px}.ok-native-list-action-title{font-size:15px;font-weight:600;color:var(--nl-accent)}.ok-native-list-action-title[data-tone="danger"]{color:var(--nl-negative)}
|
|
421
|
+
.ok-native-list-account-action-row{justify-content:flex-start;gap:12px;padding:0 12px}.ok-native-list-account-action-row .ok-native-list-action-title{color:var(--nl-secondary);font-size:16px;line-height:24px;font-weight:400}
|
|
422
|
+
.ok-native-list-system{justify-content:center;padding:8px 12px;background:var(--nl-bg);color:var(--nl-secondary);cursor:default}.ok-native-list-system[data-variant="retry"],.ok-native-list-system[data-variant="noMatch"],.ok-native-list-system[data-variant="end"]{justify-content:flex-start}.ok-native-list-system[data-variant="retry"]{cursor:pointer}
|
|
423
|
+
.ok-native-list-rail{padding:4px;gap:6px;border-radius:8px}.ok-native-list-rail .ok-native-list-visual{width:20px;height:20px;flex-basis:20px}.ok-native-list-rail .ok-native-list-visual>img,.ok-native-list-rail .ok-native-list-visual-main{width:20px;height:20px}.ok-native-list-rail-title{font-size:12px;font-weight:500;white-space:nowrap}
|
|
424
|
+
.ok-native-list-media{display:block;padding:0 5px;background:transparent;border-radius:16px}.ok-native-list-media-image{display:block;width:100%;aspect-ratio:1;border-radius:10px;background:var(--nl-strong);object-fit:cover}.ok-native-list-media-image[data-state="empty"]{background:transparent}.ok-native-list-media-image[data-state="error"]{display:flex;align-items:center;justify-content:center;color:var(--nl-icon-subdued);font-size:24px}.ok-native-list-media-meta{padding-top:7px}.ok-native-list-media-subtitle-row{display:flex;align-items:center;gap:6px}.ok-native-list-media-subtitle{flex:1;min-width:0;font-size:12px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-network{width:14px;height:14px;border-radius:50%}.ok-native-list-media-title{font-size:16px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-close{position:absolute;right:9px;top:4px;border:0;background:color-mix(in srgb,var(--nl-inverse) 72%,transparent);color:var(--nl-inverse-text);width:24px;height:24px;border-radius:50%;font:18px/20px inherit;cursor:pointer}
|
|
425
|
+
.ok-native-list-metric{display:flex;flex-direction:column;align-items:flex-start;padding:12px;border-radius:12px;gap:5px;background:var(--nl-row)}.ok-native-list-metric-value{font-size:22px;line-height:28px;font-weight:700}.ok-native-list-composite{display:flex;flex-direction:column;align-items:stretch;padding:14px;border-radius:12px;gap:12px;background:var(--nl-subdued)}.ok-native-list-composite-heading{font-size:14px;letter-spacing:1px;color:var(--nl-secondary)}.ok-native-list-composite-row{display:flex;gap:12px}.ok-native-list-composite-cell{flex:1;min-width:0}.ok-native-list-composite-cell[data-shaded="true"]{padding:10px;border-radius:10px;background:color-mix(in srgb,var(--nl-primary) 5%,transparent)}.ok-native-list-composite-value{font-size:18px;font-weight:600}.ok-native-list-divider{height:1px;background:var(--nl-separator)}.ok-native-list-progress{height:4px;border-radius:2px;overflow:hidden;background:var(--nl-negative)}.ok-native-list-progress>span{display:block;height:100%;border-radius:2px;background:var(--nl-positive)}
|
|
426
|
+
.ok-native-list-data{padding:6px 12px}.ok-native-list-index{flex:0 0 28px;color:var(--nl-secondary);font-size:13px}.ok-native-list-favorite{flex:0 0 24px;color:var(--nl-icon-subdued);font-size:22px}.ok-native-list-favorite[data-active="true"]{color:var(--nl-accent)}.ok-native-list-data-cell{display:flex;flex-direction:column;min-width:0}.ok-native-list-data-cell[data-align="center"]{align-items:center}.ok-native-list-data-cell[data-align="end"]{align-items:flex-end}.ok-native-list-data-primary{display:flex;align-items:center;gap:5px;max-width:100%;font-size:16px;font-weight:500;white-space:nowrap}.ok-native-list-unread{width:7px;height:7px;flex:0 0 7px;border-radius:50%;background:var(--nl-accent)}.ok-native-list-thumbnail{width:64px;height:64px;border-radius:10px;object-fit:cover}
|
|
427
|
+
.ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:44px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;touch-action:none}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;border:0;background:transparent;display:flex;flex:1;max-height:22px;min-height:12px;width:100%;align-items:center;justify-content:center;padding:0;color:var(--nl-secondary);font:600 11px/1 inherit;cursor:pointer}.ok-native-list-index-button[data-active="true"]{color:var(--nl-accent)}.ok-native-list-index-preview{position:absolute;z-index:8;left:50%;top:50%;display:flex;width:72px;height:72px;align-items:center;justify-content:center;transform:translate(-50%,-50%) scale(.92);border-radius:16px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:28px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease}.ok-native-list-index-preview[data-visible="true"]{opacity:1;transform:translate(-50%,-50%) scale(1)}
|
|
428
|
+
.ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
|
|
429
|
+
@media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
|
|
430
|
+
`;
|
|
431
|
+
function createElement(document, tag, className, text) {
|
|
432
|
+
const element = document.createElement(tag);
|
|
433
|
+
if (className) element.className = className;
|
|
434
|
+
if (text !== undefined) element.textContent = text;
|
|
435
|
+
return element;
|
|
436
|
+
}
|
|
437
|
+
function setData(element, name, value) {
|
|
438
|
+
if (value === undefined) delete element.dataset[name];else element.dataset[name] = String(value);
|
|
439
|
+
}
|
|
440
|
+
function safeImageUri(uri) {
|
|
441
|
+
const trimmed = uri.trim();
|
|
442
|
+
if (/^(https?:|data:image\/|blob:|file:)/i.test(trimmed) || trimmed.startsWith('/')) return trimmed;
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
function createImage(context, source, className) {
|
|
446
|
+
const uri = safeImageUri(source.uri);
|
|
447
|
+
if (!uri) return undefined;
|
|
448
|
+
const image = context.document.createElement('img');
|
|
449
|
+
if (className) image.className = className;
|
|
450
|
+
image.src = uri;
|
|
451
|
+
image.alt = '';
|
|
452
|
+
image.draggable = false;
|
|
453
|
+
image.loading = 'lazy';
|
|
454
|
+
image.decoding = 'async';
|
|
455
|
+
image.style.objectFit = source.contentFit === 'fill' ? 'fill' : source.contentFit ?? 'cover';
|
|
456
|
+
return image;
|
|
457
|
+
}
|
|
458
|
+
function iconGlyph(name) {
|
|
459
|
+
const normalized = name.toLocaleLowerCase();
|
|
460
|
+
if (normalized.includes('chevron')) {
|
|
461
|
+
if (normalized.includes('top')) return '⌃';
|
|
462
|
+
if (normalized.includes('bottom')) return '⌄';
|
|
463
|
+
return '›';
|
|
464
|
+
}
|
|
465
|
+
if (normalized.includes('plus') || normalized.includes('add')) return '+';
|
|
466
|
+
if (normalized.includes('minus')) return '−';
|
|
467
|
+
if (normalized.includes('star')) return '☆';
|
|
468
|
+
if (normalized.includes('drag') || normalized.includes('grabber')) return '≡';
|
|
469
|
+
if (normalized.includes('dot') || normalized.includes('more')) return '•••';
|
|
470
|
+
if (normalized.includes('pencil') || normalized.includes('edit')) return '✎';
|
|
471
|
+
if (normalized.includes('error') || normalized.includes('exclamation')) return '!';
|
|
472
|
+
if (normalized.includes('info')) return 'i';
|
|
473
|
+
if (normalized.includes('question')) return '?';
|
|
474
|
+
if (normalized.includes('swap')) return '⇄';
|
|
475
|
+
if (normalized.includes('trend') || normalized.includes('arrow')) return '↗';
|
|
476
|
+
return name.slice(0, 1).toLocaleUpperCase();
|
|
477
|
+
}
|
|
478
|
+
function visualFromRow(row) {
|
|
479
|
+
if (row.type === 'identity') return row.leading;
|
|
480
|
+
if (row.type === 'rail') return row.visual;
|
|
481
|
+
if (row.type === 'activity') return row.leading;
|
|
482
|
+
if (row.type === 'message') return row.leading;
|
|
483
|
+
if (row.type === 'dataRow') return row.leading;
|
|
484
|
+
if (row.type === 'metricCard') return row.visual;
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
function createVisual(context, visual) {
|
|
488
|
+
if (!visual) return undefined;
|
|
489
|
+
if (visual.kind === 'stackedImages') {
|
|
490
|
+
const stack = createElement(context.document, 'span', 'ok-native-list-stacked');
|
|
491
|
+
visual.images.forEach(source => {
|
|
492
|
+
const image = createImage(context, source);
|
|
493
|
+
if (image) stack.appendChild(image);
|
|
494
|
+
});
|
|
495
|
+
return stack;
|
|
496
|
+
}
|
|
497
|
+
const frame = createElement(context.document, 'span', 'ok-native-list-visual');
|
|
498
|
+
const shape = visual.kind === 'icon' ? 'circle' : visual.shape ?? 'circle';
|
|
499
|
+
frame.style.borderRadius = shape === 'square' ? '0' : shape === 'rounded' ? '10px' : '50%';
|
|
500
|
+
if (visual.kind === 'icon') {
|
|
501
|
+
frame.style.background = visual.backgroundColor ?? 'var(--nl-strong)';
|
|
502
|
+
const fallback = createElement(context.document, 'span', 'ok-native-list-visual-fallback', iconGlyph(visual.name));
|
|
503
|
+
if (visual.tintColor) fallback.style.color = visual.tintColor;
|
|
504
|
+
frame.appendChild(fallback);
|
|
505
|
+
return frame;
|
|
506
|
+
}
|
|
507
|
+
if ('backgroundColor' in visual && visual.backgroundColor) frame.style.background = visual.backgroundColor;
|
|
508
|
+
const source = visual.kind === 'image' ? visual.image : visual.image;
|
|
509
|
+
const image = source ? createImage(context, source) : undefined;
|
|
510
|
+
if (image) {
|
|
511
|
+
image.className = 'ok-native-list-visual-main';
|
|
512
|
+
frame.appendChild(image);
|
|
513
|
+
} else {
|
|
514
|
+
frame.appendChild(createElement(context.document, 'span', 'ok-native-list-visual-fallback', 'fallbackText' in visual ? visual.fallbackText ?? '' : ''));
|
|
515
|
+
}
|
|
516
|
+
if (visual.kind === 'token' && visual.networkImage) {
|
|
517
|
+
const corner = createImage(context, visual.networkImage, 'ok-native-list-visual-corner');
|
|
518
|
+
if (corner) frame.appendChild(corner);
|
|
519
|
+
} else if ('cornerIcon' in visual && visual.cornerIcon) {
|
|
520
|
+
const corner = createElement(context.document, 'span', 'ok-native-list-visual-corner ok-native-list-visual-fallback', iconGlyph(visual.cornerIcon.name));
|
|
521
|
+
if (visual.cornerIcon.tintColor) corner.style.color = visual.cornerIcon.tintColor;
|
|
522
|
+
if (visual.cornerIcon.backgroundColor) corner.style.background = visual.cornerIcon.backgroundColor;
|
|
523
|
+
frame.appendChild(corner);
|
|
524
|
+
}
|
|
525
|
+
return frame;
|
|
526
|
+
}
|
|
527
|
+
function toneColor(tone, fallback) {
|
|
528
|
+
if (tone === 'positive') return 'var(--nl-positive)';
|
|
529
|
+
if (tone === 'negative') return 'var(--nl-negative)';
|
|
530
|
+
if (tone === 'secondary') return 'var(--nl-secondary)';
|
|
531
|
+
return fallback === 'secondary' ? 'var(--nl-secondary)' : 'var(--nl-primary)';
|
|
532
|
+
}
|
|
533
|
+
function createBadge(context, badge) {
|
|
534
|
+
const element = createElement(context.document, 'span', 'ok-native-list-badge', badge.text);
|
|
535
|
+
setData(element, 'tone', badge.tone);
|
|
536
|
+
return element;
|
|
537
|
+
}
|
|
538
|
+
function selectionKeysForTarget(target, rowKey, snapshot) {
|
|
539
|
+
if (!target || target.scope === 'row') {
|
|
540
|
+
return snapshot.rows.some(row => row.key === rowKey && isSelectableRow(row)) ? [rowKey] : [];
|
|
541
|
+
}
|
|
542
|
+
if (target.scope === 'section') {
|
|
543
|
+
return snapshot.rows.filter(row => row.sectionKey === target.sectionKey && isSelectableRow(row)).map(row => row.key);
|
|
544
|
+
}
|
|
545
|
+
return snapshot.rows.filter(isSelectableRow).map(row => row.key);
|
|
546
|
+
}
|
|
547
|
+
function checkboxState(target, fallback, rowKey, snapshot, selectedKeys) {
|
|
548
|
+
if (target?.scope === 'section') {
|
|
549
|
+
return checkboxStateForSection(target.sectionKey, snapshot.rows, selectedKeys);
|
|
550
|
+
}
|
|
551
|
+
return checkboxStateForKeys(selectionKeysForTarget(target, rowKey, snapshot), selectedKeys);
|
|
552
|
+
}
|
|
553
|
+
function createCheckbox(context, rowKey, accessory) {
|
|
554
|
+
if (accessory.loading) {
|
|
555
|
+
const spinner = createElement(context.document, 'span', 'ok-native-list-spinner');
|
|
556
|
+
spinner.setAttribute('aria-label', 'Loading');
|
|
557
|
+
return spinner;
|
|
558
|
+
}
|
|
559
|
+
const state = checkboxState(accessory.target, accessory.state, rowKey, context.snapshot, context.selectedKeys);
|
|
560
|
+
const element = createElement(context.document, 'button', 'ok-native-list-checkbox');
|
|
561
|
+
element.setAttribute('type', 'button');
|
|
562
|
+
element.setAttribute('role', 'checkbox');
|
|
563
|
+
element.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
|
|
564
|
+
element.setAttribute('aria-label', 'Select');
|
|
565
|
+
element.toggleAttribute('disabled', Boolean(accessory.disabled));
|
|
566
|
+
setData(element, 'state', state);
|
|
567
|
+
setData(element, 'checkboxFallback', accessory.state);
|
|
568
|
+
setData(element, 'nativeListAction', accessory.actionKey ?? 'selection');
|
|
569
|
+
setData(element, 'selectionScope', accessory.target?.scope ?? 'row');
|
|
570
|
+
if (accessory.target?.scope === 'section') setData(element, 'selectionKey', accessory.target.sectionKey);else if (accessory.target?.scope === 'row') setData(element, 'selectionKey', rowKey);
|
|
571
|
+
return element;
|
|
572
|
+
}
|
|
573
|
+
function createIconAction(context, name, actionKey, disabled, tintColor) {
|
|
574
|
+
const element = createElement(context.document, actionKey ? 'button' : 'span', 'ok-native-list-icon-button', iconGlyph(name));
|
|
575
|
+
if (element.tagName === 'BUTTON') {
|
|
576
|
+
element.setAttribute('type', 'button');
|
|
577
|
+
element.toggleAttribute('disabled', Boolean(disabled));
|
|
578
|
+
}
|
|
579
|
+
if (actionKey) setData(element, 'nativeListAction', actionKey);
|
|
580
|
+
if (tintColor) element.style.color = tintColor;
|
|
581
|
+
return element;
|
|
582
|
+
}
|
|
583
|
+
function markActionAnchorSource(element, source, slot) {
|
|
584
|
+
setData(element, 'nativeListAnchorSource', source);
|
|
585
|
+
if (slot !== undefined) setData(element, 'nativeListAnchorSlot', slot);
|
|
586
|
+
}
|
|
587
|
+
function createAccessory(context, rowKey, accessory, slot) {
|
|
588
|
+
if (accessory.kind === 'checkbox') {
|
|
589
|
+
const element = createCheckbox(context, rowKey, accessory);
|
|
590
|
+
markActionAnchorSource(element, 'trailingAccessory', slot);
|
|
591
|
+
return element;
|
|
592
|
+
}
|
|
593
|
+
if (accessory.kind === 'icon') {
|
|
594
|
+
const element = createIconAction(context, accessory.name, accessory.actionKey, accessory.disabled, accessory.tintColor);
|
|
595
|
+
markActionAnchorSource(element, 'trailingAccessory', slot);
|
|
596
|
+
return element;
|
|
597
|
+
}
|
|
598
|
+
if (accessory.kind === 'spinner') {
|
|
599
|
+
return createElement(context.document, 'span', 'ok-native-list-spinner');
|
|
600
|
+
}
|
|
601
|
+
const actionKey = 'actionKey' in accessory ? accessory.actionKey : undefined;
|
|
602
|
+
const element = createElement(context.document, actionKey ? 'button' : 'span', 'ok-native-list-accessory');
|
|
603
|
+
if (element.tagName === 'BUTTON') {
|
|
604
|
+
element.setAttribute('type', 'button');
|
|
605
|
+
element.classList.add('ok-native-list-icon-button');
|
|
606
|
+
setData(element, 'nativeListAction', actionKey);
|
|
607
|
+
}
|
|
608
|
+
switch (accessory.kind) {
|
|
609
|
+
case 'value':
|
|
610
|
+
element.textContent = accessory.text;
|
|
611
|
+
if (accessory.secondary) element.classList.add('ok-native-list-accessory-secondary');
|
|
612
|
+
break;
|
|
613
|
+
case 'valuePair':
|
|
614
|
+
{
|
|
615
|
+
const primary = createElement(context.document, 'span', undefined, accessory.primary);
|
|
616
|
+
primary.style.color = toneColor(accessory.primaryTone, 'primary');
|
|
617
|
+
const secondary = createElement(context.document, 'span', 'ok-native-list-secondary', accessory.secondary);
|
|
618
|
+
secondary.style.color = toneColor(accessory.secondaryTone, 'secondary');
|
|
619
|
+
element.replaceChildren(primary, secondary);
|
|
620
|
+
element.classList.add('ok-native-list-amounts');
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
case 'radio':
|
|
624
|
+
element.textContent = accessory.checked ? '●' : '○';
|
|
625
|
+
break;
|
|
626
|
+
case 'switch':
|
|
627
|
+
element.textContent = accessory.value ? 'ON' : 'OFF';
|
|
628
|
+
setData(element, 'nativeListAction', accessory.actionKey);
|
|
629
|
+
break;
|
|
630
|
+
case 'chevron':
|
|
631
|
+
element.textContent = '›';
|
|
632
|
+
break;
|
|
633
|
+
case 'menu':
|
|
634
|
+
element.textContent = '⋮';
|
|
635
|
+
break;
|
|
636
|
+
case 'drag':
|
|
637
|
+
element.textContent = '≡';
|
|
638
|
+
break;
|
|
639
|
+
case 'progress':
|
|
640
|
+
element.textContent = String(Math.round(accessory.value * 100)) + '%';
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
if (actionKey) markActionAnchorSource(element, 'trailingAccessory', slot);
|
|
644
|
+
return element;
|
|
645
|
+
}
|
|
646
|
+
function appendAccessories(parent, context, rowKey, accessories) {
|
|
647
|
+
if (!accessories?.length) return;
|
|
648
|
+
const container = createElement(context.document, 'span', 'ok-native-list-accessories');
|
|
649
|
+
accessories.forEach((accessory, slot) => container.appendChild(createAccessory(context, rowKey, accessory, slot)));
|
|
650
|
+
parent.appendChild(container);
|
|
651
|
+
}
|
|
652
|
+
function createTextColumn(context, title, subtitle, tertiary, tertiaryTone, badges) {
|
|
653
|
+
const column = createElement(context.document, 'span', 'ok-native-list-flex');
|
|
654
|
+
const titleLine = createElement(context.document, 'span', 'ok-native-list-title', title);
|
|
655
|
+
if (badges?.length) {
|
|
656
|
+
const badgeLine = createElement(context.document, 'span', 'ok-native-list-badges');
|
|
657
|
+
badges.forEach(badge => badgeLine.appendChild(createBadge(context, badge)));
|
|
658
|
+
titleLine.appendChild(badgeLine);
|
|
659
|
+
}
|
|
660
|
+
column.appendChild(titleLine);
|
|
661
|
+
if (subtitle) column.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', subtitle));
|
|
662
|
+
if (tertiary) {
|
|
663
|
+
const element = createElement(context.document, 'span', tertiaryTone === 'info' ? 'ok-native-list-secondary ok-native-list-info' : 'ok-native-list-secondary ok-native-list-tertiary', tertiary);
|
|
664
|
+
column.appendChild(element);
|
|
665
|
+
}
|
|
666
|
+
return column;
|
|
667
|
+
}
|
|
668
|
+
function createSectionHeader(context, row) {
|
|
669
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-section');
|
|
670
|
+
setData(body, 'variant', row.variant);
|
|
671
|
+
if (row.titleIcon) {
|
|
672
|
+
const titleIcon = createIconAction(context, row.titleIcon.name, row.titleIcon.actionKey, row.titleIcon.disabled, row.titleIcon.tintColor);
|
|
673
|
+
markActionAnchorSource(titleIcon, 'leadingAction');
|
|
674
|
+
body.appendChild(titleIcon);
|
|
675
|
+
}
|
|
676
|
+
body.appendChild(createTextColumn(context, row.title, row.subtitle));
|
|
677
|
+
if (row.value) {
|
|
678
|
+
const value = createElement(context.document, row.valueActionKey ? 'button' : 'span', row.valueActionKey ? 'ok-native-list-action-button ok-native-list-section-value' : 'ok-native-list-value ok-native-list-section-value', row.value);
|
|
679
|
+
if (row.valueActionKey) {
|
|
680
|
+
value.setAttribute('type', 'button');
|
|
681
|
+
setData(value, 'nativeListAction', row.valueActionKey);
|
|
682
|
+
markActionAnchorSource(value, 'trailingAccessory', 0);
|
|
683
|
+
}
|
|
684
|
+
body.appendChild(value);
|
|
685
|
+
}
|
|
686
|
+
if (row.valueIcon) {
|
|
687
|
+
const valueIcon = createIconAction(context, row.valueIcon.name, row.valueIcon.actionKey, row.valueIcon.disabled, row.valueIcon.tintColor);
|
|
688
|
+
markActionAnchorSource(valueIcon, 'trailingAccessory', 1);
|
|
689
|
+
body.appendChild(valueIcon);
|
|
690
|
+
}
|
|
691
|
+
if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
|
|
692
|
+
return body;
|
|
693
|
+
}
|
|
694
|
+
function createActionRow(context, row) {
|
|
695
|
+
const body = createElement(context.document, 'div', ['ok-native-list-row', 'ok-native-list-action-row', row.presentation === 'accountSelector' ? 'ok-native-list-account-action-row' : ''].filter(Boolean).join(' '));
|
|
696
|
+
if (row.icon) body.appendChild(createVisual(context, row.icon));
|
|
697
|
+
const title = createElement(context.document, 'span', 'ok-native-list-action-title', row.title);
|
|
698
|
+
setData(title, 'tone', row.tone);
|
|
699
|
+
body.appendChild(title);
|
|
700
|
+
if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
|
|
701
|
+
appendAccessories(body, context, row.key, row.trailing);
|
|
702
|
+
return body;
|
|
703
|
+
}
|
|
704
|
+
function createSystemRow(context, row) {
|
|
705
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-system');
|
|
706
|
+
setData(body, 'variant', row.variant);
|
|
707
|
+
if (row.variant === 'loading') body.appendChild(createElement(context.document, 'span', 'ok-native-list-spinner'));
|
|
708
|
+
const message = row.variant === 'spacer' ? '' : row.message ?? (row.variant === 'end' ? 'End' : '');
|
|
709
|
+
if (message) body.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', message));
|
|
710
|
+
return body;
|
|
711
|
+
}
|
|
712
|
+
function createRailRow(context, row) {
|
|
713
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-rail');
|
|
714
|
+
const visual = createVisual(context, row.visual);
|
|
715
|
+
if (visual) body.appendChild(visual);
|
|
716
|
+
body.appendChild(createElement(context.document, 'span', 'ok-native-list-rail-title', row.title));
|
|
717
|
+
if (row.status && row.status !== 'none') body.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.status));
|
|
718
|
+
if (row.badge) body.appendChild(createBadge(context, row.badge));
|
|
719
|
+
return body;
|
|
720
|
+
}
|
|
721
|
+
function createMediaRow(context, row) {
|
|
722
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-media');
|
|
723
|
+
if (row.image && !row.imageState) {
|
|
724
|
+
const image = createImage(context, row.image, 'ok-native-list-media-image');
|
|
725
|
+
if (image) body.appendChild(image);
|
|
726
|
+
} else {
|
|
727
|
+
const placeholder = createElement(context.document, 'span', 'ok-native-list-media-image', row.imageState === 'error' ? '▧' : '');
|
|
728
|
+
setData(placeholder, 'state', row.imageState ?? 'empty');
|
|
729
|
+
body.appendChild(placeholder);
|
|
730
|
+
}
|
|
731
|
+
const metadata = createElement(context.document, 'div', 'ok-native-list-media-meta');
|
|
732
|
+
const subtitleLine = createElement(context.document, 'div', 'ok-native-list-media-subtitle-row');
|
|
733
|
+
subtitleLine.appendChild(createElement(context.document, 'span', 'ok-native-list-media-subtitle', row.subtitle || '-'));
|
|
734
|
+
if (row.networkImage) {
|
|
735
|
+
const network = createImage(context, row.networkImage, 'ok-native-list-media-network');
|
|
736
|
+
if (network) subtitleLine.appendChild(network);
|
|
737
|
+
}
|
|
738
|
+
metadata.appendChild(subtitleLine);
|
|
739
|
+
metadata.appendChild(createElement(context.document, 'div', 'ok-native-list-media-title', row.title));
|
|
740
|
+
if (row.badge) metadata.appendChild(createBadge(context, row.badge));
|
|
741
|
+
body.appendChild(metadata);
|
|
742
|
+
if (row.closeActionKey) {
|
|
743
|
+
const close = createElement(context.document, 'button', 'ok-native-list-media-close', '×');
|
|
744
|
+
close.setAttribute('type', 'button');
|
|
745
|
+
close.setAttribute('aria-label', 'Close');
|
|
746
|
+
setData(close, 'nativeListAction', row.closeActionKey);
|
|
747
|
+
markActionAnchorSource(close, 'mediaClose');
|
|
748
|
+
body.appendChild(close);
|
|
749
|
+
}
|
|
750
|
+
return body;
|
|
751
|
+
}
|
|
752
|
+
function createMetricCell(context, metric, shaded) {
|
|
753
|
+
const cell = createElement(context.document, 'div', 'ok-native-list-composite-cell');
|
|
754
|
+
setData(cell, 'shaded', shaded);
|
|
755
|
+
cell.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', metric.label));
|
|
756
|
+
const valueLine = createElement(context.document, 'div');
|
|
757
|
+
valueLine.style.display = 'flex';
|
|
758
|
+
valueLine.style.alignItems = 'center';
|
|
759
|
+
valueLine.style.gap = '6px';
|
|
760
|
+
if (metric.visual) {
|
|
761
|
+
const visual = createVisual(context, metric.visual);
|
|
762
|
+
if (visual) {
|
|
763
|
+
visual.style.width = '16px';
|
|
764
|
+
visual.style.height = '16px';
|
|
765
|
+
visual.style.flexBasis = '16px';
|
|
766
|
+
const image = visual.querySelector('img');
|
|
767
|
+
if (image) {
|
|
768
|
+
image.style.width = '16px';
|
|
769
|
+
image.style.height = '16px';
|
|
770
|
+
}
|
|
771
|
+
valueLine.appendChild(visual);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const value = createElement(context.document, 'span', 'ok-native-list-composite-value', metric.value);
|
|
775
|
+
value.style.color = toneColor(metric.tone, 'primary');
|
|
776
|
+
valueLine.appendChild(value);
|
|
777
|
+
cell.appendChild(valueLine);
|
|
778
|
+
return cell;
|
|
779
|
+
}
|
|
780
|
+
function createMetricRow(context, row) {
|
|
781
|
+
if (row.variant === 'activity' || row.variant === 'performance') {
|
|
782
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-composite');
|
|
783
|
+
body.appendChild(createElement(context.document, 'div', 'ok-native-list-composite-heading', row.title));
|
|
784
|
+
const metrics = row.metrics ?? [];
|
|
785
|
+
const firstLine = createElement(context.document, 'div', 'ok-native-list-composite-row');
|
|
786
|
+
metrics.slice(0, 2).forEach(metric => firstLine.appendChild(createMetricCell(context, metric, false)));
|
|
787
|
+
body.appendChild(firstLine);
|
|
788
|
+
if (row.variant === 'activity') {
|
|
789
|
+
body.appendChild(createElement(context.document, 'div', 'ok-native-list-divider'));
|
|
790
|
+
} else {
|
|
791
|
+
const progress = createElement(context.document, 'div', 'ok-native-list-progress');
|
|
792
|
+
const fill = createElement(context.document, 'span');
|
|
793
|
+
fill.style.width = String(Math.round((row.progress ?? 0) * 100)) + '%';
|
|
794
|
+
progress.appendChild(fill);
|
|
795
|
+
body.appendChild(progress);
|
|
796
|
+
}
|
|
797
|
+
const secondLine = createElement(context.document, 'div', 'ok-native-list-composite-row');
|
|
798
|
+
metrics.slice(2).forEach(metric => secondLine.appendChild(createMetricCell(context, metric, row.variant === 'performance')));
|
|
799
|
+
body.appendChild(secondLine);
|
|
800
|
+
return body;
|
|
801
|
+
}
|
|
802
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-metric');
|
|
803
|
+
if (row.visual) {
|
|
804
|
+
const visual = createVisual(context, row.visual);
|
|
805
|
+
if (visual) body.appendChild(visual);
|
|
806
|
+
}
|
|
807
|
+
body.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', row.title));
|
|
808
|
+
body.appendChild(createElement(context.document, 'div', 'ok-native-list-metric-value', row.value));
|
|
809
|
+
if (row.trend) {
|
|
810
|
+
const trend = createElement(context.document, 'div', 'ok-native-list-secondary', row.trend);
|
|
811
|
+
trend.style.color = row.trendTone === 'positive' ? 'var(--nl-positive)' : row.trendTone === 'negative' ? 'var(--nl-negative)' : 'var(--nl-secondary)';
|
|
812
|
+
body.appendChild(trend);
|
|
813
|
+
}
|
|
814
|
+
if (row.subtitle) body.appendChild(createElement(context.document, 'div', 'ok-native-list-secondary', row.subtitle));
|
|
815
|
+
if (row.badge) body.appendChild(createBadge(context, row.badge));
|
|
816
|
+
return body;
|
|
817
|
+
}
|
|
818
|
+
function createDataRow(context, row) {
|
|
819
|
+
const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-data');
|
|
820
|
+
if (row.checkbox) body.appendChild(createCheckbox(context, row.key, row.checkbox));
|
|
821
|
+
if (row.index !== undefined) body.appendChild(createElement(context.document, 'span', 'ok-native-list-index', String(row.index)));
|
|
822
|
+
if (row.favorite) {
|
|
823
|
+
const favorite = createElement(context.document, 'span', 'ok-native-list-favorite', row.favoriteActive ? '★' : '☆');
|
|
824
|
+
setData(favorite, 'active', row.favoriteActive);
|
|
825
|
+
body.appendChild(favorite);
|
|
826
|
+
}
|
|
827
|
+
if (row.leading) {
|
|
828
|
+
const visual = createVisual(context, row.leading);
|
|
829
|
+
if (visual) body.appendChild(visual);
|
|
830
|
+
}
|
|
831
|
+
row.columns.forEach(column => {
|
|
832
|
+
const cell = createElement(context.document, 'span', 'ok-native-list-data-cell');
|
|
833
|
+
cell.style.flex = String(column.weight ?? 1);
|
|
834
|
+
setData(cell, 'align', column.alignment ?? 'start');
|
|
835
|
+
const primary = createElement(context.document, 'span', 'ok-native-list-data-primary');
|
|
836
|
+
primary.style.color = toneColor(column.tone, 'primary');
|
|
837
|
+
if (column.secondaryLeadingText) primary.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', column.secondaryLeadingText));
|
|
838
|
+
primary.appendChild(createElement(context.document, 'span', undefined, column.text));
|
|
839
|
+
if (column.key === 'asset' && row.badges?.length) {
|
|
840
|
+
row.badges.forEach(badge => primary.appendChild(createBadge(context, badge)));
|
|
841
|
+
}
|
|
842
|
+
cell.appendChild(primary);
|
|
843
|
+
if (column.secondaryText) {
|
|
844
|
+
const secondary = createElement(context.document, 'span', 'ok-native-list-secondary', column.secondaryText);
|
|
845
|
+
secondary.style.color = toneColor(column.secondaryTone, 'secondary');
|
|
846
|
+
cell.appendChild(secondary);
|
|
847
|
+
}
|
|
848
|
+
body.appendChild(cell);
|
|
849
|
+
});
|
|
850
|
+
return body;
|
|
851
|
+
}
|
|
852
|
+
function createIdentityActivityOrMessageRow(context, row) {
|
|
853
|
+
const presentation = row.type === 'identity' ? row.presentation : undefined;
|
|
854
|
+
const body = createElement(context.document, 'div', ['ok-native-list-row', 'ok-native-list-standard', row.type === 'identity' && !presentation ? 'ok-native-list-identity-row' : '', presentation === 'networkSelector' ? 'ok-native-list-network-row' : '', presentation === 'walletSidebar' ? 'ok-native-list-wallet-row' : '', presentation === 'accountSelector' ? 'ok-native-list-account-row' : ''].filter(Boolean).join(' '));
|
|
855
|
+
if (row.type === 'identity' && row.leadingAction) {
|
|
856
|
+
const action = createIconAction(context, row.leadingAction.name, row.leadingAction.actionKey, row.leadingAction.disabled, row.leadingAction.tintColor);
|
|
857
|
+
markActionAnchorSource(action, 'leadingAction');
|
|
858
|
+
body.appendChild(action);
|
|
859
|
+
}
|
|
860
|
+
const visual = createVisual(context, visualFromRow(row));
|
|
861
|
+
if (visual) body.appendChild(visual);
|
|
862
|
+
if (row.type === 'activity' && row.secondaryLeading) {
|
|
863
|
+
const secondVisual = createVisual(context, row.secondaryLeading);
|
|
864
|
+
if (secondVisual) body.appendChild(secondVisual);
|
|
865
|
+
}
|
|
866
|
+
if (row.type === 'message' && row.unread) body.appendChild(createElement(context.document, 'span', 'ok-native-list-unread'));
|
|
867
|
+
const title = row.title;
|
|
868
|
+
const subtitle = row.type === 'identity' ? row.subtitle : row.type === 'activity' ? row.description : row.body;
|
|
869
|
+
const column = createTextColumn(context, title, subtitle, row.type === 'identity' ? row.tertiary : undefined, row.type === 'identity' ? row.tertiaryTone : undefined, row.type === 'identity' ? row.badges : undefined);
|
|
870
|
+
if (row.type === 'activity' && row.status) column.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.status));
|
|
871
|
+
if (row.type === 'activity' && row.footerActions?.length) {
|
|
872
|
+
const actions = createElement(context.document, 'span', 'ok-native-list-actions');
|
|
873
|
+
row.footerActions.forEach((action, slot) => {
|
|
874
|
+
const button = createElement(context.document, 'button', 'ok-native-list-action-button', action.label);
|
|
875
|
+
button.setAttribute('type', 'button');
|
|
876
|
+
button.toggleAttribute('disabled', Boolean(action.disabled));
|
|
877
|
+
setData(button, 'tone', action.tone);
|
|
878
|
+
setData(button, 'nativeListAction', action.key);
|
|
879
|
+
markActionAnchorSource(button, 'footerAction', slot);
|
|
880
|
+
actions.appendChild(button);
|
|
881
|
+
});
|
|
882
|
+
column.appendChild(actions);
|
|
883
|
+
}
|
|
884
|
+
body.appendChild(column);
|
|
885
|
+
if (row.type === 'activity') {
|
|
886
|
+
const amounts = createElement(context.document, 'span', 'ok-native-list-amounts');
|
|
887
|
+
if (row.primaryAmount) amounts.appendChild(createElement(context.document, 'span', 'ok-native-list-value', row.primaryAmount));
|
|
888
|
+
if (row.secondaryAmount) amounts.appendChild(createElement(context.document, 'span', 'ok-native-list-secondary', row.secondaryAmount));
|
|
889
|
+
body.appendChild(amounts);
|
|
890
|
+
} else if (row.type === 'message') {
|
|
891
|
+
body.appendChild(createElement(context.document, 'span', 'ok-native-list-time', row.time));
|
|
892
|
+
if (row.thumbnail) {
|
|
893
|
+
const thumbnail = createImage(context, row.thumbnail, 'ok-native-list-thumbnail');
|
|
894
|
+
if (thumbnail) body.appendChild(thumbnail);
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
appendAccessories(body, context, row.key, row.trailing);
|
|
898
|
+
}
|
|
899
|
+
return body;
|
|
900
|
+
}
|
|
901
|
+
function createWalletGroupRow(context, row) {
|
|
902
|
+
const body = createElement(context.document, 'div', 'ok-native-list-wallet-group');
|
|
903
|
+
[row.parent, ...row.children].forEach((member, memberIndex) => {
|
|
904
|
+
const memberElement = createElement(context.document, 'div', 'ok-native-list-wallet-member');
|
|
905
|
+
setData(memberElement, 'nativeListGroupMemberKey', member.key);
|
|
906
|
+
setData(memberElement, 'nativeListGroupParent', memberIndex === 0);
|
|
907
|
+
setData(memberElement, 'nativeListSelected', member.selected);
|
|
908
|
+
memberElement.appendChild(createIdentityActivityOrMessageRow(context, member));
|
|
909
|
+
body.appendChild(memberElement);
|
|
910
|
+
});
|
|
911
|
+
return body;
|
|
912
|
+
}
|
|
913
|
+
function createRowBody(context, row) {
|
|
914
|
+
switch (row.type) {
|
|
915
|
+
case 'walletGroup':
|
|
916
|
+
return createWalletGroupRow(context, row);
|
|
917
|
+
case 'sectionHeader':
|
|
918
|
+
return createSectionHeader(context, row);
|
|
919
|
+
case 'action':
|
|
920
|
+
return createActionRow(context, row);
|
|
921
|
+
case 'system':
|
|
922
|
+
return createSystemRow(context, row);
|
|
923
|
+
case 'rail':
|
|
924
|
+
return createRailRow(context, row);
|
|
925
|
+
case 'mediaTile':
|
|
926
|
+
return createMediaRow(context, row);
|
|
927
|
+
case 'metricCard':
|
|
928
|
+
return createMetricRow(context, row);
|
|
929
|
+
case 'dataRow':
|
|
930
|
+
return createDataRow(context, row);
|
|
931
|
+
case 'identity':
|
|
932
|
+
case 'activity':
|
|
933
|
+
case 'message':
|
|
934
|
+
return createIdentityActivityOrMessageRow(context, row);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
export class NativeListWebEngine {
|
|
938
|
+
rows = [];
|
|
939
|
+
selectedKeys = new Set();
|
|
940
|
+
layout = {
|
|
941
|
+
items: [],
|
|
942
|
+
contentWidth: 0,
|
|
943
|
+
contentHeight: 0,
|
|
944
|
+
horizontal: false
|
|
945
|
+
};
|
|
946
|
+
mounted = new Map();
|
|
947
|
+
pool = [];
|
|
948
|
+
suppressClickUntil = 0;
|
|
949
|
+
pullDistance = 0;
|
|
950
|
+
actionAnchorInstanceId = String(++webNativeListInstanceCounter);
|
|
951
|
+
actionAnchorCounter = 0;
|
|
952
|
+
bindingEpochCounter = 0;
|
|
953
|
+
lastViewportWidth = -1;
|
|
954
|
+
lastViewportHeight = -1;
|
|
955
|
+
destroyed = false;
|
|
956
|
+
constructor(host, snapshot, callbacks, virtualizationEnabled = true) {
|
|
957
|
+
this.document = host.ownerDocument;
|
|
958
|
+
this.snapshot = validateSnapshot(snapshot);
|
|
959
|
+
this.callbacks = callbacks;
|
|
960
|
+
this.virtualizationEnabled = virtualizationEnabled;
|
|
961
|
+
this.previousHostPosition = host.style.position;
|
|
962
|
+
if (!host.style.position) host.style.position = 'relative';
|
|
963
|
+
this.root = createElement(this.document, 'div', 'ok-native-list-root');
|
|
964
|
+
const style = this.document.createElement('style');
|
|
965
|
+
style.textContent = WEB_LIST_CSS;
|
|
966
|
+
this.viewportFrame = createElement(this.document, 'div', 'ok-native-list-viewport-frame');
|
|
967
|
+
this.viewport = createElement(this.document, 'div', 'ok-native-list-viewport');
|
|
968
|
+
this.viewport.setAttribute('role', 'list');
|
|
969
|
+
this.content = createElement(this.document, 'div', 'ok-native-list-content');
|
|
970
|
+
this.footer = createElement(this.document, 'div', 'ok-native-list-footer');
|
|
971
|
+
this.sticky = createElement(this.document, 'div', 'ok-native-list-item ok-native-list-sticky');
|
|
972
|
+
this.sticky.setAttribute('aria-hidden', 'true');
|
|
973
|
+
this.sticky.hidden = true;
|
|
974
|
+
this.indexRail = createElement(this.document, 'div', 'ok-native-list-index-rail');
|
|
975
|
+
this.indexRail.setAttribute('role', 'navigation');
|
|
976
|
+
this.indexRail.setAttribute('aria-label', 'Section index');
|
|
977
|
+
this.indexRail.hidden = true;
|
|
978
|
+
this.indexPreview = createElement(this.document, 'div', 'ok-native-list-index-preview');
|
|
979
|
+
this.indexPreview.setAttribute('aria-live', 'polite');
|
|
980
|
+
this.refreshIndicator = createElement(this.document, 'div', 'ok-native-list-refresh', 'Refreshing');
|
|
981
|
+
this.refreshIndicator.setAttribute('role', 'status');
|
|
982
|
+
this.reorderPreview = createElement(this.document, 'div', 'ok-native-list-reorder-preview');
|
|
983
|
+
this.reorderPreview.hidden = true;
|
|
984
|
+
this.reorderPreview.setAttribute('aria-hidden', 'true');
|
|
985
|
+
this.document.body.appendChild(this.reorderPreview);
|
|
986
|
+
this.viewport.append(this.content);
|
|
987
|
+
this.viewportFrame.append(this.viewport, this.sticky, this.indexRail, this.indexPreview, this.refreshIndicator);
|
|
988
|
+
this.root.append(style, this.viewportFrame, this.footer);
|
|
989
|
+
host.appendChild(this.root);
|
|
990
|
+
this.viewport.addEventListener('scroll', this.handleScroll, {
|
|
991
|
+
passive: true
|
|
992
|
+
});
|
|
993
|
+
this.root.addEventListener('click', this.handleClick);
|
|
994
|
+
this.root.addEventListener('keydown', this.handleKeyDown);
|
|
995
|
+
this.viewport.addEventListener('pointerdown', this.handleReorderPointerDown, {
|
|
996
|
+
passive: false
|
|
997
|
+
});
|
|
998
|
+
this.viewport.addEventListener('pointermove', this.handleReorderPointerMove, {
|
|
999
|
+
passive: false
|
|
1000
|
+
});
|
|
1001
|
+
this.viewport.addEventListener('pointerup', this.handleReorderPointerEnd);
|
|
1002
|
+
this.viewport.addEventListener('pointercancel', this.handleReorderPointerCancel);
|
|
1003
|
+
this.viewport.addEventListener('touchmove', this.handleReorderTouchMove, {
|
|
1004
|
+
passive: false
|
|
1005
|
+
});
|
|
1006
|
+
this.viewport.addEventListener('touchend', this.handleReorderTouchEnd, {
|
|
1007
|
+
passive: false
|
|
1008
|
+
});
|
|
1009
|
+
this.viewport.addEventListener('touchcancel', this.handleReorderTouchCancel);
|
|
1010
|
+
this.indexRail.addEventListener('pointerdown', this.handleIndexPointer);
|
|
1011
|
+
this.indexRail.addEventListener('pointermove', this.handleIndexPointer);
|
|
1012
|
+
this.indexRail.addEventListener('click', this.handleIndexClick);
|
|
1013
|
+
this.viewport.addEventListener('pointerdown', this.handlePullStart, {
|
|
1014
|
+
passive: true
|
|
1015
|
+
});
|
|
1016
|
+
this.viewport.addEventListener('pointermove', this.handlePullMove, {
|
|
1017
|
+
passive: false
|
|
1018
|
+
});
|
|
1019
|
+
this.viewport.addEventListener('pointerup', this.handlePullEnd);
|
|
1020
|
+
this.viewport.addEventListener('pointercancel', this.handlePullEnd);
|
|
1021
|
+
const ResizeObserverConstructor = this.document.defaultView?.ResizeObserver;
|
|
1022
|
+
if (ResizeObserverConstructor) {
|
|
1023
|
+
this.resizeObserver = new ResizeObserverConstructor(() => this.recomputeLayout());
|
|
1024
|
+
this.resizeObserver.observe(this.viewport);
|
|
1025
|
+
} else {
|
|
1026
|
+
this.document.defaultView?.addEventListener('resize', this.handleWindowResize);
|
|
1027
|
+
}
|
|
1028
|
+
this.applySnapshot(this.snapshot);
|
|
1029
|
+
}
|
|
1030
|
+
updateCallbacks(callbacks) {
|
|
1031
|
+
this.callbacks = callbacks;
|
|
1032
|
+
}
|
|
1033
|
+
setVirtualizationEnabled(enabled) {
|
|
1034
|
+
if (this.virtualizationEnabled === enabled) return;
|
|
1035
|
+
this.virtualizationEnabled = enabled;
|
|
1036
|
+
this.renderWindow();
|
|
1037
|
+
}
|
|
1038
|
+
applySnapshot(snapshot) {
|
|
1039
|
+
const validated = validateSnapshot(snapshot);
|
|
1040
|
+
this.invalidateActionAnchor('snapshot');
|
|
1041
|
+
this.setSnapshot(validated);
|
|
1042
|
+
}
|
|
1043
|
+
applyPatches(patches) {
|
|
1044
|
+
if (patches.length === 0) return;
|
|
1045
|
+
const next = applyRowPatches(this.snapshot, patches);
|
|
1046
|
+
this.invalidateActionAnchor('snapshot');
|
|
1047
|
+
const selection = new Set(this.selectedKeys);
|
|
1048
|
+
patches.forEach(patch => {
|
|
1049
|
+
const selected = 'selected' in patch.changes ? patch.changes.selected : undefined;
|
|
1050
|
+
if (selected === true) selection.add(patch.key);else if (selected === false) selection.delete(patch.key);
|
|
1051
|
+
});
|
|
1052
|
+
this.setSnapshot(next, selection);
|
|
1053
|
+
}
|
|
1054
|
+
reconcileSelection(keys) {
|
|
1055
|
+
const selectable = new Set(this.snapshot.rows.filter(isSelectableRow).map(row => row.key));
|
|
1056
|
+
if (keys.some(key => !selectable.has(key))) return;
|
|
1057
|
+
if (this.snapshot.selection?.mode === 'single' && keys.length > 1) return;
|
|
1058
|
+
this.selectedKeys = new Set(keys);
|
|
1059
|
+
this.updateVisibleSelection();
|
|
1060
|
+
this.renderFooter();
|
|
1061
|
+
}
|
|
1062
|
+
scrollToIndex(index, scroll) {
|
|
1063
|
+
if (index < 0 || index >= this.rows.length) {
|
|
1064
|
+
this.emitScrollFailure(index, 'index-out-of-range');
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
if (!this.canScroll()) {
|
|
1068
|
+
this.pendingScroll = {
|
|
1069
|
+
kind: 'index',
|
|
1070
|
+
index,
|
|
1071
|
+
scroll
|
|
1072
|
+
};
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const item = this.layout.items[index];
|
|
1076
|
+
if (!item) {
|
|
1077
|
+
this.pendingScroll = {
|
|
1078
|
+
kind: 'index',
|
|
1079
|
+
index,
|
|
1080
|
+
scroll
|
|
1081
|
+
};
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const viewportLength = this.viewportLength();
|
|
1085
|
+
const contentLength = this.contentLength();
|
|
1086
|
+
const currentOffset = this.currentOffset();
|
|
1087
|
+
const offset = calculateAlignedScrollOffset({
|
|
1088
|
+
itemOffset: itemStart(item, this.layout.horizontal),
|
|
1089
|
+
itemLength: this.layout.horizontal ? item.width : item.height,
|
|
1090
|
+
viewportLength,
|
|
1091
|
+
contentLength,
|
|
1092
|
+
currentOffset,
|
|
1093
|
+
alignment: scroll.alignment,
|
|
1094
|
+
viewPosition: scroll.viewPosition,
|
|
1095
|
+
viewOffset: scroll.viewOffset
|
|
1096
|
+
});
|
|
1097
|
+
this.scrollToAbsoluteOffset(offset, scroll.animated);
|
|
1098
|
+
}
|
|
1099
|
+
scrollToKey(key, scroll) {
|
|
1100
|
+
const index = this.rows.findIndex(row => row.key === key);
|
|
1101
|
+
if (index >= 0) this.scrollToIndex(index, scroll);
|
|
1102
|
+
}
|
|
1103
|
+
scrollToOffset(offset, animated) {
|
|
1104
|
+
validateOffset(offset);
|
|
1105
|
+
if (!this.canScroll()) {
|
|
1106
|
+
this.pendingScroll = {
|
|
1107
|
+
kind: 'offset',
|
|
1108
|
+
offset,
|
|
1109
|
+
animated
|
|
1110
|
+
};
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
this.scrollToAbsoluteOffset(offset, animated);
|
|
1114
|
+
}
|
|
1115
|
+
scrollToEnd(animated) {
|
|
1116
|
+
if (!this.canScroll()) {
|
|
1117
|
+
this.pendingScroll = {
|
|
1118
|
+
kind: 'end',
|
|
1119
|
+
animated
|
|
1120
|
+
};
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
this.scrollToAbsoluteOffset(Math.max(0, this.contentLength() - this.viewportLength()), animated);
|
|
1124
|
+
}
|
|
1125
|
+
scrollToLocation(params, scroll) {
|
|
1126
|
+
const index = resolveLocationIndex(this.snapshot.rows, params);
|
|
1127
|
+
if (index === undefined) {
|
|
1128
|
+
const sectionCount = this.snapshot.rows.filter(row => row.type === 'sectionHeader' && row.variant !== 'summary').length;
|
|
1129
|
+
this.emitScrollFailure(params.itemIndex, params.sectionIndex >= sectionCount ? 'section-out-of-range' : 'item-out-of-range');
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
this.scrollToIndex(index, scroll);
|
|
1133
|
+
}
|
|
1134
|
+
setRefreshing(refreshing) {
|
|
1135
|
+
this.snapshot = {
|
|
1136
|
+
...this.snapshot,
|
|
1137
|
+
capabilities: {
|
|
1138
|
+
...this.snapshot.capabilities,
|
|
1139
|
+
refreshing
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
this.updateRefreshIndicator();
|
|
1143
|
+
}
|
|
1144
|
+
setActionAnchorState(state) {
|
|
1145
|
+
const anchor = this.actionAnchor;
|
|
1146
|
+
if (!anchor || anchor.token !== state.token) return;
|
|
1147
|
+
if (!state.open) {
|
|
1148
|
+
if (state.restoreFocus && this.isActionAnchorValid(anchor)) {
|
|
1149
|
+
anchor.actionElement.focus({
|
|
1150
|
+
preventScroll: true
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
anchor.actionElement.removeAttribute('aria-expanded');
|
|
1154
|
+
this.actionAnchor = undefined;
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
if (anchor.invalidatedReason || !this.isActionAnchorValid(anchor)) {
|
|
1158
|
+
this.emitActionAnchorInvalidated(anchor, anchor.invalidatedReason ?? 'rebind');
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
anchor.open = true;
|
|
1162
|
+
anchor.actionElement.setAttribute('aria-expanded', 'true');
|
|
1163
|
+
}
|
|
1164
|
+
destroy() {
|
|
1165
|
+
if (this.destroyed) return;
|
|
1166
|
+
this.invalidateActionAnchor('destroy');
|
|
1167
|
+
this.destroyed = true;
|
|
1168
|
+
if (this.frameHandle !== undefined) this.cancelFrame(this.frameHandle);
|
|
1169
|
+
if (this.previewTimer !== undefined) this.document.defaultView?.clearTimeout(this.previewTimer);
|
|
1170
|
+
if (this.reorderMovementTimer !== undefined) this.document.defaultView?.clearTimeout(this.reorderMovementTimer);
|
|
1171
|
+
this.resizeObserver?.disconnect();
|
|
1172
|
+
this.document.defaultView?.removeEventListener('resize', this.handleWindowResize);
|
|
1173
|
+
this.viewport.removeEventListener('scroll', this.handleScroll);
|
|
1174
|
+
this.root.removeEventListener('click', this.handleClick);
|
|
1175
|
+
this.root.removeEventListener('keydown', this.handleKeyDown);
|
|
1176
|
+
this.cancelPointerReorder(true);
|
|
1177
|
+
this.viewport.removeEventListener('pointerdown', this.handleReorderPointerDown);
|
|
1178
|
+
this.viewport.removeEventListener('pointermove', this.handleReorderPointerMove);
|
|
1179
|
+
this.viewport.removeEventListener('pointerup', this.handleReorderPointerEnd);
|
|
1180
|
+
this.viewport.removeEventListener('pointercancel', this.handleReorderPointerCancel);
|
|
1181
|
+
this.viewport.removeEventListener('touchmove', this.handleReorderTouchMove);
|
|
1182
|
+
this.viewport.removeEventListener('touchend', this.handleReorderTouchEnd);
|
|
1183
|
+
this.viewport.removeEventListener('touchcancel', this.handleReorderTouchCancel);
|
|
1184
|
+
this.indexRail.removeEventListener('pointerdown', this.handleIndexPointer);
|
|
1185
|
+
this.indexRail.removeEventListener('pointermove', this.handleIndexPointer);
|
|
1186
|
+
this.indexRail.removeEventListener('click', this.handleIndexClick);
|
|
1187
|
+
this.viewport.removeEventListener('pointerdown', this.handlePullStart);
|
|
1188
|
+
this.viewport.removeEventListener('pointermove', this.handlePullMove);
|
|
1189
|
+
this.viewport.removeEventListener('pointerup', this.handlePullEnd);
|
|
1190
|
+
this.viewport.removeEventListener('pointercancel', this.handlePullEnd);
|
|
1191
|
+
const host = this.root.parentElement;
|
|
1192
|
+
this.root.remove();
|
|
1193
|
+
this.hideReorderPreview();
|
|
1194
|
+
this.reorderPreview.remove();
|
|
1195
|
+
if (host) host.style.position = this.previousHostPosition;
|
|
1196
|
+
this.mounted.clear();
|
|
1197
|
+
this.pool.length = 0;
|
|
1198
|
+
}
|
|
1199
|
+
setSnapshot(snapshot, selectedKeys) {
|
|
1200
|
+
this.snapshot = snapshot;
|
|
1201
|
+
this.rows = effectiveRows(snapshot);
|
|
1202
|
+
this.selectedKeys = selectedKeys ?? selectionStateFromSnapshot(snapshot).selectedKeys;
|
|
1203
|
+
if (this.reachedGeneration !== snapshot.generation) this.reachedGeneration = undefined;
|
|
1204
|
+
this.renderSectionIndex();
|
|
1205
|
+
this.applyTheme();
|
|
1206
|
+
this.recomputeLayout();
|
|
1207
|
+
this.renderFooter();
|
|
1208
|
+
this.updateRefreshIndicator();
|
|
1209
|
+
}
|
|
1210
|
+
applyTheme() {
|
|
1211
|
+
const theme = resolvedTheme(this.snapshot);
|
|
1212
|
+
const values = {
|
|
1213
|
+
'--nl-bg': theme.background,
|
|
1214
|
+
'--nl-row': theme.rowBackground,
|
|
1215
|
+
'--nl-selected': theme.rowSelectedBackground,
|
|
1216
|
+
'--nl-pressed': theme.rowPressedBackground,
|
|
1217
|
+
'--nl-subdued': theme.subduedBackground,
|
|
1218
|
+
'--nl-strong': theme.strongBackground,
|
|
1219
|
+
'--nl-primary': theme.primaryText,
|
|
1220
|
+
'--nl-secondary': theme.secondaryText,
|
|
1221
|
+
'--nl-disabled': theme.disabledText,
|
|
1222
|
+
'--nl-icon': theme.icon,
|
|
1223
|
+
'--nl-icon-subdued': theme.iconSubdued,
|
|
1224
|
+
'--nl-separator': theme.separator,
|
|
1225
|
+
'--nl-accent': theme.accent,
|
|
1226
|
+
'--nl-positive': theme.positive,
|
|
1227
|
+
'--nl-negative': theme.negative,
|
|
1228
|
+
'--nl-critical': theme.criticalBackground,
|
|
1229
|
+
'--nl-inverse': theme.inverseBackground,
|
|
1230
|
+
'--nl-inverse-text': theme.inverseText,
|
|
1231
|
+
'--nl-info': theme.info
|
|
1232
|
+
};
|
|
1233
|
+
Object.entries(values).forEach(([name, value]) => {
|
|
1234
|
+
if (!value) return;
|
|
1235
|
+
this.root.style.setProperty(name, value);
|
|
1236
|
+
this.reorderPreview.style.setProperty(name, value);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
recomputeLayout = () => {
|
|
1240
|
+
if (this.destroyed) return;
|
|
1241
|
+
const viewportWidth = this.viewport.clientWidth;
|
|
1242
|
+
const viewportHeight = this.viewport.clientHeight;
|
|
1243
|
+
if (this.lastViewportWidth >= 0 && (viewportWidth !== this.lastViewportWidth || viewportHeight !== this.lastViewportHeight)) {
|
|
1244
|
+
this.invalidateActionAnchor('layout');
|
|
1245
|
+
}
|
|
1246
|
+
this.lastViewportWidth = viewportWidth;
|
|
1247
|
+
this.lastViewportHeight = viewportHeight;
|
|
1248
|
+
const previousHorizontal = this.layout.horizontal;
|
|
1249
|
+
this.layout = computeWebListLayout(this.snapshot, viewportWidth, viewportHeight, this.reorderCompactKey);
|
|
1250
|
+
this.content.style.width = String(this.layout.contentWidth) + 'px';
|
|
1251
|
+
this.content.style.height = String(this.layout.contentHeight) + 'px';
|
|
1252
|
+
if (previousHorizontal !== this.layout.horizontal) {
|
|
1253
|
+
this.viewport.scrollLeft = 0;
|
|
1254
|
+
this.viewport.scrollTop = 0;
|
|
1255
|
+
}
|
|
1256
|
+
this.renderWindow();
|
|
1257
|
+
this.performPendingScroll();
|
|
1258
|
+
};
|
|
1259
|
+
renderWindow() {
|
|
1260
|
+
const viewportLength = this.viewportLength();
|
|
1261
|
+
const visible = webLayoutItemsForMount(this.layout, this.currentOffset(), viewportLength, this.virtualizationEnabled, viewportLength * OVERSCAN_VIEWPORTS);
|
|
1262
|
+
const desired = new Set(visible.map(item => item.index));
|
|
1263
|
+
this.mounted.forEach((element, index) => {
|
|
1264
|
+
if (!desired.has(index)) {
|
|
1265
|
+
this.invalidateActionAnchorForElement(element);
|
|
1266
|
+
this.mounted.delete(index);
|
|
1267
|
+
element.remove();
|
|
1268
|
+
this.pool.push(element);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
visible.forEach(layoutItem => {
|
|
1272
|
+
const row = this.rows[layoutItem.index];
|
|
1273
|
+
if (!row) return;
|
|
1274
|
+
let element = this.mounted.get(layoutItem.index);
|
|
1275
|
+
if (!element) {
|
|
1276
|
+
element = this.pool.pop() ?? createElement(this.document, 'div', 'ok-native-list-item');
|
|
1277
|
+
setData(element, 'nativeListAnimateReorder', false);
|
|
1278
|
+
this.mounted.set(layoutItem.index, element);
|
|
1279
|
+
this.content.appendChild(element);
|
|
1280
|
+
}
|
|
1281
|
+
this.positionElement(element, layoutItem);
|
|
1282
|
+
const signature = webRowRenderSignature(row);
|
|
1283
|
+
if (element.dataset.nativeListRowKey !== row.key || element.dataset.renderSignature !== signature) {
|
|
1284
|
+
this.renderElement(element, layoutItem.index, row);
|
|
1285
|
+
element.dataset.renderSignature = signature;
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
this.updateVisibleSelection();
|
|
1289
|
+
this.updateVisibleState();
|
|
1290
|
+
}
|
|
1291
|
+
positionElement(element, item) {
|
|
1292
|
+
element.style.transform = 'translate3d(' + String(item.x) + 'px,' + String(item.y) + 'px,0)';
|
|
1293
|
+
element.style.width = String(item.width) + 'px';
|
|
1294
|
+
element.style.height = String(item.height) + 'px';
|
|
1295
|
+
}
|
|
1296
|
+
renderElement(element, index, row, overlay = false) {
|
|
1297
|
+
this.invalidateActionAnchorForElement(element);
|
|
1298
|
+
const bindingEpoch = String(++this.bindingEpochCounter);
|
|
1299
|
+
element.className = overlay ? 'ok-native-list-item ok-native-list-sticky' : 'ok-native-list-item';
|
|
1300
|
+
setData(element, 'nativeListRowKey', row.key);
|
|
1301
|
+
setData(element, 'nativeListBindingEpoch', bindingEpoch);
|
|
1302
|
+
setData(element, 'nativeListRowIndex', index);
|
|
1303
|
+
setData(element, 'nativeListDisabled', Boolean(row.disabled));
|
|
1304
|
+
setData(element, 'nativeListReorderable', this.isReorderable(row));
|
|
1305
|
+
setData(element, 'nativeListDragging', this.pointerReorder?.active && this.pointerReorder.sourceKey === row.key);
|
|
1306
|
+
setData(element, 'separator', row.separator);
|
|
1307
|
+
setData(element, 'groupPosition', row.groupPosition);
|
|
1308
|
+
setData(element, 'tableAlternate', this.snapshot.layout.kind === 'table' && row.type === 'dataRow' && (row.index ?? index) % 2 === 0);
|
|
1309
|
+
element.setAttribute('role', 'listitem');
|
|
1310
|
+
element.setAttribute('aria-label', row.accessibilityLabel ?? this.rowLabel(row));
|
|
1311
|
+
if (!row.disabled && !(row.type === 'system' && row.variant === 'spacer')) {
|
|
1312
|
+
element.tabIndex = 0;
|
|
1313
|
+
} else {
|
|
1314
|
+
element.removeAttribute('tabindex');
|
|
1315
|
+
}
|
|
1316
|
+
element.draggable = false;
|
|
1317
|
+
const context = {
|
|
1318
|
+
document: this.document,
|
|
1319
|
+
snapshot: this.snapshot,
|
|
1320
|
+
selectedKeys: this.selectedKeys,
|
|
1321
|
+
itemIndex: index
|
|
1322
|
+
};
|
|
1323
|
+
element.replaceChildren(createRowBody(context, row));
|
|
1324
|
+
}
|
|
1325
|
+
renderFooter() {
|
|
1326
|
+
const row = this.snapshot.fixedFooter;
|
|
1327
|
+
this.invalidateActionAnchorForElement(this.footer);
|
|
1328
|
+
this.footer.replaceChildren();
|
|
1329
|
+
if (!row) return;
|
|
1330
|
+
const element = createElement(this.document, 'div', 'ok-native-list-item');
|
|
1331
|
+
this.renderElement(element, -1, row);
|
|
1332
|
+
element.style.position = 'relative';
|
|
1333
|
+
element.style.transform = 'none';
|
|
1334
|
+
element.style.width = '100%';
|
|
1335
|
+
element.style.height = String(estimateWebRowHeight(row, this.snapshot, this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH)) + 'px';
|
|
1336
|
+
this.footer.appendChild(element);
|
|
1337
|
+
}
|
|
1338
|
+
renderSectionIndex() {
|
|
1339
|
+
this.indexRail.replaceChildren();
|
|
1340
|
+
if (!sectionIndexEnabled(this.snapshot)) {
|
|
1341
|
+
this.indexRail.hidden = true;
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const fragment = this.document.createDocumentFragment();
|
|
1345
|
+
this.snapshot.rows.forEach((row, index) => {
|
|
1346
|
+
if (row.type !== 'sectionHeader' || !row.indexTitle) return;
|
|
1347
|
+
const button = createElement(this.document, 'button', 'ok-native-list-index-button', row.indexTitle);
|
|
1348
|
+
button.setAttribute('type', 'button');
|
|
1349
|
+
button.setAttribute('aria-label', 'Jump to ' + row.indexTitle);
|
|
1350
|
+
setData(button, 'sectionPosition', index);
|
|
1351
|
+
setData(button, 'sectionKey', row.key);
|
|
1352
|
+
fragment.appendChild(button);
|
|
1353
|
+
});
|
|
1354
|
+
this.indexRail.appendChild(fragment);
|
|
1355
|
+
this.indexRail.hidden = this.indexRail.childElementCount === 0;
|
|
1356
|
+
}
|
|
1357
|
+
updateVisibleSelection() {
|
|
1358
|
+
const update = (element, row) => {
|
|
1359
|
+
if (!row) return;
|
|
1360
|
+
const selected = this.selectedKeys.has(row.key);
|
|
1361
|
+
setData(element, 'nativeListSelected', selected);
|
|
1362
|
+
element.setAttribute('aria-selected', String(selected));
|
|
1363
|
+
element.querySelectorAll('.ok-native-list-checkbox').forEach(checkbox => {
|
|
1364
|
+
const scope = checkbox.dataset.selectionScope;
|
|
1365
|
+
const key = checkbox.dataset.selectionKey;
|
|
1366
|
+
const target = scope === 'section' && key ? {
|
|
1367
|
+
scope: 'section',
|
|
1368
|
+
sectionKey: key
|
|
1369
|
+
} : scope === 'list' ? {
|
|
1370
|
+
scope: 'list'
|
|
1371
|
+
} : {
|
|
1372
|
+
scope: 'row'
|
|
1373
|
+
};
|
|
1374
|
+
const state = checkboxState(target, checkbox.dataset.checkboxFallback ?? 'unchecked', row.key, this.snapshot, this.selectedKeys);
|
|
1375
|
+
setData(checkbox, 'state', state);
|
|
1376
|
+
checkbox.setAttribute('aria-checked', state === 'indeterminate' ? 'mixed' : String(state === 'checked'));
|
|
1377
|
+
});
|
|
1378
|
+
};
|
|
1379
|
+
this.mounted.forEach((element, index) => update(element, this.rows[index]));
|
|
1380
|
+
const stickyIndex = Number(this.sticky.dataset.nativeListRowIndex);
|
|
1381
|
+
if (!Number.isNaN(stickyIndex)) update(this.sticky, this.rows[stickyIndex]);
|
|
1382
|
+
}
|
|
1383
|
+
updateVisibleState() {
|
|
1384
|
+
const visible = visibleWebLayoutItems(this.layout, this.currentOffset(), this.viewportLength());
|
|
1385
|
+
const first = visible[0];
|
|
1386
|
+
const last = visible.at(-1);
|
|
1387
|
+
const signature = String(first?.index ?? -1) + ':' + String(last?.index ?? -1) + ':' + (first?.key ?? '') + ':' + (last?.key ?? '');
|
|
1388
|
+
if (signature !== this.lastVisibleSignature) {
|
|
1389
|
+
this.lastVisibleSignature = signature;
|
|
1390
|
+
this.callbacks.onVisibleRangeChanged?.({
|
|
1391
|
+
firstKey: first?.key,
|
|
1392
|
+
lastKey: last?.key,
|
|
1393
|
+
firstIndex: first?.index ?? -1,
|
|
1394
|
+
lastIndex: last?.index ?? -1
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
this.checkEndReached(last?.index ?? -1);
|
|
1398
|
+
this.updateStickyHeader(first?.index ?? -1);
|
|
1399
|
+
this.updateSectionIndex(first?.index ?? -1);
|
|
1400
|
+
}
|
|
1401
|
+
updateStickyHeader(firstVisibleIndex) {
|
|
1402
|
+
if (!this.snapshot.layout.stickyHeaders || this.layout.horizontal || firstVisibleIndex < 0) {
|
|
1403
|
+
this.sticky.hidden = true;
|
|
1404
|
+
this.stickyKey = undefined;
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
let index = -1;
|
|
1408
|
+
for (let cursor = firstVisibleIndex; cursor >= 0; cursor -= 1) {
|
|
1409
|
+
const row = this.rows[cursor];
|
|
1410
|
+
if (row?.type === 'sectionHeader' && row.variant !== 'summary') {
|
|
1411
|
+
index = cursor;
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const row = this.rows[index];
|
|
1416
|
+
const item = this.layout.items[index];
|
|
1417
|
+
if (!row || row.type !== 'sectionHeader' || !item) {
|
|
1418
|
+
this.sticky.hidden = true;
|
|
1419
|
+
this.stickyKey = undefined;
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
const signature = webRowRenderSignature(row);
|
|
1423
|
+
if (this.stickyKey !== row.key || this.sticky.dataset.renderSignature !== signature) {
|
|
1424
|
+
this.renderElement(this.sticky, index, row, true);
|
|
1425
|
+
this.sticky.dataset.renderSignature = signature;
|
|
1426
|
+
this.stickyKey = row.key;
|
|
1427
|
+
}
|
|
1428
|
+
this.sticky.hidden = false;
|
|
1429
|
+
this.sticky.style.left = String(item.x) + 'px';
|
|
1430
|
+
this.sticky.style.right = 'auto';
|
|
1431
|
+
this.sticky.style.width = String(item.width) + 'px';
|
|
1432
|
+
this.sticky.style.height = String(item.height) + 'px';
|
|
1433
|
+
let nextIndex = -1;
|
|
1434
|
+
for (let cursor = index + 1; cursor < this.rows.length; cursor += 1) {
|
|
1435
|
+
const candidate = this.rows[cursor];
|
|
1436
|
+
if (candidate?.type === 'sectionHeader' && candidate.variant !== 'summary') {
|
|
1437
|
+
nextIndex = cursor;
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const next = this.layout.items[nextIndex];
|
|
1442
|
+
const translate = next ? Math.min(0, next.y - this.viewport.scrollTop - item.height) : 0;
|
|
1443
|
+
this.sticky.style.transform = 'translate3d(0,' + String(translate) + 'px,0)';
|
|
1444
|
+
this.updateVisibleSelection();
|
|
1445
|
+
}
|
|
1446
|
+
updateSectionIndex(firstVisibleIndex) {
|
|
1447
|
+
let activeKey;
|
|
1448
|
+
this.snapshot.rows.forEach((row, index) => {
|
|
1449
|
+
if (index <= firstVisibleIndex && row.type === 'sectionHeader' && row.indexTitle) activeKey = row.key;
|
|
1450
|
+
});
|
|
1451
|
+
this.indexRail.querySelectorAll('[data-section-key]').forEach(button => setData(button, 'active', button.dataset.sectionKey === activeKey));
|
|
1452
|
+
}
|
|
1453
|
+
checkEndReached(lastVisibleIndex) {
|
|
1454
|
+
if (!this.snapshot.capabilities?.loadMore || this.reachedGeneration === this.snapshot.generation || this.rows.length === 0) return;
|
|
1455
|
+
const threshold = Math.max(1, Math.ceil(this.rows.length * (this.snapshot.capabilities.endReachedThreshold ?? 0.2)));
|
|
1456
|
+
if (lastVisibleIndex < this.rows.length - threshold) return;
|
|
1457
|
+
this.reachedGeneration = this.snapshot.generation;
|
|
1458
|
+
this.callbacks.onEndReached?.({
|
|
1459
|
+
generation: this.snapshot.generation,
|
|
1460
|
+
lastKey: this.rows.at(-1)?.key
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
emitScrollFailure(index, reason) {
|
|
1464
|
+
const average = this.layout.items.length === 0 ? 0 : this.layout.items.reduce((sum, item) => sum + (this.layout.horizontal ? item.width : item.height), 0) / this.layout.items.length;
|
|
1465
|
+
this.callbacks.onScrollToIndexFailed?.(scrollFailure(this.rows, index, reason, average));
|
|
1466
|
+
}
|
|
1467
|
+
canScroll() {
|
|
1468
|
+
const viewportLength = this.layout.horizontal ? this.viewport.clientWidth : this.viewport.clientHeight;
|
|
1469
|
+
return viewportLength > 0 && this.layout.items.length > 0;
|
|
1470
|
+
}
|
|
1471
|
+
viewportLength() {
|
|
1472
|
+
return this.layout.horizontal ? this.viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH : this.viewport.clientHeight || DEFAULT_VIEWPORT_HEIGHT;
|
|
1473
|
+
}
|
|
1474
|
+
contentLength() {
|
|
1475
|
+
return this.layout.horizontal ? this.layout.contentWidth : this.layout.contentHeight;
|
|
1476
|
+
}
|
|
1477
|
+
currentOffset() {
|
|
1478
|
+
return this.layout.horizontal ? this.viewport.scrollLeft : this.viewport.scrollTop;
|
|
1479
|
+
}
|
|
1480
|
+
scrollToAbsoluteOffset(offset, animated) {
|
|
1481
|
+
const resolved = Math.min(Math.max(0, offset), Math.max(0, this.contentLength() - this.viewportLength()));
|
|
1482
|
+
this.viewport.scrollTo(this.layout.horizontal ? {
|
|
1483
|
+
left: resolved,
|
|
1484
|
+
top: 0,
|
|
1485
|
+
behavior: animated ? 'smooth' : 'auto'
|
|
1486
|
+
} : {
|
|
1487
|
+
left: 0,
|
|
1488
|
+
top: resolved,
|
|
1489
|
+
behavior: animated ? 'smooth' : 'auto'
|
|
1490
|
+
});
|
|
1491
|
+
this.scheduleFrame();
|
|
1492
|
+
}
|
|
1493
|
+
performPendingScroll() {
|
|
1494
|
+
const pending = this.pendingScroll;
|
|
1495
|
+
if (!pending || !this.canScroll()) return;
|
|
1496
|
+
this.pendingScroll = undefined;
|
|
1497
|
+
if (pending.kind === 'index') this.scrollToIndex(pending.index, pending.scroll);else if (pending.kind === 'offset') this.scrollToOffset(pending.offset, pending.animated);else this.scrollToEnd(pending.animated);
|
|
1498
|
+
}
|
|
1499
|
+
rowAtElement(element) {
|
|
1500
|
+
if (!element) return undefined;
|
|
1501
|
+
const key = element.dataset.nativeListRowKey;
|
|
1502
|
+
return this.rows.find(row => row.key === key) ?? (this.snapshot.fixedFooter?.key === key ? this.snapshot.fixedFooter : undefined);
|
|
1503
|
+
}
|
|
1504
|
+
rowLabel(row) {
|
|
1505
|
+
if (row.type === 'walletGroup') return row.parent.title;
|
|
1506
|
+
if ('title' in row) return row.title;
|
|
1507
|
+
if ('message' in row && row.message) return row.message;
|
|
1508
|
+
return row.type;
|
|
1509
|
+
}
|
|
1510
|
+
isReorderable(row) {
|
|
1511
|
+
return isWebRowReorderable(this.snapshot, row);
|
|
1512
|
+
}
|
|
1513
|
+
activateSelection(target, sourceRow) {
|
|
1514
|
+
const action = target.scope === 'row' ? {
|
|
1515
|
+
scope: 'row',
|
|
1516
|
+
key: sourceRow.key
|
|
1517
|
+
} : target;
|
|
1518
|
+
const result = reduceSelection({
|
|
1519
|
+
mode: this.snapshot.selection?.mode ?? 'none',
|
|
1520
|
+
selectedKeys: this.selectedKeys
|
|
1521
|
+
}, action, this.snapshot.rows);
|
|
1522
|
+
if (result.delta.addedKeys.length === 0 && result.delta.removedKeys.length === 0) return;
|
|
1523
|
+
this.selectedKeys = result.state.selectedKeys;
|
|
1524
|
+
this.updateVisibleSelection();
|
|
1525
|
+
this.renderFooter();
|
|
1526
|
+
this.callbacks.onSelectionDelta?.(result.delta);
|
|
1527
|
+
}
|
|
1528
|
+
emitRowAction(row, actionKey, actionElement, rowElement) {
|
|
1529
|
+
const anchor = actionElement && rowElement ? this.createActionAnchor(actionElement, rowElement) : undefined;
|
|
1530
|
+
this.callbacks.onRowAction?.({
|
|
1531
|
+
rowKey: row.key,
|
|
1532
|
+
actionKey,
|
|
1533
|
+
sectionKey: row.sectionKey,
|
|
1534
|
+
anchor
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
createActionAnchor(actionElement, rowElement, sourceOverride) {
|
|
1538
|
+
const source = sourceOverride ?? actionElement.dataset.nativeListAnchorSource;
|
|
1539
|
+
const bindingEpoch = rowElement.dataset.nativeListBindingEpoch;
|
|
1540
|
+
if (!source || !bindingEpoch || !rowElement.contains(actionElement)) return undefined;
|
|
1541
|
+
this.invalidateActionAnchor('rebind');
|
|
1542
|
+
const rect = actionElement.getBoundingClientRect();
|
|
1543
|
+
const token = [this.actionAnchorInstanceId, this.snapshot.generation, ++this.actionAnchorCounter, bindingEpoch].join(':');
|
|
1544
|
+
const slotValue = actionElement.dataset.nativeListAnchorSlot;
|
|
1545
|
+
const direction = this.document.defaultView?.getComputedStyle(actionElement).direction === 'rtl' || actionElement.closest('[dir="rtl"]') ? 'rtl' : 'ltr';
|
|
1546
|
+
this.actionAnchor = {
|
|
1547
|
+
token,
|
|
1548
|
+
actionElement,
|
|
1549
|
+
rowElement,
|
|
1550
|
+
bindingEpoch,
|
|
1551
|
+
open: false
|
|
1552
|
+
};
|
|
1553
|
+
return webActionAnchorPayload(token, rect, source, this.snapshot.generation, direction, slotValue === undefined ? undefined : Number(slotValue));
|
|
1554
|
+
}
|
|
1555
|
+
isActionAnchorValid(anchor) {
|
|
1556
|
+
return anchor.actionElement.isConnected && anchor.rowElement.isConnected && anchor.rowElement.contains(anchor.actionElement) && anchor.rowElement.dataset.nativeListBindingEpoch === anchor.bindingEpoch;
|
|
1557
|
+
}
|
|
1558
|
+
invalidateActionAnchorForElement(element) {
|
|
1559
|
+
const anchor = this.actionAnchor;
|
|
1560
|
+
if (anchor && (element === anchor.rowElement || element.contains(anchor.actionElement))) {
|
|
1561
|
+
this.invalidateActionAnchor('rebind');
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
invalidateActionAnchor(reason) {
|
|
1565
|
+
const anchor = this.actionAnchor;
|
|
1566
|
+
if (!anchor || anchor.invalidatedReason) return;
|
|
1567
|
+
anchor.invalidatedReason = reason;
|
|
1568
|
+
anchor.actionElement.removeAttribute('aria-expanded');
|
|
1569
|
+
if (anchor.open) this.emitActionAnchorInvalidated(anchor, reason);
|
|
1570
|
+
}
|
|
1571
|
+
emitActionAnchorInvalidated(anchor, reason) {
|
|
1572
|
+
if (this.actionAnchor !== anchor) return;
|
|
1573
|
+
this.actionAnchor = undefined;
|
|
1574
|
+
this.callbacks.onActionAnchorInvalidated?.({
|
|
1575
|
+
token: anchor.token,
|
|
1576
|
+
reason
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
handleRowPress(row, rowElement, sourceElement = rowElement) {
|
|
1580
|
+
if (row.disabled) return;
|
|
1581
|
+
if (this.snapshot.selection?.rowPressToggles && this.snapshot.selection.mode !== 'none' && isSelectableRow(row)) {
|
|
1582
|
+
this.activateSelection({
|
|
1583
|
+
scope: 'row'
|
|
1584
|
+
}, row);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
const actionKey = row.type === 'action' ? row.actionKey : row.type === 'system' && row.variant === 'retry' ? row.actionKey : 'press';
|
|
1588
|
+
if (rowElement && sourceElement) {
|
|
1589
|
+
const anchor = this.createActionAnchor(sourceElement, rowElement, 'row');
|
|
1590
|
+
this.callbacks.onRowAction?.({
|
|
1591
|
+
rowKey: row.key,
|
|
1592
|
+
actionKey,
|
|
1593
|
+
sectionKey: row.sectionKey,
|
|
1594
|
+
anchor
|
|
1595
|
+
});
|
|
1596
|
+
} else {
|
|
1597
|
+
this.emitRowAction(row, actionKey);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
handleClick = event => {
|
|
1601
|
+
if (Date.now() < this.suppressClickUntil) {
|
|
1602
|
+
event.preventDefault();
|
|
1603
|
+
event.stopPropagation();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const target = event.target;
|
|
1607
|
+
if (!(target instanceof Element)) return;
|
|
1608
|
+
const rowElement = target.closest('[data-native-list-row-key]');
|
|
1609
|
+
const row = this.rowAtElement(rowElement);
|
|
1610
|
+
if (!row || row.disabled) return;
|
|
1611
|
+
const memberElement = target.closest('[data-native-list-group-member-key]');
|
|
1612
|
+
const memberKey = memberElement?.dataset.nativeListGroupMemberKey;
|
|
1613
|
+
const sourceRow = row.type === 'walletGroup' && memberKey ? [row.parent, ...row.children].find(member => member.key === memberKey) ?? row : row;
|
|
1614
|
+
const action = target.closest('[data-native-list-action]');
|
|
1615
|
+
if (action) {
|
|
1616
|
+
const scope = action.dataset.selectionScope;
|
|
1617
|
+
if (scope === 'row') this.activateSelection({
|
|
1618
|
+
scope: 'row'
|
|
1619
|
+
}, row);else if (scope === 'section' && action.dataset.selectionKey) this.activateSelection({
|
|
1620
|
+
scope: 'section',
|
|
1621
|
+
sectionKey: action.dataset.selectionKey
|
|
1622
|
+
}, row);else if (scope === 'list') this.activateSelection({
|
|
1623
|
+
scope: 'list'
|
|
1624
|
+
}, row);else if (action.dataset.nativeListAction) this.emitRowAction(sourceRow, action.dataset.nativeListAction, action, rowElement ?? undefined);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
this.handleRowPress(sourceRow, rowElement ?? undefined, memberElement ?? rowElement ?? undefined);
|
|
1628
|
+
};
|
|
1629
|
+
handleKeyDown = event => {
|
|
1630
|
+
if (event.key === 'Escape') {
|
|
1631
|
+
if (this.pointerReorder?.active) {
|
|
1632
|
+
event.preventDefault();
|
|
1633
|
+
this.cancelPointerReorder(true);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (this.keyboardReorder) {
|
|
1637
|
+
event.preventDefault();
|
|
1638
|
+
this.cancelKeyboardReorder();
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if (this.keyboardReorder && (event.key === 'ArrowUp' || event.key === 'ArrowLeft')) {
|
|
1643
|
+
event.preventDefault();
|
|
1644
|
+
this.moveKeyboardReorder(-1);
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
if (this.keyboardReorder && (event.key === 'ArrowDown' || event.key === 'ArrowRight')) {
|
|
1648
|
+
event.preventDefault();
|
|
1649
|
+
this.moveKeyboardReorder(1);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
1653
|
+
const target = event.target;
|
|
1654
|
+
if (!(target instanceof HTMLElement)) return;
|
|
1655
|
+
if (target.matches('button')) return;
|
|
1656
|
+
const row = this.rowAtElement(target.closest('[data-native-list-row-key]'));
|
|
1657
|
+
if (!row) return;
|
|
1658
|
+
event.preventDefault();
|
|
1659
|
+
if (event.key === ' ' && this.isReorderable(row)) {
|
|
1660
|
+
if (this.keyboardReorder) this.finishKeyboardReorder();else this.startKeyboardReorder(row);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
this.handleRowPress(row, target.closest('[data-native-list-row-key]') ?? undefined);
|
|
1664
|
+
};
|
|
1665
|
+
startKeyboardReorder(row) {
|
|
1666
|
+
this.keyboardReorder = {
|
|
1667
|
+
sourceKey: row.key,
|
|
1668
|
+
originalRows: this.snapshot.rows
|
|
1669
|
+
};
|
|
1670
|
+
this.updateReorderVisualState();
|
|
1671
|
+
}
|
|
1672
|
+
moveKeyboardReorder(delta) {
|
|
1673
|
+
const state = this.keyboardReorder;
|
|
1674
|
+
if (!state) return;
|
|
1675
|
+
const fromIndex = this.snapshot.rows.findIndex(row => row.key === state.sourceKey);
|
|
1676
|
+
const toIndex = fromIndex + delta;
|
|
1677
|
+
const from = this.snapshot.rows[fromIndex];
|
|
1678
|
+
const to = this.snapshot.rows[toIndex];
|
|
1679
|
+
if (!from || !to || !this.isReorderable(to) || from.sectionKey !== to.sectionKey) return;
|
|
1680
|
+
const rows = moveWebReorderRow(this.snapshot.rows, fromIndex, toIndex);
|
|
1681
|
+
this.remapMountedRows(rows);
|
|
1682
|
+
this.setSnapshot({
|
|
1683
|
+
...this.snapshot,
|
|
1684
|
+
rows
|
|
1685
|
+
}, this.selectedKeys);
|
|
1686
|
+
this.updateReorderVisualState();
|
|
1687
|
+
this.scrollToIndex(toIndex, {
|
|
1688
|
+
animated: false,
|
|
1689
|
+
alignment: 'nearest',
|
|
1690
|
+
viewPosition: 0,
|
|
1691
|
+
viewOffset: 0
|
|
1692
|
+
});
|
|
1693
|
+
[...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey)?.focus({
|
|
1694
|
+
preventScroll: true
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
finishKeyboardReorder() {
|
|
1698
|
+
const state = this.keyboardReorder;
|
|
1699
|
+
if (!state) return;
|
|
1700
|
+
const reorderEvent = webReorderEventForRows(state.originalRows, this.snapshot.rows, state.sourceKey);
|
|
1701
|
+
this.keyboardReorder = undefined;
|
|
1702
|
+
this.updateReorderVisualState();
|
|
1703
|
+
if (reorderEvent) this.callbacks.onReorder?.(reorderEvent);
|
|
1704
|
+
}
|
|
1705
|
+
cancelKeyboardReorder() {
|
|
1706
|
+
const state = this.keyboardReorder;
|
|
1707
|
+
if (!state) return;
|
|
1708
|
+
this.keyboardReorder = undefined;
|
|
1709
|
+
this.remapMountedRows(state.originalRows);
|
|
1710
|
+
this.setSnapshot({
|
|
1711
|
+
...this.snapshot,
|
|
1712
|
+
rows: cancelWebReorderRows(state.originalRows)
|
|
1713
|
+
}, this.selectedKeys);
|
|
1714
|
+
this.updateReorderVisualState();
|
|
1715
|
+
[...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey)?.focus({
|
|
1716
|
+
preventScroll: true
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
handleScroll = () => {
|
|
1720
|
+
this.invalidateActionAnchor('scroll');
|
|
1721
|
+
this.scheduleFrame();
|
|
1722
|
+
};
|
|
1723
|
+
handleWindowResize = () => {
|
|
1724
|
+
const state = this.pointerReorder;
|
|
1725
|
+
if (state?.active) state.viewportRect = this.reorderViewportRect();
|
|
1726
|
+
this.recomputeLayout();
|
|
1727
|
+
};
|
|
1728
|
+
scheduleFrame() {
|
|
1729
|
+
if (this.frameHandle !== undefined || this.destroyed) return;
|
|
1730
|
+
this.frameHandle = this.requestFrame(() => {
|
|
1731
|
+
this.frameHandle = undefined;
|
|
1732
|
+
if (this.virtualizationEnabled) this.renderWindow();else this.updateVisibleState();
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
requestFrame(callback) {
|
|
1736
|
+
const view = this.document.defaultView;
|
|
1737
|
+
return view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : view?.setTimeout(() => callback(Date.now()), 16) ?? 0;
|
|
1738
|
+
}
|
|
1739
|
+
cancelFrame(handle) {
|
|
1740
|
+
const view = this.document.defaultView;
|
|
1741
|
+
if (view?.cancelAnimationFrame) view.cancelAnimationFrame(handle);else view?.clearTimeout(handle);
|
|
1742
|
+
}
|
|
1743
|
+
selectIndexPosition(position, title) {
|
|
1744
|
+
this.scrollToIndex(position, {
|
|
1745
|
+
animated: false,
|
|
1746
|
+
alignment: 'start',
|
|
1747
|
+
viewPosition: 0,
|
|
1748
|
+
viewOffset: 0
|
|
1749
|
+
});
|
|
1750
|
+
this.indexPreview.textContent = title;
|
|
1751
|
+
setData(this.indexPreview, 'visible', true);
|
|
1752
|
+
if (this.previewTimer !== undefined) this.document.defaultView?.clearTimeout(this.previewTimer);
|
|
1753
|
+
this.previewTimer = this.document.defaultView?.setTimeout(() => {
|
|
1754
|
+
setData(this.indexPreview, 'visible', false);
|
|
1755
|
+
}, 180);
|
|
1756
|
+
}
|
|
1757
|
+
indexButtonAtEvent(event) {
|
|
1758
|
+
const direct = event.target?.closest('[data-section-position]');
|
|
1759
|
+
if (direct) return direct;
|
|
1760
|
+
return this.document.elementFromPoint(event.clientX, event.clientY)?.closest('[data-section-position]') ?? undefined;
|
|
1761
|
+
}
|
|
1762
|
+
handleIndexPointer = event => {
|
|
1763
|
+
if (event.type === 'pointermove' && event.buttons === 0) return;
|
|
1764
|
+
const button = this.indexButtonAtEvent(event);
|
|
1765
|
+
if (!button) return;
|
|
1766
|
+
event.preventDefault();
|
|
1767
|
+
this.selectIndexPosition(Number(button.dataset.sectionPosition), button.textContent ?? '');
|
|
1768
|
+
};
|
|
1769
|
+
handleIndexClick = event => {
|
|
1770
|
+
const target = event.target;
|
|
1771
|
+
if (!(target instanceof Element)) return;
|
|
1772
|
+
const button = target.closest('[data-section-position]');
|
|
1773
|
+
if (!button) return;
|
|
1774
|
+
this.selectIndexPosition(Number(button.dataset.sectionPosition), button.textContent ?? '');
|
|
1775
|
+
};
|
|
1776
|
+
handlePullStart = event => {
|
|
1777
|
+
if (this.pointerReorder?.pointerId === event.pointerId || !this.snapshot.capabilities?.pullToRefresh || this.viewport.scrollTop > 0 || event.pointerType !== 'touch' && event.pointerType !== 'pen') return;
|
|
1778
|
+
this.pullStartY = event.clientY;
|
|
1779
|
+
this.pullDistance = 0;
|
|
1780
|
+
};
|
|
1781
|
+
handlePullMove = event => {
|
|
1782
|
+
if (this.pullStartY === undefined) return;
|
|
1783
|
+
this.pullDistance = Math.max(0, Math.min(96, event.clientY - this.pullStartY));
|
|
1784
|
+
if (this.pullDistance <= 0) return;
|
|
1785
|
+
event.preventDefault();
|
|
1786
|
+
this.refreshIndicator.textContent = this.pullDistance >= 64 ? 'Release to refresh' : 'Pull to refresh';
|
|
1787
|
+
setData(this.refreshIndicator, 'visible', true);
|
|
1788
|
+
this.refreshIndicator.style.transform = 'translate(-50%,' + String(Math.min(32, this.pullDistance / 2)) + 'px)';
|
|
1789
|
+
};
|
|
1790
|
+
handlePullEnd = () => {
|
|
1791
|
+
if (this.pullStartY === undefined) return;
|
|
1792
|
+
const shouldRefresh = this.pullDistance >= 64;
|
|
1793
|
+
this.pullStartY = undefined;
|
|
1794
|
+
this.pullDistance = 0;
|
|
1795
|
+
this.refreshIndicator.style.removeProperty('transform');
|
|
1796
|
+
this.updateRefreshIndicator();
|
|
1797
|
+
if (!shouldRefresh) return;
|
|
1798
|
+
this.callbacks.onRefresh?.();
|
|
1799
|
+
this.callbacks.onRowAction?.({
|
|
1800
|
+
actionKey: 'nativeList.refresh'
|
|
1801
|
+
});
|
|
1802
|
+
};
|
|
1803
|
+
updateRefreshIndicator() {
|
|
1804
|
+
const refreshing = Boolean(this.snapshot.capabilities?.refreshing);
|
|
1805
|
+
this.refreshIndicator.textContent = refreshing ? 'Refreshing' : '';
|
|
1806
|
+
setData(this.refreshIndicator, 'visible', refreshing);
|
|
1807
|
+
}
|
|
1808
|
+
handleReorderPointerDown = event => {
|
|
1809
|
+
if (this.pointerReorder || this.keyboardReorder || event.isPrimary === false || event.pointerType === 'mouse' && event.button !== 0) {
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
const target = event.target;
|
|
1813
|
+
if (!(target instanceof Element)) return;
|
|
1814
|
+
if (target.closest('button,input,textarea,select,a')) return;
|
|
1815
|
+
const rowElement = target.closest('[data-native-list-row-index]');
|
|
1816
|
+
const index = Number(rowElement?.dataset.nativeListRowIndex);
|
|
1817
|
+
const row = this.rows[index];
|
|
1818
|
+
if (!row || !this.isReorderable(row)) return;
|
|
1819
|
+
if (row.type === 'walletGroup' && target.closest('[data-native-list-group-parent]')?.dataset.nativeListGroupParent !== 'true') return;
|
|
1820
|
+
const view = this.document.defaultView;
|
|
1821
|
+
const state = {
|
|
1822
|
+
pointerId: event.pointerId,
|
|
1823
|
+
pointerType: event.pointerType || 'mouse',
|
|
1824
|
+
sourceKey: row.key,
|
|
1825
|
+
currentIndex: index,
|
|
1826
|
+
startX: event.clientX,
|
|
1827
|
+
startY: event.clientY,
|
|
1828
|
+
clientX: event.clientX,
|
|
1829
|
+
clientY: event.clientY,
|
|
1830
|
+
originalRows: this.snapshot.rows,
|
|
1831
|
+
workingRows: [...this.snapshot.rows],
|
|
1832
|
+
active: false
|
|
1833
|
+
};
|
|
1834
|
+
this.pointerReorder = state;
|
|
1835
|
+
if (state.pointerType === 'mouse') {
|
|
1836
|
+
this.captureReorderPointer(state);
|
|
1837
|
+
} else {
|
|
1838
|
+
state.longPressTimer = view?.setTimeout(() => this.activatePointerReorder(state), REORDER_TOUCH_LONG_PRESS_MS);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
activatePointerReorder(state) {
|
|
1842
|
+
if (this.pointerReorder !== state) return;
|
|
1843
|
+
this.clearReorderLongPress(state);
|
|
1844
|
+
state.active = true;
|
|
1845
|
+
state.activatedAt = Date.now();
|
|
1846
|
+
state.viewportRect = this.reorderViewportRect();
|
|
1847
|
+
this.captureReorderPointer(state);
|
|
1848
|
+
this.showReorderPreview(state);
|
|
1849
|
+
if (state.workingRows[state.currentIndex]?.type === 'walletGroup' && this.reorderCompactKey !== state.sourceKey) {
|
|
1850
|
+
this.reorderCompactKey = state.sourceKey;
|
|
1851
|
+
this.mounted.forEach(element => setData(element, 'nativeListAnimateReorder', true));
|
|
1852
|
+
this.recomputeLayout();
|
|
1853
|
+
}
|
|
1854
|
+
this.updateReorderVisualState();
|
|
1855
|
+
this.scheduleReorderAutoScroll();
|
|
1856
|
+
}
|
|
1857
|
+
captureReorderPointer(state) {
|
|
1858
|
+
try {
|
|
1859
|
+
this.viewport.setPointerCapture?.(state.pointerId);
|
|
1860
|
+
} catch {
|
|
1861
|
+
// A canceled pointer can disappear before the long-press timer fires.
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
releaseReorderPointer(state) {
|
|
1865
|
+
try {
|
|
1866
|
+
if (this.viewport.hasPointerCapture?.(state.pointerId)) this.viewport.releasePointerCapture?.(state.pointerId);
|
|
1867
|
+
} catch {
|
|
1868
|
+
// The browser may have released capture while dispatching pointercancel.
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
clearReorderLongPress(state) {
|
|
1872
|
+
if (state.longPressTimer === undefined) return;
|
|
1873
|
+
this.document.defaultView?.clearTimeout(state.longPressTimer);
|
|
1874
|
+
state.longPressTimer = undefined;
|
|
1875
|
+
}
|
|
1876
|
+
handleReorderPointerMove = event => {
|
|
1877
|
+
const state = this.pointerReorder;
|
|
1878
|
+
if (!state || state.pointerId !== event.pointerId) return;
|
|
1879
|
+
state.clientX = event.clientX;
|
|
1880
|
+
state.clientY = event.clientY;
|
|
1881
|
+
if (!state.active) {
|
|
1882
|
+
if (state.pointerType !== 'mouse') {
|
|
1883
|
+
this.cancelPointerReorder(false);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
if (!hasExceededWebReorderMouseThreshold(event.clientX - state.startX, event.clientY - state.startY)) return;
|
|
1887
|
+
this.activatePointerReorder(state);
|
|
1888
|
+
if (!state.active) return;
|
|
1889
|
+
event.preventDefault();
|
|
1890
|
+
this.scheduleReorderMove();
|
|
1891
|
+
this.scheduleReorderAutoScroll();
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
event.preventDefault();
|
|
1895
|
+
this.scheduleReorderMove();
|
|
1896
|
+
this.scheduleReorderAutoScroll();
|
|
1897
|
+
};
|
|
1898
|
+
handleReorderTouchMove = event => {
|
|
1899
|
+
const state = this.pointerReorder;
|
|
1900
|
+
if (!state?.active || state.pointerType === 'mouse') return;
|
|
1901
|
+
const touch = event.touches[0];
|
|
1902
|
+
if (!touch) return;
|
|
1903
|
+
event.preventDefault();
|
|
1904
|
+
state.clientX = touch.clientX;
|
|
1905
|
+
state.clientY = touch.clientY;
|
|
1906
|
+
this.scheduleReorderMove();
|
|
1907
|
+
this.scheduleReorderAutoScroll();
|
|
1908
|
+
};
|
|
1909
|
+
handleReorderTouchEnd = event => {
|
|
1910
|
+
const state = this.pointerReorder;
|
|
1911
|
+
if (!state?.active || state.pointerType === 'mouse') return;
|
|
1912
|
+
event.preventDefault();
|
|
1913
|
+
this.completePointerReorder(state);
|
|
1914
|
+
};
|
|
1915
|
+
handleReorderTouchCancel = () => {
|
|
1916
|
+
const state = this.pointerReorder;
|
|
1917
|
+
if (!state?.active || state.pointerType === 'mouse') return;
|
|
1918
|
+
this.cancelPointerReorder(true);
|
|
1919
|
+
};
|
|
1920
|
+
setCurrentOffset(offset) {
|
|
1921
|
+
const maximum = Math.max(0, this.contentLength() - this.viewportLength());
|
|
1922
|
+
const next = Math.max(0, Math.min(maximum, offset));
|
|
1923
|
+
if (this.layout.horizontal) this.viewport.scrollLeft = next;else this.viewport.scrollTop = next;
|
|
1924
|
+
this.scheduleFrame();
|
|
1925
|
+
}
|
|
1926
|
+
showReorderPreview(state) {
|
|
1927
|
+
this.hideReorderPreview();
|
|
1928
|
+
const source = [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey);
|
|
1929
|
+
const row = source?.firstElementChild;
|
|
1930
|
+
if (!source || !row) return;
|
|
1931
|
+
const rect = source.getBoundingClientRect();
|
|
1932
|
+
const groupParent = row.querySelector('[data-native-list-group-parent="true"]>.ok-native-list-wallet-row');
|
|
1933
|
+
const previewRow = groupParent ?? row;
|
|
1934
|
+
const previewHeight = groupParent ? 68 : rect.height;
|
|
1935
|
+
state.previewOffsetX = state.startX - rect.left;
|
|
1936
|
+
state.previewOffsetY = Math.min(previewHeight, Math.max(0, state.startY - rect.top));
|
|
1937
|
+
this.reorderPreview.replaceChildren(previewRow.cloneNode(true));
|
|
1938
|
+
const sourceRow = state.workingRows[state.currentIndex];
|
|
1939
|
+
const badgeText = sourceRow ? webWalletGroupReorderBadge(sourceRow) : undefined;
|
|
1940
|
+
if (badgeText) {
|
|
1941
|
+
this.reorderPreview.appendChild(createElement(this.document, 'span', 'ok-native-list-reorder-count', badgeText));
|
|
1942
|
+
}
|
|
1943
|
+
setData(this.reorderPreview, 'nativeListSelected', source.dataset.nativeListSelected === 'true');
|
|
1944
|
+
this.reorderPreview.style.width = String(rect.width) + 'px';
|
|
1945
|
+
this.reorderPreview.style.height = String(previewHeight) + 'px';
|
|
1946
|
+
this.reorderPreview.style.transition = 'none';
|
|
1947
|
+
this.reorderPreview.hidden = false;
|
|
1948
|
+
this.updateReorderPreview(state);
|
|
1949
|
+
}
|
|
1950
|
+
animateReorderPreviewToCurrent(state) {
|
|
1951
|
+
const destination = [...this.mounted.values()].find(element => element.dataset.nativeListRowKey === state.sourceKey);
|
|
1952
|
+
const destinationIndex = this.rows.findIndex(row => row.key === state.sourceKey);
|
|
1953
|
+
const destinationLayout = this.layout.items.find(item => item.index === destinationIndex);
|
|
1954
|
+
if (this.reorderPreview.hidden || !destination || !destinationLayout) {
|
|
1955
|
+
this.hideReorderPreview();
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
this.reorderSettlingKey = state.sourceKey;
|
|
1959
|
+
const viewportRect = this.viewport.getBoundingClientRect();
|
|
1960
|
+
const left = viewportRect.left + destinationLayout.x - this.viewport.scrollLeft;
|
|
1961
|
+
const top = viewportRect.top + destinationLayout.y - this.viewport.scrollTop;
|
|
1962
|
+
this.reorderPreview.getBoundingClientRect();
|
|
1963
|
+
this.reorderPreview.style.transition = 'transform ' + String(REORDER_DROP_TRANSITION_MS) + 'ms ' + WEB_REORDER_ANIMATION.dropTimingFunction + ', box-shadow ' + String(REORDER_DROP_TRANSITION_MS) + 'ms ' + WEB_REORDER_ANIMATION.dropTimingFunction;
|
|
1964
|
+
this.reorderPreview.style.boxShadow = 'none';
|
|
1965
|
+
this.reorderPreview.style.transform = 'translate3d(' + String(left) + 'px,' + String(top) + 'px,0) scale(1)';
|
|
1966
|
+
this.reorderDropTimer = this.document.defaultView?.setTimeout(() => {
|
|
1967
|
+
this.reorderDropTimer = undefined;
|
|
1968
|
+
this.finishReorderDrop();
|
|
1969
|
+
}, REORDER_DROP_TRANSITION_MS);
|
|
1970
|
+
}
|
|
1971
|
+
clearReorderPreviewVisual() {
|
|
1972
|
+
this.reorderPreview.hidden = true;
|
|
1973
|
+
this.reorderPreview.replaceChildren();
|
|
1974
|
+
this.reorderPreview.style.removeProperty('transform');
|
|
1975
|
+
this.reorderPreview.style.removeProperty('transition');
|
|
1976
|
+
this.reorderPreview.style.removeProperty('box-shadow');
|
|
1977
|
+
this.reorderPreview.removeAttribute('data-native-list-selected');
|
|
1978
|
+
}
|
|
1979
|
+
finishReorderDrop() {
|
|
1980
|
+
this.clearReorderPreviewVisual();
|
|
1981
|
+
this.reorderSettlingKey = undefined;
|
|
1982
|
+
if (this.reorderCompactKey) {
|
|
1983
|
+
this.reorderExpandingKey = this.reorderCompactKey;
|
|
1984
|
+
this.reorderCompactKey = undefined;
|
|
1985
|
+
this.mounted.forEach(element => setData(element, 'nativeListAnimateReorder', true));
|
|
1986
|
+
this.recomputeLayout();
|
|
1987
|
+
}
|
|
1988
|
+
this.updateReorderVisualState();
|
|
1989
|
+
}
|
|
1990
|
+
hideReorderPreview() {
|
|
1991
|
+
if (this.reorderDropTimer !== undefined) {
|
|
1992
|
+
this.document.defaultView?.clearTimeout(this.reorderDropTimer);
|
|
1993
|
+
this.reorderDropTimer = undefined;
|
|
1994
|
+
}
|
|
1995
|
+
this.clearReorderPreviewVisual();
|
|
1996
|
+
this.reorderSettlingKey = undefined;
|
|
1997
|
+
this.reorderExpandingKey = undefined;
|
|
1998
|
+
if (this.reorderCompactKey) {
|
|
1999
|
+
this.reorderCompactKey = undefined;
|
|
2000
|
+
this.recomputeLayout();
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
updateReorderPreview(state) {
|
|
2004
|
+
if (this.reorderPreview.hidden || state.previewOffsetX === undefined || state.previewOffsetY === undefined) return;
|
|
2005
|
+
const x = state.clientX - state.previewOffsetX;
|
|
2006
|
+
const y = state.clientY - state.previewOffsetY;
|
|
2007
|
+
this.reorderPreview.style.transform = 'translate3d(' + String(x) + 'px,' + String(y) + 'px,0)';
|
|
2008
|
+
}
|
|
2009
|
+
reorderViewportRect() {
|
|
2010
|
+
const {
|
|
2011
|
+
left,
|
|
2012
|
+
top,
|
|
2013
|
+
right,
|
|
2014
|
+
bottom,
|
|
2015
|
+
width,
|
|
2016
|
+
height
|
|
2017
|
+
} = this.viewport.getBoundingClientRect();
|
|
2018
|
+
return {
|
|
2019
|
+
left,
|
|
2020
|
+
top,
|
|
2021
|
+
right,
|
|
2022
|
+
bottom,
|
|
2023
|
+
width,
|
|
2024
|
+
height
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
scheduleReorderMove() {
|
|
2028
|
+
if (this.reorderMoveFrame !== undefined || !this.pointerReorder?.active) return;
|
|
2029
|
+
this.reorderMoveFrame = this.requestFrame(this.runReorderMove);
|
|
2030
|
+
}
|
|
2031
|
+
runReorderMove = () => {
|
|
2032
|
+
this.reorderMoveFrame = undefined;
|
|
2033
|
+
const state = this.pointerReorder;
|
|
2034
|
+
if (!state?.active) return;
|
|
2035
|
+
this.updateReorderPreview(state);
|
|
2036
|
+
this.movePointerReorderTo(state.clientX, state.clientY);
|
|
2037
|
+
};
|
|
2038
|
+
flushReorderMove(state) {
|
|
2039
|
+
if (this.reorderMoveFrame !== undefined) {
|
|
2040
|
+
this.cancelFrame(this.reorderMoveFrame);
|
|
2041
|
+
this.reorderMoveFrame = undefined;
|
|
2042
|
+
}
|
|
2043
|
+
if (this.pointerReorder !== state || !state.active) return;
|
|
2044
|
+
this.updateReorderPreview(state);
|
|
2045
|
+
this.movePointerReorderTo(state.clientX, state.clientY);
|
|
2046
|
+
}
|
|
2047
|
+
stopReorderMove() {
|
|
2048
|
+
if (this.reorderMoveFrame === undefined) return;
|
|
2049
|
+
this.cancelFrame(this.reorderMoveFrame);
|
|
2050
|
+
this.reorderMoveFrame = undefined;
|
|
2051
|
+
}
|
|
2052
|
+
reorderIndexAtPointer(state, clientX, clientY) {
|
|
2053
|
+
const rect = state.viewportRect ?? this.reorderViewportRect();
|
|
2054
|
+
const local = this.layout.horizontal ? Math.max(0, Math.min(Math.max(0, rect.width - 1), clientX - rect.left)) : Math.max(0, Math.min(Math.max(0, rect.height - 1), clientY - rect.top));
|
|
2055
|
+
const coordinate = this.currentOffset() + local;
|
|
2056
|
+
const centerAt = index => {
|
|
2057
|
+
const item = this.layout.items[index];
|
|
2058
|
+
return item ? (itemStart(item, this.layout.horizontal) + itemEnd(item, this.layout.horizontal)) / 2 : Number.POSITIVE_INFINITY;
|
|
2059
|
+
};
|
|
2060
|
+
let low = 0;
|
|
2061
|
+
let high = this.layout.items.length;
|
|
2062
|
+
while (low < high) {
|
|
2063
|
+
const middle = Math.floor((low + high) / 2);
|
|
2064
|
+
if (centerAt(middle) < coordinate) low = middle + 1;else high = middle;
|
|
2065
|
+
}
|
|
2066
|
+
const previous = Math.max(0, low - 1);
|
|
2067
|
+
const next = Math.min(this.layout.items.length - 1, low);
|
|
2068
|
+
const nearest = Math.abs(centerAt(previous) - coordinate) <= Math.abs(centerAt(next) - coordinate) ? previous : next;
|
|
2069
|
+
return this.layout.items[nearest]?.index ?? -1;
|
|
2070
|
+
}
|
|
2071
|
+
movePointerReorderTo(clientX, clientY) {
|
|
2072
|
+
const state = this.pointerReorder;
|
|
2073
|
+
if (!state?.active) return;
|
|
2074
|
+
const fromIndex = state.currentIndex;
|
|
2075
|
+
const toIndex = this.reorderIndexAtPointer(state, clientX, clientY);
|
|
2076
|
+
const from = state.workingRows[fromIndex];
|
|
2077
|
+
const to = state.workingRows[toIndex];
|
|
2078
|
+
if (!from || !to || fromIndex === toIndex || !this.isReorderable(from) || !this.isReorderable(to) || from.sectionKey !== to.sectionKey) return;
|
|
2079
|
+
const [moved] = state.workingRows.splice(fromIndex, 1);
|
|
2080
|
+
if (!moved) return;
|
|
2081
|
+
state.workingRows.splice(toIndex, 0, moved);
|
|
2082
|
+
state.currentIndex = toIndex;
|
|
2083
|
+
const canReuseLayout = this.canReuseReorderLayout(fromIndex, toIndex);
|
|
2084
|
+
this.snapshot = {
|
|
2085
|
+
...this.snapshot,
|
|
2086
|
+
rows: state.workingRows
|
|
2087
|
+
};
|
|
2088
|
+
this.rows = state.workingRows;
|
|
2089
|
+
this.remapMountedRowsForMove(fromIndex, toIndex);
|
|
2090
|
+
if (canReuseLayout) this.renderWindow();else this.recomputeLayout();
|
|
2091
|
+
this.updateReorderVisualState();
|
|
2092
|
+
}
|
|
2093
|
+
canReuseReorderLayout(fromIndex, toIndex) {
|
|
2094
|
+
const start = Math.min(fromIndex, toIndex);
|
|
2095
|
+
const end = Math.max(fromIndex, toIndex);
|
|
2096
|
+
const first = this.layout.items[start];
|
|
2097
|
+
if (!first || this.layout.items.length !== this.rows.length) return false;
|
|
2098
|
+
for (let index = start + 1; index <= end; index += 1) {
|
|
2099
|
+
const item = this.layout.items[index];
|
|
2100
|
+
if (!item || item.width !== first.width || item.height !== first.height) return false;
|
|
2101
|
+
}
|
|
2102
|
+
return true;
|
|
2103
|
+
}
|
|
2104
|
+
remapMountedRowsForMove(fromIndex, toIndex) {
|
|
2105
|
+
const remapped = [];
|
|
2106
|
+
this.mounted.forEach((element, index) => {
|
|
2107
|
+
let nextIndex = index;
|
|
2108
|
+
if (index === fromIndex) nextIndex = toIndex;else if (fromIndex < toIndex && index > fromIndex && index <= toIndex) nextIndex = index - 1;else if (fromIndex > toIndex && index >= toIndex && index < fromIndex) nextIndex = index + 1;
|
|
2109
|
+
setData(element, 'nativeListAnimateReorder', true);
|
|
2110
|
+
setData(element, 'nativeListRowIndex', nextIndex);
|
|
2111
|
+
remapped.push([nextIndex, element]);
|
|
2112
|
+
});
|
|
2113
|
+
this.mounted.clear();
|
|
2114
|
+
remapped.forEach(([index, element]) => this.mounted.set(index, element));
|
|
2115
|
+
}
|
|
2116
|
+
reorderAutoScrollVelocity() {
|
|
2117
|
+
const state = this.pointerReorder;
|
|
2118
|
+
if (!state?.active) return 0;
|
|
2119
|
+
const rect = state.viewportRect ?? this.reorderViewportRect();
|
|
2120
|
+
const point = this.layout.horizontal ? state.clientX : state.clientY;
|
|
2121
|
+
const start = this.layout.horizontal ? rect.left : rect.top;
|
|
2122
|
+
const end = this.layout.horizontal ? rect.right : rect.bottom;
|
|
2123
|
+
return webReorderAutoScrollVelocity(point, start, end, Date.now() - (state.activatedAt ?? 0));
|
|
2124
|
+
}
|
|
2125
|
+
scheduleReorderAutoScroll() {
|
|
2126
|
+
if (this.reorderAutoScrollFrame !== undefined || !this.pointerReorder?.active) return;
|
|
2127
|
+
this.reorderAutoScrollFrame = this.requestFrame(this.runReorderAutoScroll);
|
|
2128
|
+
}
|
|
2129
|
+
runReorderAutoScroll = () => {
|
|
2130
|
+
this.reorderAutoScrollFrame = undefined;
|
|
2131
|
+
const state = this.pointerReorder;
|
|
2132
|
+
if (!state?.active) return;
|
|
2133
|
+
const velocity = this.reorderAutoScrollVelocity();
|
|
2134
|
+
if (velocity === 0) return;
|
|
2135
|
+
const before = this.currentOffset();
|
|
2136
|
+
this.setCurrentOffset(before + velocity);
|
|
2137
|
+
if (this.currentOffset() !== before) {
|
|
2138
|
+
this.movePointerReorderTo(state.clientX, state.clientY);
|
|
2139
|
+
this.scheduleReorderAutoScroll();
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
handleReorderPointerEnd = event => {
|
|
2143
|
+
const state = this.pointerReorder;
|
|
2144
|
+
if (!state || state.pointerId !== event.pointerId) return;
|
|
2145
|
+
this.clearReorderLongPress(state);
|
|
2146
|
+
this.releaseReorderPointer(state);
|
|
2147
|
+
if (!state.active) {
|
|
2148
|
+
this.pointerReorder = undefined;
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
event.preventDefault();
|
|
2152
|
+
state.clientX = event.clientX;
|
|
2153
|
+
state.clientY = event.clientY;
|
|
2154
|
+
this.completePointerReorder(state);
|
|
2155
|
+
};
|
|
2156
|
+
completePointerReorder(state) {
|
|
2157
|
+
this.flushReorderMove(state);
|
|
2158
|
+
const finalRows = this.snapshot.rows;
|
|
2159
|
+
const reorderEvent = webReorderEventForRows(state.originalRows, finalRows, state.sourceKey);
|
|
2160
|
+
this.animateReorderPreviewToCurrent(state);
|
|
2161
|
+
this.pointerReorder = undefined;
|
|
2162
|
+
this.stopReorderMove();
|
|
2163
|
+
this.stopReorderAutoScroll();
|
|
2164
|
+
this.suppressClickUntil = Date.now() + 300;
|
|
2165
|
+
this.updateReorderVisualState();
|
|
2166
|
+
if (reorderEvent) this.callbacks.onReorder?.(reorderEvent);
|
|
2167
|
+
}
|
|
2168
|
+
handleReorderPointerCancel = event => {
|
|
2169
|
+
if (this.pointerReorder?.pointerId !== event.pointerId) return;
|
|
2170
|
+
if (this.pointerReorder.active && this.pointerReorder.pointerType !== 'mouse') return;
|
|
2171
|
+
this.cancelPointerReorder(true);
|
|
2172
|
+
};
|
|
2173
|
+
stopReorderAutoScroll() {
|
|
2174
|
+
if (this.reorderAutoScrollFrame === undefined) return;
|
|
2175
|
+
this.cancelFrame(this.reorderAutoScrollFrame);
|
|
2176
|
+
this.reorderAutoScrollFrame = undefined;
|
|
2177
|
+
}
|
|
2178
|
+
cancelPointerReorder(restore) {
|
|
2179
|
+
const state = this.pointerReorder;
|
|
2180
|
+
if (!state) return;
|
|
2181
|
+
this.clearReorderLongPress(state);
|
|
2182
|
+
this.releaseReorderPointer(state);
|
|
2183
|
+
this.pointerReorder = undefined;
|
|
2184
|
+
this.stopReorderMove();
|
|
2185
|
+
this.stopReorderAutoScroll();
|
|
2186
|
+
if (state.active && restore) {
|
|
2187
|
+
this.remapMountedRows(state.originalRows);
|
|
2188
|
+
this.setSnapshot({
|
|
2189
|
+
...this.snapshot,
|
|
2190
|
+
rows: cancelWebReorderRows(state.originalRows)
|
|
2191
|
+
}, this.selectedKeys);
|
|
2192
|
+
this.suppressClickUntil = Date.now() + 300;
|
|
2193
|
+
}
|
|
2194
|
+
if (state.active && restore) this.animateReorderPreviewToCurrent(state);else this.hideReorderPreview();
|
|
2195
|
+
this.updateReorderVisualState();
|
|
2196
|
+
}
|
|
2197
|
+
remapMountedRows(rows) {
|
|
2198
|
+
const elementByKey = new Map();
|
|
2199
|
+
this.mounted.forEach(element => {
|
|
2200
|
+
const key = element.dataset.nativeListRowKey;
|
|
2201
|
+
if (key) elementByKey.set(key, element);
|
|
2202
|
+
setData(element, 'nativeListAnimateReorder', true);
|
|
2203
|
+
});
|
|
2204
|
+
this.mounted.clear();
|
|
2205
|
+
rows.forEach((row, index) => {
|
|
2206
|
+
const element = elementByKey.get(row.key);
|
|
2207
|
+
if (element) {
|
|
2208
|
+
setData(element, 'nativeListRowIndex', index);
|
|
2209
|
+
this.mounted.set(index, element);
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
updateReorderVisualState() {
|
|
2214
|
+
const sourceKey = this.pointerReorder?.active ? this.pointerReorder.sourceKey : this.keyboardReorder?.sourceKey ?? this.reorderSettlingKey;
|
|
2215
|
+
setData(this.root, 'nativeListDragging', Boolean(sourceKey));
|
|
2216
|
+
if (this.reorderMovementTimer !== undefined) {
|
|
2217
|
+
this.document.defaultView?.clearTimeout(this.reorderMovementTimer);
|
|
2218
|
+
this.reorderMovementTimer = undefined;
|
|
2219
|
+
}
|
|
2220
|
+
if (!sourceKey) {
|
|
2221
|
+
this.reorderMovementTimer = this.document.defaultView?.setTimeout(() => {
|
|
2222
|
+
this.reorderMovementTimer = undefined;
|
|
2223
|
+
this.reorderExpandingKey = undefined;
|
|
2224
|
+
this.mounted.forEach(element => {
|
|
2225
|
+
setData(element, 'nativeListAnimateReorder', false);
|
|
2226
|
+
setData(element, 'nativeListReorderExpanding', false);
|
|
2227
|
+
});
|
|
2228
|
+
}, WEB_REORDER_ANIMATION.outOfWayDurationMs);
|
|
2229
|
+
}
|
|
2230
|
+
this.mounted.forEach(element => {
|
|
2231
|
+
const dragging = element.dataset.nativeListRowKey === sourceKey;
|
|
2232
|
+
setData(element, 'nativeListReorderExpanding', element.dataset.nativeListRowKey === this.reorderExpandingKey);
|
|
2233
|
+
setData(element, 'nativeListDragging', dragging);
|
|
2234
|
+
if (dragging) element.setAttribute('aria-grabbed', 'true');else element.removeAttribute('aria-grabbed');
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
//# sourceMappingURL=NativeListWebEngine.js.map
|