@react-shimeji/core 0.2.0 → 0.2.2

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/dist/index.d.cts CHANGED
@@ -219,7 +219,7 @@ interface ShimejiEngineOptions {
219
219
  gravity?: number;
220
220
  /** Maximum elapsed time applied to one animation update. Defaults to 100 ms. */
221
221
  maxDeltaTime?: number;
222
- /** CSS class added to the generated work area. */
222
+ /** @deprecated There is no shared work area; retained for source compatibility. */
223
223
  workAreaClassName?: string;
224
224
  /** CSS class added to each generated mascot element. */
225
225
  mascotClassName?: string;
@@ -326,8 +326,6 @@ declare class ShimejiEngine {
326
326
  private readonly disposers;
327
327
  private readonly intervals;
328
328
  private readonly options;
329
- private readonly originalContainerPosition;
330
- private readonly adjustedContainerPosition;
331
329
  private pointer;
332
330
  private animationFrame;
333
331
  private lastFrameTime;
@@ -464,7 +462,7 @@ interface PlatformRectangle extends Rectangle {
464
462
  }
465
463
  /** Resolves a platform option into DOM elements, excluding engine-owned nodes. */
466
464
  declare function resolvePlatformElements(source: string | readonly HTMLElement[], document: Document, excludedRoot?: HTMLElement): HTMLElement[];
467
- /** Reads platform bounds once and converts viewport coordinates to work-area coordinates. */
465
+ /** Reads platform bounds once and converts viewport coordinates to the supplied origin. */
468
466
  declare function readPlatformRectangles(elements: readonly HTMLElement[], workAreaRectangle: Pick<DOMRect, "left" | "top">): PlatformRectangle[];
469
467
 
470
468
  /** A resolved image and optional atlas crop for one sprite frame. */
@@ -496,9 +494,9 @@ declare function isIndividualSprite(sprite: SpriteRectangle | IndividualSprite |
496
494
 
497
495
  /** DOM nodes and resources owned by one mascot. */
498
496
  interface MascotDomHandle {
499
- /** Pointer-interactive mascot wrapper. */
497
+ /** Fixed, pointer-transparent mascot wrapper. */
500
498
  element: HTMLDivElement;
501
- /** Child element on which sprite images are painted. */
499
+ /** Pointer-interactive child element on which sprite images are painted. */
502
500
  spriteElement: HTMLDivElement;
503
501
  /** Per-mascot spritesheet URL lease. */
504
502
  spriteLease: SpriteLease;
package/dist/index.d.ts CHANGED
@@ -219,7 +219,7 @@ interface ShimejiEngineOptions {
219
219
  gravity?: number;
220
220
  /** Maximum elapsed time applied to one animation update. Defaults to 100 ms. */
221
221
  maxDeltaTime?: number;
222
- /** CSS class added to the generated work area. */
222
+ /** @deprecated There is no shared work area; retained for source compatibility. */
223
223
  workAreaClassName?: string;
224
224
  /** CSS class added to each generated mascot element. */
225
225
  mascotClassName?: string;
@@ -326,8 +326,6 @@ declare class ShimejiEngine {
326
326
  private readonly disposers;
327
327
  private readonly intervals;
328
328
  private readonly options;
329
- private readonly originalContainerPosition;
330
- private readonly adjustedContainerPosition;
331
329
  private pointer;
332
330
  private animationFrame;
333
331
  private lastFrameTime;
@@ -464,7 +462,7 @@ interface PlatformRectangle extends Rectangle {
464
462
  }
465
463
  /** Resolves a platform option into DOM elements, excluding engine-owned nodes. */
466
464
  declare function resolvePlatformElements(source: string | readonly HTMLElement[], document: Document, excludedRoot?: HTMLElement): HTMLElement[];
467
- /** Reads platform bounds once and converts viewport coordinates to work-area coordinates. */
465
+ /** Reads platform bounds once and converts viewport coordinates to the supplied origin. */
468
466
  declare function readPlatformRectangles(elements: readonly HTMLElement[], workAreaRectangle: Pick<DOMRect, "left" | "top">): PlatformRectangle[];
469
467
 
470
468
  /** A resolved image and optional atlas crop for one sprite frame. */
@@ -496,9 +494,9 @@ declare function isIndividualSprite(sprite: SpriteRectangle | IndividualSprite |
496
494
 
497
495
  /** DOM nodes and resources owned by one mascot. */
498
496
  interface MascotDomHandle {
499
- /** Pointer-interactive mascot wrapper. */
497
+ /** Fixed, pointer-transparent mascot wrapper. */
500
498
  element: HTMLDivElement;
501
- /** Child element on which sprite images are painted. */
499
+ /** Pointer-interactive child element on which sprite images are painted. */
502
500
  spriteElement: HTMLDivElement;
503
501
  /** Per-mascot spritesheet URL lease. */
504
502
  spriteLease: SpriteLease;
package/dist/index.js CHANGED
@@ -1,41 +1,23 @@
1
1
  // src/dom.ts
2
2
  var DomManager = class {
3
- /** Creates an isolated work area inside the supplied host element. */
4
- constructor(container, sprites, workAreaClassName) {
3
+ /** Uses the supplied element only as a mount point for independent mascots. */
4
+ constructor(container, sprites) {
5
5
  this.container = container;
6
6
  this.sprites = sprites;
7
- const workArea = document.createElement("div");
8
- workArea.dataset.reactShimejiWorkArea = "true";
9
- if (workAreaClassName) workArea.className = workAreaClassName;
10
- Object.assign(workArea.style, {
11
- position: "absolute",
12
- inset: "0",
13
- width: "100%",
14
- height: "100%",
15
- overflow: "hidden",
16
- pointerEvents: "none",
17
- zIndex: "2147483643"
18
- });
19
- this.workArea = workArea;
20
- this.ensureMounted();
21
7
  }
22
8
  container;
23
9
  sprites;
24
- /** Generated element that contains all mascots. */
25
- workArea;
26
- /** Reattaches the work area if application code temporarily removed it. */
10
+ handles = /* @__PURE__ */ new Set();
11
+ /** Reattaches mascot elements if application code temporarily removed them. */
27
12
  ensureMounted() {
28
- if (this.workArea.parentElement !== this.container) this.container.appendChild(this.workArea);
13
+ for (const handle of this.handles) {
14
+ if (handle.element.parentElement !== this.container) this.container.appendChild(handle.element);
15
+ }
29
16
  }
30
- /** Returns the current local work-area rectangle. */
17
+ /** Returns viewport bounds because fixed mascots use viewport coordinates. */
31
18
  getBounds() {
32
- return this.getBoundsFromClientRectangle(this.workArea.getBoundingClientRect());
33
- }
34
- /** Converts a previously-read work-area client rectangle into local bounds. */
35
- getBoundsFromClientRectangle(rectangle) {
36
- const width = rectangle.width || this.container.clientWidth || window.innerWidth;
37
- const height = rectangle.height || this.container.clientHeight || window.innerHeight;
38
- return { x: 0, y: 0, width, height };
19
+ const view = this.container.ownerDocument.defaultView ?? window;
20
+ return { x: 0, y: 0, width: view.innerWidth, height: view.innerHeight };
39
21
  }
40
22
  /** Creates a mascot node and acquires its spritesheet resource. */
41
23
  createMascot(spec, mascotId, mascotClassName) {
@@ -43,12 +25,14 @@ var DomManager = class {
43
25
  const element = document.createElement("div");
44
26
  element.dataset.shimejiId = mascotId;
45
27
  if (mascotClassName) element.className = mascotClassName;
46
- Object.assign(element.style, { position: "absolute", left: "0", top: "0", width: "0", height: "0", pointerEvents: "auto", touchAction: "none", userSelect: "none", willChange: "transform" });
28
+ Object.assign(element.style, { position: "fixed", left: "0", top: "0", width: "0", height: "0", pointerEvents: "none", zIndex: "9999", userSelect: "none", willChange: "transform" });
47
29
  const spriteElement = document.createElement("div");
48
- Object.assign(spriteElement.style, { position: "absolute", left: "0", top: "0", backgroundRepeat: "no-repeat", transformOrigin: "center center", pointerEvents: "none" });
30
+ Object.assign(spriteElement.style, { position: "absolute", left: "0", top: "0", backgroundRepeat: "no-repeat", transformOrigin: "center center", pointerEvents: "auto", touchAction: "none", userSelect: "none" });
49
31
  element.appendChild(spriteElement);
50
- this.workArea.appendChild(element);
51
- return { element, spriteElement, spriteLease };
32
+ const handle = { element, spriteElement, spriteLease };
33
+ this.handles.add(handle);
34
+ this.container.appendChild(element);
35
+ return handle;
52
36
  }
53
37
  /** Paints one mascot state into its existing DOM nodes. */
54
38
  render(handle, spec, state) {
@@ -80,12 +64,18 @@ var DomManager = class {
80
64
  }
81
65
  /** Removes one mascot node and releases its temporary image URL. */
82
66
  removeMascot(handle) {
67
+ if (!this.handles.delete(handle)) return;
83
68
  handle.element.remove();
84
69
  handle.spriteLease.release();
85
70
  }
86
- /** Removes the work area owned by this manager. */
71
+ /** Returns whether an element belongs to one of this manager's mascots. */
72
+ owns(element) {
73
+ for (const handle of this.handles) if (handle.element === element || handle.element.contains(element)) return true;
74
+ return false;
75
+ }
76
+ /** Removes every mascot element and releases its temporary image URL. */
87
77
  destroy() {
88
- this.workArea.remove();
78
+ for (const handle of [...this.handles]) this.removeMascot(handle);
89
79
  }
90
80
  };
91
81
 
@@ -939,6 +929,7 @@ function environmentRectangle(bounds) {
939
929
  };
940
930
  }
941
931
  var PLATFORM_NEARBY_DISTANCE = 400;
932
+ var PLATFORM_EDGE_TOLERANCE = 16;
942
933
  function distanceToRectangle(point, rectangle) {
943
934
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
944
935
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1012,9 +1003,11 @@ var Mascot = class {
1012
1003
  }
1013
1004
  if (!started) break;
1014
1005
  }
1015
- const wasOnPlatformTop = environment.mascot.environment.activeIE.visible && environment.mascot.environment.activeIE.topBorder.isOn(this.state);
1006
+ const activeIE = environment.mascot.environment.activeIE;
1007
+ const wasOnPlatformTop = activeIE.visible && activeIE.topBorder.isOn(this.state);
1016
1008
  const completed = this.actions.tick(deltaMs, environment, bounds);
1017
- if (wasOnPlatformTop && !platforms.some((platform) => isOnTop(this.state, platform))) {
1009
+ const remainedNearPlatform = this.state.x >= activeIE.left - PLATFORM_EDGE_TOLERANCE && this.state.x <= activeIE.right + PLATFORM_EDGE_TOLERANCE;
1010
+ if (wasOnPlatformTop && !remainedNearPlatform && !platforms.some((platform) => isOnTop(this.state, platform))) {
1018
1011
  this.actions.cancel();
1019
1012
  this.currentBehavior = this.findFallBehavior();
1020
1013
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms));
@@ -1099,7 +1092,7 @@ var Mascot = class {
1099
1092
  return nearby;
1100
1093
  }
1101
1094
  installPointerHandlers() {
1102
- const element = this.domHandle.element;
1095
+ const element = this.domHandle.spriteElement;
1103
1096
  const listen = (target, type, listener) => {
1104
1097
  target.addEventListener(type, listener);
1105
1098
  this.disposers.push(() => target.removeEventListener(type, listener));
@@ -1108,8 +1101,7 @@ var Mascot = class {
1108
1101
  const pointerEvent = event;
1109
1102
  if (pointerEvent.button !== 0) return;
1110
1103
  event.preventDefault();
1111
- const bounds = this.dom.workArea.getBoundingClientRect();
1112
- const point = { x: pointerEvent.clientX - bounds.left, y: pointerEvent.clientY - bounds.top };
1104
+ const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1113
1105
  this.pointerId = pointerEvent.pointerId;
1114
1106
  this.pointerDown = point;
1115
1107
  this.lastPointer = point;
@@ -1123,8 +1115,7 @@ var Mascot = class {
1123
1115
  listen(document, "pointermove", (event) => {
1124
1116
  const pointerEvent = event;
1125
1117
  if (!this.state.dragging || pointerEvent.pointerId !== this.pointerId) return;
1126
- const bounds = this.dom.workArea.getBoundingClientRect();
1127
- const point = { x: pointerEvent.clientX - bounds.left, y: pointerEvent.clientY - bounds.top };
1118
+ const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1128
1119
  const previous = this.lastPointer ?? point;
1129
1120
  this.state.vx = (point.x - previous.x) * 0.8;
1130
1121
  this.state.vy = (point.y - previous.y) * 0.8;
@@ -1272,10 +1263,7 @@ var ShimejiEngine = class {
1272
1263
  if (!container) throw new TypeError("ShimejiEngine requires a container element");
1273
1264
  this.options = { ...defaults, ...options, random: options.random };
1274
1265
  this.platformSource = this.options.platforms;
1275
- this.originalContainerPosition = container.style.position;
1276
- this.adjustedContainerPosition = getComputedStyle(container).position === "static";
1277
- if (this.adjustedContainerPosition) container.style.position = "relative";
1278
- this.dom = new DomManager(container, this.sprites, this.options.workAreaClassName || void 0);
1266
+ this.dom = new DomManager(container, this.sprites);
1279
1267
  this.initialize();
1280
1268
  }
1281
1269
  container;
@@ -1287,8 +1275,6 @@ var ShimejiEngine = class {
1287
1275
  disposers = [];
1288
1276
  intervals = /* @__PURE__ */ new Set();
1289
1277
  options;
1290
- originalContainerPosition;
1291
- adjustedContainerPosition;
1292
1278
  pointer = { x: 0, y: 0, dx: 0, dy: 0 };
1293
1279
  animationFrame;
1294
1280
  lastFrameTime;
@@ -1303,9 +1289,8 @@ var ShimejiEngine = class {
1303
1289
  this.initialized = true;
1304
1290
  this.listen(document, "pointermove", (event) => {
1305
1291
  const pointerEvent = event;
1306
- const rectangle = this.dom.workArea.getBoundingClientRect();
1307
- const x = pointerEvent.clientX - rectangle.left;
1308
- const y = pointerEvent.clientY - rectangle.top;
1292
+ const x = pointerEvent.clientX;
1293
+ const y = pointerEvent.clientY;
1309
1294
  this.pointer = { x, y, dx: x - this.pointer.x, dy: y - this.pointer.y };
1310
1295
  });
1311
1296
  this.listen(window, "resize", () => this.renderAll());
@@ -1409,7 +1394,6 @@ var ShimejiEngine = class {
1409
1394
  this.sprites.destroy();
1410
1395
  this.specs.clear();
1411
1396
  this.events.clear();
1412
- if (this.adjustedContainerPosition && this.container.style.position === "relative") this.container.style.position = this.originalContainerPosition;
1413
1397
  this.destroyed = true;
1414
1398
  this.initialized = false;
1415
1399
  }
@@ -1432,10 +1416,9 @@ var ShimejiEngine = class {
1432
1416
  for (const mascot of this.mascots.values()) mascot.tick(0, bounds, platforms);
1433
1417
  }
1434
1418
  readFrameGeometry() {
1435
- const workAreaRectangle = this.dom.workArea.getBoundingClientRect();
1436
- const bounds = this.dom.getBoundsFromClientRectangle(workAreaRectangle);
1437
- const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument, this.dom.workArea);
1438
- return { bounds, platforms: readPlatformRectangles(elements, workAreaRectangle) };
1419
+ const bounds = this.dom.getBounds();
1420
+ const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument).filter((element) => !this.dom.owns(element));
1421
+ return { bounds, platforms: readPlatformRectangles(elements, { left: 0, top: 0 }) };
1439
1422
  }
1440
1423
  emitState() {
1441
1424
  this.events.emit("statechange", this.getState());