|
1 | | -import { Component, Prop, Part, forEach } from '@pictogrammers/element'; |
| 1 | +import { Component, Prop, Part } from '@pictogrammers/element'; |
2 | 2 |
|
3 | 3 | import template from './nodeResize.html'; |
4 | 4 | import style from './nodeResize.css'; |
@@ -29,121 +29,96 @@ export default class PgNodeResize extends HTMLElement { |
29 | 29 | @Part() $southEast: HTMLDivElement; |
30 | 30 |
|
31 | 31 | connectedCallback() { |
32 | | - let startX = 0, |
33 | | - startY = 0, |
34 | | - startWidth = 12, |
35 | | - startHeight = 3; |
| 32 | + let startX = 0, startY = 0, startWidth = 0, startHeight = 0; |
| 33 | + |
36 | 34 | const start = () => { |
37 | 35 | startX = this.x; |
38 | 36 | startY = this.y; |
39 | 37 | startWidth = this.width; |
40 | 38 | startHeight = this.height; |
41 | 39 | this.classList.toggle('preview', true); |
42 | 40 | }; |
43 | | - drag({ |
44 | | - source: this.$northWest, |
45 | | - gridSize: this.gridSize, |
46 | | - start, |
47 | | - move: (dx, dy) => { |
48 | | - this.previewX(dx); |
49 | | - this.previewY(dy); |
50 | | - this.previewWidth(dx * -1); |
51 | | - this.previewHeight(dy * -1); |
52 | | - }, |
53 | | - snap: (dx, dy) => { |
54 | | - this.emit( |
55 | | - startX + dx, |
56 | | - startY + dy, |
57 | | - startWidth + dx * -1, |
58 | | - startHeight + dy * -1 |
59 | | - ); |
60 | | - }, |
61 | | - end: (dx, dy, complete) => { |
62 | | - if (complete) { |
63 | | - this.emit( |
64 | | - startX + dx, |
65 | | - startY + dy, |
66 | | - startWidth + dx * -1, |
67 | | - startHeight + dy * -1 |
68 | | - ); |
69 | | - } else { |
70 | | - this.emit(startX, startY, startWidth, startHeight); |
71 | | - } |
72 | | - this.classList.toggle('preview', false); |
73 | | - }, |
74 | | - }); |
75 | | - drag({ |
76 | | - source: this.$south, |
77 | | - gridSize: this.gridSize, |
78 | | - start, |
79 | | - move: (dx, dy) => { |
80 | | - console.log('dy', dy); |
81 | | - if (startHeight === this.minHeight) { |
82 | | - this.$south.classList.toggle('stop', dy < 0); |
83 | | - } |
84 | | - this.previewHeight(dy); |
85 | | - }, |
86 | | - snap: (dx, dy) => { |
87 | | - if (startHeight + dy > this.minHeight) { |
88 | | - this.emit(startX, startY, startWidth, startHeight + dy); |
89 | | - } else { |
90 | | - this.emit(startX, startY, startWidth, this.minHeight); |
91 | | - |
92 | | - } |
93 | | - }, |
94 | | - end: (dx, dy, complete) => { |
95 | | - if (complete) { |
96 | | - this.emit( |
97 | | - startX, |
98 | | - startY, |
99 | | - startWidth, |
100 | | - startHeight + dy |
101 | | - ); |
102 | | - } else { |
103 | | - this.emit(startX, startY, startWidth, startHeight); |
104 | | - } |
105 | | - this.$south.classList.toggle('stop', false); |
106 | | - this.classList.toggle('preview', false); |
107 | | - this.previewHeight(0); |
108 | | - }, |
109 | | - }); |
110 | | - } |
111 | 41 |
|
112 | | - render(changes: any) { |
| 42 | + // wDir: -1 = west edge (x + width change), 0 = no width, 1 = east edge (width only) |
| 43 | + // hDir: -1 = north edge (y + height change), 0 = no height, 1 = south edge (height only) |
| 44 | + const addHandle = (source: HTMLElement, wDir: -1 | 0 | 1, hDir: -1 | 0 | 1) => { |
| 45 | + drag({ |
| 46 | + source, |
| 47 | + gridSize: this.gridSize, |
| 48 | + start, |
| 49 | + move: (dx, dy) => { |
| 50 | + // Sub-grid residual must mirror dragUtil's asymmetric floor/ceil snap algorithm. |
| 51 | + // For positive deltas: floor; for negative: ceil. This avoids a ~gridSize jump |
| 52 | + // each time snap fires and the anchor moves. |
| 53 | + const sgX = this.#subGrid(dx); |
| 54 | + const sgY = this.#subGrid(dy); |
| 55 | + this.style.setProperty('--node-resize-delta-x', `${wDir === -1 ? sgX : 0}px`); |
| 56 | + this.style.setProperty('--node-resize-delta-width', `${wDir !== 0 ? wDir * sgX : 0}px`); |
| 57 | + this.style.setProperty('--node-resize-delta-y', `${hDir === -1 ? sgY : 0}px`); |
| 58 | + this.style.setProperty('--node-resize-delta-height', `${hDir !== 0 ? hDir * sgY : 0}px`); |
| 59 | + const atMinW = wDir !== 0 && startWidth + wDir * dx < this.minWidth; |
| 60 | + const atMinH = hDir !== 0 && startHeight + hDir * dy < this.minHeight; |
| 61 | + source.classList.toggle('stop', atMinW || atMinH); |
| 62 | + }, |
| 63 | + snap: (dx, dy) => { |
| 64 | + this.emit(...this.#compute(startX, startY, startWidth, startHeight, dx, dy, wDir, hDir)); |
| 65 | + }, |
| 66 | + end: (dx, dy, complete) => { |
| 67 | + if (complete) { |
| 68 | + this.emit(...this.#compute(startX, startY, startWidth, startHeight, dx, dy, wDir, hDir)); |
| 69 | + } else { |
| 70 | + this.emit(startX, startY, startWidth, startHeight); |
| 71 | + } |
| 72 | + source.classList.toggle('stop', false); |
| 73 | + this.classList.toggle('preview', false); |
| 74 | + this.style.setProperty('--node-resize-delta-x', '0px'); |
| 75 | + this.style.setProperty('--node-resize-delta-y', '0px'); |
| 76 | + this.style.setProperty('--node-resize-delta-width', '0px'); |
| 77 | + this.style.setProperty('--node-resize-delta-height', '0px'); |
| 78 | + }, |
| 79 | + }); |
| 80 | + }; |
113 | 81 |
|
| 82 | + addHandle(this.$northWest, -1, -1); |
| 83 | + addHandle(this.$north, 0, -1); |
| 84 | + addHandle(this.$northEast, 1, -1); |
| 85 | + addHandle(this.$west, -1, 0); |
| 86 | + addHandle(this.$east, 1, 0); |
| 87 | + addHandle(this.$southWest, -1, 1); |
| 88 | + addHandle(this.$south, 0, 1); |
| 89 | + addHandle(this.$southEast, 1, 1); |
114 | 90 | } |
115 | 91 |
|
116 | | - /** |
117 | | - * Emits delta size changes or 0 for no change. |
118 | | - * @param x Delta |
119 | | - * @param y Delta |
120 | | - * @param width Delta |
121 | | - * @param height Delta |
122 | | - */ |
123 | | - emit(x: number, y: number, width: number, height: number) { |
124 | | - this.dispatchEvent(new CustomEvent('change', { |
125 | | - detail: { |
126 | | - x, |
127 | | - y, |
128 | | - width, |
129 | | - height, |
130 | | - } |
131 | | - })); |
132 | | - } |
133 | | - |
134 | | - previewX(x: number) { |
135 | | - this.style.setProperty('--node-resize-delta-x', `${x % this.gridSize}px`); |
136 | | - } |
| 92 | + render(_changes: any) {} |
137 | 93 |
|
138 | | - previewY(y: number) { |
139 | | - this.style.setProperty('--node-resize-delta-y', `${y % this.gridSize}px`); |
| 94 | + // Mirrors dragUtil's snap: floor for positive deltas, ceil for negative. |
| 95 | + // Returns pixel residual past the last snap boundary so CSS preview tracks smoothly. |
| 96 | + #subGrid(delta: number): number { |
| 97 | + const { gridSize } = this; |
| 98 | + const half = gridSize / 2; |
| 99 | + const snapped = delta + half < 0 |
| 100 | + ? Math.ceil((delta + half) / gridSize) * gridSize |
| 101 | + : Math.floor((delta + half) / gridSize) * gridSize; |
| 102 | + return delta - snapped; |
140 | 103 | } |
141 | 104 |
|
142 | | - previewWidth(width: number) { |
143 | | - this.style.setProperty('--node-resize-delta-width', `${width % this.gridSize}px`); |
| 105 | + #compute( |
| 106 | + startX: number, startY: number, |
| 107 | + startWidth: number, startHeight: number, |
| 108 | + dx: number, dy: number, |
| 109 | + wDir: -1 | 0 | 1, hDir: -1 | 0 | 1 |
| 110 | + ): [number, number, number, number] { |
| 111 | + const newWidth = wDir !== 0 ? Math.max(startWidth + wDir * dx, this.minWidth) : startWidth; |
| 112 | + const newHeight = hDir !== 0 ? Math.max(startHeight + hDir * dy, this.minHeight) : startHeight; |
| 113 | + // West/north edges: shift x/y to keep the opposite edge stationary |
| 114 | + const newX = wDir === -1 ? startX + (startWidth - newWidth) : startX; |
| 115 | + const newY = hDir === -1 ? startY + (startHeight - newHeight) : startY; |
| 116 | + return [newX, newY, newWidth, newHeight]; |
144 | 117 | } |
145 | 118 |
|
146 | | - previewHeight(height: number) { |
147 | | - this.style.setProperty('--node-resize-delta-height', `${((height + (this.gridSize / 2)) % this.gridSize) - (this.gridSize / 2)}px`); |
| 119 | + emit(x: number, y: number, width: number, height: number) { |
| 120 | + this.dispatchEvent(new CustomEvent('change', { |
| 121 | + detail: { x, y, width, height }, |
| 122 | + })); |
148 | 123 | } |
149 | 124 | } |
0 commit comments