@grafloria/element 0.4.66 → 0.4.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafloria/element",
3
- "version": "0.4.66",
3
+ "version": "0.4.67",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -996,6 +996,8 @@ export function bindDashboardGrid(api, group, options = {}) {
996
996
  let slabGesture = null;
997
997
  /** The gesture as it is NOW, for the same reason. */
998
998
  const currentGesture = () => gesture;
999
+ /** Where the hand was at the PREVIOUS move event — the strip is tested along the segment between the two. */
1000
+ let prevWorld = null;
999
1001
  /** The PARENT running a resize of OUR section from a press this tool claimed. */
1000
1002
  let forwardSlab = null;
1001
1003
  const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
@@ -1236,6 +1238,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1236
1238
  };
1237
1239
  // -- the gesture machine ----------------------------------------------------
1238
1240
  const beginGestureVisuals = (g) => {
1241
+ prevWorld = null;
1239
1242
  engine.beginGesture();
1240
1243
  const snap = snapshotAll();
1241
1244
  g.startCells = snap.cells;
@@ -1432,6 +1435,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1432
1435
  nodeOf(g).setPosition(desired.x, desired.y);
1433
1436
  ghostStyleFastPath(g, desired);
1434
1437
  }
1438
+ prevWorld = g.lastWorld;
1435
1439
  g.lastWorld = { x: ev.world.x, y: ev.world.y };
1436
1440
  g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
1437
1441
  /** The ghost's pixel size on THIS board — a palette chip has spans, not a size. */
@@ -2194,7 +2198,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2194
2198
  };
2195
2199
  /** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
2196
2200
  const resolveTileZone = (g, ev) => {
2197
- var _a, _b, _c, _d, _e;
2201
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2198
2202
  const roots = zoneRoots();
2199
2203
  // THE CONTAINERS THIS GESTURE HAS MOVED, at the frames they rest in. A
2200
2204
  // drag's own pushes must never change what the hand means — settled for a
@@ -2207,7 +2211,45 @@ export function bindDashboardGrid(api, group, options = {}) {
2207
2211
  if (((_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.tabIndexAt) && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
2208
2212
  // A group never becomes a tab: over a container's strip it is over the
2209
2213
  // container's margin — a cell on the parent board, pushing with intent.
2210
- const hit = stripUnder({ x: ev.world.x, y: ev.world.y, roots, held: (_c = (_b = g.strip) === null || _b === void 0 ? void 0 : _b.containerId) !== null && _c !== void 0 ? _c : null, stay: STRIP_STAY / (clientPerWorld().y || 1), restFrames: rests });
2214
+ const scaleY = clientPerWorld().y || 1;
2215
+ let hit = stripUnder({ x: ev.world.x, y: ev.world.y, roots, held: (_c = (_b = g.strip) === null || _b === void 0 ? void 0 : _b.containerId) !== null && _c !== void 0 ? _c : null, stay: STRIP_STAY / scaleY, restFrames: rests });
2216
+ if (!hit && prevWorld && !g.strip) {
2217
+ // A HAND MOVES FASTER THAN A STRIP IS TALL. (Only for a hand ARRIVING:
2218
+ // one already holding the strip leaves it by the stay, which reaches
2219
+ // down and never up — the band above must stay reachable.) The strip is 30 px and a
2220
+ // hand covers 40 to 80 px between events, so testing only where the
2221
+ // pointer LANDS skips it — the user, coming down from above the panel:
2222
+ // "it's not passing by the tab header, it drops directly to inside or
2223
+ // outside." The segment it travelled is tested too, in steps of half a
2224
+ // strip: crossing the rows and landing within one strip's height of
2225
+ // them means the tabs. Flying far past them does not — a fast drag
2226
+ // into the page must never snag on the header.
2227
+ const dx = ev.world.x - prevWorld.x;
2228
+ const dy = ev.world.y - prevWorld.y;
2229
+ const n = Math.ceil(Math.hypot(dx, dy) / (TAB_STRIP_HEIGHT / 2));
2230
+ let crossed = null;
2231
+ for (let i = 1; i < n && !crossed; i++)
2232
+ crossed = (_e = (_d = stripUnder({ x: prevWorld.x + (dx * i) / n, y: prevWorld.y + (dy * i) / n, roots, held: null, stay: 0, restFrames: rests })) === null || _d === void 0 ? void 0 : _d.containerId) !== null && _e !== void 0 ? _e : null;
2233
+ if (crossed) {
2234
+ const grp = diagram.getGroup(crossed);
2235
+ const f = (_f = rests.get(crossed)) !== null && _f !== void 0 ? _f : (grp ? frameOfGroup(grp) : null);
2236
+ if (f) {
2237
+ const top = f.y;
2238
+ const bottom = f.y + TAB_STRIP_HEIGHT;
2239
+ // A CROSSING is the two events on OPPOSITE sides of the rows — a
2240
+ // hand that swept along the tabs from beside the panel and ended
2241
+ // above it did not cross them, it went past them. And the sides
2242
+ // still take the corners (0.4.47): a hand landing in the outer
2243
+ // fifth meant "after it", whatever it crossed on the way.
2244
+ const through = (prevWorld.y < top && ev.world.y > bottom) || (prevWorld.y > bottom && ev.world.y < top);
2245
+ const away = ev.world.y < top ? top - ev.world.y : ev.world.y > bottom ? ev.world.y - bottom : 0;
2246
+ const rx = (ev.world.x - f.x) / Math.max(1, f.width);
2247
+ const inSideBand = rx < BESIDE_BAND || rx > 1 - BESIDE_BAND;
2248
+ if (through && !inSideBand && ev.world.x >= f.x && ev.world.x <= f.x + f.width && away <= TAB_STRIP_HEIGHT / scaleY)
2249
+ hit = { containerId: crossed };
2250
+ }
2251
+ }
2252
+ }
2211
2253
  if (hit) {
2212
2254
  // The SLOT is the painted strip's business — its tabs are laid out by
2213
2255
  // the browser. A container the gesture shifted sideways is painted
@@ -2228,7 +2270,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2228
2270
  strip,
2229
2271
  prev: beside
2230
2272
  ? { containerId: beside.id, side: beside.side, frame0: beside.frame0, vacated: cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows()) }
2231
- : ((_e = (_d = g.leg) === null || _d === void 0 ? void 0 : _d.adopted.besideState()) !== null && _e !== void 0 ? _e : null), // a beside another board holds for the ghost, through its leg
2273
+ : ((_h = (_g = g.leg) === null || _g === void 0 ? void 0 : _g.adopted.besideState()) !== null && _h !== void 0 ? _h : null), // a beside another board holds for the ghost, through its leg
2232
2274
  maxDepth: nesting,
2233
2275
  ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0, // a group's widgets sit one board deeper than wherever it lands
2234
2276
  restFrames: rests,
@@ -190,50 +190,6 @@ export interface StripProbe {
190
190
  export declare function stripUnder(p: StripProbe): {
191
191
  containerId: string;
192
192
  } | null;
193
- export interface ContainerProbe {
194
- x: number;
195
- y: number;
196
- roots: ZoneBoard[];
197
- /** Containers the gesture has displaced, at the frames they rest in. */
198
- restFrames?: ReadonlyMap<string, ZoneRect>;
199
- /** The groups inside the dragged tile: never a target, so never lit. */
200
- ghostSubtree?: ReadonlySet<string>;
201
- /** The containers the dragged tile's own board sits in: their bands do not apply to it, so they are not lit either. */
202
- homeChain?: ReadonlySet<string>;
203
- }
204
- /** A container's edges, for showing a hand what they mean. */
205
- export interface ContainerBands {
206
- containerId: string;
207
- frame: ZoneRect;
208
- stripHeight: number;
209
- band: number;
210
- bandY?: number;
211
- topOutside?: number;
212
- }
213
- /**
214
- * The deepest container under a pointer whose EDGES mean something — the one
215
- * whose bands a drag should be shown while it is held there.
216
- *
217
- * `bandOf` decides; this only reports where the decision lives, on the same
218
- * tree and the same frames, so a painted lane and the zone it stands for can
219
- * never disagree. Without it the bands are invisible: 0.4.62 made the top and
220
- * bottom a fixed 30 px, which is a fine target and an impossible guess — to
221
- * put a widget ABOVE a panel you point just BELOW its header, inside what
222
- * reads as page content. ("Nothing moves it any more, but how can I drag
223
- * something on top of the tab group?")
224
- */
225
- export declare function containerUnder(p: ContainerProbe): ContainerBands | null;
226
- /**
227
- * Where a container's bands are, in the same world units as its frame. TOP
228
- * appears twice: hanging above the frame, where a hand looking for "above it"
229
- * goes, and under the strip, which is the only one a container pressed against
230
- * the top of its canvas can offer. Both mean the same thing.
231
- */
232
- export declare function bandRects(c: ContainerBands): {
233
- side: BesideSide;
234
- name: string;
235
- rect: ZoneRect;
236
- }[];
237
193
  export declare function resolve(input: ResolveInput): Zone;
238
194
  /** A join target as the walk sees it: another tab container, its frame anchored by the binder while the pointer is inside it. */
239
195
  export interface TabTarget {
@@ -125,82 +125,24 @@ export function stripUnder(p) {
125
125
  visit(r, 0, 0);
126
126
  return bestId === null ? null : { containerId: bestId };
127
127
  }
128
- /**
129
- * The deepest container under a pointer whose EDGES mean something — the one
130
- * whose bands a drag should be shown while it is held there.
131
- *
132
- * `bandOf` decides; this only reports where the decision lives, on the same
133
- * tree and the same frames, so a painted lane and the zone it stands for can
134
- * never disagree. Without it the bands are invisible: 0.4.62 made the top and
135
- * bottom a fixed 30 px, which is a fine target and an impossible guess — to
136
- * put a widget ABOVE a panel you point just BELOW its header, inside what
137
- * reads as page content. ("Nothing moves it any more, but how can I drag
138
- * something on top of the tab group?")
139
- */
140
- export function containerUnder(p) {
141
- const { x, y } = p;
142
- let best = null;
143
- let bestDepth = -1;
144
- const visit = (b, dx, dy) => {
145
- var _a, _b, _c, _d;
146
- for (const c of b.children()) {
147
- if ((_a = p.ghostSubtree) === null || _a === void 0 ? void 0 : _a.has(c.id))
148
- continue;
149
- const rest = (_b = p.restFrames) === null || _b === void 0 ? void 0 : _b.get(c.id);
150
- const atRest = rest && inRect(rest, x, y) ? rest : null;
151
- const frame = atRest !== null && atRest !== void 0 ? atRest : c.frame;
152
- const tx = atRest ? x : x + dx;
153
- const ty = atRest ? y : y + dy;
154
- const up = (_c = c.topOutside) !== null && _c !== void 0 ? _c : 0;
155
- const inOverhang = up > 0 && tx >= frame.x && tx <= frame.x + frame.width && ty < frame.y && ty >= frame.y - up;
156
- if (!inRect(frame, tx, ty) && !inOverhang)
157
- continue;
158
- if (c.band > 0 && !((_d = p.homeChain) === null || _d === void 0 ? void 0 : _d.has(c.id)) && b.depth >= bestDepth) {
159
- best = Object.assign(Object.assign({ containerId: c.id, frame, stripHeight: c.stripHeight, band: c.band }, (c.bandY === undefined ? {} : { bandY: c.bandY })), (c.topOutside === undefined ? {} : { topOutside: c.topOutside }));
160
- bestDepth = b.depth;
161
- }
162
- if (c.inner)
163
- visit(c.inner, atRest ? c.frame.x - atRest.x : dx, atRest ? c.frame.y - atRest.y : dy);
164
- }
165
- };
166
- for (const r of p.roots)
167
- visit(r, 0, 0);
168
- return best;
169
- }
170
- /**
171
- * Where a container's bands are, in the same world units as its frame. TOP
172
- * appears twice: hanging above the frame, where a hand looking for "above it"
173
- * goes, and under the strip, which is the only one a container pressed against
174
- * the top of its canvas can offer. Both mean the same thing.
175
- */
176
- export function bandRects(c) {
177
- var _a;
178
- const bodyY = c.frame.y + c.stripHeight;
179
- const bodyH = Math.max(1, c.frame.height - c.stripHeight);
180
- const w = Math.max(1, c.frame.width);
181
- const side = w * c.band;
182
- const depth = c.bandY !== undefined && c.bandY > 0 ? Math.min(c.bandY, bodyH / 2) : c.band * bodyH;
183
- const up = (_a = c.topOutside) !== null && _a !== void 0 ? _a : 0;
184
- return [
185
- { side: 'left', name: 'left', rect: { x: c.frame.x, y: bodyY, width: side, height: bodyH } },
186
- { side: 'right', name: 'right', rect: { x: c.frame.x + w - side, y: bodyY, width: side, height: bodyH } },
187
- // above the frame: the whole width, since no side band is up there to take the corners
188
- ...(up > 0 ? [{ side: 'top', name: 'top', rect: { x: c.frame.x, y: c.frame.y - up, width: w, height: up } }] : []),
189
- // and under the strip; the sides take the corners here (bandOf tests rx first)
190
- { side: 'top', name: 'top-inside', rect: { x: c.frame.x + side, y: bodyY, width: w - 2 * side, height: depth } },
191
- { side: 'bottom', name: 'bottom', rect: { x: c.frame.x + side, y: bodyY + bodyH - depth, width: w - 2 * side, height: depth } },
192
- ];
193
- }
194
128
  /**
195
129
  * The band a point is in for a TILE drag: the sides and the bottom as
196
130
  * `bandOf` gives them, and "top" ONLY from the band hanging above the frame.
197
- * The rows under the strip are the page.
131
+ * The rows under the strip are the page. A lane there meant "above" too
132
+ * (0.4.62–0.4.66), so a hand coming down at speed read above, above, page —
133
+ * the answer never changed across the header and the tabs looked skipped.
134
+ * One place means above: above (0.4.67).
198
135
  */
199
136
  function tileBand(f, stripHeight, x, y, band, bandY, topOutside, grow = 1) {
200
137
  const up = (topOutside !== null && topOutside !== void 0 ? topOutside : 0) * grow;
201
- if (up > 0 && x >= f.x && x <= f.x + f.width && y < f.y && y >= f.y - up)
202
- return 'top';
203
- return bandOf(f, stripHeight, x, y, band, bandY === undefined ? undefined : bandY * grow);
138
+ if (up > 0 && x >= f.x && x <= f.x + f.width && y < f.y && y >= f.y - up) {
139
+ // the sides take the corners here too (0.4.48's precedence): a widget
140
+ // carried along the top of a panel to its far right means "after it"
141
+ const rx = (x - f.x) / Math.max(1, f.width);
142
+ return rx < band ? 'left' : rx > 1 - band ? 'right' : 'top';
143
+ }
144
+ const side = bandOf(f, stripHeight, x, y, band, bandY === undefined ? undefined : bandY * grow);
145
+ return side === 'top' ? null : side;
204
146
  }
205
147
  /** The board a container sits on, found by id through the tree. */
206
148
  function boardOf(roots, containerId) {
@@ -239,7 +181,12 @@ export function resolve(input) {
239
181
  const grow = 1 + BESIDE_STAY / BESIDE_BAND;
240
182
  const stay = tileBand(p.frame0, stripH, x, y, BESIDE_BAND + BESIDE_STAY, depth, c0 === null || c0 === void 0 ? void 0 : c0.topOutside, grow);
241
183
  const other = tileBand(p.frame0, stripH, x, y, BESIDE_BAND, depth, c0 === null || c0 === void 0 ? void 0 : c0.topOutside);
242
- const onVacated = !!p.vacated && inRect(p.vacated, x, y, input.gap);
184
+ // The cell an "above" took is the container's own rest rows, so "over the
185
+ // vacated cell" would hold the beside 130 px into what is the PAGE — a
186
+ // fast hand then saw no change at all across the header. Inside the body
187
+ // the vacated cell does not count; beside the frame it still does.
188
+ const insideBody = inRect(p.frame0, x, y) && y >= p.frame0.y + stripH;
189
+ const onVacated = !!p.vacated && !insideBody && inRect(p.vacated, x, y, input.gap);
243
190
  const held = stay === p.side || (!(other !== null && other !== p.side) && onVacated);
244
191
  if (held) {
245
192
  const board = boardOf(input.roots, p.containerId);
@@ -311,8 +258,10 @@ export function resolve(input) {
311
258
  const frame = atRest !== null && atRest !== void 0 ? atRest : c.frame;
312
259
  const tx = atRest ? x : x + dx;
313
260
  const ty = atRest ? y : y + dy;
314
- if (tileBand(frame, c.stripHeight, tx, ty, c.band, c.bandY, c.topOutside) === 'top')
315
- return { kind: 'beside', board, containerId: c.id, side: 'top', kept: false };
261
+ // only the band above the frame can answer from out here and its corners are the sides'
262
+ const side = tileBand(frame, c.stripHeight, tx, ty, c.band, c.bandY, c.topOutside);
263
+ if (side)
264
+ return { kind: 'beside', board, containerId: c.id, side, kept: false };
316
265
  const deeper = c.inner ? overhang(c.inner, atRest ? c.frame.x - atRest.x : dx, atRest ? c.frame.y - atRest.y : dy) : null;
317
266
  if (deeper)
318
267
  return deeper;