@signal9/era-ui 1.11.3 → 1.11.5

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.
@@ -88,12 +88,13 @@
88
88
  // that gaps the close button from the pane edge) OUTSIDE the pane. n + w are
89
89
  // that thin outward-only. s + e straddle (translate ±50%, double thickness)
90
90
  // so they also reach xs-inset-sm INWARD — on the right, into the close-button
91
- // gap. Corners are h-md/2 squares straddling each corner; each reaches h-md/4
92
- // along both edges, so the edges are inset by that at both ends and stop right
93
- // where a corner begins instead of running under it. `dir` is any of n/s/e/w;
94
- // a 2-letter dir is a corner. Handles live in a layer aligned to the pane's
95
- // border box (markup) and are data-pane-control so they never start a pane
96
- // drag.
91
+ // gap. Corners are 2·xs-inset-sm squares straddling each corner the same
92
+ // thickness as the blue rails, so they protrude the matching xs-inset-sm and
93
+ // reach xs-inset-sm along both edges; the edges are inset by that at both ends
94
+ // and stop right where a corner begins instead of running under it. `dir` is
95
+ // any of n/s/e/w; a 2-letter dir is a corner. Handles live in a layer aligned
96
+ // to the pane's border box (markup) and are data-pane-control so they never
97
+ // start a pane drag.
97
98
  type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
98
99
  const RESIZE_HANDLES: { dir: ResizeDir; cursor: string; place: string }[] = [
99
100
  // edges FIRST so the corners (rendered after) paint on top of them and win
@@ -105,25 +106,25 @@
105
106
  dir: 'n',
106
107
  cursor: 'cursor-ns-resize',
107
108
  place:
108
- 'top:0;left:calc(var(--era-h-md)/4);right:calc(var(--era-h-md)/4);translate:0 -100%;height:var(--era-xs-inset-sm)'
109
+ 'top:0;left:var(--era-xs-inset-sm);right:var(--era-xs-inset-sm);translate:0 -100%;height:var(--era-xs-inset-sm)'
109
110
  },
110
111
  {
111
112
  dir: 's',
112
113
  cursor: 'cursor-ns-resize',
113
114
  place:
114
- 'bottom:0;left:calc(var(--era-h-md)/4);right:calc(var(--era-h-md)/4);translate:0 50%;height:calc(var(--era-xs-inset-sm)*2)'
115
+ 'bottom:0;left:var(--era-xs-inset-sm);right:var(--era-xs-inset-sm);translate:0 50%;height:calc(var(--era-xs-inset-sm)*2)'
115
116
  },
116
117
  {
117
118
  dir: 'w',
118
119
  cursor: 'cursor-ew-resize',
119
120
  place:
120
- 'left:0;top:calc(var(--era-h-md)/4);bottom:calc(var(--era-h-md)/4);translate:-100% 0;width:var(--era-xs-inset-sm)'
121
+ 'left:0;top:var(--era-xs-inset-sm);bottom:var(--era-xs-inset-sm);translate:-100% 0;width:var(--era-xs-inset-sm)'
121
122
  },
122
123
  {
123
124
  dir: 'e',
124
125
  cursor: 'cursor-ew-resize',
125
126
  place:
126
- 'right:0;top:calc(var(--era-h-md)/4);bottom:calc(var(--era-h-md)/4);translate:50% 0;width:calc(var(--era-xs-inset-sm)*2)'
127
+ 'right:0;top:var(--era-xs-inset-sm);bottom:var(--era-xs-inset-sm);translate:50% 0;width:calc(var(--era-xs-inset-sm)*2)'
127
128
  },
128
129
  // corners LAST — a square at each corner (diagonal gap filler), painted
129
130
  // above the edges so the red grab zone is always on top of the blue.
@@ -131,19 +132,19 @@
131
132
  dir: 'nw',
132
133
  cursor: 'cursor-nwse-resize',
133
134
  place:
134
- 'top:0;left:0;translate:-50% -50%;width:calc(var(--era-h-md)/2);height:calc(var(--era-h-md)/2)'
135
+ 'top:0;left:0;translate:-50% -50%;width:calc(var(--era-xs-inset-sm)*2);height:calc(var(--era-xs-inset-sm)*2)'
135
136
  },
136
137
  {
137
138
  dir: 'ne',
138
139
  cursor: 'cursor-nesw-resize',
139
140
  place:
140
- 'top:0;right:0;translate:50% -50%;width:calc(var(--era-h-md)/2);height:calc(var(--era-h-md)/2)'
141
+ 'top:0;right:0;translate:50% -50%;width:calc(var(--era-xs-inset-sm)*2);height:calc(var(--era-xs-inset-sm)*2)'
141
142
  },
142
143
  {
143
144
  dir: 'sw',
144
145
  cursor: 'cursor-nesw-resize',
145
146
  place:
146
- 'bottom:0;left:0;translate:-50% 50%;width:calc(var(--era-h-md)/2);height:calc(var(--era-h-md)/2)'
147
+ 'bottom:0;left:0;translate:-50% 50%;width:calc(var(--era-xs-inset-sm)*2);height:calc(var(--era-xs-inset-sm)*2)'
147
148
  },
148
149
  {
149
150
  // All four corners straddle (translate ±50%): 6px out AND 6px in, so they
@@ -151,14 +152,20 @@
151
152
  dir: 'se',
152
153
  cursor: 'cursor-nwse-resize',
153
154
  place:
154
- 'bottom:0;right:0;translate:50% 50%;width:calc(var(--era-h-md)/2);height:calc(var(--era-h-md)/2)'
155
+ 'bottom:0;right:0;translate:50% 50%;width:calc(var(--era-xs-inset-sm)*2);height:calc(var(--era-xs-inset-sm)*2)'
155
156
  }
156
157
  ];
157
158
 
158
- // Pointer-capture resize. The active edges follow `dir`: e/s grow from the
159
- // anchored top-left; w/n move the left/top edge, so both size AND position
160
- // change (position is the neodrag offset style:width/height leaves the
161
- // transform intact). min-clamping stops the moving edge instead of the box.
159
+ // Pointer-capture resize. e/s grow from the anchored top-left — pure size, so
160
+ // they're already rock-solid. w/n also move the left/top edge: the box must
161
+ // grow AND shift so the far edge stays put. We do that shift with `margin`, not
162
+ // neodrag's position/transform. Why: on any `position` change neodrag clears its
163
+ // transform and re-applies it on its OWN rAF a frame later, so for one frame the
164
+ // box is new-size + stale-origin — the far edge wiggles every move. `margin` is
165
+ // neodrag-independent and lands in the same frame as style:width, so both edges
166
+ // commit together and the anchored edge holds still. On release we fold the
167
+ // margin back into neodrag's offset (position) and clear it, so neodrag's model
168
+ // matches the DOM and the next drag tracks cleanly.
162
169
  function startResize(e: PointerEvent, dir: ResizeDir) {
163
170
  if (!ref) return;
164
171
  e.preventDefault();
@@ -179,26 +186,25 @@
179
186
  let latest: { dx: number; dy: number } | null = null;
180
187
  const apply = () => {
181
188
  frame = 0;
182
- if (!latest) return;
189
+ if (!latest || !ref) return;
183
190
  const { dx, dy } = latest;
184
191
  let w = startW;
185
192
  let h = startH;
186
- let px = startPos.x;
187
- let py = startPos.y;
193
+ let mx = 0;
194
+ let my = 0;
188
195
  if (dir.includes('e')) w = Math.max(minWidth, startW + dx);
189
196
  if (dir.includes('w')) {
190
197
  w = Math.max(minWidth, startW - dx);
191
- px = startPos.x + (startW - w);
198
+ mx = startW - w; // ≤ 0: the left edge slides out, right edge fixed
192
199
  }
193
200
  if (dir.includes('s')) h = Math.max(minHeight, startH + dy);
194
201
  if (dir.includes('n')) {
195
202
  h = Math.max(minHeight, startH - dy);
196
- py = startPos.y + (startH - h);
203
+ my = startH - h;
197
204
  }
198
205
  size = { width: w, height: h };
199
- // Only e/s corners never move the origin — skip the position write (and
200
- // its Compartment re-resolve) unless it actually changed.
201
- if (px !== position.x || py !== position.y) position = { x: px, y: py };
206
+ ref.style.marginLeft = mx ? `${mx}px` : '';
207
+ ref.style.marginTop = my ? `${my}px` : '';
202
208
  };
203
209
  const onMove = (ev: PointerEvent) => {
204
210
  latest = { dx: ev.clientX - startX, dy: ev.clientY - startY };
@@ -207,6 +213,16 @@
207
213
  const onUp = (ev: PointerEvent) => {
208
214
  if (frame) cancelAnimationFrame(frame);
209
215
  apply(); // flush the final delta synchronously
216
+ // Fold the margin shift into neodrag's offset and clear the margin — a
217
+ // net-zero swap that leaves the pane exactly where it looks, but with
218
+ // neodrag's position matching the DOM again so the next drag is true.
219
+ const mx = parseFloat(ref!.style.marginLeft) || 0;
220
+ const my = parseFloat(ref!.style.marginTop) || 0;
221
+ if (mx || my) {
222
+ position = { x: startPos.x + mx, y: startPos.y + my };
223
+ ref!.style.marginLeft = '';
224
+ ref!.style.marginTop = '';
225
+ }
210
226
  // Clearing `resizing` re-adds controls (see controlsComp), re-measuring
211
227
  // the handle zone at the new size.
212
228
  resizing = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "1.11.3",
3
+ "version": "1.11.5",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",