feat: implement geometry binding functionality within the map interaction engine

This commit is contained in:
taDuc
2026-05-21 18:39:50 +07:00
parent 7e025fb449
commit 8f0e912d9e
4 changed files with 147 additions and 54 deletions
+7 -1
View File
@@ -57,6 +57,7 @@ type MapProps = {
focusPadding?: number | import("maplibre-gl").PaddingOptions;
imageOverlay?: MapImageOverlay | null;
onImageOverlayChange?: (overlay: MapImageOverlay) => void;
onBindGeometries?: (targetId: string | number, sourceIds: (string | number)[]) => void;
};
const Map = forwardRef<MapHandle, MapProps>(function Map({
@@ -85,6 +86,7 @@ const Map = forwardRef<MapHandle, MapProps>(function Map({
focusPadding,
imageOverlay = null,
onImageOverlayChange,
onBindGeometries,
}, ref) {
// Ref giữ mode mới nhất cho MapLibre handlers được register một lần.
const modeRef = useRef<MapProps["mode"]>(mode);
@@ -108,7 +110,9 @@ const Map = forwardRef<MapHandle, MapProps>(function Map({
const imageOverlayRef = useRef<MapImageOverlay | null>(imageOverlay);
// Ref callback update overlay mới nhất để interaction không stale.
const onImageOverlayChangeRef = useRef<MapProps["onImageOverlayChange"]>(onImageOverlayChange);
// Ref callback bind geometry mới nhất để interaction không stale.
const onBindGeometriesRef = useRef<MapProps["onBindGeometries"]>(onBindGeometries);
useEffect(() => { modeRef.current = mode; }, [mode]);
useEffect(() => { draftRef.current = draft; }, [draft]);
useEffect(() => { onSelectFeatureIdsRef.current = onSelectFeatureIds; }, [onSelectFeatureIds]);
@@ -120,6 +124,7 @@ const Map = forwardRef<MapHandle, MapProps>(function Map({
useEffect(() => { onUpdateRef.current = onUpdateFeature; }, [onUpdateFeature]);
useEffect(() => { imageOverlayRef.current = imageOverlay; }, [imageOverlay]);
useEffect(() => { onImageOverlayChangeRef.current = onImageOverlayChange; }, [onImageOverlayChange]);
useEffect(() => { onBindGeometriesRef.current = onBindGeometries; }, [onBindGeometries]);
// Hook sở hữu lifecycle MapLibre instance và các control camera/projection.
const {
@@ -164,6 +169,7 @@ const Map = forwardRef<MapHandle, MapProps>(function Map({
onHideRef,
onUpdateRef,
onHoverFeatureChangeRef,
onBindGeometriesRef,
});
// Hook đồng bộ draft/layer/filter/highlight từ React state xuống MapLibre source/layer.
+12 -1
View File
@@ -16,6 +16,7 @@ type EngineBinding = {
cleanup: () => void;
cancel?: () => void;
clearSelection?: (skipNotify?: boolean) => void;
syncSelection?: (ids: (string | number)[]) => void;
};
type UseMapInteractionProps = {
@@ -32,6 +33,7 @@ type UseMapInteractionProps = {
onHideRef: React.MutableRefObject<((id: string | number) => void) | undefined>;
onUpdateRef: React.MutableRefObject<((id: string | number, geometry: Geometry) => void) | undefined>;
onHoverFeatureChangeRef: React.MutableRefObject<((payload: MapHoverPayload | null) => void) | undefined>;
onBindGeometriesRef?: React.MutableRefObject<((targetId: string | number, sourceIds: (string | number)[]) => void) | undefined>;
};
export function useMapInteraction({
@@ -48,6 +50,7 @@ export function useMapInteraction({
onHideRef,
onUpdateRef,
onHoverFeatureChangeRef,
onBindGeometriesRef,
}: UseMapInteractionProps) {
const editingEngineRef = useRef<ReturnType<typeof createEditingEngine> | null>(null);
const engineBindingsRef = useRef<Partial<Record<EditorMode, EngineBinding>>>({});
@@ -72,6 +75,13 @@ export function useMapInteraction({
}
}, [mode, selectedFeatureIds]);
useEffect(() => {
const selectEngine = engineBindingsRef.current.select;
if (selectEngine?.syncSelection) {
selectEngine.syncSelection(selectedFeatureIds);
}
}, [selectedFeatureIds]);
useEffect(() => {
const previousMode = previousModeRef.current;
if (previousMode !== mode) {
@@ -170,7 +180,8 @@ export function useMapInteraction({
: undefined,
(ids) => onSelectFeatureIdsRef.current?.(ids),
(id: string | number) => onSetModeRef.current?.("replay", id),
() => Boolean(editingEngineRef.current?.editingRef.current)
() => Boolean(editingEngineRef.current?.editingRef.current),
(targetId, sourceIds) => onBindGeometriesRef?.current?.(targetId, sourceIds)
);
const cleanupPoint = initPoint(