Skip to content

Commit

Permalink
feat(map): 支持事件绑定
Browse files Browse the repository at this point in the history
  • Loading branch information
wangxingkang committed Aug 30, 2022
1 parent f47641d commit 9847dfc
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 21 deletions.
110 changes: 110 additions & 0 deletions src/hooks/useEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useRef, useEffect } from 'react';
import reduce from 'lodash/reduce';
import { useDeepCompareEffect, useUnmount } from '@pansy/react-hooks';
import isEqual from 'lodash/isEqual';
import { isFunction } from '@pansy/shared';

import type { SyntheticEvent } from 'react';

export type Instance = {
on(type: string, handle: (...args: any[]) => void): void;
off(type: string, handle: (...args: any[]) => void): void;
};

export type Listeners<Events extends Record<string, string>> = {
[T in keyof Events]: (evt: SyntheticEvent<any>) => void;
};

const getPropsEvents = (props: Record<string, any> = {}) => {
return reduce(
props,
(result, value, key) => {
if (isFunction(value) && /^on[A-Z]/.test(key)) {
// @ts-ignore
result[key] = value;
}

return result;
},
{},
);
};

export const useEvents = <Ins extends Instance, Events extends Record<string, string>>(
ins: Ins,
events: Events,
props: Record<string, any> = {},
) => {
const listeners = useRef<Partial<Listeners<Events>>>({});

const propsEvents = getPropsEvents(props);

useEffect(() => {
if (ins) {
listeners.current = listenEvents(events, props, ins);
}
}, [ins]);

useDeepCompareEffect(() => {
if (ins) {
listeners.current = updateEvents(listeners.current, propsEvents, ins);
}
}, [propsEvents]);

useUnmount(() => {
unlistenEvents(listeners.current, ins);
listeners.current = {};
});

const listenEvents = (
partialEvents: Record<string, string> = {},
props: Record<string, any> = {},
ins: Ins,
) =>
Object.keys(partialEvents).reduce((listeners, event) => {
const propEvent = props[event];

if (propEvent) {
ins.on(partialEvents[event], propEvent);
}

return listeners;
}, {});

const updateEvents = (
listeners: Partial<Listeners<Events>> = {},
currentProps: Record<string, any> = {},
ins: Ins,
) => {
const toListenOff = Object.keys(events).filter(
(eventKey) =>
(listeners[eventKey] && typeof currentProps[eventKey] !== 'function') ||
!isEqual(listeners[eventKey], currentProps[eventKey]),
);

toListenOff.forEach((key) => {
if (listeners[key]) {
ins.off(events[key], listeners[key] as any);
delete listeners[key];
}
});

const toListenOn = Object.keys(events)
.filter((key) => !listeners[key] && typeof currentProps[key] === 'function')
// @ts-ignore
.reduce((acc, next) => ((acc[next] = events[next]), acc), {});

const newListeners = listenEvents(toListenOn, currentProps, ins);

return { ...listeners, ...newListeners };
};

const unlistenEvents = (listeners: Partial<Listeners<Events>> = {}, ins: Ins) => {
const toListenOff = Object.keys(events).filter((eventKey) => listeners[eventKey]);

toListenOff.forEach((key) => {
// @ts-ignore
ins.off(events[key], listeners[key]);
});
};
};
63 changes: 62 additions & 1 deletion src/map/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
import type { Map } from 'mapbox-gl';
import type { PropKey } from './types';
import type { PropKey, EventMapping } from './types';

export const mapEventMap: EventMapping = {
onError: 'error',

onLoad: 'load',
onIdle: 'idle',
onRemove: 'remove',
onRender: 'render',
onResize: 'resize',

onWebglContextLost: 'webglcontextlost',
onWebglContextRestored: 'webglcontextrestored',

onDataloading: 'dataloading',
onData: 'data',
onTileDataLoading: 'tiledataloading',
onSourceDataLoading: 'sourcedataloading',
onStyleDataLoading: 'styledataloading',
onSourceData: 'sourcedata',
onStyleData: 'styledata',

onBoxZoomCancel: 'boxzoomcancel',
onBoxZoomStart: 'boxzoomstart',
onBoxZoomEnd: 'boxzoomend',

onTouchCancel: 'touchcancel',
onTouchMove: 'touchmove',
onTouchEnd: 'touchend',
onTouchStart: 'touchstart',

onClick: 'click',
onContextMenu: 'contextmenu',
onDoubleClick: 'dblclick',
onMouseMove: 'mousemove',
onMouseUp: 'mouseup',
onMouseDown: 'mousedown',
onMouseOut: 'mouseout',
onMouseOver: 'mouseover',

onMoveStart: 'movestart',
onMove: 'move',
onMoveEnd: 'moveend',

onZoomStart: 'zoomstart',
onZoom: 'zoom',
onZoomEnd: 'zoomend',

onRotateStart: 'rotatestart',
onRotate: 'rotate',
onRotateEnd: 'rotateend',

onDragStart: 'dragstart',
onDrag: 'drag',
onDragEnd: 'dragend',

onPitchStart: 'pitchstart',
onPitch: 'pitch',
onPitchEnd: 'pitchend',

onWheel: 'wheel',
};

/** 静态属性 */
export const StaticProps: PropKey[] = [
Expand Down
6 changes: 5 additions & 1 deletion src/map/demos/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Map } from '@pansy/react-mapbox-gl';
export default () => {
return (
<div style={{ height: 500 }}>
<Map>
<Map
onClick={(e) => {
console.log(e);
}}
>
<span style={{ zIndex: 100 }}>{123}</span>
</Map>
</div>
Expand Down
19 changes: 6 additions & 13 deletions src/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { getTargetElement } from '@pansy/shared/react';
import MapboxWorker from 'mapbox-gl/dist/mapbox-gl-csp-worker';

import { MapContext } from '@/context';
import { useEvents } from '@/hooks/useEvents';

import { allProps, setterMap, converterMap } from './config';
import { allProps, mapEventMap } from './config';

import 'mapbox-gl/dist/mapbox-gl.css';

import type { FC } from 'react';
import type { MapboxOptions } from 'mapbox-gl';
import type { MapProps } from './types';
import type { MapProps, EventMapping } from './types';

// @ts-ignore
Mapbox.workerClass = MapboxWorker;
Expand All @@ -22,6 +23,8 @@ export const Map: FC<MapProps> = (props) => {
const [ready, readyAction] = useBoolean(false);
const [map, setMap] = useState<Mapbox.Map>();

useEvents<Mapbox.Map, EventMapping>(map as Mapbox.Map, mapEventMap, props);

useEffect(() => {
const container = getTargetElement(containerRef);

Expand Down Expand Up @@ -53,23 +56,13 @@ export const Map: FC<MapProps> = (props) => {
allProps.forEach((key) => {
if (key in props) {
// @ts-ignore
options[key] = getSetterValue(key, props);
options[key] = props[key];
}
});

return options;
};

const getSetterValue = (key: string, props: MapProps) => {
if (key in converterMap) {
// @ts-ignore
return converterMap[key](props[key]);
}

// @ts-ignore
return props[key];
};

return (
<MapContext.Provider value={map}>
<div ref={containerRef} style={{ height: '100%', width: '100%' }}>
Expand Down
75 changes: 69 additions & 6 deletions src/map/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,76 @@
import type { ReactElement, CSSProperties } from 'react';
import type { MapboxOptions } from 'mapbox-gl';
import type { ReactElement } from 'react';
import type { MapboxOptions, MapEventType } from 'mapbox-gl';

export interface MapOptions extends MapboxOptions {}

export interface MapProps extends Omit<MapOptions, 'container'> {
className?: string;
children?: ReactElement;
}
export type MapEvents = {
onError: (e: MapEventType['error']) => void;

onLoad: (e: MapEventType['load']) => void;
onIdle: (e: MapEventType['idle']) => void;
onRemove: (e: MapEventType['remove']) => void;
onRender: (e: MapEventType['render']) => void;
onResize: (e: MapEventType['resize']) => void;

onWebglContextLost: (e: MapEventType['webglcontextlost']) => void;
onWebglContextRestored: (e: MapEventType['webglcontextrestored']) => void;

onDataloading: (e: MapEventType['dataloading']) => void;
onData: (e: MapEventType['data']) => void;
onTileDataLoading: (e: MapEventType['tiledataloading']) => void;
onSourceDataLoading: (e: MapEventType['sourcedataloading']) => void;
onStyleDataLoading: (e: MapEventType['styledataloading']) => void;
onSourceData: (e: MapEventType['sourcedata']) => void;
onStyleData: (e: MapEventType['styledata']) => void;

onBoxZoomCancel: (e: MapEventType['boxzoomcancel']) => void;
onBoxZoomStart: (e: MapEventType['boxzoomstart']) => void;
onBoxZoomEnd: (e: MapEventType['boxzoomend']) => void;

onTouchCancel: (e: MapEventType['touchcancel']) => void;
onTouchMove: (e: MapEventType['touchmove']) => void;
onTouchEnd: (e: MapEventType['touchend']) => void;
onTouchStart: (e: MapEventType['touchstart']) => void;

onClick: (e: MapEventType['click']) => void;
onContextMenu: (e: MapEventType['contextmenu']) => void;
onDoubleClick: (e: MapEventType['dblclick']) => void;
onMouseMove: (e: MapEventType['mousemove']) => void;
onMouseUp: (e: MapEventType['mouseup']) => void;
onMouseDown: (e: MapEventType['mousedown']) => void;
onMouseOut: (e: MapEventType['mouseout']) => void;
onMouseOver: (e: MapEventType['mouseover']) => void;

onMoveStart: (e: MapEventType['movestart']) => void;
onMove: (e: MapEventType['move']) => void;
onMoveEnd: (e: MapEventType['moveend']) => void;

onZoomStart: (e: MapEventType['zoomstart']) => void;
onZoom: (e: MapEventType['zoom']) => void;
onZoomEnd: (e: MapEventType['zoomend']) => void;

onRotateStart: (e: MapEventType['rotatestart']) => void;
onRotate: (e: MapEventType['rotate']) => void;
onRotateEnd: (e: MapEventType['rotateend']) => void;

onDragStart: (e: MapEventType['dragstart']) => void;
onDrag: (e: MapEventType['drag']) => void;
onDragEnd: (e: MapEventType['dragend']) => void;

onPitchStart: (e: MapEventType['pitchstart']) => void;
onPitch: (e: MapEventType['pitch']) => void;
onPitchEnd: (e: MapEventType['pitchend']) => void;

onWheel: (e: MapEventType['wheel']) => void;
};

export type EventMapping = { [T in keyof MapEvents]: string };

export type KeysOfUnion<T> = T extends T ? keyof T : never;

export type PropKey = KeysOfUnion<MapboxOptions>;

export interface MapProps extends Omit<MapOptions, 'container'>, Partial<MapEvents> {
className?: string;
children?: ReactElement;
}

0 comments on commit 9847dfc

Please sign in to comment.