diff --git a/index.html b/index.html index 2e4f46c..db58067 100644 --- a/index.html +++ b/index.html @@ -156,6 +156,10 @@ console.log({ evt }); }); + contextmenu.on('open', function (evt) { + console.log({ evt }); + }); + map.on('moveend', () => { console.log('moveend', contextmenu.isOpen()); }); diff --git a/src/main.ts b/src/main.ts index b727ea6..c36c243 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,9 +3,21 @@ import type { Coordinate } from 'ol/coordinate'; import OlMap from 'ol/Map'; import Control from 'ol/control/Control'; import BaseEvent from 'ol/events/Event'; +import { EventsKey } from 'ol/events'; +import { Types as ObjectEventTypes } from 'ol/ObjectEventType'; +import { CombinedOnSignature, EventTypes as OlEventTypes, OnSignature } from 'ol/Observable'; +import { ObjectEvent } from 'ol/Object'; import { CSS_CLASSES, DEFAULT_ITEMS, DEFAULT_OPTIONS } from './constants'; -import { CallbackObject, CustomEventTypes, EventTypes, Item, MenuEntry, Options } from './types'; +import { + CallbackObject, + ContextMenuEvent, + CustomEventTypes, + EventTypes, + Item, + MenuEntry, + Options, +} from './types'; import emitter from './emitter'; import { addMenuEntries, getLineHeight } from './helpers/dom'; @@ -40,6 +52,54 @@ export default class ContextMenu extends Control { protected menuEntries: Map = new Map(); + declare on: OnSignature & + OnSignature< + `${CustomEventTypes.BEFOREOPEN}` | `${CustomEventTypes.OPEN}`, + ContextMenuEvent, + EventsKey + > & + OnSignature & + CombinedOnSignature< + | `${CustomEventTypes.BEFOREOPEN}` + | `${CustomEventTypes.OPEN}` + | ObjectEventTypes + | `${CustomEventTypes.CLOSE}` + | OlEventTypes, + EventsKey + >; + + declare once: OnSignature & + OnSignature< + `${CustomEventTypes.BEFOREOPEN}` | `${CustomEventTypes.OPEN}`, + ContextMenuEvent, + EventsKey + > & + OnSignature & + CombinedOnSignature< + | `${CustomEventTypes.BEFOREOPEN}` + | `${CustomEventTypes.OPEN}` + | ObjectEventTypes + | `${CustomEventTypes.CLOSE}` + | OlEventTypes, + EventsKey + >; + + declare un: OnSignature & + OnSignature< + `${CustomEventTypes.BEFOREOPEN}` | `${CustomEventTypes.OPEN}`, + ContextMenuEvent, + EventsKey + > & + OnSignature & + CombinedOnSignature< + | `${CustomEventTypes.BEFOREOPEN}` + | `${CustomEventTypes.OPEN}` + | ObjectEventTypes + | `${CustomEventTypes.CLOSE}` + | OlEventTypes, + void + >; + options: Options; constructor(opts: Partial = {}) { @@ -72,14 +132,6 @@ export default class ContextMenu extends Control { }; this.disabled = false; this.opened = false; - - emitter.on( - CustomEventTypes.ADD_MENU_ENTRY, - (item: MenuEntry, element: HTMLLIElement) => { - this.handleAddMenuEntry(item, element); - }, - this - ); } clear() { @@ -164,6 +216,14 @@ export default class ContextMenu extends Control { this.handleMapMove(); }); + emitter.on( + CustomEventTypes.ADD_MENU_ENTRY, + (item: MenuEntry, element: HTMLLIElement) => { + this.handleAddMenuEntry(item, element); + }, + this + ); + this.items = this.options.defaultItems ? this.options.items.concat(DEFAULT_ITEMS) : this.options.items; @@ -190,6 +250,8 @@ export default class ContextMenu extends Control { this.map .getViewport() .removeEventListener(this.options.eventType, this.contextMenuEventListener, false); + + emitter.off(CustomEventTypes.ADD_MENU_ENTRY); } protected removeMenuEntry(id: string) { @@ -203,11 +265,13 @@ export default class ContextMenu extends Control { protected handleContextMenu(evt: MouseEvent) { this.coordinate = this.map.getEventCoordinate(evt); this.pixel = this.map.getEventPixel(evt); - this.dispatchEvent({ - type: CustomEventTypes.BEFOREOPEN, - pixel: this.pixel, - coordinate: this.coordinate, - } as unknown as BaseEvent); + this.dispatchEvent( + new ContextMenuEvent({ + type: CustomEventTypes.BEFOREOPEN, + pixel: this.pixel, + coordinate: this.coordinate, + }) + ); if (this.disabled) return; @@ -238,6 +302,14 @@ export default class ContextMenu extends Control { this.opened = true; this.positionContainer(); this.container.classList.remove(CSS_CLASSES.hidden); + + this.dispatchEvent( + new ContextMenuEvent({ + type: CustomEventTypes.OPEN, + pixel: this.pixel, + coordinate: this.coordinate, + }) + ); } protected getMenuEntriesLength(): number { diff --git a/src/types.ts b/src/types.ts index 69b318e..1bb016c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import type { Coordinate } from 'ol/coordinate'; import type { Pixel } from 'ol/pixel'; import { Map as OlMap } from 'ol'; -import BaseEvent from 'ol/events/Event'; +import BaseEvent from 'ol/events/Event.js'; export enum EventTypes { CONTEXTMENU = 'contextmenu', @@ -16,9 +16,19 @@ export enum CustomEventTypes { ADD_MENU_ENTRY = 'add-menu-entry', } -export interface ContextMenuEvent extends BaseEvent { - coordinate: Coordinate; - pixel: Pixel; +export class ContextMenuEvent extends BaseEvent { + public coordinate: Coordinate; + public pixel: Pixel; + + constructor(options: { + type: `${CustomEventTypes.BEFOREOPEN}` | `${CustomEventTypes.OPEN}`; + coordinate: Coordinate; + pixel: Pixel; + }) { + super(options.type); + this.pixel = options.pixel; + this.coordinate = options.coordinate; + } } export type CallbackObject = { diff --git a/test/instance.spec.ts b/test/instance.spec.ts index f1440bf..a20d307 100644 --- a/test/instance.spec.ts +++ b/test/instance.spec.ts @@ -1,6 +1,9 @@ +import OlMap from 'ol/Map'; +import BaseEvent from 'ol/events/Event'; + import { DEFAULT_ITEMS, DEFAULT_OPTIONS } from '../src/constants'; import ContextMenu from '../src/main'; -import { Item } from '../src/types'; +import { ContextMenuEvent, Item } from '../src/types'; const items: Item[] = [ '-', @@ -46,6 +49,11 @@ describe('Instance options', () => { describe('Instance methods', () => { const menu = new ContextMenu(); + // Add map to initialize the emitter + new OlMap({ + controls: [menu], + }); + test('getDefaultItems()', () => { expect(toJSON(menu.getDefaultItems())).toEqual(toJSON(DEFAULT_ITEMS)); }); @@ -103,3 +111,21 @@ describe('Throw errors', () => { }).toThrow(); }); }); + +// EVENTS +// The lines below are not a test per se, but as an ESLint indicator if the events are not correctly declared +const context = new ContextMenu(); + +context.on('beforeopen', (evt: ContextMenuEvent) => { + evt.pixel; + evt.coordinate; +}); + +context.on('open', (evt: ContextMenuEvent) => { + evt.pixel; + evt.coordinate; +}); + +context.on('close', (evt: BaseEvent) => { + evt.target; +}); diff --git a/vite.config.ts b/vite.config.ts index e199010..4dd404f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,9 +9,10 @@ import { CSS_CLASSES } from './src/constants'; const pkg = JSON.parse(readFileSync('./package.json', 'utf8')); // eslint-disable-next-line @typescript-eslint/no-shadow -const external = ['ol/control/Control', 'ol/PluggableMap', 'ol']; +const external = ['ol/control/Control', 'ol/events/Event', 'ol/PluggableMap', 'ol']; const globals = { 'ol/control/Control': 'ol.control.Control', + 'ol/events/Event': 'ol.events.Event', ol: 'ol', };