@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.cjs CHANGED
@@ -47,42 +47,24 @@ module.exports = __toCommonJS(index_exports);
47
47
 
48
48
  // src/dom.ts
49
49
  var DomManager = class {
50
- /** Creates an isolated work area inside the supplied host element. */
51
- constructor(container, sprites, workAreaClassName) {
50
+ /** Uses the supplied element only as a mount point for independent mascots. */
51
+ constructor(container, sprites) {
52
52
  this.container = container;
53
53
  this.sprites = sprites;
54
- const workArea = document.createElement("div");
55
- workArea.dataset.reactShimejiWorkArea = "true";
56
- if (workAreaClassName) workArea.className = workAreaClassName;
57
- Object.assign(workArea.style, {
58
- position: "absolute",
59
- inset: "0",
60
- width: "100%",
61
- height: "100%",
62
- overflow: "hidden",
63
- pointerEvents: "none",
64
- zIndex: "2147483643"
65
- });
66
- this.workArea = workArea;
67
- this.ensureMounted();
68
54
  }
69
55
  container;
70
56
  sprites;
71
- /** Generated element that contains all mascots. */
72
- workArea;
73
- /** Reattaches the work area if application code temporarily removed it. */
57
+ handles = /* @__PURE__ */ new Set();
58
+ /** Reattaches mascot elements if application code temporarily removed them. */
74
59
  ensureMounted() {
75
- if (this.workArea.parentElement !== this.container) this.container.appendChild(this.workArea);
60
+ for (const handle of this.handles) {
61
+ if (handle.element.parentElement !== this.container) this.container.appendChild(handle.element);
62
+ }
76
63
  }
77
- /** Returns the current local work-area rectangle. */
64
+ /** Returns viewport bounds because fixed mascots use viewport coordinates. */
78
65
  getBounds() {
79
- return this.getBoundsFromClientRectangle(this.workArea.getBoundingClientRect());
80
- }
81
- /** Converts a previously-read work-area client rectangle into local bounds. */
82
- getBoundsFromClientRectangle(rectangle) {
83
- const width = rectangle.width || this.container.clientWidth || window.innerWidth;
84
- const height = rectangle.height || this.container.clientHeight || window.innerHeight;
85
- return { x: 0, y: 0, width, height };
66
+ const view = this.container.ownerDocument.defaultView ?? window;
67
+ return { x: 0, y: 0, width: view.innerWidth, height: view.innerHeight };
86
68
  }
87
69
  /** Creates a mascot node and acquires its spritesheet resource. */
88
70
  createMascot(spec, mascotId, mascotClassName) {
@@ -90,12 +72,14 @@ var DomManager = class {
90
72
  const element = document.createElement("div");
91
73
  element.dataset.shimejiId = mascotId;
92
74
  if (mascotClassName) element.className = mascotClassName;
93
- Object.assign(element.style, { position: "absolute", left: "0", top: "0", width: "0", height: "0", pointerEvents: "auto", touchAction: "none", userSelect: "none", willChange: "transform" });
75
+ Object.assign(element.style, { position: "fixed", left: "0", top: "0", width: "0", height: "0", pointerEvents: "none", zIndex: "9999", userSelect: "none", willChange: "transform" });
94
76
  const spriteElement = document.createElement("div");
95
- Object.assign(spriteElement.style, { position: "absolute", left: "0", top: "0", backgroundRepeat: "no-repeat", transformOrigin: "center center", pointerEvents: "none" });
77
+ Object.assign(spriteElement.style, { position: "absolute", left: "0", top: "0", backgroundRepeat: "no-repeat", transformOrigin: "center center", pointerEvents: "auto", touchAction: "none", userSelect: "none" });
96
78
  element.appendChild(spriteElement);
97
- this.workArea.appendChild(element);
98
- return { element, spriteElement, spriteLease };
79
+ const handle = { element, spriteElement, spriteLease };
80
+ this.handles.add(handle);
81
+ this.container.appendChild(element);
82
+ return handle;
99
83
  }
100
84
  /** Paints one mascot state into its existing DOM nodes. */
101
85
  render(handle, spec, state) {
@@ -127,12 +111,18 @@ var DomManager = class {
127
111
  }
128
112
  /** Removes one mascot node and releases its temporary image URL. */
129
113
  removeMascot(handle) {
114
+ if (!this.handles.delete(handle)) return;
130
115
  handle.element.remove();
131
116
  handle.spriteLease.release();
132
117
  }
133
- /** Removes the work area owned by this manager. */
118
+ /** Returns whether an element belongs to one of this manager's mascots. */
119
+ owns(element) {
120
+ for (const handle of this.handles) if (handle.element === element || handle.element.contains(element)) return true;
121
+ return false;
122
+ }
123
+ /** Removes every mascot element and releases its temporary image URL. */
134
124
  destroy() {
135
- this.workArea.remove();
125
+ for (const handle of [...this.handles]) this.removeMascot(handle);
136
126
  }
137
127
  };
138
128
 
@@ -986,6 +976,7 @@ function environmentRectangle(bounds) {
986
976
  };
987
977
  }
988
978
  var PLATFORM_NEARBY_DISTANCE = 400;
979
+ var PLATFORM_EDGE_TOLERANCE = 16;
989
980
  function distanceToRectangle(point, rectangle) {
990
981
  const dx = Math.max(rectangle.x - point.x, 0, point.x - rectangle.x - rectangle.width);
991
982
  const dy = Math.max(rectangle.y - point.y, 0, point.y - rectangle.y - rectangle.height);
@@ -1059,9 +1050,11 @@ var Mascot = class {
1059
1050
  }
1060
1051
  if (!started) break;
1061
1052
  }
1062
- const wasOnPlatformTop = environment.mascot.environment.activeIE.visible && environment.mascot.environment.activeIE.topBorder.isOn(this.state);
1053
+ const activeIE = environment.mascot.environment.activeIE;
1054
+ const wasOnPlatformTop = activeIE.visible && activeIE.topBorder.isOn(this.state);
1063
1055
  const completed = this.actions.tick(deltaMs, environment, bounds);
1064
- if (wasOnPlatformTop && !platforms.some((platform) => isOnTop(this.state, platform))) {
1056
+ const remainedNearPlatform = this.state.x >= activeIE.left - PLATFORM_EDGE_TOLERANCE && this.state.x <= activeIE.right + PLATFORM_EDGE_TOLERANCE;
1057
+ if (wasOnPlatformTop && !remainedNearPlatform && !platforms.some((platform) => isOnTop(this.state, platform))) {
1065
1058
  this.actions.cancel();
1066
1059
  this.currentBehavior = this.findFallBehavior();
1067
1060
  if (this.currentBehavior) this.startBehavior(this.createEnvironment(bounds, platforms));
@@ -1146,7 +1139,7 @@ var Mascot = class {
1146
1139
  return nearby;
1147
1140
  }
1148
1141
  installPointerHandlers() {
1149
- const element = this.domHandle.element;
1142
+ const element = this.domHandle.spriteElement;
1150
1143
  const listen = (target, type, listener) => {
1151
1144
  target.addEventListener(type, listener);
1152
1145
  this.disposers.push(() => target.removeEventListener(type, listener));
@@ -1155,8 +1148,7 @@ var Mascot = class {
1155
1148
  const pointerEvent = event;
1156
1149
  if (pointerEvent.button !== 0) return;
1157
1150
  event.preventDefault();
1158
- const bounds = this.dom.workArea.getBoundingClientRect();
1159
- const point = { x: pointerEvent.clientX - bounds.left, y: pointerEvent.clientY - bounds.top };
1151
+ const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1160
1152
  this.pointerId = pointerEvent.pointerId;
1161
1153
  this.pointerDown = point;
1162
1154
  this.lastPointer = point;
@@ -1170,8 +1162,7 @@ var Mascot = class {
1170
1162
  listen(document, "pointermove", (event) => {
1171
1163
  const pointerEvent = event;
1172
1164
  if (!this.state.dragging || pointerEvent.pointerId !== this.pointerId) return;
1173
- const bounds = this.dom.workArea.getBoundingClientRect();
1174
- const point = { x: pointerEvent.clientX - bounds.left, y: pointerEvent.clientY - bounds.top };
1165
+ const point = { x: pointerEvent.clientX, y: pointerEvent.clientY };
1175
1166
  const previous = this.lastPointer ?? point;
1176
1167
  this.state.vx = (point.x - previous.x) * 0.8;
1177
1168
  this.state.vy = (point.y - previous.y) * 0.8;
@@ -1319,10 +1310,7 @@ var ShimejiEngine = class {
1319
1310
  if (!container) throw new TypeError("ShimejiEngine requires a container element");
1320
1311
  this.options = { ...defaults, ...options, random: options.random };
1321
1312
  this.platformSource = this.options.platforms;
1322
- this.originalContainerPosition = container.style.position;
1323
- this.adjustedContainerPosition = getComputedStyle(container).position === "static";
1324
- if (this.adjustedContainerPosition) container.style.position = "relative";
1325
- this.dom = new DomManager(container, this.sprites, this.options.workAreaClassName || void 0);
1313
+ this.dom = new DomManager(container, this.sprites);
1326
1314
  this.initialize();
1327
1315
  }
1328
1316
  container;
@@ -1334,8 +1322,6 @@ var ShimejiEngine = class {
1334
1322
  disposers = [];
1335
1323
  intervals = /* @__PURE__ */ new Set();
1336
1324
  options;
1337
- originalContainerPosition;
1338
- adjustedContainerPosition;
1339
1325
  pointer = { x: 0, y: 0, dx: 0, dy: 0 };
1340
1326
  animationFrame;
1341
1327
  lastFrameTime;
@@ -1350,9 +1336,8 @@ var ShimejiEngine = class {
1350
1336
  this.initialized = true;
1351
1337
  this.listen(document, "pointermove", (event) => {
1352
1338
  const pointerEvent = event;
1353
- const rectangle = this.dom.workArea.getBoundingClientRect();
1354
- const x = pointerEvent.clientX - rectangle.left;
1355
- const y = pointerEvent.clientY - rectangle.top;
1339
+ const x = pointerEvent.clientX;
1340
+ const y = pointerEvent.clientY;
1356
1341
  this.pointer = { x, y, dx: x - this.pointer.x, dy: y - this.pointer.y };
1357
1342
  });
1358
1343
  this.listen(window, "resize", () => this.renderAll());
@@ -1456,7 +1441,6 @@ var ShimejiEngine = class {
1456
1441
  this.sprites.destroy();
1457
1442
  this.specs.clear();
1458
1443
  this.events.clear();
1459
- if (this.adjustedContainerPosition && this.container.style.position === "relative") this.container.style.position = this.originalContainerPosition;
1460
1444
  this.destroyed = true;
1461
1445
  this.initialized = false;
1462
1446
  }
@@ -1479,10 +1463,9 @@ var ShimejiEngine = class {
1479
1463
  for (const mascot of this.mascots.values()) mascot.tick(0, bounds, platforms);
1480
1464
  }
1481
1465
  readFrameGeometry() {
1482
- const workAreaRectangle = this.dom.workArea.getBoundingClientRect();
1483
- const bounds = this.dom.getBoundsFromClientRectangle(workAreaRectangle);
1484
- const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument, this.dom.workArea);
1485
- return { bounds, platforms: readPlatformRectangles(elements, workAreaRectangle) };
1466
+ const bounds = this.dom.getBounds();
1467
+ const elements = resolvePlatformElements(this.platformSource, this.container.ownerDocument).filter((element) => !this.dom.owns(element));
1468
+ return { bounds, platforms: readPlatformRectangles(elements, { left: 0, top: 0 }) };
1486
1469
  }
1487
1470
  emitState() {
1488
1471
  this.events.emit("statechange", this.getState());