Skip to content

Commit 7c519d6

Browse files
javier-godoypaodb
authored andcommitted
feat!: invoke all the applicable handlers for event
1 parent 32f1e8c commit 7c519d6

File tree

2 files changed

+132
-15
lines changed

2 files changed

+132
-15
lines changed

src/main/resources/META-INF/frontend/fc-xterm/xterm-element.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,32 @@ class CustomKeyEventHandlerRegistry {
7474
delete this.handlers[id];
7575
}
7676

77-
handle(context: any, ev: KeyboardEvent) : void {
78-
//invoke latest applicable handler for event
79-
let j=-1;
77+
handle(context: XTermElement, ev: KeyboardEvent) : boolean {
78+
//invoke all the applicable handlers for event
79+
let stopImmediatePropagation = ev.stopImmediatePropagation.bind(ev);
80+
81+
let immediatePropagationStopped = false;
82+
ev.stopImmediatePropagation= () => {
83+
stopImmediatePropagation();
84+
immediatePropagationStopped=true;
85+
};
86+
87+
let handled = false;
8088
for(const i in this.handlers) {
81-
if (/\d+/.test(i) && this.handlers[i].predicate(ev)) j=Math.max(j, parseInt(i));
82-
}
83-
if (j>=0) {
84-
ev.cancelBubble=true;
85-
this.handlers[j].handle?.call(context, ev);
86-
}
89+
if (/\d+/.test(i) && this.handlers[i].predicate(ev)) {
90+
handled=true;
91+
this.handlers[i].handle?.call(context, ev);
92+
if (immediatePropagationStopped) break;
93+
}
94+
}
95+
96+
if ((ev as any).requestCustomEvent) {
97+
context.dispatchEvent(new CustomEvent('CustomKey', {detail: ev}));
98+
}
99+
100+
return handled;
87101
}
102+
88103
}
89104

90105
export interface TerminalMixin {
@@ -153,11 +168,7 @@ export class XTermElement extends LitElement implements TerminalMixin {
153168

154169
term.attachCustomKeyEventHandler(ev => {
155170
if (ev.type!=='keydown') return false;
156-
157-
this.customKeyEventHandlers.handle(this, ev);
158-
if (ev.cancelBubble) return false;
159-
160-
return true;
171+
return !this.customKeyEventHandlers.handle(this, ev);
161172
});
162173
}
163174

@@ -187,7 +198,7 @@ export class XTermElement extends LitElement implements TerminalMixin {
187198
}
188199

189200
registerCustomKeyListener(customKey: CustomKey) : integer {
190-
let handler : KeyboardEventHandler = (ev: KeyboardEvent) => this.dispatchEvent(new CustomEvent('CustomKey', {detail: ev}));
201+
let handler : KeyboardEventHandler = (ev: KeyboardEvent) => (ev as any).requestCustomEvent = true;
191202
return this.customKeyEventHandlers.register(customKey, handler).id;
192203
}
193204

src/test/java/com/flowingcode/vaadin/addons/xterm/integration/XTermIT.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import static org.hamcrest.Matchers.is;
2323
import static org.hamcrest.Matchers.not;
24+
import static org.hamcrest.Matchers.notNullValue;
2425
import static org.junit.Assert.assertThat;
2526
import com.vaadin.testbench.TestBenchElement;
2627
import org.hamcrest.Description;
@@ -85,4 +86,109 @@ public void writeText() throws InterruptedException {
8586
assertThat(term.currentLine(), is("WORLD"));
8687
assertThat(term.cursorPosition(), is(pos.advance(0, 1).plus(5, 0)));
8788
}
89+
90+
private Integer getKeyCount() {
91+
return ((Long)getCommandExecutor().executeScript("return keyCount")).intValue();
92+
}
93+
94+
@Test
95+
public void customKeyHandlerLowLevel() throws InterruptedException {
96+
XTermElement term = $(XTermElement.class).first();
97+
term.executeScript("window.keyCount=0");
98+
99+
// register an event handler
100+
term.executeScript(
101+
"r1=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>++keyCount)");
102+
assertThat(getKeyCount(), is(0));
103+
104+
// fire it
105+
term.sendKeys("E");
106+
assertThat(term.currentLine(), is("E"));
107+
assertThat(getKeyCount(), is(1));
108+
109+
// register another event handler for the same key
110+
term.executeScript(
111+
"r2=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>++keyCount)");
112+
113+
// fire it: increment is performed twice (by each handler)
114+
term.sendKeys("E");
115+
assertThat(term.currentLine(), is("EE"));
116+
assertThat(getKeyCount(), is(3));
117+
118+
// deregister
119+
term.executeScript("r1.dispose()");
120+
term.sendKeys("E");
121+
assertThat(getKeyCount(), is(4));
122+
123+
term.executeScript("r2.dispose()");
124+
term.sendKeys("E");
125+
assertThat(getKeyCount(), is(4));
126+
}
127+
128+
@Test
129+
public void customKeyHandlerRegistrationOrder() throws InterruptedException {
130+
// assert that custom key handlers are processed in registration order
131+
XTermElement term = $(XTermElement.class).first();
132+
term.executeScript("window.keyCount=0");
133+
134+
term.executeScript(
135+
"r1=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>keyCount=keyCount*10+1)");
136+
term.executeScript(
137+
"r2=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>keyCount=keyCount*10+2)");
138+
term.sendKeys("E");
139+
assertThat(getKeyCount(), is(12));
140+
term.executeScript("r1.dispose()");
141+
term.executeScript("r2.dispose()");
142+
}
143+
144+
@Test
145+
public void customKeyHandlerStopImmediatePropagation() throws InterruptedException {
146+
// since custom key handlers are processed in registration order
147+
// then r2 will prevent the processing of r3
148+
XTermElement term = $(XTermElement.class).first();
149+
term.executeScript("window.keyCount=0");
150+
151+
term.executeScript(
152+
"r1=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>keyCount=keyCount*10+1)");
153+
term.executeScript(
154+
"r2=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>ev.stopImmediatePropagation())");
155+
term.executeScript(
156+
"r3=this.customKeyEventHandlers.register(ev=> ev.key=='E', ev=>keyCount=keyCount*10+3)");
157+
term.sendKeys("E");
158+
assertThat(getKeyCount(), is(1));
159+
term.executeScript("r1.dispose()");
160+
term.executeScript("r2.dispose()");
161+
term.executeScript("r3.dispose()");
162+
}
163+
164+
@Test
165+
public void customKeyHandlerHighLevel() throws InterruptedException {
166+
XTermElement term = $(XTermElement.class).first();
167+
term.executeScript("window.keyCount=0");
168+
169+
// register interest on CustomKey event
170+
Long id1 = (Long) term.executeScript("return this.registerCustomKeyListener({key:'E'})");
171+
term.executeScript("this.addEventListener('CustomKey', ()=>++keyCount)");
172+
assertThat(id1, is(notNullValue()));
173+
term.sendKeys("E");
174+
assertThat(getKeyCount(), is(1));
175+
176+
// register interest again on CustomKey event for the same key
177+
// increment is performed by the CustomKey listener, which is called once
178+
Long id2 = (Long) term.executeScript("return this.registerCustomKeyListener({key:'E'})");
179+
assertThat(id2, is(notNullValue()));
180+
assertThat(id2, is(not(id1)));
181+
term.sendKeys("E");
182+
assertThat(getKeyCount(), is(2));
183+
184+
// deregister
185+
term.executeScript("this.customKeyEventHandlers.remove(arguments[0])", id1);
186+
term.sendKeys("E");
187+
assertThat(getKeyCount(), is(3));
188+
189+
term.executeScript("this.customKeyEventHandlers.remove(arguments[0])", id2);
190+
term.sendKeys("E");
191+
assertThat(getKeyCount(), is(3));
192+
}
193+
88194
}

0 commit comments

Comments
 (0)