gistda-sphere-react 1.0.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.
Files changed (41) hide show
  1. package/README.md +827 -0
  2. package/dist/index.d.mts +1081 -0
  3. package/dist/index.d.ts +1081 -0
  4. package/dist/index.js +2057 -0
  5. package/dist/index.mjs +2013 -0
  6. package/package.json +70 -0
  7. package/src/__tests__/Layer.test.tsx +133 -0
  8. package/src/__tests__/Marker.test.tsx +183 -0
  9. package/src/__tests__/SphereContext.test.tsx +120 -0
  10. package/src/__tests__/SphereMap.test.tsx +240 -0
  11. package/src/__tests__/geometry.test.tsx +454 -0
  12. package/src/__tests__/hooks.test.tsx +173 -0
  13. package/src/__tests__/setup.ts +204 -0
  14. package/src/__tests__/useMapControls.test.tsx +168 -0
  15. package/src/__tests__/useOverlays.test.tsx +265 -0
  16. package/src/__tests__/useRoute.test.tsx +219 -0
  17. package/src/__tests__/useSearch.test.tsx +205 -0
  18. package/src/__tests__/useTags.test.tsx +179 -0
  19. package/src/components/Circle.tsx +189 -0
  20. package/src/components/Dot.tsx +150 -0
  21. package/src/components/Layer.tsx +177 -0
  22. package/src/components/Marker.tsx +204 -0
  23. package/src/components/Polygon.tsx +223 -0
  24. package/src/components/Polyline.tsx +211 -0
  25. package/src/components/Popup.tsx +130 -0
  26. package/src/components/Rectangle.tsx +194 -0
  27. package/src/components/SphereMap.tsx +315 -0
  28. package/src/components/index.ts +18 -0
  29. package/src/context/MapContext.tsx +41 -0
  30. package/src/context/SphereContext.tsx +348 -0
  31. package/src/context/index.ts +15 -0
  32. package/src/hooks/index.ts +42 -0
  33. package/src/hooks/useMapEvent.ts +66 -0
  34. package/src/hooks/useOverlays.ts +278 -0
  35. package/src/hooks/useRoute.ts +232 -0
  36. package/src/hooks/useSearch.ts +143 -0
  37. package/src/hooks/useSphere.ts +18 -0
  38. package/src/hooks/useTags.ts +129 -0
  39. package/src/index.ts +124 -0
  40. package/src/types/index.ts +1 -0
  41. package/src/types/sphere.ts +671 -0
@@ -0,0 +1,204 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
+ import { useMapContext } from "../context/MapContext";
3
+ import { useSphereContext } from "../context/SphereContext";
4
+ import type {
5
+ Icon,
6
+ Location,
7
+ MarkerOptions,
8
+ PopupOptions,
9
+ Range,
10
+ SphereMarker,
11
+ } from "../types";
12
+
13
+ export interface MarkerProps {
14
+ position: Location;
15
+ icon?: Icon;
16
+ title?: string;
17
+ detail?: string;
18
+ popup?: PopupOptions;
19
+ visibleRange?: Range;
20
+ clickable?: boolean;
21
+ draggable?: boolean;
22
+ zIndex?: number;
23
+ rotate?: number;
24
+ onClick?: (marker: SphereMarker) => void;
25
+ onDrag?: (marker: SphereMarker) => void;
26
+ onDrop?: (marker: SphereMarker, location: Location) => void;
27
+ onHover?: (marker: SphereMarker) => void;
28
+ onLeave?: (marker: SphereMarker) => void;
29
+ }
30
+
31
+ export interface MarkerRef {
32
+ getMarker(): SphereMarker | null;
33
+ togglePopup(show?: boolean): void;
34
+ setPosition(location: Location, animate?: boolean): void;
35
+ setRotation(angle: number): void;
36
+ }
37
+
38
+ export const Marker = forwardRef<MarkerRef, MarkerProps>(function Marker(
39
+ {
40
+ position,
41
+ icon,
42
+ title,
43
+ detail,
44
+ popup,
45
+ visibleRange,
46
+ clickable,
47
+ draggable,
48
+ zIndex,
49
+ rotate,
50
+ onClick,
51
+ onDrag,
52
+ onDrop,
53
+ onHover,
54
+ onLeave,
55
+ },
56
+ ref
57
+ ) {
58
+ const { map, isReady } = useMapContext();
59
+ const { sphere } = useSphereContext();
60
+ const markerRef = useRef<SphereMarker | null>(null);
61
+ const callbacksRef = useRef({ onClick, onDrag, onDrop, onHover, onLeave });
62
+
63
+ useEffect(() => {
64
+ callbacksRef.current = { onClick, onDrag, onDrop, onHover, onLeave };
65
+ }, [onClick, onDrag, onDrop, onHover, onLeave]);
66
+
67
+ useEffect(() => {
68
+ if (!(isReady && map && sphere)) {
69
+ return;
70
+ }
71
+
72
+ const options: MarkerOptions = {};
73
+
74
+ if (icon) {
75
+ options.icon = icon;
76
+ }
77
+ if (title) {
78
+ options.title = title;
79
+ }
80
+ if (detail) {
81
+ options.detail = detail;
82
+ }
83
+ if (popup) {
84
+ options.popup = popup;
85
+ }
86
+ if (visibleRange) {
87
+ options.visibleRange = visibleRange;
88
+ }
89
+ if (typeof clickable === "boolean") {
90
+ options.clickable = clickable;
91
+ }
92
+ if (typeof draggable === "boolean") {
93
+ options.draggable = draggable;
94
+ }
95
+ if (typeof zIndex === "number") {
96
+ options.zIndex = zIndex;
97
+ }
98
+ if (typeof rotate === "number") {
99
+ options.rotate = rotate;
100
+ }
101
+
102
+ const marker = new sphere.Marker(position, options);
103
+ markerRef.current = marker;
104
+
105
+ map.Overlays.add(marker);
106
+
107
+ const handleOverlayClick = (data: {
108
+ overlay: SphereMarker;
109
+ location: Location;
110
+ }) => {
111
+ if (data.overlay === marker) {
112
+ callbacksRef.current.onClick?.(marker);
113
+ }
114
+ };
115
+
116
+ const handleOverlayDrag = (overlay: SphereMarker) => {
117
+ if (overlay === marker) {
118
+ callbacksRef.current.onDrag?.(marker);
119
+ }
120
+ };
121
+
122
+ const handleOverlayDrop = (overlay: SphereMarker) => {
123
+ if (overlay === marker) {
124
+ const newLocation = marker.location() as Location;
125
+ callbacksRef.current.onDrop?.(marker, newLocation);
126
+ }
127
+ };
128
+
129
+ const handleOverlayHover = (overlay: SphereMarker) => {
130
+ if (overlay === marker) {
131
+ callbacksRef.current.onHover?.(marker);
132
+ }
133
+ };
134
+
135
+ const handleOverlayLeave = (overlay: SphereMarker) => {
136
+ if (overlay === marker) {
137
+ callbacksRef.current.onLeave?.(marker);
138
+ }
139
+ };
140
+
141
+ map.Event.bind("overlayClick", handleOverlayClick);
142
+ map.Event.bind("overlayDrag", handleOverlayDrag);
143
+ map.Event.bind("overlayDrop", handleOverlayDrop);
144
+ map.Event.bind("overlayHover", handleOverlayHover);
145
+ map.Event.bind("overlayLeave", handleOverlayLeave);
146
+
147
+ return () => {
148
+ map.Event.unbind("overlayClick", handleOverlayClick);
149
+ map.Event.unbind("overlayDrag", handleOverlayDrag);
150
+ map.Event.unbind("overlayDrop", handleOverlayDrop);
151
+ map.Event.unbind("overlayHover", handleOverlayHover);
152
+ map.Event.unbind("overlayLeave", handleOverlayLeave);
153
+ map.Overlays.remove(marker);
154
+ markerRef.current = null;
155
+ };
156
+ }, [
157
+ isReady,
158
+ map,
159
+ sphere,
160
+ position,
161
+ icon,
162
+ title,
163
+ detail,
164
+ popup,
165
+ visibleRange,
166
+ clickable,
167
+ draggable,
168
+ zIndex,
169
+ rotate,
170
+ ]);
171
+
172
+ useEffect(() => {
173
+ if (markerRef.current && position) {
174
+ markerRef.current.location(position, false);
175
+ }
176
+ }, [position]);
177
+
178
+ useEffect(() => {
179
+ if (markerRef.current && typeof rotate === "number") {
180
+ markerRef.current.update({ rotate });
181
+ }
182
+ }, [rotate]);
183
+
184
+ useImperativeHandle(
185
+ ref,
186
+ () => ({
187
+ getMarker: () => markerRef.current,
188
+ togglePopup: (show?: boolean) => {
189
+ markerRef.current?.pop(show);
190
+ },
191
+ setPosition: (location: Location, animate = false) => {
192
+ markerRef.current?.location(location, animate);
193
+ },
194
+ setRotation: (angle: number) => {
195
+ markerRef.current?.update({ rotate: angle });
196
+ },
197
+ }),
198
+ []
199
+ );
200
+
201
+ return null;
202
+ });
203
+
204
+ export default Marker;
@@ -0,0 +1,223 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
+ import { useMapContext } from "../context/MapContext";
3
+ import { useSphereContext } from "../context/SphereContext";
4
+ import type {
5
+ Bound,
6
+ GeometryOptions,
7
+ LineStyleType,
8
+ Location,
9
+ MarkerOptions,
10
+ PopupOptions,
11
+ Range,
12
+ SpherePolygon,
13
+ } from "../types";
14
+
15
+ export interface PolygonProps {
16
+ positions: Location[];
17
+ title?: string;
18
+ detail?: string;
19
+ label?: string;
20
+ labelOptions?: MarkerOptions;
21
+ popup?: PopupOptions;
22
+ visibleRange?: Range;
23
+ lineWidth?: number;
24
+ lineColor?: string;
25
+ fillColor?: string;
26
+ lineStyle?: LineStyleType;
27
+ pivot?: Location;
28
+ clickable?: boolean;
29
+ draggable?: boolean;
30
+ pointer?: boolean;
31
+ zIndex?: number;
32
+ editable?: boolean;
33
+ onClick?: (polygon: SpherePolygon) => void;
34
+ onDrag?: (polygon: SpherePolygon) => void;
35
+ onDrop?: (polygon: SpherePolygon) => void;
36
+ }
37
+
38
+ export interface PolygonRef {
39
+ getPolygon(): SpherePolygon | null;
40
+ togglePopup(show?: boolean, location?: Location): void;
41
+ getPivot(): Location | null;
42
+ getCentroid(): Location | null;
43
+ getBound(): Bound | null;
44
+ getArea(language?: string): number | string | null;
45
+ rotate(angle: number): void;
46
+ updateStyle(options: Partial<GeometryOptions>): void;
47
+ toGeoJSON(): object | null;
48
+ }
49
+
50
+ export const Polygon = forwardRef<PolygonRef, PolygonProps>(function Polygon(
51
+ {
52
+ positions,
53
+ title,
54
+ detail,
55
+ label,
56
+ labelOptions,
57
+ popup,
58
+ visibleRange,
59
+ lineWidth,
60
+ lineColor,
61
+ fillColor,
62
+ lineStyle,
63
+ pivot,
64
+ clickable,
65
+ draggable,
66
+ pointer,
67
+ zIndex,
68
+ editable,
69
+ onClick,
70
+ onDrag,
71
+ onDrop,
72
+ },
73
+ ref,
74
+ ) {
75
+ const { map, isReady } = useMapContext();
76
+ const { sphere } = useSphereContext();
77
+ const polygonRef = useRef<SpherePolygon | null>(null);
78
+ const callbacksRef = useRef({ onClick, onDrag, onDrop });
79
+
80
+ useEffect(() => {
81
+ callbacksRef.current = { onClick, onDrag, onDrop };
82
+ }, [onClick, onDrag, onDrop]);
83
+
84
+ useEffect(() => {
85
+ if (!(isReady && map && sphere) || positions.length < 3) {
86
+ return;
87
+ }
88
+
89
+ const options: GeometryOptions = {};
90
+
91
+ if (title) {
92
+ options.title = title;
93
+ }
94
+ if (detail) {
95
+ options.detail = detail;
96
+ }
97
+ if (label) {
98
+ options.label = label;
99
+ }
100
+ if (labelOptions) {
101
+ options.labelOptions = labelOptions;
102
+ }
103
+ if (popup) {
104
+ options.popup = popup;
105
+ }
106
+ if (visibleRange) {
107
+ options.visibleRange = visibleRange;
108
+ }
109
+ if (typeof lineWidth === "number") {
110
+ options.lineWidth = lineWidth;
111
+ }
112
+ if (lineColor) {
113
+ options.lineColor = lineColor;
114
+ }
115
+ if (fillColor) {
116
+ options.fillColor = fillColor;
117
+ }
118
+ if (lineStyle) {
119
+ options.lineStyle = lineStyle;
120
+ }
121
+ if (pivot) {
122
+ options.pivot = pivot;
123
+ }
124
+ if (typeof clickable === "boolean") {
125
+ options.clickable = clickable;
126
+ }
127
+ if (typeof draggable === "boolean") {
128
+ options.draggable = draggable;
129
+ }
130
+ if (typeof pointer === "boolean") {
131
+ options.pointer = pointer;
132
+ }
133
+ if (typeof zIndex === "number") {
134
+ options.zIndex = zIndex;
135
+ }
136
+ if (typeof editable === "boolean") {
137
+ options.editable = editable;
138
+ }
139
+
140
+ const polygon = new sphere.Polygon(positions, options);
141
+ polygonRef.current = polygon;
142
+
143
+ map.Overlays.add(polygon);
144
+
145
+ const handleOverlayClick = (data: { overlay: SpherePolygon }) => {
146
+ if (data.overlay === polygon) {
147
+ callbacksRef.current.onClick?.(polygon);
148
+ }
149
+ };
150
+
151
+ const handleOverlayDrag = (overlay: SpherePolygon) => {
152
+ if (overlay === polygon) {
153
+ callbacksRef.current.onDrag?.(polygon);
154
+ }
155
+ };
156
+
157
+ const handleOverlayDrop = (overlay: SpherePolygon) => {
158
+ if (overlay === polygon) {
159
+ callbacksRef.current.onDrop?.(polygon);
160
+ }
161
+ };
162
+
163
+ map.Event.bind("overlayClick", handleOverlayClick);
164
+ map.Event.bind("overlayDrag", handleOverlayDrag);
165
+ map.Event.bind("overlayDrop", handleOverlayDrop);
166
+
167
+ return () => {
168
+ map.Event.unbind("overlayClick", handleOverlayClick);
169
+ map.Event.unbind("overlayDrag", handleOverlayDrag);
170
+ map.Event.unbind("overlayDrop", handleOverlayDrop);
171
+ map.Overlays.remove(polygon);
172
+ polygonRef.current = null;
173
+ };
174
+ }, [
175
+ isReady,
176
+ map,
177
+ sphere,
178
+ positions,
179
+ title,
180
+ detail,
181
+ label,
182
+ labelOptions,
183
+ popup,
184
+ visibleRange,
185
+ lineWidth,
186
+ lineColor,
187
+ fillColor,
188
+ lineStyle,
189
+ pivot,
190
+ clickable,
191
+ draggable,
192
+ pointer,
193
+ zIndex,
194
+ editable,
195
+ ]);
196
+
197
+ useImperativeHandle(
198
+ ref,
199
+ () => ({
200
+ getPolygon: () => polygonRef.current,
201
+ togglePopup: (show?: boolean, location?: Location) => {
202
+ polygonRef.current?.pop(show, location);
203
+ },
204
+ getPivot: () => polygonRef.current?.pivot() ?? null,
205
+ getCentroid: () => polygonRef.current?.centroid() ?? null,
206
+ getBound: () => polygonRef.current?.bound() ?? null,
207
+ getArea: (language?: string) =>
208
+ polygonRef.current?.size(language) ?? null,
209
+ rotate: (angle: number) => {
210
+ polygonRef.current?.rotate(angle);
211
+ },
212
+ updateStyle: (options: Partial<GeometryOptions>) => {
213
+ polygonRef.current?.update(options);
214
+ },
215
+ toGeoJSON: () => polygonRef.current?.toJSON() ?? null,
216
+ }),
217
+ [],
218
+ );
219
+
220
+ return null;
221
+ });
222
+
223
+ export default Polygon;
@@ -0,0 +1,211 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
+ import { useMapContext } from "../context/MapContext";
3
+ import { useSphereContext } from "../context/SphereContext";
4
+ import type {
5
+ Bound,
6
+ GeometryOptions,
7
+ LineStyleType,
8
+ Location,
9
+ MarkerOptions,
10
+ PopupOptions,
11
+ Range,
12
+ SpherePolyline,
13
+ } from "../types";
14
+
15
+ export interface PolylineProps {
16
+ positions: Location[];
17
+ title?: string;
18
+ detail?: string;
19
+ label?: string;
20
+ labelOptions?: MarkerOptions;
21
+ popup?: PopupOptions;
22
+ visibleRange?: Range;
23
+ lineWidth?: number;
24
+ lineColor?: string;
25
+ lineStyle?: LineStyleType;
26
+ pivot?: Location;
27
+ clickable?: boolean;
28
+ draggable?: boolean;
29
+ pointer?: boolean;
30
+ zIndex?: number;
31
+ onClick?: (polyline: SpherePolyline) => void;
32
+ onDrag?: (polyline: SpherePolyline) => void;
33
+ onDrop?: (polyline: SpherePolyline) => void;
34
+ }
35
+
36
+ export interface PolylineRef {
37
+ getPolyline(): SpherePolyline | null;
38
+ togglePopup(show?: boolean, location?: Location): void;
39
+ getPivot(): Location | null;
40
+ getCentroid(): Location | null;
41
+ getBound(): Bound | null;
42
+ getLength(language?: string): number | string | null;
43
+ rotate(angle: number): void;
44
+ updateStyle(options: Partial<GeometryOptions>): void;
45
+ }
46
+
47
+ export const Polyline = forwardRef<PolylineRef, PolylineProps>(
48
+ function Polyline(
49
+ {
50
+ positions,
51
+ title,
52
+ detail,
53
+ label,
54
+ labelOptions,
55
+ popup,
56
+ visibleRange,
57
+ lineWidth,
58
+ lineColor,
59
+ lineStyle,
60
+ pivot,
61
+ clickable,
62
+ draggable,
63
+ pointer,
64
+ zIndex,
65
+ onClick,
66
+ onDrag,
67
+ onDrop,
68
+ },
69
+ ref
70
+ ) {
71
+ const { map, isReady } = useMapContext();
72
+ const { sphere } = useSphereContext();
73
+ const polylineRef = useRef<SpherePolyline | null>(null);
74
+ const callbacksRef = useRef({ onClick, onDrag, onDrop });
75
+
76
+ useEffect(() => {
77
+ callbacksRef.current = { onClick, onDrag, onDrop };
78
+ }, [onClick, onDrag, onDrop]);
79
+
80
+ useEffect(() => {
81
+ if (!(isReady && map && sphere) || positions.length < 2) {
82
+ return;
83
+ }
84
+
85
+ const options: GeometryOptions = {};
86
+
87
+ if (title) {
88
+ options.title = title;
89
+ }
90
+ if (detail) {
91
+ options.detail = detail;
92
+ }
93
+ if (label) {
94
+ options.label = label;
95
+ }
96
+ if (labelOptions) {
97
+ options.labelOptions = labelOptions;
98
+ }
99
+ if (popup) {
100
+ options.popup = popup;
101
+ }
102
+ if (visibleRange) {
103
+ options.visibleRange = visibleRange;
104
+ }
105
+ if (typeof lineWidth === "number") {
106
+ options.lineWidth = lineWidth;
107
+ }
108
+ if (lineColor) {
109
+ options.lineColor = lineColor;
110
+ }
111
+ if (lineStyle) {
112
+ options.lineStyle = lineStyle;
113
+ }
114
+ if (pivot) {
115
+ options.pivot = pivot;
116
+ }
117
+ if (typeof clickable === "boolean") {
118
+ options.clickable = clickable;
119
+ }
120
+ if (typeof draggable === "boolean") {
121
+ options.draggable = draggable;
122
+ }
123
+ if (typeof pointer === "boolean") {
124
+ options.pointer = pointer;
125
+ }
126
+ if (typeof zIndex === "number") {
127
+ options.zIndex = zIndex;
128
+ }
129
+
130
+ const polyline = new sphere.Polyline(positions, options);
131
+ polylineRef.current = polyline;
132
+
133
+ map.Overlays.add(polyline);
134
+
135
+ const handleOverlayClick = (data: { overlay: SpherePolyline }) => {
136
+ if (data.overlay === polyline) {
137
+ callbacksRef.current.onClick?.(polyline);
138
+ }
139
+ };
140
+
141
+ const handleOverlayDrag = (overlay: SpherePolyline) => {
142
+ if (overlay === polyline) {
143
+ callbacksRef.current.onDrag?.(polyline);
144
+ }
145
+ };
146
+
147
+ const handleOverlayDrop = (overlay: SpherePolyline) => {
148
+ if (overlay === polyline) {
149
+ callbacksRef.current.onDrop?.(polyline);
150
+ }
151
+ };
152
+
153
+ map.Event.bind("overlayClick", handleOverlayClick);
154
+ map.Event.bind("overlayDrag", handleOverlayDrag);
155
+ map.Event.bind("overlayDrop", handleOverlayDrop);
156
+
157
+ return () => {
158
+ map.Event.unbind("overlayClick", handleOverlayClick);
159
+ map.Event.unbind("overlayDrag", handleOverlayDrag);
160
+ map.Event.unbind("overlayDrop", handleOverlayDrop);
161
+ map.Overlays.remove(polyline);
162
+ polylineRef.current = null;
163
+ };
164
+ }, [
165
+ isReady,
166
+ map,
167
+ sphere,
168
+ positions,
169
+ title,
170
+ detail,
171
+ label,
172
+ labelOptions,
173
+ popup,
174
+ visibleRange,
175
+ lineWidth,
176
+ lineColor,
177
+ lineStyle,
178
+ pivot,
179
+ clickable,
180
+ draggable,
181
+ pointer,
182
+ zIndex,
183
+ ]);
184
+
185
+ useImperativeHandle(
186
+ ref,
187
+ () => ({
188
+ getPolyline: () => polylineRef.current,
189
+ togglePopup: (show?: boolean, location?: Location) => {
190
+ polylineRef.current?.pop(show, location);
191
+ },
192
+ getPivot: () => polylineRef.current?.pivot() ?? null,
193
+ getCentroid: () => polylineRef.current?.centroid() ?? null,
194
+ getBound: () => polylineRef.current?.bound() ?? null,
195
+ getLength: (language?: string) =>
196
+ polylineRef.current?.size(language) ?? null,
197
+ rotate: (angle: number) => {
198
+ polylineRef.current?.rotate(angle);
199
+ },
200
+ updateStyle: (options: Partial<GeometryOptions>) => {
201
+ polylineRef.current?.update(options);
202
+ },
203
+ }),
204
+ []
205
+ );
206
+
207
+ return null;
208
+ }
209
+ );
210
+
211
+ export default Polyline;