@deck.gl-community/editable-layers 9.1.1 → 9.2.0-beta.3

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 (54) hide show
  1. package/dist/edit-modes/draw-line-string-mode.d.ts +3 -1
  2. package/dist/edit-modes/draw-line-string-mode.d.ts.map +1 -1
  3. package/dist/edit-modes/draw-line-string-mode.js +19 -21
  4. package/dist/edit-modes/draw-line-string-mode.js.map +1 -1
  5. package/dist/edit-modes/draw-polygon-mode.d.ts +3 -1
  6. package/dist/edit-modes/draw-polygon-mode.d.ts.map +1 -1
  7. package/dist/edit-modes/draw-polygon-mode.js +19 -22
  8. package/dist/edit-modes/draw-polygon-mode.js.map +1 -1
  9. package/dist/edit-modes/edit-mode.d.ts +2 -1
  10. package/dist/edit-modes/edit-mode.d.ts.map +1 -1
  11. package/dist/edit-modes/geojson-edit-mode.d.ts +1 -0
  12. package/dist/edit-modes/geojson-edit-mode.d.ts.map +1 -1
  13. package/dist/edit-modes/geojson-edit-mode.js +1 -0
  14. package/dist/edit-modes/geojson-edit-mode.js.map +1 -1
  15. package/dist/edit-modes/measure-distance-mode.d.ts.map +1 -1
  16. package/dist/edit-modes/rotate-mode.d.ts.map +1 -1
  17. package/dist/edit-modes/scale-mode.d.ts.map +1 -1
  18. package/dist/edit-modes/types.d.ts +1 -0
  19. package/dist/edit-modes/types.d.ts.map +1 -1
  20. package/dist/editable-layers/editable-geojson-layer.d.ts +2 -1
  21. package/dist/editable-layers/editable-geojson-layer.d.ts.map +1 -1
  22. package/dist/editable-layers/editable-geojson-layer.js +5 -0
  23. package/dist/editable-layers/editable-geojson-layer.js.map +1 -1
  24. package/dist/editable-layers/editable-layer.d.ts +3 -1
  25. package/dist/editable-layers/editable-layer.d.ts.map +1 -1
  26. package/dist/editable-layers/editable-layer.js +7 -1
  27. package/dist/editable-layers/editable-layer.js.map +1 -1
  28. package/dist/index.cjs +847 -79
  29. package/dist/index.cjs.map +4 -4
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/lib/layers/segments-layer.d.ts +1 -1
  35. package/dist/lib/layers/segments-layer.d.ts.map +1 -1
  36. package/dist/lib/layers/segments-layer.js +1 -1
  37. package/dist/lib/layers/segments-layer.js.map +1 -1
  38. package/dist/lib/nebula-core.d.ts.map +1 -1
  39. package/dist/utils/memoize.d.ts.map +1 -1
  40. package/dist/widgets/edit-mode-tray-widget.d.ts +71 -0
  41. package/dist/widgets/edit-mode-tray-widget.d.ts.map +1 -0
  42. package/dist/widgets/edit-mode-tray-widget.js +217 -0
  43. package/dist/widgets/edit-mode-tray-widget.js.map +1 -0
  44. package/package.json +12 -11
  45. package/src/edit-modes/draw-line-string-mode.ts +23 -24
  46. package/src/edit-modes/draw-polygon-mode.ts +24 -27
  47. package/src/edit-modes/edit-mode.ts +4 -1
  48. package/src/edit-modes/geojson-edit-mode.ts +2 -0
  49. package/src/edit-modes/types.ts +3 -0
  50. package/src/editable-layers/editable-geojson-layer.ts +8 -1
  51. package/src/editable-layers/editable-layer.ts +10 -2
  52. package/src/index.ts +8 -0
  53. package/src/lib/layers/segments-layer.ts +1 -1
  54. package/src/widgets/edit-mode-tray-widget.tsx +342 -0
@@ -44,6 +44,9 @@ export type BasePointerEvent = {
44
44
  // Represents a click event
45
45
  export type ClickEvent = BasePointerEvent;
46
46
 
47
+ // Represents a double click event
48
+ export type DoubleClickEvent = BasePointerEvent;
49
+
47
50
  // Represents an event that occurs when the pointer goes down and the cursor starts moving
48
51
  export type StartDraggingEvent = BasePointerEvent & {
49
52
  pointerDownPicks?: Pick[] | null;
@@ -12,7 +12,8 @@ import {
12
12
  StartDraggingEvent,
13
13
  StopDraggingEvent,
14
14
  DraggingEvent,
15
- PointerMoveEvent
15
+ PointerMoveEvent,
16
+ DoubleClickEvent
16
17
  } from '../edit-modes/types';
17
18
 
18
19
  import {ViewMode} from '../edit-modes/view-mode';
@@ -567,6 +568,12 @@ export class EditableGeoJsonLayer extends EditableLayer<
567
568
  this.getActiveMode().handleClick(event, this.getModeProps(this.props) as any);
568
569
  }
569
570
 
571
+ onLayerDoubleClick(event: DoubleClickEvent): void {
572
+ if (this.getActiveMode().handleDoubleClick) {
573
+ this.getActiveMode().handleDoubleClick(event, this.getModeProps(this.props) as any);
574
+ }
575
+ }
576
+
570
577
  onLayerKeyUp(event: KeyboardEvent): void {
571
578
  this.getActiveMode().handleKeyUp(event, this.getModeProps(this.props) as any);
572
579
  }
@@ -11,11 +11,12 @@ import {
11
11
  ClickEvent,
12
12
  StartDraggingEvent,
13
13
  StopDraggingEvent,
14
- PointerMoveEvent
14
+ PointerMoveEvent,
15
+ DoubleClickEvent
15
16
  } from '../edit-modes/types';
16
17
  import {Position} from '../utils/geojson-types';
17
18
 
18
- const EVENT_TYPES = ['click', 'pointermove', 'panstart', 'panmove', 'panend', 'keyup'];
19
+ const EVENT_TYPES = ['click', 'pointermove', 'panstart', 'panmove', 'panend', 'keyup', 'dblclick'];
19
20
 
20
21
  // TODO(v9): remove generic layer
21
22
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -37,6 +38,9 @@ export abstract class EditableLayer<
37
38
  onLayerClick(event: ClickEvent): void {
38
39
  // default implementation - do nothing
39
40
  }
41
+ onLayerDoubleClick(event: DoubleClickEvent): void {
42
+ // default implementation - do nothing
43
+ }
40
44
 
41
45
  onStartDragging(event: StartDraggingEvent): void {
42
46
  // default implementation - do nothing
@@ -133,6 +137,10 @@ export abstract class EditableLayer<
133
137
  });
134
138
  }
135
139
 
140
+ _ondblclick({srcEvent}: any) {
141
+ this.onLayerDoubleClick(srcEvent);
142
+ }
143
+
136
144
  _onkeyup({srcEvent}: {srcEvent: KeyboardEvent}) {
137
145
  this.onLayerKeyUp(srcEvent);
138
146
  }
package/src/index.ts CHANGED
@@ -29,6 +29,14 @@ export {EditableH3ClusterLayer} from './editable-layers/editable-h3-cluster-laye
29
29
  export {SelectionLayer} from './editable-layers/selection-layer';
30
30
  export {ElevatedEditHandleLayer} from './editable-layers/elevated-edit-handle-layer';
31
31
 
32
+ // Widgets
33
+ export {EditModeTrayWidget} from './widgets/edit-mode-tray-widget';
34
+ export type {
35
+ EditModeTrayWidgetProps,
36
+ EditModeTrayWidgetModeOption,
37
+ EditModeTrayWidgetSelectEvent
38
+ } from './widgets/edit-mode-tray-widget';
39
+
32
40
  // Layers move to deck.gl-community/layers?
33
41
  export {JunctionScatterplotLayer} from './editable-layers/junction-scatterplot-layer';
34
42
 
@@ -6,7 +6,7 @@ import {ArrowStyles, DEFAULT_STYLE, MAX_ARROWS} from '../style';
6
6
  import {NebulaLayer} from '../nebula-layer';
7
7
  import {toDeckColor} from '../../utils/utils';
8
8
  import {DeckCache} from '../deck-renderer/deck-cache';
9
- import {PathMarkerLayer} from '@deck.gl-community/layers';
9
+ import {PathMarkerLayer} from '../../../../layers/src/path-marker-layer/path-marker-layer';
10
10
 
11
11
  const NEBULA_TO_DECK_DIRECTIONS = {
12
12
  [ArrowStyles.NONE]: {forward: false, backward: false},
@@ -0,0 +1,342 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {render} from 'preact';
6
+ import type {ComponentChild, JSX} from 'preact';
7
+ import {
8
+ Widget,
9
+ type WidgetProps,
10
+ type WidgetPlacement,
11
+ type Deck
12
+ } from '@deck.gl/core';
13
+ import type {
14
+ GeoJsonEditModeConstructor,
15
+ GeoJsonEditModeType
16
+ } from '../edit-modes/geojson-edit-mode';
17
+
18
+ export type EditModeTrayWidgetModeOption = {
19
+ /**
20
+ * Optional identifier for the mode button.
21
+ * If not provided, one will be inferred from the supplied mode.
22
+ */
23
+ id?: string;
24
+ /** Edit mode constructor or instance that the button should activate. */
25
+ mode: GeoJsonEditModeConstructor | GeoJsonEditModeType;
26
+ /**
27
+ * The icon or element rendered inside the button.
28
+ * A simple string can also be supplied for text labels.
29
+ */
30
+ icon?: ComponentChild;
31
+ /** Optional text label rendered below the icon when provided. */
32
+ label?: string;
33
+ /** Optional tooltip text applied to the button element. */
34
+ title?: string;
35
+ };
36
+
37
+ export type EditModeTrayWidgetSelectEvent = {
38
+ id: string;
39
+ mode: GeoJsonEditModeConstructor | GeoJsonEditModeType;
40
+ option: EditModeTrayWidgetModeOption;
41
+ };
42
+
43
+ export type EditModeTrayWidgetProps = WidgetProps & {
44
+ /** Placement for the widget root element. */
45
+ placement?: WidgetPlacement;
46
+ /** Layout direction for mode buttons. */
47
+ layout?: 'vertical' | 'horizontal';
48
+ /** Collection of modes rendered in the tray. */
49
+ modes?: EditModeTrayWidgetModeOption[];
50
+ /** Identifier of the currently active mode. */
51
+ selectedModeId?: string | null;
52
+ /** Currently active mode instance/constructor. */
53
+ activeMode?: GeoJsonEditModeConstructor | GeoJsonEditModeType | null;
54
+ /** Callback fired when the user selects a mode. */
55
+ onSelectMode?: (event: EditModeTrayWidgetSelectEvent) => void;
56
+ };
57
+
58
+ const ROOT_STYLE: Partial<CSSStyleDeclaration> = {
59
+ position: 'absolute',
60
+ display: 'flex',
61
+ pointerEvents: 'auto',
62
+ userSelect: 'none',
63
+ zIndex: '99'
64
+ };
65
+
66
+ const TRAY_BASE_STYLE: JSX.CSSProperties = {
67
+ display: 'flex',
68
+ gap: '6px',
69
+ background: 'rgba(36, 40, 41, 0.88)',
70
+ borderRadius: '999px',
71
+ padding: '6px',
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.25)'
75
+ };
76
+
77
+ const BUTTON_BASE_STYLE: JSX.CSSProperties = {
78
+ appearance: 'none',
79
+ background: 'transparent',
80
+ border: 'none',
81
+ color: '#f0f0f0',
82
+ width: '34px',
83
+ height: '34px',
84
+ display: 'flex',
85
+ flexDirection: 'column',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ borderRadius: '50%',
89
+ cursor: 'pointer',
90
+ padding: '0',
91
+ transition: 'background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease'
92
+ };
93
+
94
+ const BUTTON_ACTIVE_STYLE: JSX.CSSProperties = {
95
+ background: '#0071e3',
96
+ color: '#ffffff',
97
+ boxShadow: '0 0 0 2px rgba(255, 255, 255, 0.35)'
98
+ };
99
+
100
+ const BUTTON_LABEL_STYLE: JSX.CSSProperties = {
101
+ fontSize: '10px',
102
+ marginTop: '2px',
103
+ lineHeight: '12px'
104
+ };
105
+
106
+ export class EditModeTrayWidget extends Widget<EditModeTrayWidgetProps> {
107
+ static override defaultProps = {
108
+ id: 'edit-mode-tray',
109
+ placement: 'top-left',
110
+ layout: 'vertical',
111
+ modes: [],
112
+ style: {},
113
+ className: ''
114
+ } satisfies Required<WidgetProps> &
115
+ Required<Pick<EditModeTrayWidgetProps, 'placement' | 'layout'>> &
116
+ EditModeTrayWidgetProps;
117
+
118
+ placement: WidgetPlacement = 'top-left';
119
+ className = 'deck-widget-edit-mode-tray';
120
+ layout: 'vertical' | 'horizontal' = 'vertical';
121
+ selectedModeId: string | null = null;
122
+ deck?: Deck | null = null;
123
+ private appliedCustomClassName: string | null = null;
124
+
125
+ constructor(props: EditModeTrayWidgetProps = {}) {
126
+ super({...EditModeTrayWidget.defaultProps, ...props});
127
+ this.placement = props.placement ?? EditModeTrayWidget.defaultProps.placement;
128
+ this.layout = props.layout ?? EditModeTrayWidget.defaultProps.layout;
129
+ this.selectedModeId = this.resolveSelectedModeId(props.modes ?? [], props);
130
+ }
131
+
132
+ override setProps(props: Partial<EditModeTrayWidgetProps>): void {
133
+ if (props.placement !== undefined) {
134
+ this.placement = props.placement;
135
+ }
136
+ if (props.layout !== undefined) {
137
+ this.layout = props.layout;
138
+ }
139
+
140
+ const modes = props.modes ?? this.props.modes ?? [];
141
+ this.selectedModeId = this.resolveSelectedModeId(modes, props);
142
+
143
+ super.setProps(props);
144
+ this.renderTray();
145
+ }
146
+
147
+ override onAdd({deck}: {deck: Deck}): void {
148
+ this.deck = deck;
149
+ }
150
+
151
+ override onRemove(): void {
152
+ this.deck = null;
153
+ const root = this.rootElement;
154
+ if (root) {
155
+ render(null, root);
156
+ }
157
+ this.rootElement = null;
158
+ }
159
+
160
+ override onRenderHTML(rootElement: HTMLElement): void {
161
+ const style = {...ROOT_STYLE, ...this.props.style};
162
+ Object.assign(rootElement.style, style);
163
+ if (this.appliedCustomClassName && this.appliedCustomClassName !== this.props.className) {
164
+ rootElement.classList.remove(this.appliedCustomClassName);
165
+ this.appliedCustomClassName = null;
166
+ }
167
+ if (this.props.className) {
168
+ rootElement.classList.add(this.props.className);
169
+ this.appliedCustomClassName = this.props.className;
170
+ }
171
+ rootElement.classList.add(this.className);
172
+
173
+ this.renderTray();
174
+ }
175
+
176
+ private renderTray() {
177
+ const root = this.rootElement;
178
+ if (!root) {
179
+ return;
180
+ }
181
+
182
+ const modes = this.props.modes ?? [];
183
+ const selectedId = this.selectedModeId;
184
+ const direction = this.layout === 'horizontal' ? 'row' : 'column';
185
+
186
+ const trayStyle: JSX.CSSProperties = {
187
+ ...TRAY_BASE_STYLE,
188
+ flexDirection: direction
189
+ };
190
+
191
+ const stopEvent = (event: Event) => {
192
+ event.stopPropagation();
193
+ if (typeof (event as any).stopImmediatePropagation === 'function') {
194
+ (event as any).stopImmediatePropagation();
195
+ }
196
+ };
197
+
198
+ const ui = (
199
+ <div
200
+ style={trayStyle}
201
+ onPointerDown={stopEvent}
202
+ onPointerMove={stopEvent}
203
+ onPointerUp={stopEvent}
204
+ onMouseDown={stopEvent}
205
+ onMouseMove={stopEvent}
206
+ onMouseUp={stopEvent}
207
+ onTouchStart={stopEvent}
208
+ onTouchMove={stopEvent}
209
+ onTouchEnd={stopEvent}
210
+ >
211
+ {modes.map((option, index) => {
212
+ const id = this.getModeId(option, index);
213
+ const active = id === selectedId;
214
+ const label = option.label ?? '';
215
+ const title = option.title ?? label;
216
+
217
+ const buttonStyle: JSX.CSSProperties = {
218
+ ...BUTTON_BASE_STYLE,
219
+ ...(active ? BUTTON_ACTIVE_STYLE : {})
220
+ };
221
+
222
+ return (
223
+ <button
224
+ key={id}
225
+ type="button"
226
+ title={title || undefined}
227
+ aria-pressed={active}
228
+ style={buttonStyle}
229
+ onClick={(event) => {
230
+ stopEvent(event);
231
+ this.handleSelect(option, id);
232
+ }}
233
+ >
234
+ {option.icon}
235
+ {label ? <span style={BUTTON_LABEL_STYLE}>{label}</span> : null}
236
+ </button>
237
+ );
238
+ })}
239
+ </div>
240
+ );
241
+
242
+ render(ui, root);
243
+ }
244
+
245
+ private handleSelect(option: EditModeTrayWidgetModeOption, id: string) {
246
+ if (this.selectedModeId !== id) {
247
+ this.selectedModeId = id;
248
+ this.renderTray();
249
+ }
250
+
251
+ this.props.onSelectMode?.({
252
+ id,
253
+ mode: option.mode,
254
+ option
255
+ });
256
+ }
257
+
258
+ private resolveSelectedModeId(
259
+ modes: EditModeTrayWidgetModeOption[],
260
+ props: Partial<EditModeTrayWidgetProps>
261
+ ): string | null {
262
+ if (props.selectedModeId !== undefined) {
263
+ return props.selectedModeId;
264
+ }
265
+
266
+ const activeMode = props.activeMode ?? this.props?.activeMode ?? null;
267
+ if (activeMode) {
268
+ const match = this.findOptionByMode(modes, activeMode);
269
+ if (match) {
270
+ return this.getModeId(match.option, match.index);
271
+ }
272
+ }
273
+
274
+ if (this.selectedModeId) {
275
+ const existing = this.findOptionById(modes, this.selectedModeId);
276
+ if (existing) {
277
+ return this.selectedModeId;
278
+ }
279
+ }
280
+
281
+ const first = modes[0];
282
+ return first ? this.getModeId(first, 0) : null;
283
+ }
284
+
285
+ private findOptionByMode(
286
+ modes: EditModeTrayWidgetModeOption[],
287
+ activeMode: GeoJsonEditModeConstructor | GeoJsonEditModeType
288
+ ): {option: EditModeTrayWidgetModeOption; index: number} | null {
289
+ for (let index = 0; index < modes.length; index++) {
290
+ const option = modes[index];
291
+ if (option.mode === activeMode) {
292
+ return {option, index};
293
+ }
294
+ if (this.isSameMode(option.mode, activeMode)) {
295
+ return {option, index};
296
+ }
297
+ }
298
+ return null;
299
+ }
300
+
301
+ private findOptionById(
302
+ modes: EditModeTrayWidgetModeOption[],
303
+ id: string
304
+ ): {option: EditModeTrayWidgetModeOption; index: number} | null {
305
+ for (let index = 0; index < modes.length; index++) {
306
+ if (this.getModeId(modes[index], index) === id) {
307
+ return {option: modes[index], index};
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+
313
+ private getModeId(option: EditModeTrayWidgetModeOption, index: number): string {
314
+ if (option.id) {
315
+ return option.id;
316
+ }
317
+
318
+ const mode = option.mode as any;
319
+ if (mode) {
320
+ if (typeof mode === 'function' && mode.name) {
321
+ return mode.name;
322
+ }
323
+ if (mode && mode.constructor && mode.constructor.name) {
324
+ return mode.constructor.name;
325
+ }
326
+ }
327
+
328
+ return `mode-${index}`;
329
+ }
330
+
331
+ private isSameMode(
332
+ modeA: GeoJsonEditModeConstructor | GeoJsonEditModeType,
333
+ modeB: GeoJsonEditModeConstructor | GeoJsonEditModeType
334
+ ): boolean {
335
+ if (modeA === modeB) {
336
+ return true;
337
+ }
338
+ const constructorA = (modeA as GeoJsonEditModeType)?.constructor;
339
+ const constructorB = (modeB as GeoJsonEditModeType)?.constructor;
340
+ return Boolean(constructorA && constructorB && constructorA === constructorB);
341
+ }
342
+ }