@tuidom/core 0.1.0
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.
- package/dist/backend/iTerminalBackend.d.ts +41 -0
- package/dist/backend/iTerminalBackend.js +1 -0
- package/dist/common/colorUtils.d.ts +19 -0
- package/dist/common/colorUtils.js +30 -0
- package/dist/common/displayLine.d.ts +72 -0
- package/dist/common/displayLine.js +190 -0
- package/dist/common/disposable.d.ts +9 -0
- package/dist/common/disposable.js +18 -0
- package/dist/common/geometryPromitives.d.ts +47 -0
- package/dist/common/geometryPromitives.js +115 -0
- package/dist/common/iTerminalSurface.d.ts +68 -0
- package/dist/common/iTerminalSurface.js +8 -0
- package/dist/common/measureTextWidth.d.ts +21 -0
- package/dist/common/measureTextWidth.js +43 -0
- package/dist/common/styleFlags.d.ts +17 -0
- package/dist/common/styleFlags.js +16 -0
- package/dist/common/textTruncation.d.ts +30 -0
- package/dist/common/textTruncation.js +124 -0
- package/dist/common/typingUtils.d.ts +1 -0
- package/dist/common/typingUtils.js +3 -0
- package/dist/common/unicodeWidth.d.ts +18 -0
- package/dist/common/unicodeWidth.js +339 -0
- package/dist/dom/borderStyle.d.ts +31 -0
- package/dist/dom/borderStyle.js +40 -0
- package/dist/dom/compositeElement.d.ts +20 -0
- package/dist/dom/compositeElement.js +44 -0
- package/dist/dom/events/contextMenuEventSource.d.ts +17 -0
- package/dist/dom/events/contextMenuEventSource.js +44 -0
- package/dist/dom/events/focusManager.d.ts +13 -0
- package/dist/dom/events/focusManager.js +56 -0
- package/dist/dom/events/mouseEventDispatcher.d.ts +24 -0
- package/dist/dom/events/mouseEventDispatcher.js +177 -0
- package/dist/dom/events/tuiEventBase.d.ts +25 -0
- package/dist/dom/events/tuiEventBase.js +39 -0
- package/dist/dom/events/tuiFocusEvent.d.ts +6 -0
- package/dist/dom/events/tuiFocusEvent.js +8 -0
- package/dist/dom/events/tuiKeyboardEvent.d.ts +21 -0
- package/dist/dom/events/tuiKeyboardEvent.js +20 -0
- package/dist/dom/events/tuiMouseEvent.d.ts +40 -0
- package/dist/dom/events/tuiMouseEvent.js +32 -0
- package/dist/dom/events/tuiPasteEvent.d.ts +10 -0
- package/dist/dom/events/tuiPasteEvent.js +13 -0
- package/dist/dom/overlayLayer.d.ts +92 -0
- package/dist/dom/overlayLayer.js +343 -0
- package/dist/dom/styles/index.d.ts +4 -0
- package/dist/dom/styles/index.js +2 -0
- package/dist/dom/styles/styleTokens.d.ts +114 -0
- package/dist/dom/styles/styleTokens.js +122 -0
- package/dist/dom/styles/tuiStyle.d.ts +75 -0
- package/dist/dom/styles/tuiStyle.js +121 -0
- package/dist/dom/tuiApplication.d.ts +57 -0
- package/dist/dom/tuiApplication.js +231 -0
- package/dist/dom/tuiElement.d.ts +528 -0
- package/dist/dom/tuiElement.js +1168 -0
- package/dist/dom/tuiSelector.d.ts +9 -0
- package/dist/dom/tuiSelector.js +82 -0
- package/dist/dom/validateTree.d.ts +42 -0
- package/dist/dom/validateTree.js +125 -0
- package/dist/input/convertToken.d.ts +3 -0
- package/dist/input/convertToken.js +114 -0
- package/dist/input/keyEvent.d.ts +46 -0
- package/dist/input/keyEvent.js +26 -0
- package/dist/input/keyInputParser.d.ts +72 -0
- package/dist/input/keyInputParser.js +249 -0
- package/dist/input/mouseTracking.d.ts +18 -0
- package/dist/input/mouseTracking.js +18 -0
- package/dist/input/parseInput.d.ts +13 -0
- package/dist/input/parseInput.js +17 -0
- package/dist/input/rawTerminalToken.d.ts +142 -0
- package/dist/input/rawTerminalToken.js +2 -0
- package/dist/input/serializeKey.d.ts +14 -0
- package/dist/input/serializeKey.js +179 -0
- package/dist/input/serializeMouse.d.ts +26 -0
- package/dist/input/serializeMouse.js +38 -0
- package/dist/input/tokenize.d.ts +59 -0
- package/dist/input/tokenize.js +681 -0
- package/dist/rendering/cell.d.ts +24 -0
- package/dist/rendering/cell.js +46 -0
- package/dist/rendering/damage.d.ts +35 -0
- package/dist/rendering/damage.js +99 -0
- package/dist/rendering/grid.d.ts +49 -0
- package/dist/rendering/grid.js +216 -0
- package/dist/rendering/gridSnapshot.d.ts +33 -0
- package/dist/rendering/gridSnapshot.js +32 -0
- package/dist/rendering/gridToSvg.d.ts +29 -0
- package/dist/rendering/gridToSvg.js +145 -0
- package/dist/rendering/terminalRenderer.d.ts +28 -0
- package/dist/rendering/terminalRenderer.js +161 -0
- package/dist/rendering/terminalScreen.d.ts +25 -0
- package/dist/rendering/terminalScreen.js +50 -0
- package/package.json +29 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BoxConstraints, Size } from "../common/geometryPromitives.js";
|
|
2
|
+
import { TUIElement } from "./tuiElement.js";
|
|
3
|
+
/**
|
|
4
|
+
* Base class for composite elements that own a single root child built
|
|
5
|
+
* imperatively in the subclass constructor via {@link setRootChild}.
|
|
6
|
+
*
|
|
7
|
+
* The composite fills its constraints, lays the child out tight to the
|
|
8
|
+
* resulting size and proxies intrinsic dimensions to the child.
|
|
9
|
+
*/
|
|
10
|
+
export declare abstract class CompositeElement extends TUIElement {
|
|
11
|
+
private rootChild;
|
|
12
|
+
/** Replace the root child; called once from the subclass constructor. */
|
|
13
|
+
protected setRootChild(child: TUIElement): void;
|
|
14
|
+
getRootChild(): TUIElement | null;
|
|
15
|
+
getMinIntrinsicWidth(height: number): number;
|
|
16
|
+
getMaxIntrinsicWidth(height: number): number;
|
|
17
|
+
getMinIntrinsicHeight(width: number): number;
|
|
18
|
+
getMaxIntrinsicHeight(width: number): number;
|
|
19
|
+
protected performLayout(constraints: BoxConstraints): Size;
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BoxConstraints, Size } from "../common/geometryPromitives.js";
|
|
2
|
+
import { TUIElement } from "./tuiElement.js";
|
|
3
|
+
/**
|
|
4
|
+
* Base class for composite elements that own a single root child built
|
|
5
|
+
* imperatively in the subclass constructor via {@link setRootChild}.
|
|
6
|
+
*
|
|
7
|
+
* The composite fills its constraints, lays the child out tight to the
|
|
8
|
+
* resulting size and proxies intrinsic dimensions to the child.
|
|
9
|
+
*/
|
|
10
|
+
export class CompositeElement extends TUIElement {
|
|
11
|
+
rootChild = null;
|
|
12
|
+
/** Replace the root child; called once from the subclass constructor. */
|
|
13
|
+
setRootChild(child) {
|
|
14
|
+
if (this.rootChild) {
|
|
15
|
+
this.removeChild(this.rootChild);
|
|
16
|
+
}
|
|
17
|
+
this.rootChild = child;
|
|
18
|
+
this.appendChild(child);
|
|
19
|
+
this.markDirty();
|
|
20
|
+
}
|
|
21
|
+
getRootChild() {
|
|
22
|
+
return this.rootChild;
|
|
23
|
+
}
|
|
24
|
+
// ─── Proxy to rootChild ───
|
|
25
|
+
getMinIntrinsicWidth(height) {
|
|
26
|
+
return this.rootChild?.getMinIntrinsicWidth(height) ?? 0;
|
|
27
|
+
}
|
|
28
|
+
getMaxIntrinsicWidth(height) {
|
|
29
|
+
return this.rootChild?.getMaxIntrinsicWidth(height) ?? 0;
|
|
30
|
+
}
|
|
31
|
+
getMinIntrinsicHeight(width) {
|
|
32
|
+
return this.rootChild?.getMinIntrinsicHeight(width) ?? 0;
|
|
33
|
+
}
|
|
34
|
+
getMaxIntrinsicHeight(width) {
|
|
35
|
+
return this.rootChild?.getMaxIntrinsicHeight(width) ?? 0;
|
|
36
|
+
}
|
|
37
|
+
performLayout(constraints) {
|
|
38
|
+
const resultSize = super.performLayout(constraints);
|
|
39
|
+
if (this.rootChild) {
|
|
40
|
+
this.layoutChild(this.rootChild, 0, 0, BoxConstraints.tight(resultSize));
|
|
41
|
+
}
|
|
42
|
+
return resultSize;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TUIElement } from "../tuiElement.js";
|
|
2
|
+
import type { TUIKeyboardEvent } from "./tuiKeyboardEvent.js";
|
|
3
|
+
import type { TUIMouseEvent } from "./tuiMouseEvent.js";
|
|
4
|
+
import { TUIContextMenuEvent } from "./tuiMouseEvent.js";
|
|
5
|
+
/**
|
|
6
|
+
* Единственное место, знающее, какой ввод означает «открой контекстное меню»:
|
|
7
|
+
* правый клик, клавиша ContextMenu (Kitty 57363) и Shift+F10 сливаются в одно
|
|
8
|
+
* событие "contextmenu" с нормализованным якорем — потребитель не различает,
|
|
9
|
+
* откуда меню пришло.
|
|
10
|
+
*/
|
|
11
|
+
/** Правый click → contextmenu с якорем-точкой; прочие кнопки — null. */
|
|
12
|
+
export declare function contextMenuEventFromClick(click: TUIMouseEvent): TUIContextMenuEvent | null;
|
|
13
|
+
/**
|
|
14
|
+
* Keydown ContextMenu / Shift+F10 → contextmenu с якорем-элементом; прочие
|
|
15
|
+
* клавиши — null. Координаты — глобальная позиция target как fallback.
|
|
16
|
+
*/
|
|
17
|
+
export declare function contextMenuEventFromKeydown(event: TUIKeyboardEvent, target: TUIElement): TUIContextMenuEvent | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TUIContextMenuEvent } from "./tuiMouseEvent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Единственное место, знающее, какой ввод означает «открой контекстное меню»:
|
|
4
|
+
* правый клик, клавиша ContextMenu (Kitty 57363) и Shift+F10 сливаются в одно
|
|
5
|
+
* событие "contextmenu" с нормализованным якорем — потребитель не различает,
|
|
6
|
+
* откуда меню пришло.
|
|
7
|
+
*/
|
|
8
|
+
/** Правый click → contextmenu с якорем-точкой; прочие кнопки — null. */
|
|
9
|
+
export function contextMenuEventFromClick(click) {
|
|
10
|
+
if (click.button !== "right")
|
|
11
|
+
return null;
|
|
12
|
+
return new TUIContextMenuEvent({
|
|
13
|
+
trigger: "mouse",
|
|
14
|
+
button: click.button,
|
|
15
|
+
screenX: click.screenX,
|
|
16
|
+
screenY: click.screenY,
|
|
17
|
+
localX: click.localX,
|
|
18
|
+
localY: click.localY,
|
|
19
|
+
shiftKey: click.shiftKey,
|
|
20
|
+
altKey: click.altKey,
|
|
21
|
+
ctrlKey: click.ctrlKey,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Keydown ContextMenu / Shift+F10 → contextmenu с якорем-элементом; прочие
|
|
26
|
+
* клавиши — null. Координаты — глобальная позиция target как fallback.
|
|
27
|
+
*/
|
|
28
|
+
export function contextMenuEventFromKeydown(event, target) {
|
|
29
|
+
if (event.ctrlKey || event.altKey || event.metaKey)
|
|
30
|
+
return null;
|
|
31
|
+
const isContextMenuKey = event.key === "ContextMenu";
|
|
32
|
+
const isShiftF10 = event.key === "F10" && event.shiftKey;
|
|
33
|
+
if (!isContextMenuKey && !isShiftF10)
|
|
34
|
+
return null;
|
|
35
|
+
return new TUIContextMenuEvent({
|
|
36
|
+
trigger: "keyboard",
|
|
37
|
+
button: "none",
|
|
38
|
+
screenX: target.globalPosition.x,
|
|
39
|
+
screenY: target.globalPosition.y,
|
|
40
|
+
localX: 0,
|
|
41
|
+
localY: 0,
|
|
42
|
+
shiftKey: event.shiftKey,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TUIElement } from "../tuiElement.js";
|
|
2
|
+
export declare class FocusManager {
|
|
3
|
+
activeElement: TUIElement | null;
|
|
4
|
+
private rootElement;
|
|
5
|
+
private focusScopeStack;
|
|
6
|
+
constructor(rootElement: TUIElement);
|
|
7
|
+
/** Ограничить Tab-навигацию поддеревом `element` (модальные оверлеи). */
|
|
8
|
+
pushFocusScope(element: TUIElement): void;
|
|
9
|
+
/** Снять ранее установленный focus-scope. Удаляет по идентичности — устойчиво к не-LIFO снятию. */
|
|
10
|
+
popFocusScope(element: TUIElement): void;
|
|
11
|
+
setFocus(element: TUIElement | null): void;
|
|
12
|
+
cycleFocus(direction: "forward" | "backward"): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TUIFocusEvent } from "./tuiFocusEvent.js";
|
|
2
|
+
export class FocusManager {
|
|
3
|
+
activeElement = null;
|
|
4
|
+
rootElement;
|
|
5
|
+
focusScopeStack = [];
|
|
6
|
+
constructor(rootElement) {
|
|
7
|
+
this.rootElement = rootElement;
|
|
8
|
+
}
|
|
9
|
+
/** Ограничить Tab-навигацию поддеревом `element` (модальные оверлеи). */
|
|
10
|
+
pushFocusScope(element) {
|
|
11
|
+
this.focusScopeStack.push(element);
|
|
12
|
+
}
|
|
13
|
+
/** Снять ранее установленный focus-scope. Удаляет по идентичности — устойчиво к не-LIFO снятию. */
|
|
14
|
+
popFocusScope(element) {
|
|
15
|
+
const index = this.focusScopeStack.lastIndexOf(element);
|
|
16
|
+
if (index !== -1) {
|
|
17
|
+
this.focusScopeStack.splice(index, 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
setFocus(element) {
|
|
21
|
+
if (element === this.activeElement)
|
|
22
|
+
return;
|
|
23
|
+
const oldElement = this.activeElement;
|
|
24
|
+
if (oldElement) {
|
|
25
|
+
// Состояние стиля — ДО события: blur-слушатели видят его снятым.
|
|
26
|
+
oldElement.setStyleState("focus", false);
|
|
27
|
+
const blurEvent = new TUIFocusEvent("blur", element);
|
|
28
|
+
this.activeElement = null;
|
|
29
|
+
oldElement.dispatchEvent(blurEvent);
|
|
30
|
+
}
|
|
31
|
+
this.activeElement = element;
|
|
32
|
+
if (element) {
|
|
33
|
+
// Только target (как :focus в CSS); потомкам доступен селектор
|
|
34
|
+
// "in:focus", предок при нужде слушает focus/blur в bubble-фазе.
|
|
35
|
+
element.setStyleState("focus", true);
|
|
36
|
+
const focusEvent = new TUIFocusEvent("focus", oldElement);
|
|
37
|
+
element.dispatchEvent(focusEvent);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
cycleFocus(direction) {
|
|
41
|
+
const scope = this.focusScopeStack[this.focusScopeStack.length - 1] ?? this.rootElement;
|
|
42
|
+
const focusable = scope.getDepthFirstFocusableOrder();
|
|
43
|
+
if (focusable.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
const currentIndex = this.activeElement ? focusable.indexOf(this.activeElement) : -1;
|
|
46
|
+
let nextIndex;
|
|
47
|
+
if (direction === "forward") {
|
|
48
|
+
nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % focusable.length;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
nextIndex =
|
|
52
|
+
currentIndex === -1 ? focusable.length - 1 : (currentIndex - 1 + focusable.length) % focusable.length;
|
|
53
|
+
}
|
|
54
|
+
this.setFocus(focusable[nextIndex]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MouseToken } from "../../input/rawTerminalToken.js";
|
|
2
|
+
import type { TUIElement } from "../tuiElement.js";
|
|
3
|
+
export declare class MouseEventDispatcher {
|
|
4
|
+
private hoveredElement;
|
|
5
|
+
private pressedElement;
|
|
6
|
+
private pressedButton;
|
|
7
|
+
private lastClickTime;
|
|
8
|
+
private lastClickTarget;
|
|
9
|
+
private lastClickPosition;
|
|
10
|
+
private lastPosition;
|
|
11
|
+
private now;
|
|
12
|
+
constructor(now?: () => number);
|
|
13
|
+
handleMouseToken(token: MouseToken, root: TUIElement): void;
|
|
14
|
+
private handlePress;
|
|
15
|
+
private isCapturing;
|
|
16
|
+
private handleRelease;
|
|
17
|
+
private handleMove;
|
|
18
|
+
private handleScroll;
|
|
19
|
+
private dispatchEnterLeave;
|
|
20
|
+
private getAncestorList;
|
|
21
|
+
private getAncestorSet;
|
|
22
|
+
private dispatchOn;
|
|
23
|
+
private buildInit;
|
|
24
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Point } from "../../common/geometryPromitives.js";
|
|
2
|
+
import { contextMenuEventFromClick } from "./contextMenuEventSource.js";
|
|
3
|
+
import { TUIMouseEvent } from "./tuiMouseEvent.js";
|
|
4
|
+
const DOUBLE_CLICK_THRESHOLD = 300;
|
|
5
|
+
export class MouseEventDispatcher {
|
|
6
|
+
hoveredElement = null;
|
|
7
|
+
pressedElement = null;
|
|
8
|
+
pressedButton = null;
|
|
9
|
+
lastClickTime = 0;
|
|
10
|
+
lastClickTarget = null;
|
|
11
|
+
lastClickPosition = null;
|
|
12
|
+
lastPosition = null;
|
|
13
|
+
now;
|
|
14
|
+
constructor(now) {
|
|
15
|
+
this.now = now ?? (() => Date.now());
|
|
16
|
+
}
|
|
17
|
+
handleMouseToken(token, root) {
|
|
18
|
+
const screenX = token.x - 1;
|
|
19
|
+
const screenY = token.y - 1;
|
|
20
|
+
const point = new Point(screenX, screenY);
|
|
21
|
+
const target = root.elementFromPoint(point);
|
|
22
|
+
switch (token.action) {
|
|
23
|
+
case "press":
|
|
24
|
+
this.handlePress(target, token, screenX, screenY);
|
|
25
|
+
break;
|
|
26
|
+
case "release":
|
|
27
|
+
this.handleRelease(target, token, screenX, screenY);
|
|
28
|
+
break;
|
|
29
|
+
case "move":
|
|
30
|
+
this.handleMove(target, token, screenX, screenY);
|
|
31
|
+
break;
|
|
32
|
+
case "scroll-up":
|
|
33
|
+
case "scroll-down":
|
|
34
|
+
case "scroll-left":
|
|
35
|
+
case "scroll-right":
|
|
36
|
+
this.handleScroll(target, token, screenX, screenY);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
this.lastPosition = point;
|
|
40
|
+
}
|
|
41
|
+
handlePress(target, token, screenX, screenY) {
|
|
42
|
+
if (!target)
|
|
43
|
+
return;
|
|
44
|
+
this.pressedElement = target;
|
|
45
|
+
this.pressedButton = token.button;
|
|
46
|
+
this.dispatchOn(target, "mousedown", token, screenX, screenY);
|
|
47
|
+
}
|
|
48
|
+
// True while a button is held on an element that opted into pointer capture.
|
|
49
|
+
isCapturing() {
|
|
50
|
+
return (this.pressedElement?.capturesPointer === true &&
|
|
51
|
+
this.pressedButton !== null &&
|
|
52
|
+
this.pressedButton !== "none");
|
|
53
|
+
}
|
|
54
|
+
handleRelease(target, token, screenX, screenY) {
|
|
55
|
+
// While capturing, the release belongs to the captor regardless of what's under the cursor.
|
|
56
|
+
const effectiveTarget = this.isCapturing() ? this.pressedElement : target;
|
|
57
|
+
if (!effectiveTarget)
|
|
58
|
+
return;
|
|
59
|
+
this.dispatchOn(effectiveTarget, "mouseup", token, screenX, screenY);
|
|
60
|
+
if (this.pressedElement === effectiveTarget && this.pressedButton === token.button) {
|
|
61
|
+
const clickEvent = new TUIMouseEvent("click", this.buildInit(effectiveTarget, token, screenX, screenY));
|
|
62
|
+
effectiveTarget.dispatchEvent(clickEvent);
|
|
63
|
+
const contextMenuEvent = contextMenuEventFromClick(clickEvent);
|
|
64
|
+
if (contextMenuEvent) {
|
|
65
|
+
effectiveTarget.dispatchEvent(contextMenuEvent);
|
|
66
|
+
}
|
|
67
|
+
// A double click is two clicks on the same cell, not merely on the same
|
|
68
|
+
// element: clicking two different words of one editor in quick succession
|
|
69
|
+
// must stay two single clicks.
|
|
70
|
+
const now = this.now();
|
|
71
|
+
const samePosition = this.lastClickPosition !== null &&
|
|
72
|
+
this.lastClickPosition.x === screenX &&
|
|
73
|
+
this.lastClickPosition.y === screenY;
|
|
74
|
+
if (this.lastClickTarget === effectiveTarget &&
|
|
75
|
+
samePosition &&
|
|
76
|
+
now - this.lastClickTime < DOUBLE_CLICK_THRESHOLD) {
|
|
77
|
+
this.dispatchOn(effectiveTarget, "dblclick", token, screenX, screenY);
|
|
78
|
+
this.lastClickTarget = null;
|
|
79
|
+
this.lastClickPosition = null;
|
|
80
|
+
this.lastClickTime = 0;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.lastClickTarget = effectiveTarget;
|
|
84
|
+
this.lastClickPosition = new Point(screenX, screenY);
|
|
85
|
+
this.lastClickTime = now;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.pressedElement = null;
|
|
89
|
+
this.pressedButton = null;
|
|
90
|
+
}
|
|
91
|
+
handleMove(target, token, screenX, screenY) {
|
|
92
|
+
// While capturing, route moves straight to the captor and suppress enter/leave so
|
|
93
|
+
// elements under the cursor (editor, tree rows) don't flicker hover state mid-drag.
|
|
94
|
+
if (this.isCapturing() && this.pressedElement) {
|
|
95
|
+
this.dispatchOn(this.pressedElement, "mousemove", token, screenX, screenY);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const oldHovered = this.hoveredElement;
|
|
99
|
+
const newHovered = target;
|
|
100
|
+
if (oldHovered !== newHovered) {
|
|
101
|
+
this.dispatchEnterLeave(oldHovered, newHovered, token, screenX, screenY);
|
|
102
|
+
this.hoveredElement = newHovered;
|
|
103
|
+
}
|
|
104
|
+
if (newHovered) {
|
|
105
|
+
this.dispatchOn(newHovered, "mousemove", token, screenX, screenY);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
handleScroll(target, token, screenX, screenY) {
|
|
109
|
+
if (!target)
|
|
110
|
+
return;
|
|
111
|
+
const directionMap = {
|
|
112
|
+
"scroll-up": "up",
|
|
113
|
+
"scroll-down": "down",
|
|
114
|
+
"scroll-left": "left",
|
|
115
|
+
"scroll-right": "right",
|
|
116
|
+
};
|
|
117
|
+
const init = this.buildInit(target, token, screenX, screenY);
|
|
118
|
+
init.wheelDirection = directionMap[token.action];
|
|
119
|
+
const event = new TUIMouseEvent("wheel", init);
|
|
120
|
+
target.dispatchEvent(event);
|
|
121
|
+
}
|
|
122
|
+
dispatchEnterLeave(oldElement, newElement, token, screenX, screenY) {
|
|
123
|
+
const oldPath = oldElement ? this.getAncestorSet(oldElement) : new Set();
|
|
124
|
+
const newPath = newElement ? this.getAncestorSet(newElement) : new Set();
|
|
125
|
+
// mouseleave: from old element up to (but not including) common ancestors
|
|
126
|
+
if (oldElement) {
|
|
127
|
+
const leaveElements = this.getAncestorList(oldElement).filter((el) => !newPath.has(el));
|
|
128
|
+
for (const el of leaveElements) {
|
|
129
|
+
// hover-состояние — ДО события: слушатели mouseleave видят его
|
|
130
|
+
// снятым (как в DOM). Общий предок в diff не входит — не мигает.
|
|
131
|
+
el.setStyleState("hover", false);
|
|
132
|
+
this.dispatchOn(el, "mouseleave", token, screenX, screenY);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// mouseenter: from common ancestor down to new element
|
|
136
|
+
if (newElement) {
|
|
137
|
+
const enterElements = this.getAncestorList(newElement)
|
|
138
|
+
.filter((el) => !oldPath.has(el))
|
|
139
|
+
.reverse();
|
|
140
|
+
for (const el of enterElements) {
|
|
141
|
+
// hover стоит на всей цепочке target→root, как :hover в CSS.
|
|
142
|
+
el.setStyleState("hover", true);
|
|
143
|
+
this.dispatchOn(el, "mouseenter", token, screenX, screenY);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
getAncestorList(element) {
|
|
148
|
+
// Returns path from element up to root (element first, root last)
|
|
149
|
+
const path = [];
|
|
150
|
+
let current = element;
|
|
151
|
+
while (current !== null) {
|
|
152
|
+
path.push(current);
|
|
153
|
+
current = current.getParent();
|
|
154
|
+
}
|
|
155
|
+
return path;
|
|
156
|
+
}
|
|
157
|
+
getAncestorSet(element) {
|
|
158
|
+
return new Set(this.getAncestorList(element));
|
|
159
|
+
}
|
|
160
|
+
dispatchOn(target, type, token, screenX, screenY) {
|
|
161
|
+
const init = this.buildInit(target, token, screenX, screenY);
|
|
162
|
+
const event = new TUIMouseEvent(type, init);
|
|
163
|
+
target.dispatchEvent(event);
|
|
164
|
+
}
|
|
165
|
+
buildInit(target, token, screenX, screenY) {
|
|
166
|
+
return {
|
|
167
|
+
button: token.button,
|
|
168
|
+
screenX,
|
|
169
|
+
screenY,
|
|
170
|
+
localX: screenX - target.globalPosition.x,
|
|
171
|
+
localY: screenY - target.globalPosition.y,
|
|
172
|
+
shiftKey: token.shiftKey,
|
|
173
|
+
altKey: token.altKey,
|
|
174
|
+
ctrlKey: token.ctrlKey,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TUIElement } from "../tuiElement.js";
|
|
2
|
+
export declare const EventPhase: {
|
|
3
|
+
readonly NONE: 0;
|
|
4
|
+
readonly CAPTURING: 1;
|
|
5
|
+
readonly AT_TARGET: 2;
|
|
6
|
+
readonly BUBBLING: 3;
|
|
7
|
+
};
|
|
8
|
+
export type EventPhase = (typeof EventPhase)[keyof typeof EventPhase];
|
|
9
|
+
export declare class TUIEventBase {
|
|
10
|
+
readonly type: string;
|
|
11
|
+
readonly bubbles: boolean;
|
|
12
|
+
target: TUIElement | null;
|
|
13
|
+
currentTarget: TUIElement | null;
|
|
14
|
+
eventPhase: EventPhase;
|
|
15
|
+
private _propagationStopped;
|
|
16
|
+
private _immediatePropagationStopped;
|
|
17
|
+
private _defaultPrevented;
|
|
18
|
+
constructor(type: string, bubbles?: boolean);
|
|
19
|
+
stopPropagation(): void;
|
|
20
|
+
get propagationStopped(): boolean;
|
|
21
|
+
stopImmediatePropagation(): void;
|
|
22
|
+
get immediatePropagationStopped(): boolean;
|
|
23
|
+
preventDefault(): void;
|
|
24
|
+
get defaultPrevented(): boolean;
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const EventPhase = {
|
|
2
|
+
NONE: 0,
|
|
3
|
+
CAPTURING: 1,
|
|
4
|
+
AT_TARGET: 2,
|
|
5
|
+
BUBBLING: 3,
|
|
6
|
+
};
|
|
7
|
+
export class TUIEventBase {
|
|
8
|
+
type;
|
|
9
|
+
bubbles;
|
|
10
|
+
target = null;
|
|
11
|
+
currentTarget = null;
|
|
12
|
+
eventPhase = EventPhase.NONE;
|
|
13
|
+
_propagationStopped = false;
|
|
14
|
+
_immediatePropagationStopped = false;
|
|
15
|
+
_defaultPrevented = false;
|
|
16
|
+
constructor(type, bubbles = true) {
|
|
17
|
+
this.type = type;
|
|
18
|
+
this.bubbles = bubbles;
|
|
19
|
+
}
|
|
20
|
+
stopPropagation() {
|
|
21
|
+
this._propagationStopped = true;
|
|
22
|
+
}
|
|
23
|
+
get propagationStopped() {
|
|
24
|
+
return this._propagationStopped;
|
|
25
|
+
}
|
|
26
|
+
stopImmediatePropagation() {
|
|
27
|
+
this._propagationStopped = true;
|
|
28
|
+
this._immediatePropagationStopped = true;
|
|
29
|
+
}
|
|
30
|
+
get immediatePropagationStopped() {
|
|
31
|
+
return this._immediatePropagationStopped;
|
|
32
|
+
}
|
|
33
|
+
preventDefault() {
|
|
34
|
+
this._defaultPrevented = true;
|
|
35
|
+
}
|
|
36
|
+
get defaultPrevented() {
|
|
37
|
+
return this._defaultPrevented;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TUIElement } from "../tuiElement.js";
|
|
2
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
3
|
+
export declare class TUIFocusEvent extends TUIEventBase {
|
|
4
|
+
readonly relatedTarget: TUIElement | null;
|
|
5
|
+
constructor(type: "focus" | "blur", relatedTarget?: TUIElement | null);
|
|
6
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
export interface TUIKeyboardEventInit {
|
|
3
|
+
key: string;
|
|
4
|
+
code?: string;
|
|
5
|
+
ctrlKey?: boolean;
|
|
6
|
+
shiftKey?: boolean;
|
|
7
|
+
altKey?: boolean;
|
|
8
|
+
metaKey?: boolean;
|
|
9
|
+
raw?: string;
|
|
10
|
+
bubbles?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class TUIKeyboardEvent extends TUIEventBase {
|
|
13
|
+
readonly key: string;
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly ctrlKey: boolean;
|
|
16
|
+
readonly shiftKey: boolean;
|
|
17
|
+
readonly altKey: boolean;
|
|
18
|
+
readonly metaKey: boolean;
|
|
19
|
+
readonly raw: string;
|
|
20
|
+
constructor(type: "keypress" | "keydown" | "keyup", init: TUIKeyboardEventInit);
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
export class TUIKeyboardEvent extends TUIEventBase {
|
|
3
|
+
key;
|
|
4
|
+
code;
|
|
5
|
+
ctrlKey;
|
|
6
|
+
shiftKey;
|
|
7
|
+
altKey;
|
|
8
|
+
metaKey;
|
|
9
|
+
raw;
|
|
10
|
+
constructor(type, init) {
|
|
11
|
+
super(type, init.bubbles ?? true);
|
|
12
|
+
this.key = init.key;
|
|
13
|
+
this.code = init.code ?? "";
|
|
14
|
+
this.ctrlKey = init.ctrlKey ?? false;
|
|
15
|
+
this.shiftKey = init.shiftKey ?? false;
|
|
16
|
+
this.altKey = init.altKey ?? false;
|
|
17
|
+
this.metaKey = init.metaKey ?? false;
|
|
18
|
+
this.raw = init.raw ?? "";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
export type TUIMouseEventType = "mousedown" | "mouseup" | "mousemove" | "click" | "dblclick" | "mouseenter" | "mouseleave" | "wheel" | "contextmenu";
|
|
3
|
+
export type WheelDirection = "up" | "down" | "left" | "right";
|
|
4
|
+
export interface TUIMouseEventInit {
|
|
5
|
+
button: "left" | "middle" | "right" | "none";
|
|
6
|
+
screenX: number;
|
|
7
|
+
screenY: number;
|
|
8
|
+
localX: number;
|
|
9
|
+
localY: number;
|
|
10
|
+
shiftKey?: boolean;
|
|
11
|
+
altKey?: boolean;
|
|
12
|
+
ctrlKey?: boolean;
|
|
13
|
+
wheelDirection?: WheelDirection;
|
|
14
|
+
}
|
|
15
|
+
export declare class TUIMouseEvent extends TUIEventBase {
|
|
16
|
+
readonly button: "left" | "middle" | "right" | "none";
|
|
17
|
+
readonly screenX: number;
|
|
18
|
+
readonly screenY: number;
|
|
19
|
+
readonly localX: number;
|
|
20
|
+
readonly localY: number;
|
|
21
|
+
readonly shiftKey: boolean;
|
|
22
|
+
readonly altKey: boolean;
|
|
23
|
+
readonly ctrlKey: boolean;
|
|
24
|
+
readonly wheelDirection: WheelDirection | undefined;
|
|
25
|
+
constructor(type: TUIMouseEventType, init: TUIMouseEventInit);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Откуда пришёл запрос контекстного меню. Определяет трактовку якоря:
|
|
29
|
+
* "mouse" — точка клика (screenX/screenY), "keyboard" — сам target
|
|
30
|
+
* (координаты заполнены его глобальной позицией как fallback; потребитель
|
|
31
|
+
* уточняет якорь — каретка, выделенная строка).
|
|
32
|
+
*/
|
|
33
|
+
export type ContextMenuTrigger = "mouse" | "keyboard";
|
|
34
|
+
export interface TUIContextMenuEventInit extends TUIMouseEventInit {
|
|
35
|
+
trigger: ContextMenuTrigger;
|
|
36
|
+
}
|
|
37
|
+
export declare class TUIContextMenuEvent extends TUIMouseEvent {
|
|
38
|
+
readonly trigger: ContextMenuTrigger;
|
|
39
|
+
constructor(init: TUIContextMenuEventInit);
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
const NON_BUBBLING_TYPES = new Set(["mouseenter", "mouseleave"]);
|
|
3
|
+
export class TUIMouseEvent extends TUIEventBase {
|
|
4
|
+
button;
|
|
5
|
+
screenX;
|
|
6
|
+
screenY;
|
|
7
|
+
localX;
|
|
8
|
+
localY;
|
|
9
|
+
shiftKey;
|
|
10
|
+
altKey;
|
|
11
|
+
ctrlKey;
|
|
12
|
+
wheelDirection;
|
|
13
|
+
constructor(type, init) {
|
|
14
|
+
super(type, !NON_BUBBLING_TYPES.has(type));
|
|
15
|
+
this.button = init.button;
|
|
16
|
+
this.screenX = init.screenX;
|
|
17
|
+
this.screenY = init.screenY;
|
|
18
|
+
this.localX = init.localX;
|
|
19
|
+
this.localY = init.localY;
|
|
20
|
+
this.shiftKey = init.shiftKey ?? false;
|
|
21
|
+
this.altKey = init.altKey ?? false;
|
|
22
|
+
this.ctrlKey = init.ctrlKey ?? false;
|
|
23
|
+
this.wheelDirection = init.wheelDirection;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class TUIContextMenuEvent extends TUIMouseEvent {
|
|
27
|
+
trigger;
|
|
28
|
+
constructor(init) {
|
|
29
|
+
super("contextmenu", init);
|
|
30
|
+
this.trigger = init.trigger;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bracketed-paste event: the terminal delivered a clipboard paste as one literal
|
|
4
|
+
* text block. Dispatched to the focused element so it can insert the whole text in
|
|
5
|
+
* a single edit (newlines preserved), instead of replaying it as keystrokes.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TUIPasteEvent extends TUIEventBase {
|
|
8
|
+
readonly text: string;
|
|
9
|
+
constructor(text: string, bubbles?: boolean);
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TUIEventBase } from "./tuiEventBase.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bracketed-paste event: the terminal delivered a clipboard paste as one literal
|
|
4
|
+
* text block. Dispatched to the focused element so it can insert the whole text in
|
|
5
|
+
* a single edit (newlines preserved), instead of replaying it as keystrokes.
|
|
6
|
+
*/
|
|
7
|
+
export class TUIPasteEvent extends TUIEventBase {
|
|
8
|
+
text;
|
|
9
|
+
constructor(text, bubbles = true) {
|
|
10
|
+
super("paste", bubbles);
|
|
11
|
+
this.text = text;
|
|
12
|
+
}
|
|
13
|
+
}
|