@onekeyfe/react-native-native-list 6.7.0-alpha.378 → 6.7.0-alpha.383
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/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +14 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +240 -11
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +7 -4
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +6 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListButtonGroupRenderer.kt +102 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +12 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +87 -7
- package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +177 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +43 -14
- package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +33 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +22 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +2 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +2 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +3 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +69 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +55 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1912 -107
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListPressPolicyTest.kt +1 -0
- package/docs/ROW_TEMPLATES.md +60 -9
- package/docs/SPEC.md +450 -5
- package/docs/STYLE_SPEC.md +6 -0
- package/ios/HybridNativeList.swift +21 -2
- package/ios/NativeListAccessoryStack.swift +227 -39
- package/ios/NativeListActivityDetailsView.swift +18 -8
- package/ios/NativeListActivityRenderer.swift +8 -2
- package/ios/NativeListButtonGroupRenderer.swift +89 -0
- package/ios/NativeListDataRowRenderer.swift +155 -27
- package/ios/NativeListDesignAssets.swift +3 -0
- package/ios/NativeListIdentityRenderer.swift +166 -30
- package/ios/NativeListLeadingVisual.swift +49 -14
- package/ios/NativeListMessageRenderer.swift +34 -1
- package/ios/NativeListModels.swift +42 -2
- package/ios/NativeListRendererRegistry.swift +1 -0
- package/ios/NativeListResolvedText.swift +2 -1
- package/ios/NativeListRowHost.swift +14 -0
- package/ios/NativeListSectionHeaderRenderer.swift +88 -0
- package/ios/RNCNativeListView.swift +1013 -55
- package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +4 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +3 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +3 -0
- package/ios/tests/NativeListActivityPresentationTests.swift +56 -0
- package/ios/tests/NativeListLifecycleTests.swift +2720 -3
- package/lib/module/NativeList.js +175 -28
- package/lib/module/NativeList.web.js +129 -30
- package/lib/module/index.js +1 -1
- package/lib/module/models.js +1 -0
- package/lib/module/selection.js +1 -1
- package/lib/module/validation.js +421 -8
- package/lib/module/web/NativeListWebEngine.js +507 -19
- package/lib/module/web/templates/ActivityRowRenderer.js +8 -7
- package/lib/module/web/templates/ButtonGroupRowRenderer.js +47 -0
- package/lib/module/web/templates/DataRowRenderer.js +40 -5
- package/lib/module/web/templates/IdentityRowRenderer.js +90 -4
- package/lib/module/web/templates/MessageRowRenderer.js +18 -2
- package/lib/module/web/templates/RowContainerStyle.js +1 -0
- package/lib/module/web/templates/RowRendererRegistry.js +3 -0
- package/lib/module/web/templates/SectionHeaderRowRenderer.js +36 -2
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
- package/lib/typescript/src/NativeList.d.ts +1 -1
- package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
- package/lib/typescript/src/NativeList.types.d.ts +18 -0
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/models.d.ts +105 -9
- package/lib/typescript/src/validation.d.ts +12 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +32 -0
- package/lib/typescript/src/web/templates/ButtonGroupRowRenderer.d.ts +14 -0
- package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +1 -1
- package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +9 -3
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
- package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
- package/package.json +3 -3
- package/react-native-native-list.podspec +1 -0
- package/src/NativeList.nitro.ts +2 -0
- package/src/NativeList.tsx +313 -44
- package/src/NativeList.types.ts +22 -0
- package/src/NativeList.web.tsx +204 -48
- package/src/index.ts +2 -0
- package/src/models.ts +117 -3
- package/src/selection.ts +1 -0
- package/src/validation.ts +852 -6
- package/src/web/NativeListWebEngine.ts +803 -20
- package/src/web/templates/ActivityRowRenderer.ts +14 -6
- package/src/web/templates/ButtonGroupRowRenderer.ts +58 -0
- package/src/web/templates/DataRowRenderer.ts +93 -20
- package/src/web/templates/IdentityRowRenderer.ts +150 -5
- package/src/web/templates/MessageRowRenderer.ts +23 -7
- package/src/web/templates/RowContainerStyle.ts +2 -0
- package/src/web/templates/RowRendererRegistry.ts +11 -1
- package/src/web/templates/SectionHeaderRowRenderer.ts +47 -4
package/src/NativeList.tsx
CHANGED
|
@@ -32,7 +32,11 @@ import type {
|
|
|
32
32
|
EndReachedEvent,
|
|
33
33
|
VisibleRangeChangedEvent,
|
|
34
34
|
} from './models';
|
|
35
|
-
import type {
|
|
35
|
+
import type {
|
|
36
|
+
NativeListProps,
|
|
37
|
+
NativeListRef,
|
|
38
|
+
StructuralTransitionSettledEvent,
|
|
39
|
+
} from './NativeList.types';
|
|
36
40
|
import { isSelectableRow } from './selection';
|
|
37
41
|
import {
|
|
38
42
|
normalizeIndexScroll,
|
|
@@ -44,15 +48,19 @@ import {
|
|
|
44
48
|
type NormalizedPositionScroll,
|
|
45
49
|
} from './scrolling';
|
|
46
50
|
import {
|
|
47
|
-
|
|
51
|
+
applyOwnedNativePatches,
|
|
52
|
+
captureNativeListInput,
|
|
48
53
|
containNativeListInput,
|
|
49
54
|
emptyNativeListSnapshot,
|
|
50
|
-
|
|
51
|
-
validatePatches,
|
|
55
|
+
ownValidatedNativeSnapshot,
|
|
52
56
|
validateSnapshot,
|
|
53
57
|
} from './validation';
|
|
54
58
|
|
|
55
|
-
export type {
|
|
59
|
+
export type {
|
|
60
|
+
NativeListProps,
|
|
61
|
+
NativeListRef,
|
|
62
|
+
StructuralTransitionSettledEvent,
|
|
63
|
+
} from './NativeList.types';
|
|
56
64
|
export type {
|
|
57
65
|
ActionAnchorState,
|
|
58
66
|
NativeListKeyboardDismissMode,
|
|
@@ -74,6 +82,8 @@ const NativeListHost = getHostComponent<
|
|
|
74
82
|
NativeListNativeProps,
|
|
75
83
|
NativeListMethods
|
|
76
84
|
>('NativeList', () => NativeListConfig);
|
|
85
|
+
const clearedStructuralTransitionSettled = { f: undefined } as const;
|
|
86
|
+
const disclosureSlotMeasurementStyle = { alignSelf: 'stretch' } as const;
|
|
77
87
|
|
|
78
88
|
export function preloadNativeListAvatarImages(
|
|
79
89
|
sources: readonly ImageSource[]
|
|
@@ -107,6 +117,10 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
107
117
|
listHeader,
|
|
108
118
|
listFooter,
|
|
109
119
|
listEmpty,
|
|
120
|
+
listSidecar,
|
|
121
|
+
listExpandedContent,
|
|
122
|
+
listStructureToggle,
|
|
123
|
+
sidecarLayout,
|
|
110
124
|
topOverlay,
|
|
111
125
|
topScrollEdgeEffect,
|
|
112
126
|
scrollPositionThresholds,
|
|
@@ -122,6 +136,7 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
122
136
|
onReorder,
|
|
123
137
|
onEndReached,
|
|
124
138
|
onVisibleRangeChanged,
|
|
139
|
+
onStructuralTransitionSettled,
|
|
125
140
|
onScrollPositionThresholdChange,
|
|
126
141
|
onRefresh,
|
|
127
142
|
onScrollToIndexFailed,
|
|
@@ -134,27 +149,171 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
134
149
|
forwardedRef
|
|
135
150
|
) {
|
|
136
151
|
const snapshotRef = useRef<NativeListSnapshot>(emptyNativeListSnapshot());
|
|
152
|
+
const renderedSnapshotRef = useRef<NativeListSnapshot | undefined>(
|
|
153
|
+
undefined
|
|
154
|
+
);
|
|
137
155
|
const initializedSnapshot = useRef(false);
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
const pendingDisclosureSnapshotRef = useRef<
|
|
157
|
+
| {
|
|
158
|
+
source: 'prop' | 'imperative';
|
|
159
|
+
input: NativeListSnapshot;
|
|
160
|
+
owned: NativeListSnapshot;
|
|
161
|
+
expandedEpoch: number;
|
|
162
|
+
toggleEpoch: number;
|
|
163
|
+
}
|
|
164
|
+
| undefined
|
|
165
|
+
>(undefined);
|
|
166
|
+
const previousPropInputRef = useRef(inputSnapshot);
|
|
167
|
+
const [pendingDisclosureRevision, setPendingDisclosureRevision] =
|
|
168
|
+
useState(0);
|
|
169
|
+
const disclosureSlotMounts = useRef({
|
|
170
|
+
expandedPresent: false,
|
|
171
|
+
togglePresent: false,
|
|
172
|
+
expandedEpoch: 0,
|
|
173
|
+
toggleEpoch: 0,
|
|
174
|
+
});
|
|
175
|
+
const expandedPresent = listExpandedContent != null;
|
|
176
|
+
const togglePresent = listStructureToggle != null;
|
|
177
|
+
if (disclosureSlotMounts.current.expandedPresent !== expandedPresent) {
|
|
178
|
+
disclosureSlotMounts.current.expandedPresent = expandedPresent;
|
|
179
|
+
disclosureSlotMounts.current.expandedEpoch += 1;
|
|
180
|
+
}
|
|
181
|
+
if (disclosureSlotMounts.current.togglePresent !== togglePresent) {
|
|
182
|
+
disclosureSlotMounts.current.togglePresent = togglePresent;
|
|
183
|
+
disclosureSlotMounts.current.toggleEpoch += 1;
|
|
184
|
+
}
|
|
185
|
+
const expandedEpoch = disclosureSlotMounts.current.expandedEpoch;
|
|
186
|
+
const toggleEpoch = disclosureSlotMounts.current.toggleEpoch;
|
|
187
|
+
const [slotMeasurements, setSlotMeasurements] = useState({
|
|
188
|
+
heights: [0, 0, 0, 0, 0, 0],
|
|
189
|
+
readyEpochs: [-1, -1, -1, -1, -1, -1],
|
|
190
|
+
});
|
|
191
|
+
const hasSidecar = listSidecar !== undefined;
|
|
192
|
+
const sidecarWidth = sidecarLayout?.width;
|
|
193
|
+
const sidecarGap = sidecarLayout?.gap;
|
|
194
|
+
const sidecarMaxContentWidth = sidecarLayout?.maxContentWidth;
|
|
195
|
+
const hasContainerSlots = Boolean(
|
|
196
|
+
listHeader ||
|
|
197
|
+
listEmpty ||
|
|
198
|
+
listFooter ||
|
|
199
|
+
listSidecar ||
|
|
200
|
+
listExpandedContent ||
|
|
201
|
+
listStructureToggle
|
|
202
|
+
);
|
|
203
|
+
const validateContainerConfiguration = useCallback(
|
|
204
|
+
(accepted: NativeListSnapshot) => {
|
|
142
205
|
if (hasContainerSlots && accepted.layout.orientation === 'horizontal')
|
|
143
206
|
throw new Error(
|
|
144
207
|
'NativeList container slots require vertical orientation'
|
|
145
208
|
);
|
|
146
|
-
|
|
209
|
+
if (hasSidecar !== (sidecarWidth !== undefined))
|
|
210
|
+
throw new Error(
|
|
211
|
+
'listSidecar and sidecarLayout must be supplied together'
|
|
212
|
+
);
|
|
213
|
+
if (sidecarWidth !== undefined) {
|
|
214
|
+
const width = sidecarWidth;
|
|
215
|
+
const gap = sidecarGap ?? NaN;
|
|
216
|
+
const maxContentWidth = sidecarMaxContentWidth ?? NaN;
|
|
217
|
+
if (
|
|
218
|
+
accepted.layout.kind !== 'linear' ||
|
|
219
|
+
!Number.isFinite(width) ||
|
|
220
|
+
width <= 0 ||
|
|
221
|
+
!Number.isFinite(gap) ||
|
|
222
|
+
gap < 0 ||
|
|
223
|
+
!Number.isFinite(maxContentWidth) ||
|
|
224
|
+
maxContentWidth <= width + gap
|
|
225
|
+
)
|
|
226
|
+
throw new Error('Invalid NativeList sidecar layout');
|
|
227
|
+
}
|
|
147
228
|
},
|
|
148
|
-
[
|
|
229
|
+
[
|
|
230
|
+
hasContainerSlots,
|
|
231
|
+
hasSidecar,
|
|
232
|
+
sidecarWidth,
|
|
233
|
+
sidecarGap,
|
|
234
|
+
sidecarMaxContentWidth,
|
|
235
|
+
]
|
|
149
236
|
);
|
|
150
|
-
const
|
|
151
|
-
() =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
237
|
+
const prepareSnapshot = useCallback(
|
|
238
|
+
(next: NativeListSnapshot) => {
|
|
239
|
+
const accepted = validateSnapshot(captureNativeListInput(next));
|
|
240
|
+
validateContainerConfiguration(accepted);
|
|
241
|
+
return ownValidatedNativeSnapshot(accepted);
|
|
242
|
+
},
|
|
243
|
+
[validateContainerConfiguration]
|
|
155
244
|
);
|
|
245
|
+
const expandedReadyEpoch = slotMeasurements.readyEpochs[4];
|
|
246
|
+
const toggleReadyEpoch = slotMeasurements.readyEpochs[5];
|
|
247
|
+
const snapshot = useMemo(() => {
|
|
248
|
+
// Imperative pending snapshots use this revision to retry the readiness gate.
|
|
249
|
+
// eslint-disable-next-line no-void -- Reading this revision invalidates the readiness memo.
|
|
250
|
+
void pendingDisclosureRevision;
|
|
251
|
+
const propReplaced = previousPropInputRef.current !== inputSnapshot;
|
|
252
|
+
previousPropInputRef.current = inputSnapshot;
|
|
253
|
+
const pending = pendingDisclosureSnapshotRef.current;
|
|
254
|
+
const retained =
|
|
255
|
+
pending &&
|
|
256
|
+
!propReplaced &&
|
|
257
|
+
(pending.source === 'imperative' || pending.input === inputSnapshot) &&
|
|
258
|
+
pending.expandedEpoch === expandedEpoch &&
|
|
259
|
+
pending.toggleEpoch === toggleEpoch
|
|
260
|
+
? pending
|
|
261
|
+
: undefined;
|
|
262
|
+
const candidate =
|
|
263
|
+
retained?.owned ??
|
|
264
|
+
(!propReplaced && initializedSnapshot.current
|
|
265
|
+
? renderedSnapshotRef.current ?? snapshotRef.current
|
|
266
|
+
: containNativeListInput(() => prepareSnapshot(inputSnapshot)) ??
|
|
267
|
+
snapshotRef.current);
|
|
268
|
+
const acceptedCandidate =
|
|
269
|
+
!propReplaced && initializedSnapshot.current
|
|
270
|
+
? containNativeListInput(() => {
|
|
271
|
+
validateContainerConfiguration(candidate);
|
|
272
|
+
return candidate;
|
|
273
|
+
})
|
|
274
|
+
: candidate;
|
|
275
|
+
if (!acceptedCandidate) {
|
|
276
|
+
pendingDisclosureSnapshotRef.current = undefined;
|
|
277
|
+
return renderedSnapshotRef.current ?? snapshotRef.current;
|
|
278
|
+
}
|
|
279
|
+
const transitionId = acceptedCandidate.structuralTransition?.id;
|
|
280
|
+
const newDisclosureCommand =
|
|
281
|
+
initializedSnapshot.current &&
|
|
282
|
+
acceptedCandidate.disclosure !== undefined &&
|
|
283
|
+
transitionId !== undefined &&
|
|
284
|
+
transitionId !== snapshotRef.current.structuralTransition?.id;
|
|
285
|
+
if (
|
|
286
|
+
newDisclosureCommand &&
|
|
287
|
+
((expandedPresent && expandedReadyEpoch !== expandedEpoch) ||
|
|
288
|
+
(togglePresent && toggleReadyEpoch !== toggleEpoch))
|
|
289
|
+
) {
|
|
290
|
+
pendingDisclosureSnapshotRef.current = retained ?? {
|
|
291
|
+
source: 'prop',
|
|
292
|
+
input: inputSnapshot,
|
|
293
|
+
owned: acceptedCandidate,
|
|
294
|
+
expandedEpoch,
|
|
295
|
+
toggleEpoch,
|
|
296
|
+
};
|
|
297
|
+
return snapshotRef.current;
|
|
298
|
+
}
|
|
299
|
+
pendingDisclosureSnapshotRef.current = undefined;
|
|
300
|
+
return acceptedCandidate;
|
|
301
|
+
}, [
|
|
302
|
+
inputSnapshot,
|
|
303
|
+
prepareSnapshot,
|
|
304
|
+
validateContainerConfiguration,
|
|
305
|
+
expandedPresent,
|
|
306
|
+
togglePresent,
|
|
307
|
+
expandedEpoch,
|
|
308
|
+
toggleEpoch,
|
|
309
|
+
expandedReadyEpoch,
|
|
310
|
+
toggleReadyEpoch,
|
|
311
|
+
pendingDisclosureRevision,
|
|
312
|
+
]);
|
|
313
|
+
renderedSnapshotRef.current = snapshot;
|
|
156
314
|
const fontFamiliesJson = useNativeListFontFamiliesJson();
|
|
157
|
-
const
|
|
315
|
+
const slotHeights = slotMeasurements.heights;
|
|
316
|
+
const [viewportWidth, setViewportWidth] = useState(0);
|
|
158
317
|
const [topOverlayHeight, setTopOverlayHeight] = useState(0);
|
|
159
318
|
const hasTopOverlay =
|
|
160
319
|
Platform?.OS === 'ios' &&
|
|
@@ -162,11 +321,51 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
162
321
|
(topScrollEdgeEffect === 'hard' || topScrollEdgeEffect === 'soft') &&
|
|
163
322
|
topOverlay != null;
|
|
164
323
|
const containerSlotHeightsJson = JSON.stringify(slotHeights);
|
|
165
|
-
const
|
|
324
|
+
const defaultSlotInset =
|
|
166
325
|
snapshot.layout.contentPaddingHorizontal ??
|
|
167
326
|
snapshot.layout.contentPadding ??
|
|
168
327
|
0;
|
|
169
|
-
const
|
|
328
|
+
const bandWidth = sidecarLayout
|
|
329
|
+
? Math.min(viewportWidth, sidecarLayout.maxContentWidth)
|
|
330
|
+
: 0;
|
|
331
|
+
const slotInset = sidecarLayout
|
|
332
|
+
? Math.max(0, (viewportWidth - bandWidth) / 2) + defaultSlotInset
|
|
333
|
+
: defaultSlotInset;
|
|
334
|
+
const sidecarSpace = sidecarLayout
|
|
335
|
+
? sidecarLayout.width + sidecarLayout.gap
|
|
336
|
+
: 0;
|
|
337
|
+
const slotContents = [
|
|
338
|
+
listHeader,
|
|
339
|
+
listEmpty,
|
|
340
|
+
listFooter,
|
|
341
|
+
listSidecar,
|
|
342
|
+
listExpandedContent,
|
|
343
|
+
listStructureToggle,
|
|
344
|
+
];
|
|
345
|
+
const recordSlotLayout = (index: number, height: number, epoch: number) => {
|
|
346
|
+
if (
|
|
347
|
+
(index === 4 && epoch !== disclosureSlotMounts.current.expandedEpoch) ||
|
|
348
|
+
(index === 5 && epoch !== disclosureSlotMounts.current.toggleEpoch)
|
|
349
|
+
) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
setSlotMeasurements((previous) => {
|
|
353
|
+
if (
|
|
354
|
+
previous.heights[index] === height &&
|
|
355
|
+
previous.readyEpochs[index] === epoch
|
|
356
|
+
) {
|
|
357
|
+
return previous;
|
|
358
|
+
}
|
|
359
|
+
const heights = [...previous.heights];
|
|
360
|
+
const readyEpochs = [...previous.readyEpochs];
|
|
361
|
+
heights[index] = height;
|
|
362
|
+
readyEpochs[index] = epoch;
|
|
363
|
+
return { heights, readyEpochs };
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
const sidecarLayoutJson = sidecarLayout
|
|
367
|
+
? JSON.stringify(sidecarLayout)
|
|
368
|
+
: '';
|
|
170
369
|
const normalizedKeyboardShouldPersistTaps =
|
|
171
370
|
keyboardShouldPersistTaps === true
|
|
172
371
|
? 'always'
|
|
@@ -181,6 +380,7 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
181
380
|
onReorder,
|
|
182
381
|
onEndReached,
|
|
183
382
|
onVisibleRangeChanged,
|
|
383
|
+
onStructuralTransitionSettled,
|
|
184
384
|
onScrollPositionThresholdChange,
|
|
185
385
|
onRefresh,
|
|
186
386
|
onScrollToIndexFailed,
|
|
@@ -192,6 +392,7 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
192
392
|
onReorder,
|
|
193
393
|
onEndReached,
|
|
194
394
|
onVisibleRangeChanged,
|
|
395
|
+
onStructuralTransitionSettled,
|
|
195
396
|
onScrollPositionThresholdChange,
|
|
196
397
|
onRefresh,
|
|
197
398
|
onScrollToIndexFailed,
|
|
@@ -367,6 +568,26 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
367
568
|
prepareSnapshot(nextSnapshot)
|
|
368
569
|
);
|
|
369
570
|
if (!accepted) return;
|
|
571
|
+
const transitionId = accepted.structuralTransition?.id;
|
|
572
|
+
const waitsForSlots =
|
|
573
|
+
accepted.disclosure !== undefined &&
|
|
574
|
+
transitionId !== undefined &&
|
|
575
|
+
transitionId !== snapshotRef.current.structuralTransition?.id &&
|
|
576
|
+
((expandedPresent &&
|
|
577
|
+
slotMeasurements.readyEpochs[4] !== expandedEpoch) ||
|
|
578
|
+
(togglePresent && slotMeasurements.readyEpochs[5] !== toggleEpoch));
|
|
579
|
+
if (waitsForSlots) {
|
|
580
|
+
pendingDisclosureSnapshotRef.current = {
|
|
581
|
+
source: 'imperative',
|
|
582
|
+
input: nextSnapshot,
|
|
583
|
+
owned: accepted,
|
|
584
|
+
expandedEpoch,
|
|
585
|
+
toggleEpoch,
|
|
586
|
+
};
|
|
587
|
+
setPendingDisclosureRevision((value) => value + 1);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
pendingDisclosureSnapshotRef.current = undefined;
|
|
370
591
|
const nextSnapshotJson = JSON.stringify(accepted);
|
|
371
592
|
nativeRef.current?.applySnapshot(nextSnapshotJson);
|
|
372
593
|
snapshotRef.current = accepted;
|
|
@@ -376,16 +597,22 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
376
597
|
}
|
|
377
598
|
},
|
|
378
599
|
applyPatches(patches) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
});
|
|
600
|
+
const pending = pendingDisclosureSnapshotRef.current;
|
|
601
|
+
const accepted = containNativeListInput(() =>
|
|
602
|
+
applyOwnedNativePatches(
|
|
603
|
+
pending?.owned ?? snapshotRef.current,
|
|
604
|
+
patches
|
|
605
|
+
)
|
|
606
|
+
);
|
|
387
607
|
if (!accepted || accepted.patches.length === 0) return;
|
|
388
608
|
const nextSnapshot = accepted.snapshot;
|
|
609
|
+
if (pending) {
|
|
610
|
+
pendingDisclosureSnapshotRef.current = {
|
|
611
|
+
...pending,
|
|
612
|
+
owned: nextSnapshot,
|
|
613
|
+
};
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
389
616
|
const native = nativeRef.current;
|
|
390
617
|
const dispatch = native
|
|
391
618
|
? () => {
|
|
@@ -401,19 +628,25 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
401
628
|
updateAvatarPrefetchRef.current();
|
|
402
629
|
},
|
|
403
630
|
reconcileSelection(selectedKeys) {
|
|
404
|
-
|
|
405
|
-
const
|
|
631
|
+
const capturedKeys = captureNativeListInput(selectedKeys);
|
|
632
|
+
const pending = pendingDisclosureSnapshotRef.current;
|
|
633
|
+
if (!pending)
|
|
634
|
+
nativeRef.current?.reconcileSelection(JSON.stringify(capturedKeys));
|
|
635
|
+
const current = pending?.owned ?? snapshotRef.current;
|
|
406
636
|
if (
|
|
407
637
|
current.selection &&
|
|
408
|
-
(current.selection.mode !== 'single' ||
|
|
409
|
-
|
|
638
|
+
(current.selection.mode !== 'single' || capturedKeys.length <= 1) &&
|
|
639
|
+
capturedKeys.every((key) =>
|
|
410
640
|
current.rows.some((row) => row.key === key && isSelectableRow(row))
|
|
411
641
|
)
|
|
412
642
|
) {
|
|
413
|
-
|
|
643
|
+
const next = {
|
|
414
644
|
...current,
|
|
415
|
-
selection: { ...current.selection, selectedKeys },
|
|
645
|
+
selection: { ...current.selection, selectedKeys: capturedKeys },
|
|
416
646
|
};
|
|
647
|
+
if (pending)
|
|
648
|
+
pendingDisclosureSnapshotRef.current = { ...pending, owned: next };
|
|
649
|
+
else snapshotRef.current = next;
|
|
417
650
|
}
|
|
418
651
|
},
|
|
419
652
|
scrollToKey(paramsOrKey, animated, alignment) {
|
|
@@ -546,6 +779,11 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
546
779
|
updateAvatarPrefetchRef.current();
|
|
547
780
|
callbacksRef.current.onVisibleRangeChanged?.(payload);
|
|
548
781
|
}),
|
|
782
|
+
onStructuralTransitionSettled: callback((payloadJson: string) => {
|
|
783
|
+
callbacksRef.current.onStructuralTransitionSettled?.(
|
|
784
|
+
parsePayload<StructuralTransitionSettledEvent>(payloadJson)
|
|
785
|
+
);
|
|
786
|
+
}),
|
|
549
787
|
}),
|
|
550
788
|
[]
|
|
551
789
|
);
|
|
@@ -553,10 +791,20 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
553
791
|
return (
|
|
554
792
|
<NativeListHost
|
|
555
793
|
{...viewProps}
|
|
794
|
+
onLayout={(event) => {
|
|
795
|
+
setViewportWidth(event.nativeEvent.layout.width);
|
|
796
|
+
viewProps.onLayout?.(event);
|
|
797
|
+
}}
|
|
556
798
|
{...nativeCallbacks}
|
|
799
|
+
onStructuralTransitionSettled={
|
|
800
|
+
onStructuralTransitionSettled
|
|
801
|
+
? nativeCallbacks.onStructuralTransitionSettled
|
|
802
|
+
: clearedStructuralTransitionSettled
|
|
803
|
+
}
|
|
557
804
|
fontFamiliesJson={fontFamiliesJson}
|
|
558
805
|
snapshotJson={snapshotJson}
|
|
559
806
|
containerSlotHeightsJson={containerSlotHeightsJson}
|
|
807
|
+
sidecarLayoutJson={sidecarLayoutJson}
|
|
560
808
|
topScrollEdgeEffect={
|
|
561
809
|
hasTopOverlay ? topScrollEdgeEffect ?? 'none' : 'none'
|
|
562
810
|
}
|
|
@@ -571,22 +819,43 @@ export const NativeList = forwardRef<NativeListRef, NativeListProps>(
|
|
|
571
819
|
collapsable={false}
|
|
572
820
|
style={{
|
|
573
821
|
position: 'absolute',
|
|
574
|
-
|
|
575
|
-
|
|
822
|
+
start: index === 3 ? 0 : slotInset,
|
|
823
|
+
end: index === 3 ? undefined : slotInset + sidecarSpace,
|
|
824
|
+
width: index === 3 ? sidecarLayout?.width : undefined,
|
|
576
825
|
top: 0,
|
|
577
826
|
}}
|
|
578
827
|
onLayout={(event) => {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
);
|
|
828
|
+
if (index < 4) {
|
|
829
|
+
recordSlotLayout(
|
|
830
|
+
index,
|
|
831
|
+
Math.ceil(event.nativeEvent.layout.height),
|
|
832
|
+
0
|
|
833
|
+
);
|
|
834
|
+
}
|
|
587
835
|
}}
|
|
588
836
|
>
|
|
589
|
-
{
|
|
837
|
+
{index >= 4 ? (
|
|
838
|
+
<View
|
|
839
|
+
key={
|
|
840
|
+
index === 4
|
|
841
|
+
? `expanded-${expandedEpoch}`
|
|
842
|
+
: `toggle-${toggleEpoch}`
|
|
843
|
+
}
|
|
844
|
+
collapsable={false}
|
|
845
|
+
style={disclosureSlotMeasurementStyle}
|
|
846
|
+
onLayout={(event) =>
|
|
847
|
+
recordSlotLayout(
|
|
848
|
+
index,
|
|
849
|
+
Math.ceil(event.nativeEvent.layout.height),
|
|
850
|
+
index === 4 ? expandedEpoch : toggleEpoch
|
|
851
|
+
)
|
|
852
|
+
}
|
|
853
|
+
>
|
|
854
|
+
{content}
|
|
855
|
+
</View>
|
|
856
|
+
) : (
|
|
857
|
+
content
|
|
858
|
+
)}
|
|
590
859
|
</View>
|
|
591
860
|
))}
|
|
592
861
|
{hasTopOverlay ? (
|
package/src/NativeList.types.ts
CHANGED
|
@@ -78,6 +78,18 @@ export type NativeListWebScrollHost = Readonly<{
|
|
|
78
78
|
scrollTop: number;
|
|
79
79
|
}>;
|
|
80
80
|
|
|
81
|
+
export type NativeListSidecarLayout = Readonly<{
|
|
82
|
+
width: number;
|
|
83
|
+
gap: number;
|
|
84
|
+
maxContentWidth: number;
|
|
85
|
+
}>;
|
|
86
|
+
|
|
87
|
+
export type StructuralTransitionSettledEvent = Readonly<{
|
|
88
|
+
transitionId: string;
|
|
89
|
+
status: 'completed' | 'interrupted';
|
|
90
|
+
settledExpanded: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
|
|
81
93
|
type InitialScrollProps =
|
|
82
94
|
| Readonly<{
|
|
83
95
|
initialScrollIndex?: never;
|
|
@@ -131,6 +143,13 @@ export type NativeListProps = Omit<ViewProps, 'children'> &
|
|
|
131
143
|
listHeader?: ReactNode;
|
|
132
144
|
listFooter?: ReactNode;
|
|
133
145
|
listEmpty?: ReactNode;
|
|
146
|
+
/** Native disclosure content, retained until its transition settles. */
|
|
147
|
+
listExpandedContent?: ReactNode;
|
|
148
|
+
/** Stable native disclosure toggle after expanded content. */
|
|
149
|
+
listStructureToggle?: ReactNode;
|
|
150
|
+
/** Scrolling right column for a vertical linear list. Requires sidecarLayout. */
|
|
151
|
+
listSidecar?: ReactNode;
|
|
152
|
+
sidecarLayout?: NativeListSidecarLayout;
|
|
134
153
|
/** Fixed overlay above scrolling rows on iOS 26 and newer. */
|
|
135
154
|
topOverlay?: ReactNode;
|
|
136
155
|
/** Selects UIKit's top scroll-edge effect on iOS 26 and newer. */
|
|
@@ -156,6 +175,9 @@ export type NativeListProps = Omit<ViewProps, 'children'> &
|
|
|
156
175
|
onReorder?: (event: ReorderEvent) => void;
|
|
157
176
|
onEndReached?: (event: EndReachedEvent) => void;
|
|
158
177
|
onVisibleRangeChanged?: (event: VisibleRangeChangedEvent) => void;
|
|
178
|
+
onStructuralTransitionSettled?: (
|
|
179
|
+
event: StructuralTransitionSettledEvent
|
|
180
|
+
) => void;
|
|
159
181
|
/** Native only. Emits initial state and hysteresis transitions, not scroll frames. */
|
|
160
182
|
scrollPositionThresholds?: Readonly<{
|
|
161
183
|
start: number;
|