react-native-layout-dnd 0.1.0-alpha.0
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/LICENSE +20 -0
- package/README.md +262 -0
- package/lib/module/adapters/coordinates.js +135 -0
- package/lib/module/adapters/coordinates.js.map +1 -0
- package/lib/module/adapters/scroll.js +81 -0
- package/lib/module/adapters/scroll.js.map +1 -0
- package/lib/module/adapters/virtualizedList.js +69 -0
- package/lib/module/adapters/virtualizedList.js.map +1 -0
- package/lib/module/components/DndProvider.js +1144 -0
- package/lib/module/components/DndProvider.js.map +1 -0
- package/lib/module/components/DragHandle.js +65 -0
- package/lib/module/components/DragHandle.js.map +1 -0
- package/lib/module/components/GridController.js +151 -0
- package/lib/module/components/GridController.js.map +1 -0
- package/lib/module/components/GridDragHandle.js +15 -0
- package/lib/module/components/GridDragHandle.js.map +1 -0
- package/lib/module/components/GridZoneView.js +59 -0
- package/lib/module/components/GridZoneView.js.map +1 -0
- package/lib/module/components/SortableGrid.js +305 -0
- package/lib/module/components/SortableGrid.js.map +1 -0
- package/lib/module/components/SortableList.js +714 -0
- package/lib/module/components/SortableList.js.map +1 -0
- package/lib/module/components/dndTypes.js +4 -0
- package/lib/module/components/dndTypes.js.map +1 -0
- package/lib/module/components/gridTypes.js +4 -0
- package/lib/module/components/gridTypes.js.map +1 -0
- package/lib/module/components/handleViewProps.js +13 -0
- package/lib/module/components/handleViewProps.js.map +1 -0
- package/lib/module/contracts.js +4 -0
- package/lib/module/contracts.js.map +1 -0
- package/lib/module/controller/approval.js +98 -0
- package/lib/module/controller/approval.js.map +1 -0
- package/lib/module/controller/coordinatorBase.js +93 -0
- package/lib/module/controller/coordinatorBase.js.map +1 -0
- package/lib/module/controller/dndSession.js +621 -0
- package/lib/module/controller/dndSession.js.map +1 -0
- package/lib/module/controller/dndState.js +103 -0
- package/lib/module/controller/dndState.js.map +1 -0
- package/lib/module/controller/gridAdapter.js +191 -0
- package/lib/module/controller/gridAdapter.js.map +1 -0
- package/lib/module/controller/gridState.js +101 -0
- package/lib/module/controller/gridState.js.map +1 -0
- package/lib/module/controller/locations.js +48 -0
- package/lib/module/controller/locations.js.map +1 -0
- package/lib/module/controller/previewPath.js +73 -0
- package/lib/module/controller/previewPath.js.map +1 -0
- package/lib/module/controller/sessionCore.js +222 -0
- package/lib/module/controller/sessionCore.js.map +1 -0
- package/lib/module/controller/useDndState.js +9 -0
- package/lib/module/controller/useDndState.js.map +1 -0
- package/lib/module/controller/useGridState.js +9 -0
- package/lib/module/controller/useGridState.js.map +1 -0
- package/lib/module/controller/useStoreState.js +15 -0
- package/lib/module/controller/useStoreState.js.map +1 -0
- package/lib/module/defaults.js +7 -0
- package/lib/module/defaults.js.map +1 -0
- package/lib/module/diagnostics.js +16 -0
- package/lib/module/diagnostics.js.map +1 -0
- package/lib/module/engine/gridPlacements.js +54 -0
- package/lib/module/engine/gridPlacements.js.map +1 -0
- package/lib/module/engine/index.js +19 -0
- package/lib/module/engine/index.js.map +1 -0
- package/lib/module/engine/layout.js +36 -0
- package/lib/module/engine/layout.js.map +1 -0
- package/lib/module/engine/layoutMove.js +326 -0
- package/lib/module/engine/layoutMove.js.map +1 -0
- package/lib/module/engine/layoutState.js +344 -0
- package/lib/module/engine/layoutState.js.map +1 -0
- package/lib/module/engine/list.js +172 -0
- package/lib/module/engine/list.js.map +1 -0
- package/lib/module/engine/listIndex.js +125 -0
- package/lib/module/engine/listIndex.js.map +1 -0
- package/lib/module/engine/move.js +216 -0
- package/lib/module/engine/move.js.map +1 -0
- package/lib/module/engine/movementPolicy.js +77 -0
- package/lib/module/engine/movementPolicy.js.map +1 -0
- package/lib/module/engine/orderedTarget.js +71 -0
- package/lib/module/engine/orderedTarget.js.map +1 -0
- package/lib/module/engine/pack.js +30 -0
- package/lib/module/engine/pack.js.map +1 -0
- package/lib/module/engine/rectangles.js +12 -0
- package/lib/module/engine/rectangles.js.map +1 -0
- package/lib/module/engine/sameLayout.js +29 -0
- package/lib/module/engine/sameLayout.js.map +1 -0
- package/lib/module/engine/search.js +159 -0
- package/lib/module/engine/search.js.map +1 -0
- package/lib/module/engine/spatial.js +27 -0
- package/lib/module/engine/spatial.js.map +1 -0
- package/lib/module/engine/spatialAppRotation.js +39 -0
- package/lib/module/engine/spatialAppRotation.js.map +1 -0
- package/lib/module/engine/spatialExchange.js +164 -0
- package/lib/module/engine/spatialExchange.js.map +1 -0
- package/lib/module/engine/spatialInsertion.js +135 -0
- package/lib/module/engine/spatialInsertion.js.map +1 -0
- package/lib/module/engine/spatialPolicy.js +41 -0
- package/lib/module/engine/spatialPolicy.js.map +1 -0
- package/lib/module/engine/spatialRotation.js +48 -0
- package/lib/module/engine/spatialRotation.js.map +1 -0
- package/lib/module/engine/validation.js +205 -0
- package/lib/module/engine/validation.js.map +1 -0
- package/lib/module/gesture/DndGestureGroup.js +268 -0
- package/lib/module/gesture/DndGestureGroup.js.map +1 -0
- package/lib/module/gesture/DndGestureHost.js +32 -0
- package/lib/module/gesture/DndGestureHost.js.map +1 -0
- package/lib/module/gesture/gestureConfig.js +81 -0
- package/lib/module/gesture/gestureConfig.js.map +1 -0
- package/lib/module/gesture/gestureGrouping.js +122 -0
- package/lib/module/gesture/gestureGrouping.js.map +1 -0
- package/lib/module/gesture/gestureHost.js +106 -0
- package/lib/module/gesture/gestureHost.js.map +1 -0
- package/lib/module/index.js +23 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/pointer/dndTarget.js +101 -0
- package/lib/module/pointer/dndTarget.js.map +1 -0
- package/lib/module/pointer/geometry.js +363 -0
- package/lib/module/pointer/geometry.js.map +1 -0
- package/lib/module/pointer/layoutTarget.js +307 -0
- package/lib/module/pointer/layoutTarget.js.map +1 -0
- package/lib/module/pointer/listCellMotion.js +99 -0
- package/lib/module/pointer/listCellMotion.js.map +1 -0
- package/lib/module/pointer/listTarget.js +42 -0
- package/lib/module/pointer/listTarget.js.map +1 -0
- package/lib/module/pointer/motion.js +37 -0
- package/lib/module/pointer/motion.js.map +1 -0
- package/lib/module/runtime/dndPresentation.js +81 -0
- package/lib/module/runtime/dndPresentation.js.map +1 -0
- package/lib/module/runtime/dndRenderStore.js +129 -0
- package/lib/module/runtime/dndRenderStore.js.map +1 -0
- package/lib/module/runtime/dndRuntime.js +135 -0
- package/lib/module/runtime/dndRuntime.js.map +1 -0
- package/lib/module/runtime/dndSettlement.js +77 -0
- package/lib/module/runtime/dndSettlement.js.map +1 -0
- package/lib/module/runtime/gridRuntime.js +37 -0
- package/lib/module/runtime/gridRuntime.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/adapters/coordinates.d.ts +9 -0
- package/lib/typescript/src/adapters/coordinates.d.ts.map +1 -0
- package/lib/typescript/src/adapters/scroll.d.ts +49 -0
- package/lib/typescript/src/adapters/scroll.d.ts.map +1 -0
- package/lib/typescript/src/adapters/virtualizedList.d.ts +22 -0
- package/lib/typescript/src/adapters/virtualizedList.d.ts.map +1 -0
- package/lib/typescript/src/components/DndProvider.d.ts +4 -0
- package/lib/typescript/src/components/DndProvider.d.ts.map +1 -0
- package/lib/typescript/src/components/DragHandle.d.ts +4 -0
- package/lib/typescript/src/components/DragHandle.d.ts.map +1 -0
- package/lib/typescript/src/components/GridController.d.ts +21 -0
- package/lib/typescript/src/components/GridController.d.ts.map +1 -0
- package/lib/typescript/src/components/GridDragHandle.d.ts +8 -0
- package/lib/typescript/src/components/GridDragHandle.d.ts.map +1 -0
- package/lib/typescript/src/components/GridZoneView.d.ts +8 -0
- package/lib/typescript/src/components/GridZoneView.d.ts.map +1 -0
- package/lib/typescript/src/components/SortableGrid.d.ts +4 -0
- package/lib/typescript/src/components/SortableGrid.d.ts.map +1 -0
- package/lib/typescript/src/components/SortableList.d.ts +4 -0
- package/lib/typescript/src/components/SortableList.d.ts.map +1 -0
- package/lib/typescript/src/components/dndTypes.d.ts +94 -0
- package/lib/typescript/src/components/dndTypes.d.ts.map +1 -0
- package/lib/typescript/src/components/gridTypes.d.ts +84 -0
- package/lib/typescript/src/components/gridTypes.d.ts.map +1 -0
- package/lib/typescript/src/components/handleViewProps.d.ts +6 -0
- package/lib/typescript/src/components/handleViewProps.d.ts.map +1 -0
- package/lib/typescript/src/contracts.d.ts +145 -0
- package/lib/typescript/src/contracts.d.ts.map +1 -0
- package/lib/typescript/src/controller/approval.d.ts +66 -0
- package/lib/typescript/src/controller/approval.d.ts.map +1 -0
- package/lib/typescript/src/controller/coordinatorBase.d.ts +37 -0
- package/lib/typescript/src/controller/coordinatorBase.d.ts.map +1 -0
- package/lib/typescript/src/controller/dndSession.d.ts +152 -0
- package/lib/typescript/src/controller/dndSession.d.ts.map +1 -0
- package/lib/typescript/src/controller/dndState.d.ts +27 -0
- package/lib/typescript/src/controller/dndState.d.ts.map +1 -0
- package/lib/typescript/src/controller/gridAdapter.d.ts +44 -0
- package/lib/typescript/src/controller/gridAdapter.d.ts.map +1 -0
- package/lib/typescript/src/controller/gridState.d.ts +18 -0
- package/lib/typescript/src/controller/gridState.d.ts.map +1 -0
- package/lib/typescript/src/controller/locations.d.ts +9 -0
- package/lib/typescript/src/controller/locations.d.ts.map +1 -0
- package/lib/typescript/src/controller/previewPath.d.ts +49 -0
- package/lib/typescript/src/controller/previewPath.d.ts.map +1 -0
- package/lib/typescript/src/controller/sessionCore.d.ts +106 -0
- package/lib/typescript/src/controller/sessionCore.d.ts.map +1 -0
- package/lib/typescript/src/controller/useDndState.d.ts +8 -0
- package/lib/typescript/src/controller/useDndState.d.ts.map +1 -0
- package/lib/typescript/src/controller/useGridState.d.ts +8 -0
- package/lib/typescript/src/controller/useGridState.d.ts.map +1 -0
- package/lib/typescript/src/controller/useStoreState.d.ts +16 -0
- package/lib/typescript/src/controller/useStoreState.d.ts.map +1 -0
- package/lib/typescript/src/defaults.d.ts +5 -0
- package/lib/typescript/src/defaults.d.ts.map +1 -0
- package/lib/typescript/src/diagnostics.d.ts +9 -0
- package/lib/typescript/src/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/engine/gridPlacements.d.ts +22 -0
- package/lib/typescript/src/engine/gridPlacements.d.ts.map +1 -0
- package/lib/typescript/src/engine/index.d.ts +28 -0
- package/lib/typescript/src/engine/index.d.ts.map +1 -0
- package/lib/typescript/src/engine/layout.d.ts +4 -0
- package/lib/typescript/src/engine/layout.d.ts.map +1 -0
- package/lib/typescript/src/engine/layoutMove.d.ts +46 -0
- package/lib/typescript/src/engine/layoutMove.d.ts.map +1 -0
- package/lib/typescript/src/engine/layoutState.d.ts +22 -0
- package/lib/typescript/src/engine/layoutState.d.ts.map +1 -0
- package/lib/typescript/src/engine/list.d.ts +68 -0
- package/lib/typescript/src/engine/list.d.ts.map +1 -0
- package/lib/typescript/src/engine/listIndex.d.ts +32 -0
- package/lib/typescript/src/engine/listIndex.d.ts.map +1 -0
- package/lib/typescript/src/engine/move.d.ts +10 -0
- package/lib/typescript/src/engine/move.d.ts.map +1 -0
- package/lib/typescript/src/engine/movementPolicy.d.ts +34 -0
- package/lib/typescript/src/engine/movementPolicy.d.ts.map +1 -0
- package/lib/typescript/src/engine/orderedTarget.d.ts +32 -0
- package/lib/typescript/src/engine/orderedTarget.d.ts.map +1 -0
- package/lib/typescript/src/engine/pack.d.ts +3 -0
- package/lib/typescript/src/engine/pack.d.ts.map +1 -0
- package/lib/typescript/src/engine/rectangles.d.ts +9 -0
- package/lib/typescript/src/engine/rectangles.d.ts.map +1 -0
- package/lib/typescript/src/engine/sameLayout.d.ts +4 -0
- package/lib/typescript/src/engine/sameLayout.d.ts.map +1 -0
- package/lib/typescript/src/engine/search.d.ts +28 -0
- package/lib/typescript/src/engine/search.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatial.d.ts +16 -0
- package/lib/typescript/src/engine/spatial.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialAppRotation.d.ts +4 -0
- package/lib/typescript/src/engine/spatialAppRotation.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialExchange.d.ts +5 -0
- package/lib/typescript/src/engine/spatialExchange.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialInsertion.d.ts +5 -0
- package/lib/typescript/src/engine/spatialInsertion.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialPolicy.d.ts +9 -0
- package/lib/typescript/src/engine/spatialPolicy.d.ts.map +1 -0
- package/lib/typescript/src/engine/spatialRotation.d.ts +8 -0
- package/lib/typescript/src/engine/spatialRotation.d.ts.map +1 -0
- package/lib/typescript/src/engine/validation.d.ts +9 -0
- package/lib/typescript/src/engine/validation.d.ts.map +1 -0
- package/lib/typescript/src/gesture/DndGestureGroup.d.ts +33 -0
- package/lib/typescript/src/gesture/DndGestureGroup.d.ts.map +1 -0
- package/lib/typescript/src/gesture/DndGestureHost.d.ts +12 -0
- package/lib/typescript/src/gesture/DndGestureHost.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureConfig.d.ts +15 -0
- package/lib/typescript/src/gesture/gestureConfig.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureGrouping.d.ts +66 -0
- package/lib/typescript/src/gesture/gestureGrouping.d.ts.map +1 -0
- package/lib/typescript/src/gesture/gestureHost.d.ts +23 -0
- package/lib/typescript/src/gesture/gestureHost.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +38 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/pointer/dndTarget.d.ts +14 -0
- package/lib/typescript/src/pointer/dndTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/geometry.d.ts +28 -0
- package/lib/typescript/src/pointer/geometry.d.ts.map +1 -0
- package/lib/typescript/src/pointer/layoutTarget.d.ts +43 -0
- package/lib/typescript/src/pointer/layoutTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/listCellMotion.d.ts +43 -0
- package/lib/typescript/src/pointer/listCellMotion.d.ts.map +1 -0
- package/lib/typescript/src/pointer/listTarget.d.ts +7 -0
- package/lib/typescript/src/pointer/listTarget.d.ts.map +1 -0
- package/lib/typescript/src/pointer/motion.d.ts +20 -0
- package/lib/typescript/src/pointer/motion.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndPresentation.d.ts +37 -0
- package/lib/typescript/src/runtime/dndPresentation.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndRenderStore.d.ts +46 -0
- package/lib/typescript/src/runtime/dndRenderStore.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndRuntime.d.ts +138 -0
- package/lib/typescript/src/runtime/dndRuntime.d.ts.map +1 -0
- package/lib/typescript/src/runtime/dndSettlement.d.ts +18 -0
- package/lib/typescript/src/runtime/dndSettlement.d.ts.map +1 -0
- package/lib/typescript/src/runtime/gridRuntime.d.ts +54 -0
- package/lib/typescript/src/runtime/gridRuntime.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +180 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +136 -0
- package/src/adapters/coordinates.ts +181 -0
- package/src/adapters/scroll.ts +147 -0
- package/src/adapters/virtualizedList.ts +112 -0
- package/src/components/DndProvider.tsx +1836 -0
- package/src/components/DragHandle.tsx +89 -0
- package/src/components/GridController.tsx +213 -0
- package/src/components/GridDragHandle.tsx +11 -0
- package/src/components/GridZoneView.tsx +57 -0
- package/src/components/SortableGrid.tsx +449 -0
- package/src/components/SortableList.tsx +1028 -0
- package/src/components/dndTypes.ts +102 -0
- package/src/components/gridTypes.ts +90 -0
- package/src/components/handleViewProps.ts +35 -0
- package/src/contracts.ts +149 -0
- package/src/controller/approval.ts +179 -0
- package/src/controller/coordinatorBase.ts +118 -0
- package/src/controller/dndSession.ts +1058 -0
- package/src/controller/dndState.ts +198 -0
- package/src/controller/gridAdapter.ts +268 -0
- package/src/controller/gridState.ts +187 -0
- package/src/controller/locations.ts +67 -0
- package/src/controller/previewPath.ts +90 -0
- package/src/controller/sessionCore.ts +390 -0
- package/src/controller/useDndState.ts +26 -0
- package/src/controller/useGridState.ts +22 -0
- package/src/controller/useStoreState.ts +27 -0
- package/src/defaults.ts +4 -0
- package/src/diagnostics.ts +23 -0
- package/src/engine/gridPlacements.ts +85 -0
- package/src/engine/index.ts +99 -0
- package/src/engine/layout.ts +29 -0
- package/src/engine/layoutMove.ts +401 -0
- package/src/engine/layoutState.ts +490 -0
- package/src/engine/list.ts +307 -0
- package/src/engine/listIndex.ts +174 -0
- package/src/engine/move.ts +274 -0
- package/src/engine/movementPolicy.ts +151 -0
- package/src/engine/orderedTarget.ts +131 -0
- package/src/engine/pack.ts +24 -0
- package/src/engine/rectangles.ts +18 -0
- package/src/engine/sameLayout.ts +50 -0
- package/src/engine/search.ts +174 -0
- package/src/engine/spatial.ts +36 -0
- package/src/engine/spatialAppRotation.ts +53 -0
- package/src/engine/spatialExchange.ts +212 -0
- package/src/engine/spatialInsertion.ts +157 -0
- package/src/engine/spatialPolicy.ts +48 -0
- package/src/engine/spatialRotation.ts +56 -0
- package/src/engine/validation.ts +358 -0
- package/src/gesture/DndGestureGroup.tsx +357 -0
- package/src/gesture/DndGestureHost.tsx +50 -0
- package/src/gesture/gestureConfig.ts +123 -0
- package/src/gesture/gestureGrouping.ts +210 -0
- package/src/gesture/gestureHost.tsx +163 -0
- package/src/index.ts +162 -0
- package/src/pointer/dndTarget.ts +213 -0
- package/src/pointer/geometry.ts +584 -0
- package/src/pointer/layoutTarget.ts +515 -0
- package/src/pointer/listCellMotion.ts +174 -0
- package/src/pointer/listTarget.ts +56 -0
- package/src/pointer/motion.ts +66 -0
- package/src/runtime/dndPresentation.ts +113 -0
- package/src/runtime/dndRenderStore.ts +246 -0
- package/src/runtime/dndRuntime.ts +311 -0
- package/src/runtime/dndSettlement.ts +117 -0
- package/src/runtime/gridRuntime.ts +73 -0
- package/src/types.ts +189 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LayoutRect,
|
|
3
|
+
LayoutValidationIssue,
|
|
4
|
+
ListLayoutZone,
|
|
5
|
+
} from '../contracts';
|
|
6
|
+
|
|
7
|
+
import { isRecord } from './validation';
|
|
8
|
+
|
|
9
|
+
export type ListMeasurementContext = {
|
|
10
|
+
orientation: ListLayoutZone['orientation'];
|
|
11
|
+
/** Available width for vertical lists, or height for horizontal lists. */
|
|
12
|
+
crossSize: number;
|
|
13
|
+
/** Advance after content/font changes that invalidate measured item sizes. */
|
|
14
|
+
epoch?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CachedSize = {
|
|
18
|
+
size: number;
|
|
19
|
+
context: ListMeasurementContext;
|
|
20
|
+
measurementRevision: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function isNonnegative(value: unknown): value is number {
|
|
24
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isRevision(value: unknown): value is number {
|
|
28
|
+
return isNonnegative(value) && Number.isSafeInteger(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isContext(context: ListMeasurementContext): boolean {
|
|
32
|
+
return (
|
|
33
|
+
isRecord(context) &&
|
|
34
|
+
(context.orientation === 'vertical' ||
|
|
35
|
+
context.orientation === 'horizontal') &&
|
|
36
|
+
isNonnegative(context.crossSize) &&
|
|
37
|
+
isRevision(context.epoch ?? 0)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sameContext(a: ListMeasurementContext, b: ListMeasurementContext) {
|
|
42
|
+
return (
|
|
43
|
+
a.orientation === b.orientation &&
|
|
44
|
+
a.crossSize === b.crossSize &&
|
|
45
|
+
(a.epoch ?? 0) === (b.epoch ?? 0)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sizes belong to IDs, never array positions. A measurement is reusable after
|
|
51
|
+
* reordering, but only with matching orientation, cross-axis size and epoch.
|
|
52
|
+
* The measurement revision prevents delayed events from replacing newer sizes.
|
|
53
|
+
*/
|
|
54
|
+
export class ListSizeCache {
|
|
55
|
+
private sizes = new Map<string, CachedSize>();
|
|
56
|
+
|
|
57
|
+
get(itemId: string, context: ListMeasurementContext): number | undefined {
|
|
58
|
+
if (typeof itemId !== 'string' || !isContext(context)) return undefined;
|
|
59
|
+
const cached = this.sizes.get(itemId);
|
|
60
|
+
return cached && sameContext(cached.context, context)
|
|
61
|
+
? cached.size
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set(
|
|
66
|
+
itemId: string,
|
|
67
|
+
size: number,
|
|
68
|
+
context: ListMeasurementContext,
|
|
69
|
+
measurementRevision = 0,
|
|
70
|
+
): boolean {
|
|
71
|
+
if (
|
|
72
|
+
typeof itemId !== 'string' ||
|
|
73
|
+
!isNonnegative(size) ||
|
|
74
|
+
!isContext(context) ||
|
|
75
|
+
!isRevision(measurementRevision)
|
|
76
|
+
) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const cached = this.sizes.get(itemId);
|
|
80
|
+
if (
|
|
81
|
+
cached &&
|
|
82
|
+
((context.epoch ?? 0) < (cached.context.epoch ?? 0) ||
|
|
83
|
+
((context.epoch ?? 0) === (cached.context.epoch ?? 0) &&
|
|
84
|
+
measurementRevision < cached.measurementRevision))
|
|
85
|
+
) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
this.sizes.set(itemId, {
|
|
89
|
+
size,
|
|
90
|
+
context: { ...context },
|
|
91
|
+
measurementRevision,
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
prune(itemIds: readonly string[]): void {
|
|
97
|
+
const retained = new Set(itemIds);
|
|
98
|
+
for (const itemId of this.sizes.keys()) {
|
|
99
|
+
if (!retained.has(itemId)) this.sizes.delete(itemId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
clear(): void {
|
|
104
|
+
this.sizes.clear();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type ListLayoutEntry = {
|
|
109
|
+
itemId: string;
|
|
110
|
+
index: number;
|
|
111
|
+
/** Main-axis content coordinate, including start padding. */
|
|
112
|
+
start: number;
|
|
113
|
+
size: number;
|
|
114
|
+
source: 'measured' | 'estimated';
|
|
115
|
+
rect: LayoutRect;
|
|
116
|
+
/** Rect coordinates belong to this layout revision, unlike reusable sizes. */
|
|
117
|
+
revision: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type ListLayout = {
|
|
121
|
+
orientation: ListLayoutZone['orientation'];
|
|
122
|
+
crossSize: number;
|
|
123
|
+
revision: number;
|
|
124
|
+
entries: ListLayoutEntry[];
|
|
125
|
+
totalSize: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type ListLayoutInput = {
|
|
129
|
+
itemIds: readonly string[];
|
|
130
|
+
orientation: ListLayoutZone['orientation'];
|
|
131
|
+
crossSize: number;
|
|
132
|
+
estimatedItemSize: number;
|
|
133
|
+
/** Authoritative size used by fixed-size or caller-sized item rendering. */
|
|
134
|
+
itemSize?: number | ((itemId: string, index: number) => number);
|
|
135
|
+
cache?: ListSizeCache;
|
|
136
|
+
measurementEpoch?: number;
|
|
137
|
+
gap?: number;
|
|
138
|
+
paddingStart?: number;
|
|
139
|
+
paddingEnd?: number;
|
|
140
|
+
revision: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type ListLayoutResult =
|
|
144
|
+
| ({ valid: true } & ListLayout)
|
|
145
|
+
| { valid: false; issues: LayoutValidationIssue[] };
|
|
146
|
+
|
|
147
|
+
function invalid(
|
|
148
|
+
code: LayoutValidationIssue['code'],
|
|
149
|
+
message: string,
|
|
150
|
+
itemId?: string,
|
|
151
|
+
): ListLayoutResult {
|
|
152
|
+
return {
|
|
153
|
+
valid: false,
|
|
154
|
+
issues: [{ code, message, ...(itemId === undefined ? {} : { itemId }) }],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Compute content rects from one order snapshot without modifying the cache. */
|
|
159
|
+
export function computeListLayout(input: ListLayoutInput): ListLayoutResult {
|
|
160
|
+
if (!isRecord(input) || !Array.isArray(input.itemIds)) {
|
|
161
|
+
return invalid('invalid-structure', 'List item IDs must be an array.');
|
|
162
|
+
}
|
|
163
|
+
if (input.orientation !== 'vertical' && input.orientation !== 'horizontal') {
|
|
164
|
+
return invalid(
|
|
165
|
+
'invalid-orientation',
|
|
166
|
+
'List orientation must be vertical or horizontal.',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (!isRevision(input.revision)) {
|
|
170
|
+
return invalid(
|
|
171
|
+
'invalid-revision',
|
|
172
|
+
'Revision must be a nonnegative safe integer.',
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
!isNonnegative(input.crossSize) ||
|
|
177
|
+
!isNonnegative(input.estimatedItemSize) ||
|
|
178
|
+
!isNonnegative(input.gap ?? 0) ||
|
|
179
|
+
!isNonnegative(input.paddingStart ?? 0) ||
|
|
180
|
+
!isNonnegative(input.paddingEnd ?? 0) ||
|
|
181
|
+
!isRevision(input.measurementEpoch ?? 0) ||
|
|
182
|
+
(input.itemSize !== undefined &&
|
|
183
|
+
typeof input.itemSize !== 'function' &&
|
|
184
|
+
!isNonnegative(input.itemSize)) ||
|
|
185
|
+
(input.cache !== undefined && !(input.cache instanceof ListSizeCache))
|
|
186
|
+
) {
|
|
187
|
+
return invalid(
|
|
188
|
+
'invalid-configuration',
|
|
189
|
+
'List sizes, gap and padding must be finite and nonnegative; the cache and measurement epoch must be valid.',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const knownIds = new Set<string>();
|
|
193
|
+
for (const itemId of input.itemIds) {
|
|
194
|
+
if (typeof itemId !== 'string') {
|
|
195
|
+
return invalid(
|
|
196
|
+
'invalid-structure',
|
|
197
|
+
'Every list item ID must be a string.',
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (knownIds.has(itemId)) {
|
|
201
|
+
return invalid(
|
|
202
|
+
'duplicate-reference',
|
|
203
|
+
'List item IDs must be unique.',
|
|
204
|
+
itemId,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
knownIds.add(itemId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const context: ListMeasurementContext = {
|
|
211
|
+
orientation: input.orientation,
|
|
212
|
+
crossSize: input.crossSize,
|
|
213
|
+
epoch: input.measurementEpoch ?? 0,
|
|
214
|
+
};
|
|
215
|
+
const entries: ListLayoutEntry[] = [];
|
|
216
|
+
let start = input.paddingStart ?? 0;
|
|
217
|
+
for (let index = 0; index < input.itemIds.length; index += 1) {
|
|
218
|
+
const itemId = input.itemIds[index];
|
|
219
|
+
const measured = input.cache?.get(itemId, context);
|
|
220
|
+
let declared: number | undefined;
|
|
221
|
+
try {
|
|
222
|
+
declared =
|
|
223
|
+
typeof input.itemSize === 'function'
|
|
224
|
+
? input.itemSize(itemId, index)
|
|
225
|
+
: input.itemSize;
|
|
226
|
+
} catch {
|
|
227
|
+
return invalid(
|
|
228
|
+
'invalid-configuration',
|
|
229
|
+
'The item size callback failed.',
|
|
230
|
+
itemId,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (input.itemSize !== undefined && !isNonnegative(declared)) {
|
|
234
|
+
return invalid(
|
|
235
|
+
'invalid-configuration',
|
|
236
|
+
'Every item size must be finite and nonnegative.',
|
|
237
|
+
itemId,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const size = declared ?? measured ?? input.estimatedItemSize;
|
|
241
|
+
const source = measured === size ? 'measured' : 'estimated';
|
|
242
|
+
const rect: LayoutRect =
|
|
243
|
+
input.orientation === 'vertical'
|
|
244
|
+
? { x: 0, y: start, width: input.crossSize, height: size }
|
|
245
|
+
: { x: start, y: 0, width: size, height: input.crossSize };
|
|
246
|
+
entries.push({
|
|
247
|
+
itemId,
|
|
248
|
+
index,
|
|
249
|
+
start,
|
|
250
|
+
size,
|
|
251
|
+
source,
|
|
252
|
+
rect,
|
|
253
|
+
revision: input.revision,
|
|
254
|
+
});
|
|
255
|
+
start += size + (index < input.itemIds.length - 1 ? (input.gap ?? 0) : 0);
|
|
256
|
+
if (!Number.isFinite(start)) {
|
|
257
|
+
return invalid(
|
|
258
|
+
'invalid-configuration',
|
|
259
|
+
'List content size must remain finite.',
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const totalSize = start + (input.paddingEnd ?? 0);
|
|
264
|
+
if (!Number.isFinite(totalSize)) {
|
|
265
|
+
return invalid(
|
|
266
|
+
'invalid-configuration',
|
|
267
|
+
'List content size must remain finite.',
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
valid: true,
|
|
272
|
+
orientation: input.orientation,
|
|
273
|
+
crossSize: input.crossSize,
|
|
274
|
+
revision: input.revision,
|
|
275
|
+
entries,
|
|
276
|
+
totalSize,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Return an insertion index in the order AFTER removing the active item.
|
|
282
|
+
* Thresholds are the original non-active item centers, including the active
|
|
283
|
+
* footprint and all original gaps. Never measure a compressed preview here.
|
|
284
|
+
* An ID absent from the target list is allowed for cross-list insertion.
|
|
285
|
+
*/
|
|
286
|
+
export function computeListIndex(
|
|
287
|
+
layout: ListLayout,
|
|
288
|
+
activeItemId: string | null,
|
|
289
|
+
center: number,
|
|
290
|
+
): number | null {
|
|
291
|
+
if (!Number.isFinite(center)) return null;
|
|
292
|
+
const activeIndex = layout.entries.findIndex(
|
|
293
|
+
entry => entry.itemId === activeItemId,
|
|
294
|
+
);
|
|
295
|
+
let index = 0;
|
|
296
|
+
for (const entry of layout.entries) {
|
|
297
|
+
if (entry.itemId === activeItemId) continue;
|
|
298
|
+
const threshold = entry.start + entry.size / 2;
|
|
299
|
+
if (
|
|
300
|
+
center > threshold ||
|
|
301
|
+
(center === threshold && entry.index < activeIndex)
|
|
302
|
+
) {
|
|
303
|
+
index += 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return index;
|
|
307
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { ListLayout } from './list';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A detached snapshot shared with UI worklets: rebuild it whenever the layout
|
|
5
|
+
* geometry changes. Numeric columns are typed arrays so each crosses runtimes
|
|
6
|
+
* as one buffer copy, and unchanged columns keep their identity so the runtime
|
|
7
|
+
* serialization cache can reuse them.
|
|
8
|
+
*/
|
|
9
|
+
export type ListTargetIndex = {
|
|
10
|
+
readonly revision: number;
|
|
11
|
+
/** Boundary centers in ascending order. */
|
|
12
|
+
readonly thresholds: Float64Array;
|
|
13
|
+
/** `entry.index` of each boundary; a NaN index sorts last as Infinity. */
|
|
14
|
+
readonly entryIndices: Float64Array;
|
|
15
|
+
/** Prefixed item ID → slot in `positions` and `ranks`. */
|
|
16
|
+
readonly slots: Readonly<Record<string, number>>;
|
|
17
|
+
/** Position of the first matching ID in the original layout, by slot. */
|
|
18
|
+
readonly positions: Int32Array;
|
|
19
|
+
/** Boundary rank of the first matching ID, by slot; -1 without a boundary. */
|
|
20
|
+
readonly ranks: Int32Array;
|
|
21
|
+
/** Further boundary ranks of caller-edited duplicate IDs, by prefixed key. */
|
|
22
|
+
readonly additionalRanks?: Readonly<Record<string, readonly number[]>>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Boundary = { threshold: number; entryIndex: number; slot: number };
|
|
26
|
+
|
|
27
|
+
function compareBoundaries(a: Boundary, b: Boundary): number {
|
|
28
|
+
if (a.threshold < b.threshold) return -1;
|
|
29
|
+
if (a.threshold > b.threshold) return 1;
|
|
30
|
+
if (a.entryIndex < b.entryIndex) return -1;
|
|
31
|
+
if (a.entryIndex > b.entryIndex) return 1;
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sameSlots(
|
|
36
|
+
slots: Readonly<Record<string, number>>,
|
|
37
|
+
keys: readonly string[],
|
|
38
|
+
): boolean {
|
|
39
|
+
return (
|
|
40
|
+
Object.keys(slots).length === keys.length &&
|
|
41
|
+
keys.every((key, slot) => slots[key] === slot)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sameColumn(
|
|
46
|
+
previous: Float64Array | Int32Array,
|
|
47
|
+
next: Float64Array | Int32Array,
|
|
48
|
+
): boolean {
|
|
49
|
+
if (previous.length !== next.length) return false;
|
|
50
|
+
for (let index = 0; index < next.length; index += 1)
|
|
51
|
+
if (previous[index] !== next[index]) return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prepare once on the JS thread, then share this plain data with UI worklets.
|
|
57
|
+
* Original centers retain the active item's footprint; only the returned
|
|
58
|
+
* insertion index excludes it. No references to the mutable layout are kept.
|
|
59
|
+
* Passing the previous index of the same zone reuses its unchanged columns.
|
|
60
|
+
*/
|
|
61
|
+
export function createListTargetIndex(
|
|
62
|
+
layout: ListLayout,
|
|
63
|
+
previous?: ListTargetIndex,
|
|
64
|
+
): ListTargetIndex {
|
|
65
|
+
const boundaries: Boundary[] = [];
|
|
66
|
+
const slotByKey = new Map<string, number>();
|
|
67
|
+
const keys: string[] = [];
|
|
68
|
+
const positionList: number[] = [];
|
|
69
|
+
let ordered = true;
|
|
70
|
+
for (let position = 0; position < layout.entries.length; position += 1) {
|
|
71
|
+
const entry = layout.entries[position];
|
|
72
|
+
// Prefixing protects arbitrary item IDs such as "__proto__" on both runtimes.
|
|
73
|
+
const itemKey = `$${entry.itemId}`;
|
|
74
|
+
let slot = slotByKey.get(itemKey);
|
|
75
|
+
if (slot === undefined) {
|
|
76
|
+
slot = keys.length;
|
|
77
|
+
slotByKey.set(itemKey, slot);
|
|
78
|
+
keys.push(itemKey);
|
|
79
|
+
positionList.push(position);
|
|
80
|
+
}
|
|
81
|
+
const threshold = entry.start + entry.size / 2;
|
|
82
|
+
// NaN thresholds never pass the original comparison for any center.
|
|
83
|
+
if (Number.isNaN(threshold)) continue;
|
|
84
|
+
const boundary = {
|
|
85
|
+
threshold,
|
|
86
|
+
entryIndex: Number.isNaN(entry.index) ? Infinity : entry.index,
|
|
87
|
+
slot,
|
|
88
|
+
};
|
|
89
|
+
const last = boundaries[boundaries.length - 1];
|
|
90
|
+
if (last && compareBoundaries(last, boundary) > 0) ordered = false;
|
|
91
|
+
boundaries.push(boundary);
|
|
92
|
+
}
|
|
93
|
+
// Layouts produced by computeListLayout are already ordered: O(n) preparation.
|
|
94
|
+
// Sorting also preserves the public scan's meaning for caller-edited layouts.
|
|
95
|
+
if (!ordered) boundaries.sort(compareBoundaries);
|
|
96
|
+
const thresholds = new Float64Array(boundaries.length);
|
|
97
|
+
const entryIndices = new Float64Array(boundaries.length);
|
|
98
|
+
const ranks = new Int32Array(keys.length).fill(-1);
|
|
99
|
+
let additionalRanks: Record<string, number[]> | undefined;
|
|
100
|
+
for (let rank = 0; rank < boundaries.length; rank += 1) {
|
|
101
|
+
const boundary = boundaries[rank];
|
|
102
|
+
thresholds[rank] = boundary.threshold;
|
|
103
|
+
entryIndices[rank] = boundary.entryIndex;
|
|
104
|
+
if (ranks[boundary.slot] < 0) ranks[boundary.slot] = rank;
|
|
105
|
+
else ((additionalRanks ??= {})[keys[boundary.slot]] ??= []).push(rank);
|
|
106
|
+
}
|
|
107
|
+
const positions = Int32Array.from(positionList);
|
|
108
|
+
const reusable =
|
|
109
|
+
previous?.revision === layout.revision ? previous : undefined;
|
|
110
|
+
return {
|
|
111
|
+
revision: layout.revision,
|
|
112
|
+
thresholds,
|
|
113
|
+
entryIndices:
|
|
114
|
+
reusable && sameColumn(reusable.entryIndices, entryIndices)
|
|
115
|
+
? reusable.entryIndices
|
|
116
|
+
: entryIndices,
|
|
117
|
+
slots:
|
|
118
|
+
reusable && sameSlots(reusable.slots, keys)
|
|
119
|
+
? reusable.slots
|
|
120
|
+
: Object.fromEntries(keys.map((key, slot) => [key, slot])),
|
|
121
|
+
positions:
|
|
122
|
+
reusable && sameColumn(reusable.positions, positions)
|
|
123
|
+
? reusable.positions
|
|
124
|
+
: positions,
|
|
125
|
+
ranks:
|
|
126
|
+
reusable && sameColumn(reusable.ranks, ranks) ? reusable.ranks : ranks,
|
|
127
|
+
...(additionalRanks ? { additionalRanks } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Find an insertion index in O(log n), without allocation or layout scanning. */
|
|
132
|
+
export function findListTargetIndex(
|
|
133
|
+
index: ListTargetIndex,
|
|
134
|
+
activeItemId: string | null,
|
|
135
|
+
center: number,
|
|
136
|
+
): number | null {
|
|
137
|
+
'worklet';
|
|
138
|
+
if (!Number.isFinite(center)) return null;
|
|
139
|
+
const activeKey = activeItemId === null ? null : `$${activeItemId}`;
|
|
140
|
+
const slot = activeKey === null ? undefined : index.slots[activeKey];
|
|
141
|
+
const activePosition = slot === undefined ? -1 : index.positions[slot];
|
|
142
|
+
const activeRank = slot === undefined ? -1 : index.ranks[slot];
|
|
143
|
+
let low = 0;
|
|
144
|
+
let high = index.thresholds.length;
|
|
145
|
+
while (low < high) {
|
|
146
|
+
const middle = Math.floor((low + high) / 2);
|
|
147
|
+
const threshold = index.thresholds[middle];
|
|
148
|
+
if (
|
|
149
|
+
center > threshold ||
|
|
150
|
+
(center === threshold && index.entryIndices[middle] < activePosition)
|
|
151
|
+
) {
|
|
152
|
+
low = middle + 1;
|
|
153
|
+
} else {
|
|
154
|
+
high = middle;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let result = low;
|
|
158
|
+
if (activeRank >= 0 && activeRank < low) result -= 1;
|
|
159
|
+
// Valid layouts have unique IDs. Preserve scan semantics for caller-edited
|
|
160
|
+
// duplicate IDs too, without making the normal lookup linear.
|
|
161
|
+
const additionalRanks =
|
|
162
|
+
activeKey === null ? undefined : index.additionalRanks?.[activeKey];
|
|
163
|
+
if (additionalRanks) {
|
|
164
|
+
let first = 0;
|
|
165
|
+
let last = additionalRanks.length;
|
|
166
|
+
while (first < last) {
|
|
167
|
+
const middle = Math.floor((first + last) / 2);
|
|
168
|
+
if (additionalRanks[middle] < low) first = middle + 1;
|
|
169
|
+
else last = middle;
|
|
170
|
+
}
|
|
171
|
+
result -= first;
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|