@atolljs/islands 0.1.8 → 0.1.10

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": "@atolljs/islands",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Atoll islands — MonoWorker/PolyWorker hosts rendering framework islands (React/Vue/Svelte/Solid/Angular) over a proxy-DOM op protocol",
5
5
  "homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "htmlparser2": "^12.0.0"
20
20
  },
21
21
  "peerDependencies": {
22
- "@atolljs/core": "0.1.8"
22
+ "@atolljs/core": "0.1.10"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
package/src/island.ts CHANGED
@@ -227,6 +227,7 @@ function buildEventPayload(
227
227
  e: Event,
228
228
  target: EventTarget | null,
229
229
  targetId: number | undefined,
230
+ relatedTargetId?: number,
230
231
  ): EventPayload {
231
232
  const input = target as HTMLInputElement | null;
232
233
  const mouse = e as MouseEvent;
@@ -243,6 +244,18 @@ function buildEventPayload(
243
244
  const coord = (v: unknown): number | undefined =>
244
245
  num(v) ?? (pointerFam ? 0 : undefined);
245
246
  const button = num(mouse.button) ?? (pointerFam ? 0 : undefined);
247
+ // currentTarget is the element this listener was attached to — the exact
248
+ // element hit-testing libraries normalize pointer coords against
249
+ // (zrender's `normalizeEvent(this.dom, e)`). Checked structurally, not
250
+ // via `instanceof Element`: in in-process embeddings the worker-side
251
+ // dispatcher may point the global at ProxyElement.
252
+ const ct = e.currentTarget as Element | null;
253
+ const localRect =
254
+ ct && typeof ct.getBoundingClientRect === 'function'
255
+ ? ct.getBoundingClientRect()
256
+ : null;
257
+ const local = (v: number | undefined, origin: number): number | undefined =>
258
+ localRect && typeof v === 'number' ? v - origin : undefined;
246
259
  return {
247
260
  type: e.type,
248
261
  value: input && 'value' in input ? input.value : undefined,
@@ -252,6 +265,8 @@ function buildEventPayload(
252
265
  clientY: coord(mouse.clientY),
253
266
  screenX: coord(mouse.screenX),
254
267
  screenY: coord(mouse.screenY),
268
+ zrX: local(mouse.clientX, localRect?.left ?? 0),
269
+ zrY: local(mouse.clientY, localRect?.top ?? 0),
255
270
  button,
256
271
  which:
257
272
  typeof mouse.which === 'number' && mouse.which !== 0
@@ -273,6 +288,7 @@ function buildEventPayload(
273
288
  pointerType: typeof pointer.pointerType === 'string' ? pointer.pointerType : undefined,
274
289
  scrollTop: input instanceof HTMLElement ? input.scrollTop : undefined,
275
290
  targetId,
291
+ relatedTargetId,
276
292
  };
277
293
  }
278
294
 
@@ -547,6 +563,7 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
547
563
  e,
548
564
  target,
549
565
  target !== null ? nodeIds.get(target as Node) : undefined,
566
+ nodeIds.get((e as MouseEvent).relatedTarget as Node),
550
567
  );
551
568
  // The whole point: an event = one postMessage round-trip. The worker
552
569
  // re-renders, we apply whatever ops come back.
package/src/ops.ts CHANGED
@@ -124,6 +124,19 @@ export interface EventPayload {
124
124
  clientY?: number;
125
125
  screenX?: number;
126
126
  screenY?: number;
127
+ /**
128
+ * Pointer coords in the LISTENER element's local space:
129
+ * `clientX/Y − event.currentTarget.getBoundingClientRect()` computed
130
+ * main-thread where real rects exist. The proxy DOM has no measurement
131
+ * channel, so worker-side hit-testing libraries cannot derive local
132
+ * coords themselves: zrender's four-marker getBoundingClientRect
133
+ * transform degenerates, and e.offsetX is absent anyway. zrender's
134
+ * normalizeEvent early-returns on `e.zrX != null`, which is why the
135
+ * fields carry its names — charts (echarts SVG renderer) get working
136
+ * axis pointers/tooltips, any library can read them the same way.
137
+ */
138
+ zrX?: number;
139
+ zrY?: number;
127
140
  button?: number;
128
141
  /**
129
142
  * UIEvent.which — non-standard but set by every browser (1 = left button,
@@ -153,6 +166,15 @@ export interface EventPayload {
153
166
  scrollTop?: number;
154
167
  /** The worker instance id of `event.target`, when it maps to one. */
155
168
  targetId?: number;
169
+ /**
170
+ * The worker instance id of `event.relatedTarget` (MouseEvent/FocusEvent),
171
+ * when it maps to an op-created node. Lets over/out/leave/enter handlers
172
+ * distinguish "moved to a child" from "actually left the element" —
173
+ * zrender's mouseout gate (`toElement || relatedTarget` → isLocalEl walk)
174
+ * is the canonical consumer; without it every child transition looks like
175
+ * a real leave and charts flicker as tooltips hide/show.
176
+ */
177
+ relatedTargetId?: number;
156
178
  /**
157
179
  * The proxy-DOM node for `targetId` — SYNTHESIZED worker-side by the proxy
158
180
  * DOM when it wraps a listener (it never crosses the wire). Lets delegated
@@ -162,6 +184,13 @@ export interface EventPayload {
162
184
  * same proxy document.
163
185
  */
164
186
  target?: unknown;
187
+ /**
188
+ * The proxy-DOM node for `relatedTargetId` — SYNTHESIZED worker-side like
189
+ * `target` (never crosses the wire). Handler code can walk
190
+ * `relatedTarget.parentNode` — e.g. zrender's isLocalEl — to test
191
+ * whether the pointer moved within the listener's subtree.
192
+ */
193
+ relatedTarget?: unknown;
165
194
  /**
166
195
  * Synthesized NO-OPS — added worker-side by the proxy DOM when it wraps a
167
196
  * listener's payload (they never cross the wire). They exist so library
@@ -307,6 +307,13 @@ export class InternalDocument implements ProxyDocument {
307
307
  out.target = node;
308
308
  }
309
309
  }
310
+ // Materialize relatedTarget the same way — over/out handlers walk it
311
+ // (zrender's isLocalEl climbs parentNode to the listener root) to tell
312
+ // "moved to a child" apart from "actually left".
313
+ if (typeof payload.relatedTargetId === 'number') {
314
+ const related = instances.get(payload.relatedTargetId);
315
+ if (related !== undefined) out.relatedTarget = this._wrap(related);
316
+ }
310
317
  if (out.composedPath === undefined) {
311
318
  const path: unknown[] = [];
312
319
  let n = out.target as ProxyNode | undefined;
@@ -81,6 +81,7 @@ const clientMounts = new WeakMap<object, number>();
81
81
  */
82
82
  const ENRICHED_KEYS = new Set([
83
83
  'target',
84
+ 'relatedTarget',
84
85
  'currentTarget',
85
86
  'composedPath',
86
87
  'preventDefault',