From 6af865cffa58063e422c69a4201b2ab9f37f019a Mon Sep 17 00:00:00 2001 From: James Date: Sun, 22 Jan 2023 17:23:51 +0000 Subject: [PATCH] feat: allow keyEvents to be set to null to prevent keyboard interactions on modes --- src/modes/circle/circle.mode.ts | 19 +- src/modes/circle/circle.spec.ts | 79 +++- src/modes/freehand/freehand.mode.ts | 20 +- src/modes/freehand/freehand.spec.ts | 68 ++++ src/modes/linestring/linestring.mode.ts | 22 +- src/modes/linestring/linestring.spec.ts | 110 +++++- src/modes/point/point.spec.ts | 1 - src/modes/polygon/polygon.mode.ts | 18 +- src/modes/polygon/polygon.spec.ts | 417 ++++++++++++++------- src/modes/rectangle/rectangle.mode.spec.ts | 70 +++- src/modes/rectangle/rectangle.mode.ts | 18 +- src/modes/select/select.mode.ts | 23 +- src/modes/select/select.spec.ts | 27 +- 13 files changed, 692 insertions(+), 200 deletions(-) diff --git a/src/modes/circle/circle.mode.ts b/src/modes/circle/circle.mode.ts index ac7914a7..2173bf01 100644 --- a/src/modes/circle/circle.mode.ts +++ b/src/modes/circle/circle.mode.ts @@ -12,8 +12,8 @@ import { getDefaultStyling } from "../../util/styling"; import { TerraDrawBaseDrawMode } from "../base.mode"; type TerraDrawCircleModeKeyEvents = { - cancel: KeyboardEvent["key"]; - finish: KeyboardEvent["key"]; + cancel: KeyboardEvent["key"] | null; + finish: KeyboardEvent["key"] | null; }; type FreehandPolygonStyling = { @@ -29,16 +29,23 @@ export class TerraDrawCircleMode extends TerraDrawBaseDrawMode; - keyEvents?: TerraDrawCircleModeKeyEvents; + keyEvents?: TerraDrawCircleModeKeyEvents | null; }) { super(options); - this.keyEvents = - options && options.keyEvents ? options.keyEvents : { cancel: "Escape", finish: 'Enter' }; + // We want to have some defaults, but also allow key bindings + // to be explicitly turned off + if (options?.keyEvents === null) { + this.keyEvents = { cancel: null, finish: null } + } else { + const defaultKeyEvents = { cancel: "Escape", finish: 'Enter' } + this.keyEvents = + options && options.keyEvents ? { ...defaultKeyEvents, ...options.keyEvents } : defaultKeyEvents; + } } private close() { diff --git a/src/modes/circle/circle.spec.ts b/src/modes/circle/circle.spec.ts index 52169ef5..9ba9b677 100644 --- a/src/modes/circle/circle.spec.ts +++ b/src/modes/circle/circle.spec.ts @@ -24,6 +24,19 @@ describe("TerraDrawCircleMode", () => { fillColor: "#ffffff", }); }); + + it("constructs with null key events", () => { + new TerraDrawCircleMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: null + }); + + new TerraDrawCircleMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: { cancel: null, finish: null } + }); + + }); }); describe("lifecycle", () => { @@ -332,28 +345,60 @@ describe("TerraDrawCircleMode", () => { circleMode.register(mockConfig); }); - it("Escape - does nothing when no circle is present", () => { - circleMode.onKeyUp({ key: "Escape" }); - }); + describe('cancel', () => { + it("does nothing when no circle is present", () => { + circleMode.onKeyUp({ key: "Escape" }); + }); - it("Escape - deletes the circle when currently editing", () => { - circleMode.onClick({ - lng: 0, - lat: 0, - containerX: 0, - containerY: 0, - button: "left", - heldKeys: [], + it("deletes the circle when currently editing", () => { + circleMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(1); + + circleMode.onKeyUp({ key: "Escape" }); + + features = store.copyAll(); + expect(features.length).toBe(0); }); - let features = store.copyAll(); - expect(features.length).toBe(1); + it("does not delete the circle when currently editing if keyEvents is null", () => { + jest.resetAllMocks(); + circleMode = new TerraDrawCircleMode({ keyEvents: null }); + + const mockConfig = getMockModeConfig(circleMode.mode); + store = mockConfig.store; + onChange = mockConfig.onChange; + project = mockConfig.project; + circleMode.register(mockConfig); + + circleMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(1); + + circleMode.onKeyUp({ key: "Escape" }); + + features = store.copyAll(); + expect(features.length).toBe(1); + }); + }) - circleMode.onKeyUp({ key: "Escape" }); - features = store.copyAll(); - expect(features.length).toBe(0); - }); }); describe("onDrag", () => { diff --git a/src/modes/freehand/freehand.mode.ts b/src/modes/freehand/freehand.mode.ts index 7579d50c..e5dcdf03 100644 --- a/src/modes/freehand/freehand.mode.ts +++ b/src/modes/freehand/freehand.mode.ts @@ -12,8 +12,8 @@ import { GeoJSONStoreFeatures } from "../../store/store"; import { pixelDistance } from "../../geometry/measure/pixel-distance"; type TerraDrawFreehandModeKeyEvents = { - cancel: KeyboardEvent["key"]; - finish: KeyboardEvent["key"]; + cancel: KeyboardEvent["key"] | null; + finish: KeyboardEvent["key"] | null }; type FreehandPolygonStyling = { @@ -39,13 +39,21 @@ export class TerraDrawFreehandMode extends TerraDrawBaseDrawMode; minDistance?: number; - keyEvents?: TerraDrawFreehandModeKeyEvents; + keyEvents?: TerraDrawFreehandModeKeyEvents | null }) { super(options); this.minDistance = (options && options.minDistance) || 20; - this.keyEvents = - options && options.keyEvents ? options.keyEvents : { cancel: "Escape", finish: 'Enter' }; + + // We want to have some defaults, but also allow key bindings + // to be explicitly turned off + if (options?.keyEvents === null) { + this.keyEvents = { cancel: null, finish: null } + } else { + const defaultKeyEvents = { cancel: "Escape", finish: 'Enter' } + this.keyEvents = + options && options.keyEvents ? { ...defaultKeyEvents, ...options.keyEvents } : defaultKeyEvents; + } } private close() { @@ -81,7 +89,7 @@ export class TerraDrawFreehandMode extends TerraDrawBaseDrawMode { outlineColor: "#ffffff", }); }); + + it("constructs with null key events", () => { + new TerraDrawFreehandMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: null + }); + + new TerraDrawFreehandMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: { cancel: null, finish: null } + }); + }); }); describe("lifecycle", () => { @@ -333,6 +345,33 @@ describe("TerraDrawFreehandMode", () => { features = store.copyAll(); expect(features.length).toBe(0); }); + + it("does not delete the freehand when currently editing if cancel is null", () => { + freehandMode = new TerraDrawFreehandMode({ keyEvents: null }); + + const mockConfig = getMockModeConfig(freehandMode.mode); + store = mockConfig.store; + onChange = mockConfig.onChange; + project = mockConfig.project; + freehandMode.register(mockConfig); + + freehandMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(2); + + freehandMode.onKeyUp({ key: "Escape" }); + + features = store.copyAll(); + expect(features.length).toBe(2); + }); }); describe('finish', () => { @@ -362,6 +401,35 @@ describe("TerraDrawFreehandMode", () => { expect(onChange).toHaveBeenNthCalledWith(2, [expect.any(String)], "delete"); }); + + it("finishes drawing polygon on finish key press", () => { + freehandMode = new TerraDrawFreehandMode({ keyEvents: null }); + + const mockConfig = getMockModeConfig(freehandMode.mode); + store = mockConfig.store; + onChange = mockConfig.onChange; + project = mockConfig.project; + freehandMode.register(mockConfig); + + freehandMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(2); + + freehandMode.onKeyUp({ + key: 'Enter' + }); + + features = store.copyAll(); + expect(features.length).toBe(2); + }); }); diff --git a/src/modes/linestring/linestring.mode.ts b/src/modes/linestring/linestring.mode.ts index 85e5d32a..b20b0dd2 100644 --- a/src/modes/linestring/linestring.mode.ts +++ b/src/modes/linestring/linestring.mode.ts @@ -14,11 +14,10 @@ import { PixelDistanceBehavior } from "../pixel-distance.behavior"; import { SnappingBehavior } from "../snapping.behavior"; import { getDefaultStyling } from "../../util/styling"; import { GeoJSONStoreFeatures } from "../../store/store"; -import { createSourceMapSource } from "typescript"; type TerraDrawLineStringModeKeyEvents = { - cancel: KeyboardEvent["key"]; - finish: KeyboardEvent["key"] + cancel: KeyboardEvent["key"] | null + finish: KeyboardEvent["key"] | null }; type LineStringStyling = { @@ -49,7 +48,7 @@ export class TerraDrawLineStringMode extends TerraDrawBaseDrawMode; - keyEvents?: TerraDrawLineStringModeKeyEvents; + keyEvents?: TerraDrawLineStringModeKeyEvents | null }) { super(options); @@ -61,8 +60,15 @@ export class TerraDrawLineStringMode extends TerraDrawBaseDrawMode { @@ -19,6 +18,20 @@ describe("TerraDrawLineStringMode", () => { }); expect(lineStringMode.styles).toStrictEqual({ lineStringColor: "#ffffff" }); }); + + it("constructs with null key events", () => { + new TerraDrawLineStringMode({ + styles: { lineStringColor: "#ffffff" }, + keyEvents: null + }); + + new TerraDrawLineStringMode({ + styles: { lineStringColor: "#ffffff" }, + keyEvents: { cancel: null, finish: null } + }); + + }); + }); describe("lifecycle", () => { @@ -527,6 +540,101 @@ describe("TerraDrawLineStringMode", () => { [2, 2], ]); }); + + it("does not finish linestring when finish is set to null", () => { + lineStringMode = new TerraDrawLineStringMode({ keyEvents: null }); + const mockConfig = getMockModeConfig(lineStringMode.mode); + onChange = mockConfig.onChange; + store = mockConfig.store; + project = mockConfig.project; + lineStringMode.register(mockConfig); + + project.mockReturnValueOnce({ x: 50, y: 50 }); + project.mockReturnValueOnce({ x: 50, y: 50 }); + project.mockReturnValueOnce({ x: 100, y: 100 }); + project.mockReturnValueOnce({ x: 100, y: 100 }); + + + lineStringMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + lineStringMode.onMouseMove({ + lng: 1, + lat: 1, + containerX: 50, + containerY: 50, + button: "left", + heldKeys: [], + }); + + lineStringMode.onClick({ + lng: 1, + lat: 1, + containerX: 50, + containerY: 50, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + + // Drawn LineString and Closing point + expect(features.length).toBe(2); + + expect(features[0].geometry.coordinates).toStrictEqual([ + [0, 0], + [1, 1], + [1, 1], + ]); + + expect(features[1].geometry.coordinates).toStrictEqual([1, 1]); + + lineStringMode.onMouseMove({ + lng: 2, + lat: 2, + containerX: 100, + containerY: 100, + button: "left", + heldKeys: [], + }); + + lineStringMode.onClick({ + lng: 2, + lat: 2, + containerX: 100, + containerY: 100, + button: "left", + heldKeys: [], + }); + + expect(onChange).not.toBeCalledWith([expect.any(String)], 'delete'); + + lineStringMode.onKeyUp({ + key: 'Enter' + }); + + expect(onChange).toBeCalledTimes(6); + + features = store.copyAll(); + expect(features.length).toBe(2); + + expect(features[1].geometry.type).toStrictEqual('Point') + expect(features[1].geometry.coordinates).toStrictEqual([2, 2]); + expect(features[0].geometry.type).toStrictEqual('LineString') + expect(features[0].geometry.coordinates).toStrictEqual([ + [0, 0], + [1, 1], + [2, 2], + [2, 2], + ]); + + }); }); }); diff --git a/src/modes/point/point.spec.ts b/src/modes/point/point.spec.ts index f3b457c2..a4ec9222 100644 --- a/src/modes/point/point.spec.ts +++ b/src/modes/point/point.spec.ts @@ -4,7 +4,6 @@ import { getDefaultStyling } from "../../util/styling"; import { TerraDrawPointMode } from "./point.mode"; describe("TerraDrawPointMode", () => { - const defaultStyles = getDefaultStyling(); describe("constructor", () => { it("constructs with no options", () => { diff --git a/src/modes/polygon/polygon.mode.ts b/src/modes/polygon/polygon.mode.ts index 5e5ff74b..9d0be005 100644 --- a/src/modes/polygon/polygon.mode.ts +++ b/src/modes/polygon/polygon.mode.ts @@ -18,8 +18,8 @@ import { getDefaultStyling } from "../../util/styling"; import { GeoJSONStoreFeatures } from "../../store/store"; type TerraDrawPolygonModeKeyEvents = { - cancel: KeyboardEvent["key"]; - finish: KeyboardEvent["key"] + cancel?: KeyboardEvent["key"] | null + finish?: KeyboardEvent["key"] | null }; type PolygonStyling = { @@ -53,7 +53,7 @@ export class TerraDrawPolygonMode extends TerraDrawBaseDrawMode snapping?: boolean; pointerDistance?: number; styles?: Partial; - keyEvents?: TerraDrawPolygonModeKeyEvents; + keyEvents?: TerraDrawPolygonModeKeyEvents | null }) { super(options); @@ -65,8 +65,16 @@ export class TerraDrawPolygonMode extends TerraDrawBaseDrawMode ? options.allowSelfIntersections : true; - this.keyEvents = - options && options.keyEvents ? options.keyEvents : { cancel: "Escape", finish: 'Enter' }; + // We want to have some defaults, but also allow key bindings + // to be explicitly turned off + if (options?.keyEvents === null) { + this.keyEvents = { cancel: null, finish: null } + } else { + const defaultKeyEvents = { cancel: "Escape", finish: 'Enter' } + this.keyEvents = + options && options.keyEvents ? { ...defaultKeyEvents, ...options.keyEvents } : defaultKeyEvents; + } + } private close() { diff --git a/src/modes/polygon/polygon.spec.ts b/src/modes/polygon/polygon.spec.ts index c57c84ad..c7d31152 100644 --- a/src/modes/polygon/polygon.spec.ts +++ b/src/modes/polygon/polygon.spec.ts @@ -26,6 +26,19 @@ describe("TerraDrawPolygonMode", () => { }); expect(polygonMode.styles).toStrictEqual({ closingPointColor: "#ffffff" }); }); + + it("constructs with null key events", () => { + new TerraDrawPolygonMode({ + styles: { closingPointColor: "#ffffff" }, + keyEvents: null + }); + + new TerraDrawPolygonMode({ + styles: { closingPointColor: "#ffffff" }, + keyEvents: { cancel: null, finish: null } + }); + + }); }); describe("lifecycle", () => { @@ -334,11 +347,11 @@ describe("TerraDrawPolygonMode", () => { [number, number], [number, number] ] = [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - ] + [0, 0], + [0, 0], + [0, 0], + [0, 0], + ] ) => { unproject .mockReturnValueOnce({ lng: bbox[0][0], lat: bbox[0][1] }) @@ -359,6 +372,8 @@ describe("TerraDrawPolygonMode", () => { }); it("can create a polygon", () => { + mockClickBoundingBox(); + polygonMode.onClick({ lng: 0, lat: 0, @@ -444,7 +459,7 @@ describe("TerraDrawPolygonMode", () => { }); let features = store.copyAll(); - expect(features.length).toBe(2); + expect(features.length).toBe(1); // Create a new polygon polygonMode.onClick({ @@ -458,6 +473,7 @@ describe("TerraDrawPolygonMode", () => { features = store.copyAll(); expect(features.length).toBe(2); + }); it("can create a polygon with snapping enabled", () => { @@ -936,11 +952,11 @@ describe("TerraDrawPolygonMode", () => { [number, number], [number, number] ] = [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - ] + [0, 0], + [0, 0], + [0, 0], + [0, 0], + ] ) => { unproject .mockReturnValueOnce({ lng: bbox[0][0], lat: bbox[0][1] }) @@ -983,10 +999,40 @@ describe("TerraDrawPolygonMode", () => { features = store.copyAll(); expect(features.length).toBe(0); }); + + + it("does not delete the line when cancel is set to null", () => { + polygonMode = new TerraDrawPolygonMode({ keyEvents: { cancel: null } }); + const mockConfig = getMockModeConfig(polygonMode.mode); + + store = mockConfig.store; + project = mockConfig.project; + unproject = mockConfig.project; + polygonMode.register(mockConfig); + + polygonMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(1); + + polygonMode.onKeyUp({ key: "Escape" }); + + features = store.copyAll(); + expect(features.length).toBe(1); + }); }); describe('finish', () => { it("can create a polygon", () => { + mockClickBoundingBox(); + polygonMode.onClick({ lng: 0, lat: 0, @@ -1072,44 +1118,41 @@ describe("TerraDrawPolygonMode", () => { }); let features = store.copyAll(); - expect(features.length).toBe(2); + expect(features.length).toBe(1); - // Create a new polygon + // Finish drawing the polygon polygonMode.onKeyUp({ key: 'Enter' }); + // Creates a new polygon + polygonMode.onClick({ + lng: 4, + lat: 4, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + features = store.copyAll(); expect(features.length).toBe(2); }); }); - }); - describe("cleanUp", () => { - let store: GeoJSONStore; - let polygonMode: TerraDrawPolygonMode; - let onChange: jest.Mock; - let project: jest.Mock; - - beforeEach(() => { - jest.resetAllMocks(); - polygonMode = new TerraDrawPolygonMode(); + it("does not finish drawing polygon when finish is null", () => { + polygonMode = new TerraDrawPolygonMode({ keyEvents: { finish: null } }); const mockConfig = getMockModeConfig(polygonMode.mode); + store = mockConfig.store; - onChange = mockConfig.onChange; project = mockConfig.project; - + unproject = mockConfig.project; polygonMode.register(mockConfig); - }); - it("does not throw error if feature has not been created ", () => { - expect(() => { - polygonMode.cleanUp(); - }).not.toThrowError(); - }); + mockClickBoundingBox(); - it("cleans up correctly if drawing has started", () => { polygonMode.onClick({ lng: 0, lat: 0, @@ -1119,136 +1162,244 @@ describe("TerraDrawPolygonMode", () => { heldKeys: [], }); - expect(store.copyAll().length).toBe(1); + polygonMode.onMouseMove({ + lng: 1, + lat: 1, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); - polygonMode.cleanUp(); + polygonMode.onClick({ + lng: 1, + lat: 1, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + polygonMode.onMouseMove({ + lng: 2, + lat: 2, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); - // Removes the LineString that was being created - expect(store.copyAll().length).toBe(0); + polygonMode.onClick({ + lng: 2, + lat: 2, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + // closingPoints + project.mockReturnValueOnce({ x: 0, y: 0 }); + project.mockReturnValueOnce({ x: 0, y: 0 }); + + polygonMode.onMouseMove({ + lng: 3, + lat: 3, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + // closingPoints + project.mockReturnValueOnce({ x: 0, y: 0 }); + project.mockReturnValueOnce({ x: 0, y: 0 }); + + polygonMode.onClick({ + lng: 3, + lat: 3, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + polygonMode.onKeyUp({ + key: 'Enter' + }); + + let features = store.copyAll(); + + // 2 Closing points and 1 Polygon + // has not finished the polygon off and deleted the closing points + expect(features.length).toBe(3); }); }); +}); - describe("onDrag", () => { - it("does nothing", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); +describe("cleanUp", () => { + let store: GeoJSONStore; + let polygonMode: TerraDrawPolygonMode; + let onChange: jest.Mock; + let project: jest.Mock; - expect(() => { - polygonMode.onDrag(); - }).not.toThrowError(); - }); + beforeEach(() => { + jest.resetAllMocks(); + polygonMode = new TerraDrawPolygonMode(); + + const mockConfig = getMockModeConfig(polygonMode.mode); + store = mockConfig.store; + onChange = mockConfig.onChange; + project = mockConfig.project; + + polygonMode.register(mockConfig); }); - describe("onDragStart", () => { - it("does nothing", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); + it("does not throw error if feature has not been created ", () => { + expect(() => { + polygonMode.cleanUp(); + }).not.toThrowError(); + }); - expect(() => { - polygonMode.onDragStart(); - }).not.toThrowError(); + it("cleans up correctly if drawing has started", () => { + polygonMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], }); + + expect(store.copyAll().length).toBe(1); + + polygonMode.cleanUp(); + + // Removes the LineString that was being created + expect(store.copyAll().length).toBe(0); }); +}); - describe("onDragEnd", () => { - it("does nothing", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); +describe("onDrag", () => { + it("does nothing", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); - expect(() => { - polygonMode.onDragEnd(); - }).not.toThrowError(); - }); + expect(() => { + polygonMode.onDrag(); + }).not.toThrowError(); }); +}); - describe("styling", () => { - it("gets", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); - expect(polygonMode.styles).toStrictEqual({}); - }); +describe("onDragStart", () => { + it("does nothing", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); - it("set fails if non valid styling", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); + expect(() => { + polygonMode.onDragStart(); + }).not.toThrowError(); + }); +}); - expect(() => { - (polygonMode.styles as unknown) = "test"; - }).toThrowError(); +describe("onDragEnd", () => { + it("does nothing", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); - expect(polygonMode.styles).toStrictEqual({}); - }); + expect(() => { + polygonMode.onDragEnd(); + }).not.toThrowError(); + }); +}); - it("sets", () => { - const polygonMode = new TerraDrawPolygonMode(); - polygonMode.register(getMockModeConfig(polygonMode.mode)); +describe("styling", () => { + it("gets", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); + expect(polygonMode.styles).toStrictEqual({}); + }); - polygonMode.styles = { - closingPointColor: "#ffffff", - }; + it("set fails if non valid styling", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); - expect(polygonMode.styles).toStrictEqual({ - closingPointColor: "#ffffff", - }); + expect(() => { + (polygonMode.styles as unknown) = "test"; + }).toThrowError(); + + expect(polygonMode.styles).toStrictEqual({}); + }); + + it("sets", () => { + const polygonMode = new TerraDrawPolygonMode(); + polygonMode.register(getMockModeConfig(polygonMode.mode)); + + polygonMode.styles = { + closingPointColor: "#ffffff", + }; + + expect(polygonMode.styles).toStrictEqual({ + closingPointColor: "#ffffff", }); }); +}); - describe('styleFeature', () => { - it("returns the correct styles for polygon", () => { - const polygonMode = new TerraDrawPolygonMode({ - styles: { - fillColor: '#ffffff', - outlineColor: '#111111', - outlineWidth: 2, - fillOpacity: 0.5, - closingPointWidth: 2, - closingPointColor: '#dddddd', - closingPointOutlineWidth: 1, - closingPointOutlineColor: '#222222' - } - }); +describe('styleFeature', () => { + it("returns the correct styles for polygon", () => { + const polygonMode = new TerraDrawPolygonMode({ + styles: { + fillColor: '#ffffff', + outlineColor: '#111111', + outlineWidth: 2, + fillOpacity: 0.5, + closingPointWidth: 2, + closingPointColor: '#dddddd', + closingPointOutlineWidth: 1, + closingPointOutlineColor: '#222222' + } + }); - expect( - polygonMode.styleFeature({ - type: "Feature", - geometry: { type: "Polygon", coordinates: [] }, - properties: { mode: "polygon" } - }) - ).toMatchObject({ - polygonFillColor: '#ffffff', - polygonOutlineColor: '#111111', - polygonOutlineWidth: 2, - polygonFillOpacity: 0.5 - }); + expect( + polygonMode.styleFeature({ + type: "Feature", + geometry: { type: "Polygon", coordinates: [] }, + properties: { mode: "polygon" } + }) + ).toMatchObject({ + polygonFillColor: '#ffffff', + polygonOutlineColor: '#111111', + polygonOutlineWidth: 2, + polygonFillOpacity: 0.5 }); + }); - it("returns the correct styles for poiny", () => { - const polygonMode = new TerraDrawPolygonMode({ - styles: { - fillColor: '#ffffff', - outlineColor: '#111111', - outlineWidth: 2, - fillOpacity: 0.5, - closingPointWidth: 2, - closingPointColor: '#dddddd', - closingPointOutlineWidth: 1, - closingPointOutlineColor: '#222222' - } - }); + it("returns the correct styles for poiny", () => { + const polygonMode = new TerraDrawPolygonMode({ + styles: { + fillColor: '#ffffff', + outlineColor: '#111111', + outlineWidth: 2, + fillOpacity: 0.5, + closingPointWidth: 2, + closingPointColor: '#dddddd', + closingPointOutlineWidth: 1, + closingPointOutlineColor: '#222222' + } + }); - expect( - polygonMode.styleFeature({ - type: "Feature", - geometry: { type: "Point", coordinates: [] }, - properties: { mode: "polygon" } - }) - ).toMatchObject({ - pointWidth: 2, - pointColor: '#dddddd', - pointOutlineColor: "#222222", - pointOutlineWidth: 1 - }); + expect( + polygonMode.styleFeature({ + type: "Feature", + geometry: { type: "Point", coordinates: [] }, + properties: { mode: "polygon" } + }) + ).toMatchObject({ + pointWidth: 2, + pointColor: '#dddddd', + pointOutlineColor: "#222222", + pointOutlineWidth: 1 }); }); }); diff --git a/src/modes/rectangle/rectangle.mode.spec.ts b/src/modes/rectangle/rectangle.mode.spec.ts index f3de99a7..00eceebf 100644 --- a/src/modes/rectangle/rectangle.mode.spec.ts +++ b/src/modes/rectangle/rectangle.mode.spec.ts @@ -23,6 +23,19 @@ describe("TerraDrawRectangleMode", () => { fillColor: "#ffffff", }); }); + + it("constructs with null key events", () => { + new TerraDrawRectangleMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: null + }); + + new TerraDrawRectangleMode({ + styles: { fillColor: "#ffffff" }, + keyEvents: { cancel: null, finish: null } + }); + + }); }); describe("lifecycle", () => { @@ -174,21 +187,61 @@ describe("TerraDrawRectangleMode", () => { describe("onKeyUp", () => { + let rectangleMode: TerraDrawRectangleMode; let store: GeoJSONStore; let onChange: jest.Mock; - beforeEach(() => { + it("finishes drawing circle on finish key press", () => { rectangleMode = new TerraDrawRectangleMode(); - const mockConfig = getMockModeConfig(rectangleMode.mode); store = new GeoJSONStore(); store = mockConfig.store; onChange = mockConfig.onChange; rectangleMode.register(mockConfig); + + rectangleMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + let features = store.copyAll(); + expect(features.length).toBe(1); + + rectangleMode.onKeyUp({ + key: 'Enter' + }); + + rectangleMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + + features = store.copyAll(); + // Two as the rectangle has been closed via enter + expect(features.length).toBe(2); + + expect(onChange).toBeCalledTimes(2); + expect(onChange).toBeCalledWith([expect.any(String)], "create"); + }); - it("finishes drawing circle on finish key press", () => { + it("does not finish on key press when keyEvents null", () => { + rectangleMode = new TerraDrawRectangleMode({ keyEvents: null }); + const mockConfig = getMockModeConfig(rectangleMode.mode); + store = new GeoJSONStore(); + store = mockConfig.store; + onChange = mockConfig.onChange; + rectangleMode.register(mockConfig); + rectangleMode.onClick({ lng: 0, lat: 0, @@ -205,7 +258,18 @@ describe("TerraDrawRectangleMode", () => { key: 'Enter' }); + rectangleMode.onClick({ + lng: 0, + lat: 0, + containerX: 0, + containerY: 0, + button: "left", + heldKeys: [], + }); + features = store.copyAll(); + + // Only one as the click will close the rectangle expect(features.length).toBe(1); expect(onChange).toBeCalledTimes(1); diff --git a/src/modes/rectangle/rectangle.mode.ts b/src/modes/rectangle/rectangle.mode.ts index d8f66cb2..b6f7a59e 100644 --- a/src/modes/rectangle/rectangle.mode.ts +++ b/src/modes/rectangle/rectangle.mode.ts @@ -10,8 +10,8 @@ import { getDefaultStyling } from "../../util/styling"; import { TerraDrawBaseDrawMode } from "../base.mode"; type TerraDrawRectangleModeKeyEvents = { - cancel: KeyboardEvent["key"]; - finish: KeyboardEvent["key"]; + cancel: KeyboardEvent["key"] | null + finish: KeyboardEvent["key"] | null }; type FreehandPolygonStyling = { @@ -31,12 +31,18 @@ export class TerraDrawRectangleMode extends TerraDrawBaseDrawMode; - keyEvents?: TerraDrawRectangleModeKeyEvents; + keyEvents?: TerraDrawRectangleModeKeyEvents | null; }) { super(options); - - this.keyEvents = - options && options.keyEvents ? options.keyEvents : { cancel: "Escape", finish: 'Enter' }; + // We want to have some defaults, but also allow key bindings + // to be explicitly turned off + if (options?.keyEvents === null) { + this.keyEvents = { cancel: null, finish: null } + } else { + const defaultKeyEvents = { cancel: "Escape", finish: 'Enter' } + this.keyEvents = + options && options.keyEvents ? { ...defaultKeyEvents, ...options.keyEvents } : defaultKeyEvents; + } } private close() { diff --git a/src/modes/select/select.mode.ts b/src/modes/select/select.mode.ts index d343bfac..cdcf3532 100644 --- a/src/modes/select/select.mode.ts +++ b/src/modes/select/select.mode.ts @@ -21,10 +21,10 @@ import { GeoJSONStoreFeatures } from "../../store/store"; import { getDefaultStyling } from "../../util/styling"; type TerraDrawSelectModeKeyEvents = { - deselect: KeyboardEvent["key"]; - delete: KeyboardEvent["key"]; - rotate: KeyboardEvent["key"]; - scale: KeyboardEvent["key"]; + deselect: KeyboardEvent["key"] | null + delete: KeyboardEvent["key"] | null + rotate: KeyboardEvent["key"] | null + scale: KeyboardEvent["key"] | null }; type ModeFlags = { @@ -79,17 +79,22 @@ export class TerraDrawSelectMode extends TerraDrawBaseDrawMode styles?: Partial pointerDistance?: number; flags?: { [mode: string]: ModeFlags }; - keyEvents?: TerraDrawSelectModeKeyEvents; + keyEvents?: TerraDrawSelectModeKeyEvents | null dragEventThrottle?: number; }) { super(options); this.flags = options && options.flags ? options.flags : {}; - this.keyEvents = - options && options.keyEvents - ? options.keyEvents - : { deselect: "Escape", delete: "Delete", rotate: "r", scale: "s" }; + // We want to have some defaults, but also allow key bindings + // to be explicitly turned off + if (options?.keyEvents === null) { + this.keyEvents = { deselect: null, delete: null, rotate: null, scale: null } + } else { + const defaultKeyEvents = { deselect: "Escape", delete: "Delete", rotate: "r", scale: "s" }; + this.keyEvents = + options && options.keyEvents ? { ...defaultKeyEvents, ...options.keyEvents } : defaultKeyEvents; + } this.dragEventThrottle = (options && diff --git a/src/modes/select/select.spec.ts b/src/modes/select/select.spec.ts index 98fd22d4..026ccca8 100644 --- a/src/modes/select/select.spec.ts +++ b/src/modes/select/select.spec.ts @@ -97,11 +97,11 @@ describe("TerraDrawSelectMode", () => { [number, number], [number, number] ] = [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - ] + [0, 0], + [0, 0], + [0, 0], + [0, 0], + ] ) => { unproject .mockReturnValueOnce({ lng: bbox[0][0], lat: bbox[0][1] }) @@ -128,6 +128,23 @@ describe("TerraDrawSelectMode", () => { }, }); }); + + it("constructs with null keyEvents", () => { + new TerraDrawSelectMode({ + pointerDistance: 40, + keyEvents: null + }); + + new TerraDrawSelectMode({ + pointerDistance: 40, + keyEvents: { + deselect: null, + delete: null, + rotate: null, + scale: null, + }, + }); + }); }); describe("lifecycle", () => {