UNPKG

4.17 kBPlain TextView Raw
1// Copyright 2021 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5export const enum ResizerType {
6 WIDTH = 'width',
7 HEIGHT = 'height',
8 BIDIRECTION = 'bidirection',
9}
10
11export interface Draggable {
12 type: ResizerType;
13 initialWidth?: number;
14 initialHeight?: number;
15 update({width, height}: {width?: number, height?: number}): void;
16}
17
18export interface Delegate {
19 getDraggable(x: number, y: number): Draggable|undefined;
20}
21
22const cursorByResizerType = new Map([
23 [ResizerType.WIDTH, 'ew-resize'],
24 [ResizerType.HEIGHT, 'ns-resize'],
25 [ResizerType.BIDIRECTION, 'nwse-resize'],
26]);
27
28type OriginInfo = {
29 coord: number,
30 value: number,
31};
32
33export class DragResizeHandler {
34 private document: Document;
35 private delegate: Delegate;
36 private originX?: OriginInfo;
37 private originY?: OriginInfo;
38 private boundMousemove: (event: MouseEvent) => void;
39 private boundMousedown: (event: MouseEvent) => void;
40
41 constructor(document: Document, delegate: Delegate) {
42 this.document = document;
43 this.delegate = delegate;
44 this.boundMousemove = this.onMousemove.bind(this);
45 this.boundMousedown = this.onMousedown.bind(this);
46 }
47
48 install() {
49 this.document.body.addEventListener('mousemove', this.boundMousemove);
50 this.document.body.addEventListener('mousedown', this.boundMousedown);
51 }
52
53 uninstall() {
54 this.document.body.removeEventListener('mousemove', this.boundMousemove);
55 this.document.body.removeEventListener('mousedown', this.boundMousedown);
56 }
57
58 /**
59 * Updates the cursor style of the mouse is hovered over a resizeable area.
60 */
61 private onMousemove(event: MouseEvent) {
62 const match = this.delegate.getDraggable(event.clientX, event.clientY);
63 if (!match) {
64 this.document.body.style.cursor = 'default';
65 return;
66 }
67 this.document.body.style.cursor = cursorByResizerType.get(match.type) || 'default';
68 }
69
70 /**
71 * Starts dragging
72 */
73 private onMousedown(event: MouseEvent) {
74 const match = this.delegate.getDraggable(event.clientX, event.clientY);
75 if (!match) {
76 return;
77 }
78
79 const boundOnDrag = this.onDrag.bind(this, match);
80
81 event.stopPropagation();
82 event.preventDefault();
83
84 if (match.initialWidth !== undefined &&
85 (match.type === ResizerType.WIDTH || match.type === ResizerType.BIDIRECTION)) {
86 this.originX = {
87 coord: Math.round(event.clientX),
88 value: match.initialWidth,
89 };
90 }
91
92 if (match.initialHeight !== undefined &&
93 (match.type === ResizerType.HEIGHT || match.type === ResizerType.BIDIRECTION)) {
94 this.originY = {
95 coord: Math.round(event.clientY),
96 value: match.initialHeight,
97 };
98 }
99
100 this.document.body.removeEventListener('mousemove', this.boundMousemove);
101 this.document.body.style.cursor = cursorByResizerType.get(match.type) || 'default';
102
103 const endDrag = (event: Event) => {
104 event.stopPropagation();
105 event.preventDefault();
106 this.originX = undefined;
107 this.originY = undefined;
108
109 this.document.body.style.cursor = 'default';
110 this.document.body.removeEventListener('mousemove', boundOnDrag);
111 this.document.body.addEventListener('mousemove', this.boundMousemove);
112 };
113
114 this.document.body.addEventListener('mouseup', endDrag, {once: true});
115 window.addEventListener('mouseout', endDrag, {once: true});
116
117 this.document.body.addEventListener('mousemove', boundOnDrag);
118 }
119
120 /**
121 * Computes the new value while the cursor is being dragged and calls InspectorOverlayHost with the new value.
122 */
123 private onDrag(match: Draggable, e: MouseEvent) {
124 if (!this.originX && !this.originY) {
125 return;
126 }
127
128 let width: number|undefined;
129 let height: number|undefined;
130 if (this.originX) {
131 const delta = this.originX.coord - e.clientX;
132 width = Math.round(this.originX.value - delta);
133 }
134
135 if (this.originY) {
136 const delta = this.originY.coord - e.clientY;
137 height = Math.round(this.originY.value - delta);
138 }
139
140 match.update({width, height});
141 }
142}