Skip to content

Commit 1d3bf20

Browse files
committed
fix: region logic for width and height
1 parent 59f2b3b commit 1d3bf20

5 files changed

Lines changed: 100 additions & 23 deletions

File tree

README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,52 @@ Each instance independently manages its own drag configuration and WebContents a
8484
| `exclude` | `string` | Disabled | CSS selector for elements that should **NOT** trigger drag. **Exclusive** with `selector`. |
8585
| `maximize` | `boolean` | Disabled | Enable double-click to maximize/unmaximize. |
8686
| `fps` | `number` | Screen refresh rate | Frames per second for drag position updates. |
87-
| `attachOnInit` | `boolean` | `true` | If true, auto-attach the `BrowserWindow.webContents` on initialization. |
87+
| `attachOnInit` | `boolean` | Enabled | If true, auto-attach the `BrowserWindow.webContents` on initialization. |
8888

8989
> **Note:** `selector` and `exclude` are mutually exclusive. Use `selector` to whitelist draggable areas, or `exclude` to blacklist non-draggable elements within the drag region.
9090
91+
#### Region
92+
93+
The `region` option defines draggable bounds using up to 4 independent thresholds. Only specified attributes are checked — omitted ones impose no constraint:
94+
95+
| Attribute | Constraint | Default |
96+
|-----------|-----------|---------|
97+
| `x` | Drag only when cursor is **at or after** `x` px from the left | `0` (no left bound) |
98+
| `y` | Drag only when cursor is **at or after** `y` px from the top | `0` (no top bound) |
99+
| `width` | Drag only within `width` px **after** `x` | `` (no right bound) |
100+
| `height` | Drag only within `height` px **after** `y` | `` (no bottom bound) |
101+
102+
```
103+
x x + width
104+
┬ ┬
105+
┌──────┼────────┼──────┐
106+
│ 0,0 │ │ │
107+
│ │ │ │
108+
│ ░░░░░░░░░░──────┼─┤ y
109+
│ ░░ DRAG ░░ │
110+
│ ░░ ZONE ░░ │
111+
│ ░░░░░░░░░░──────┼─┤ y + height
112+
│ │
113+
│ │
114+
└──────────────────────┘
115+
```
116+
117+
**Examples:**
118+
119+
```js
120+
// Title bar: top 40px
121+
{ height: 40 }
122+
123+
// Sidebar handle: left 8px
124+
{ width: 8 }
125+
126+
// Custom band: between 50px and 100px from top
127+
{ y: 50, height: 100 }
128+
129+
// Scoped box: specific region
130+
{ x: 100, y: 50, width: 300, height: 150 }
131+
```
132+
91133
### Attaching and Detaching WebContents
92134

93135
For `BaseWindow` setups with multiple `WebContentsView` children, you can manually attach and detach drag sources:

src/main.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -230,27 +230,33 @@ export class Draggable {
230230

231231
private createEventHandler(wc: WebContents): MouseHandler {
232232
const dragState: DragState = { x0: 0, y0: 0, x: 0, y: 0 };
233+
let isDraggable: Promise<boolean> | boolean;
233234

234235
return (e, input) => {
235236
// If not left button, stop dragging
236237
if (input.button !== 'left') {
237-
if (dragState.interval) {
238-
clearInterval(dragState.interval);
238+
if (dragState.interval !== void 0) {
239+
console.debug('Stopping drag due to non-left button event');
240+
dragState.interval !== null && clearInterval(dragState.interval);
239241
dragState.interval = undefined;
240242
}
241243
return;
242244
}
243245

244-
// Early return: Prevent dragging when maximized (except for double-click)
245-
if (this.window!.isMaximized() && input.clickCount !== 2) { return; }
246-
247246
// If already dragging, handle mouse move and stop conditions
248247
if (dragState.interval !== void 0) {
249248

249+
// Early return: Prevent dragging when maximized (except for double-click)
250+
if (this.window!.isMaximized() && input.clickCount !== 2) {
251+
console.debug('Ignoring drag event because window is maximized');
252+
return;
253+
}
254+
250255
// Handle mouse move events, set up interval to update position
251256
if (input.type === 'mouseMove') {
252257
e.preventDefault();
253258
if (dragState.interval === null) {
259+
console.debug('Dragging started');
254260
const options = this.optionsByWebContents.get(wc)!;
255261
dragState.interval = setInterval(() => this.updatePosition(dragState), options.intervalDelay);
256262
}
@@ -259,10 +265,10 @@ export class Draggable {
259265

260266
// Stop dragging on any other event, except for mouseLeave (which triggers when moving too fast)
261267
if (input.type !== 'mouseLeave') {
268+
console.debug('Dragging stopped due to event:', input.type);
262269
dragState.interval && clearInterval(dragState.interval);
263270
dragState.interval = undefined;
264271
}
265-
return;
266272
}
267273

268274
// Handle mouse down (only if not already dragging)
@@ -275,28 +281,27 @@ export class Draggable {
275281
// is used, a mouseUp could arrive before resolution; in that case, the next mouseDown
276282
// will naturally clean up via the interval guard above.
277283
if (input.clickCount === 1) {
278-
const isDraggable = Draggable.isDraggable(wc, input, options);
284+
isDraggable = Draggable.isDraggable(wc, input, options);
279285
if (isDraggable === false) { return; }
280-
this.setInitialPosition(dragState); // Not using input.x/y because of inconsistent values
281-
286+
// Not using input.x/y because of inconsistent values
287+
this.setInitialPosition(dragState);
282288
if (isDraggable === true) { dragState.interval = null; return; }
283-
(async () => await isDraggable && (dragState.interval = null))();
289+
isDraggable.then(d => d && (dragState.interval = null));
284290
return;
285291
}
286292

287293
// Handle double-click to maximize/unmaximize
288294
if (input.clickCount === 2 && options.maximize) {
289-
const isDraggable = Draggable.isDraggable(wc, input, options);
290295
if (isDraggable === false) { return; }
291-
e.preventDefault();
292-
if (isDraggable === true) { this.toggleMaximize(); return; }
293-
(async () => await isDraggable && this.toggleMaximize())();
296+
if (isDraggable === true) { e.preventDefault(); this.toggleMaximize(); return; }
297+
isDraggable.then(d => d && (e.preventDefault(), this.toggleMaximize()));
294298
}
295299
}
296300
};
297301
}
298302

299303
private setInitialPosition(dragState: DragState) {
304+
console.debug('Setting initial drag position');
300305
const winPos = this.window!.getPosition();
301306
const mousePos = screen.getCursorScreenPoint();
302307
dragState.x0 = mousePos.x - winPos[0];
@@ -311,6 +316,7 @@ export class Draggable {
311316
}
312317

313318
private toggleMaximize() {
319+
console.debug('Toggling maximize');
314320
this.window!.isMaximized() ? this.window!.unmaximize() : this.window!.maximize();
315321
}
316322

@@ -330,8 +336,8 @@ export class Draggable {
330336
}
331337

332338
private static isDraggingRegion(region: Partial<Rectangle>, point: Point): boolean {
333-
return (region.width === void 0 || point.x <= region.width)
334-
&& (region.height === void 0 || point.y <= region.height)
339+
return (region.width === void 0 || point.x <= (region.x ?? 0) + region.width)
340+
&& (region.height === void 0 || point.y <= (region.y ?? 0) + region.height)
335341
&& (region.x === void 0 || point.x >= region.x)
336342
&& (region.y === void 0 || point.y >= region.y);
337343
}
@@ -352,8 +358,7 @@ export class Draggable {
352358
options.intervalDelay = Math.floor(1000 / options.fps);
353359
}
354360

355-
private static async closest(webContents: WebContents, p: Point, selector: string, negate?: true): Promise<boolean> {
356-
// eslint-disable-next-line @stylistic/max-len
357-
return await webContents.executeJavaScript(`${negate ? '!' : '!!'}document.elementFromPoint(${p.x}, ${p.y})?.closest(${selector})`).catch(Draggable.CATCH_FALSE);
361+
private static closest(webContents: WebContents, p: Point, selector: string, negate?: true): Promise<boolean> {
362+
return webContents.executeJavaScript(`${negate ? '!' : '!!'}document.elementFromPoint(${p.x}, ${p.y})?.closest(${selector})`).catch(Draggable.CATCH_FALSE);
358363
}
359364
}

test/sample.html

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,27 @@
55
</head>
66
<body style="margin: 0; padding: 0; cursor: default !important;">
77
<div style="height: 100px; background-color: rgb(89, 202, 89); display: flex; align-items: center; justify-content: center; gap: 10px;">
8-
<h4>Draggable!</h4>
8+
<div>
9+
<div>
10+
<div>
11+
<div>
12+
<div>
13+
<div>
14+
<div>
15+
<div>
16+
<div>
17+
<div>
18+
<h4>Draggable!</h4>
19+
</div>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
</div>
25+
</div>
26+
</div>
27+
</div>
28+
</div>
929
<div class="not-drag-1" style="background-color: rgb(194, 57, 57); display: flex; align-items: center; justify-content: center; padding: 5px;">
1030
<span>Not Draggable!</span>
1131
</div>

test/sample.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ function setupWindow() {
1919
window.contentView.addChildView(view);
2020
view.setBounds({ x: 0, y: 0, width: 800, height: 600 });
2121
view.webContents.loadFile(`${__dirname}/sample.html`);
22-
const dragHandler = Draggable.from(window, { dragZone: { height: 100 } }).attach(view.webContents, { exclude: '.not-drag-1, button'});
23-
Draggable.create(window, { selector: '.drag-2', fps: 10, maximize: true }).attach(view.webContents);
22+
const dragHandler = Draggable.from(window, { region: { height: 100 }, maximize: true }).attach(view.webContents, { exclude: '.not-drag-1, button'});
23+
Draggable.create(window, { selector: '.drag-2', fps: 10 }).attach(view.webContents);
2424
if (dragHandler !== Draggable.from(window)) {
2525
window.destroy();
2626
throw new Error('attach did not return the Draggable instance');

webpack.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const path = require('path');
2+
const TerserPlugin = require('terser-webpack-plugin');
23

34
const isDev = process.env.NODE_ENV === 'development';
45
const devtool = isDev ? 'source-map' : false;
@@ -32,6 +33,15 @@ module.exports = [
3233
},
3334
optimization: {
3435
minimize: !isDev,
36+
minimizer: [
37+
new TerserPlugin({
38+
terserOptions: {
39+
compress: {
40+
pure_funcs: ['console.debug', 'console.trace'],
41+
},
42+
},
43+
}),
44+
],
3545
},
3646
},
3747
];

0 commit comments

Comments
 (0)