Skip to content

Commit a30b0a3

Browse files
nk-codingspoenemann
authored andcommitted
pointer events support
Signed-off-by: Niklas Krieger <niklask.coding@gmail.com>
1 parent 09caf1e commit a30b0a3

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

packages/sprotty/src/base/di.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { SetModelCommand } from "./features/set-model";
3939
import { UIExtensionRegistry, SetUIExtensionVisibilityCommand } from "./ui-extensions/ui-extension-registry";
4040
import { DefaultDiagramLocker } from "./actions/diagram-locker";
4141
import { TouchTool } from "./views/touch-tool";
42+
import { PointerTool } from "./views/pointer-tool";
4243

4344
const defaultContainerModule = new ContainerModule((bind, _unbind, isBound) => {
4445
// Logging ---------------------------------------------
@@ -135,6 +136,8 @@ const defaultContainerModule = new ContainerModule((bind, _unbind, isBound) => {
135136
bind(TYPES.HiddenVNodePostprocessor).toService(CssClassPostprocessor);
136137
bind(MouseTool).toSelf().inSingletonScope();
137138
bind(TYPES.IVNodePostprocessor).toService(MouseTool);
139+
bind(PointerTool).toSelf().inSingletonScope();
140+
bind(TYPES.IVNodePostprocessor).toService(PointerTool);
138141
bind(TouchTool).toSelf().inSingletonScope();
139142
bind(TYPES.IVNodePostprocessor).toService(TouchTool);
140143
bind(KeyTool).toSelf().inSingletonScope();

packages/sprotty/src/base/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const TYPES = {
5858
ModelViewer: Symbol('ModelViewer'),
5959
MouseListener: Symbol('MouseListener'),
6060
PatcherProvider: Symbol('PatcherProvider'),
61+
IPointerListener: Symbol('IPointerListener'),
6162
IPopupModelProvider: Symbol('IPopupModelProvider'),
6263
PopupModelViewer: Symbol('PopupModelViewer'),
6364
PopupMouseListener: Symbol('PopupMouseListener'),
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 TypeFox and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { inject, injectable, multiInject, optional } from "inversify";
18+
import { VNode } from "snabbdom";
19+
import { Action, isAction } from "sprotty-protocol/lib/actions";
20+
import { IActionDispatcher } from "../actions/action-dispatcher";
21+
import { SModelElementImpl, SModelRootImpl } from "../model/smodel";
22+
import { TYPES } from "../types";
23+
import { DOMHelper } from "./dom-helper";
24+
import { IVNodePostprocessor } from "./vnode-postprocessor";
25+
import { on } from "./vnode-utils";
26+
27+
@injectable()
28+
export class PointerTool implements IVNodePostprocessor {
29+
@inject(TYPES.IActionDispatcher) protected actionDispatcher!: IActionDispatcher;
30+
@inject(TYPES.DOMHelper) protected domHelper!: DOMHelper;
31+
32+
constructor(@multiInject(TYPES.IPointerListener) @optional() protected pointerListeners: IPointerListener[] = []) {}
33+
34+
register(pointerListener: IPointerListener) {
35+
this.pointerListeners.push(pointerListener);
36+
}
37+
38+
deregister(pointerListener: IPointerListener) {
39+
const index = this.pointerListeners.indexOf(pointerListener);
40+
if (index >= 0) this.pointerListeners.splice(index, 1);
41+
}
42+
43+
protected getTargetElement(model: SModelRootImpl, event: PointerEvent): SModelElementImpl | undefined {
44+
let target = event.target as Element;
45+
const index = model.index;
46+
while (target) {
47+
if (target.id) {
48+
const element = index.getById(this.domHelper.findSModelIdByDOMElement(target));
49+
if (element !== undefined) return element;
50+
}
51+
target = target.parentNode as Element;
52+
}
53+
return undefined;
54+
}
55+
56+
protected handleEvent(methodName: PointerEventKind, model: SModelRootImpl, event: PointerEvent) {
57+
const element = this.getTargetElement(model, event);
58+
if (!element) return;
59+
const actions = this.pointerListeners
60+
.map((listener) => listener[methodName](element, event))
61+
.reduce((a, b) => a.concat(b), []);
62+
if (actions.length > 0) {
63+
event.preventDefault();
64+
for (const actionOrPromise of actions) {
65+
if (isAction(actionOrPromise)) {
66+
this.actionDispatcher.dispatch(actionOrPromise);
67+
} else {
68+
actionOrPromise.then((action: Action) => {
69+
this.actionDispatcher.dispatch(action);
70+
});
71+
}
72+
}
73+
}
74+
}
75+
76+
decorate(vnode: VNode, element: SModelElementImpl): VNode {
77+
if (element instanceof SModelRootImpl) {
78+
on(vnode, "pointerover", this.handleEvent.bind(this, "pointerOver", element) as (e: Event) => void);
79+
on(vnode, "pointerenter", this.handleEvent.bind(this, "pointerEnter", element) as (e: Event) => void);
80+
on(vnode, "pointerdown", this.handleEvent.bind(this, "pointerDown", element) as (e: Event) => void);
81+
on(vnode, "pointermove", this.handleEvent.bind(this, "pointerMove", element) as (e: Event) => void);
82+
on(vnode, "pointerup", this.handleEvent.bind(this, "pointerUp", element) as (e: Event) => void);
83+
on(vnode, "pointercancel", this.handleEvent.bind(this, "pointerCancel", element) as (e: Event) => void);
84+
on(vnode, "pointerout", this.handleEvent.bind(this, "pointerOut", element) as (e: Event) => void);
85+
on(vnode, "pointerleave", this.handleEvent.bind(this, "pointerLeave", element) as (e: Event) => void);
86+
on(
87+
vnode,
88+
"gotpointercapture",
89+
this.handleEvent.bind(this, "gotPointerCapture", element) as (e: Event) => void
90+
);
91+
on(
92+
vnode,
93+
"lostpointercapture",
94+
this.handleEvent.bind(this, "lostPointerCapture", element) as (e: Event) => void
95+
);
96+
}
97+
return vnode;
98+
}
99+
100+
postUpdate() {
101+
}
102+
}
103+
104+
export type PointerEventKind =
105+
| "pointerOver"
106+
| "pointerEnter"
107+
| "pointerDown"
108+
| "pointerMove"
109+
| "pointerUp"
110+
| "pointerCancel"
111+
| "pointerOut"
112+
| "pointerLeave"
113+
| "gotPointerCapture"
114+
| "lostPointerCapture";
115+
116+
export interface IPointerListener {
117+
118+
pointerOver(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
119+
120+
pointerEnter(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
121+
122+
pointerDown(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
123+
124+
pointerMove(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
125+
126+
pointerUp(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
127+
128+
pointerCancel(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
129+
130+
pointerOut(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
131+
132+
pointerLeave(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
133+
134+
gotPointerCapture(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
135+
136+
lostPointerCapture(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[];
137+
}
138+
139+
@injectable()
140+
export class PointerListener implements IPointerListener {
141+
142+
pointerOver(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
143+
return [];
144+
}
145+
146+
pointerEnter(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
147+
return [];
148+
}
149+
150+
pointerDown(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
151+
return [];
152+
}
153+
154+
pointerMove(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
155+
return [];
156+
}
157+
158+
pointerUp(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
159+
return [];
160+
}
161+
162+
pointerCancel(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
163+
return [];
164+
}
165+
166+
pointerOut(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
167+
return [];
168+
}
169+
170+
pointerLeave(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
171+
return [];
172+
}
173+
174+
gotPointerCapture(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
175+
return [];
176+
}
177+
178+
lostPointerCapture(target: SModelElementImpl, event: PointerEvent): (Action | Promise<Action>)[] {
179+
return [];
180+
}
181+
182+
}

packages/sprotty/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ export * from './base/ui-extensions/ui-extension';
4242

4343
export * from './base/views/key-tool';
4444
export * from './base/views/mouse-tool';
45+
export * from './base/views/pointer-tool';
4546
export * from './base/views/thunk-view';
47+
export * from './base/views/touch-tool';
4648
export * from './base/views/view';
4749
export * from './base/views/viewer-cache';
4850
export * from './base/views/viewer-options';

0 commit comments

Comments
 (0)