@blorkfield/overlay-core 0.4.2 → 0.5.0

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
@@ -353,6 +353,41 @@ interface TTFTextObstacleConfig {
353
353
  /** Click to fall config - when set, letters collapse after being clicked N times */
354
354
  clickToFall?: ClickToFallConfig;
355
355
  }
356
+ /**
357
+ * Configuration for attaching a DOM element to physics.
358
+ * The element will follow the physics body and can have pressure/shadow/click behavior.
359
+ */
360
+ interface DOMObstacleConfig {
361
+ /** The DOM element to attach to physics */
362
+ element: HTMLElement;
363
+ /** X position of the element center */
364
+ x: number;
365
+ /** Y position of the element center */
366
+ y: number;
367
+ /** Width of the collision body (defaults to element.offsetWidth) */
368
+ width?: number;
369
+ /** Height of the collision body (defaults to element.offsetHeight) */
370
+ height?: number;
371
+ /** Tags that define object behavior */
372
+ tags?: string[];
373
+ /** Pressure threshold config - when reached, element collapses */
374
+ pressureThreshold?: PressureThresholdConfig;
375
+ /** Weight for pressure calculation (default: 1) */
376
+ weight?: number;
377
+ /** Shadow config - when enabled, a cloned element remains after collapse */
378
+ shadow?: ShadowConfig;
379
+ /** Click to fall config - when set, element collapses after being clicked N times */
380
+ clickToFall?: ClickToFallConfig;
381
+ }
382
+ /**
383
+ * Result of attaching a DOM element to physics
384
+ */
385
+ interface DOMObstacleResult {
386
+ /** ID of the created physics object */
387
+ id: string;
388
+ /** The shadow element if shadow was configured (null until collapse) */
389
+ shadowElement: HTMLElement | null;
390
+ }
356
391
  /**
357
392
  * Information about an available font
358
393
  */
@@ -550,6 +585,20 @@ declare class OverlayScene {
550
585
  * Check if fonts have been initialized.
551
586
  */
552
587
  areFontsInitialized(): boolean;
588
+ /**
589
+ * Attach a DOM element to physics. The element will follow the physics body
590
+ * and can have pressure threshold, shadow, and click-to-fall behavior.
591
+ *
592
+ * When the element collapses (becomes dynamic), its CSS transform will be
593
+ * updated each frame to match the physics body position and rotation.
594
+ *
595
+ * Shadow creates a cloned DOM element that stays at the original position.
596
+ */
597
+ addDOMObstacle(config: DOMObstacleConfig): DOMObstacleResult;
598
+ /**
599
+ * Get the shadow element for a DOM obstacle (available after collapse).
600
+ */
601
+ getDOMObstacleShadow(id: string): HTMLElement | null;
553
602
  /**
554
603
  * Create text obstacles from a string. Each character becomes an individual obstacle
555
604
  * with shape extracted from the corresponding letter PNG image.
@@ -639,6 +688,10 @@ declare class OverlayScene {
639
688
  * Draw TTF glyphs using canvas fillText for clean text rendering
640
689
  */
641
690
  private drawTTFGlyphs;
691
+ /**
692
+ * Update a DOM element's CSS transform to match its physics body position and rotation.
693
+ */
694
+ private updateDOMElementTransform;
642
695
  private checkTTLExpiration;
643
696
  /** Despawn objects that have fallen below the floor by the configured distance */
644
697
  private checkDespawnBelowFloor;
@@ -697,4 +750,4 @@ declare function measureText(loadedFont: LoadedFont, text: string, fontSize: num
697
750
  */
698
751
  declare function clearFontCache(): void;
699
752
 
700
- export { type BaseEffectConfig, type Bounds, type BurstEffectConfig, type ClickToFallConfig, type ContainerOptions, type DespawnEffectConfig, type DynamicObject, type EffectConfig, type EffectObjectConfig, type EffectType, type FloorConfig, type FontInfo, type FontManifest, type GlyphData, type LoadedFont, type ObjectConfig, OverlayScene, type OverlaySceneConfig, type PressureThresholdConfig, type RainEffectConfig, type ShadowConfig, type ShapeConfig, type ShapePreset, type StreamEffectConfig, type TTFTextObstacleConfig, type TextObstacleConfig, type TextObstacleResult, type UpdateCallback, type UpdateCallbackData, type WeightConfig, clearFontCache, getGlyphData, getKerning, getLogLevel, loadFont, logger, measureText, setLogLevel };
753
+ export { type BaseEffectConfig, type Bounds, type BurstEffectConfig, type ClickToFallConfig, type ContainerOptions, type DOMObstacleConfig, type DOMObstacleResult, type DespawnEffectConfig, type DynamicObject, type EffectConfig, type EffectObjectConfig, type EffectType, type FloorConfig, type FontInfo, type FontManifest, type GlyphData, type LoadedFont, type ObjectConfig, OverlayScene, type OverlaySceneConfig, type PressureThresholdConfig, type RainEffectConfig, type ShadowConfig, type ShapeConfig, type ShapePreset, type StreamEffectConfig, type TTFTextObstacleConfig, type TextObstacleConfig, type TextObstacleResult, type UpdateCallback, type UpdateCallbackData, type WeightConfig, clearFontCache, getGlyphData, getKerning, getLogLevel, loadFont, logger, measureText, setLogLevel };
package/dist/index.d.ts CHANGED
@@ -353,6 +353,41 @@ interface TTFTextObstacleConfig {
353
353
  /** Click to fall config - when set, letters collapse after being clicked N times */
354
354
  clickToFall?: ClickToFallConfig;
355
355
  }
356
+ /**
357
+ * Configuration for attaching a DOM element to physics.
358
+ * The element will follow the physics body and can have pressure/shadow/click behavior.
359
+ */
360
+ interface DOMObstacleConfig {
361
+ /** The DOM element to attach to physics */
362
+ element: HTMLElement;
363
+ /** X position of the element center */
364
+ x: number;
365
+ /** Y position of the element center */
366
+ y: number;
367
+ /** Width of the collision body (defaults to element.offsetWidth) */
368
+ width?: number;
369
+ /** Height of the collision body (defaults to element.offsetHeight) */
370
+ height?: number;
371
+ /** Tags that define object behavior */
372
+ tags?: string[];
373
+ /** Pressure threshold config - when reached, element collapses */
374
+ pressureThreshold?: PressureThresholdConfig;
375
+ /** Weight for pressure calculation (default: 1) */
376
+ weight?: number;
377
+ /** Shadow config - when enabled, a cloned element remains after collapse */
378
+ shadow?: ShadowConfig;
379
+ /** Click to fall config - when set, element collapses after being clicked N times */
380
+ clickToFall?: ClickToFallConfig;
381
+ }
382
+ /**
383
+ * Result of attaching a DOM element to physics
384
+ */
385
+ interface DOMObstacleResult {
386
+ /** ID of the created physics object */
387
+ id: string;
388
+ /** The shadow element if shadow was configured (null until collapse) */
389
+ shadowElement: HTMLElement | null;
390
+ }
356
391
  /**
357
392
  * Information about an available font
358
393
  */
@@ -550,6 +585,20 @@ declare class OverlayScene {
550
585
  * Check if fonts have been initialized.
551
586
  */
552
587
  areFontsInitialized(): boolean;
588
+ /**
589
+ * Attach a DOM element to physics. The element will follow the physics body
590
+ * and can have pressure threshold, shadow, and click-to-fall behavior.
591
+ *
592
+ * When the element collapses (becomes dynamic), its CSS transform will be
593
+ * updated each frame to match the physics body position and rotation.
594
+ *
595
+ * Shadow creates a cloned DOM element that stays at the original position.
596
+ */
597
+ addDOMObstacle(config: DOMObstacleConfig): DOMObstacleResult;
598
+ /**
599
+ * Get the shadow element for a DOM obstacle (available after collapse).
600
+ */
601
+ getDOMObstacleShadow(id: string): HTMLElement | null;
553
602
  /**
554
603
  * Create text obstacles from a string. Each character becomes an individual obstacle
555
604
  * with shape extracted from the corresponding letter PNG image.
@@ -639,6 +688,10 @@ declare class OverlayScene {
639
688
  * Draw TTF glyphs using canvas fillText for clean text rendering
640
689
  */
641
690
  private drawTTFGlyphs;
691
+ /**
692
+ * Update a DOM element's CSS transform to match its physics body position and rotation.
693
+ */
694
+ private updateDOMElementTransform;
642
695
  private checkTTLExpiration;
643
696
  /** Despawn objects that have fallen below the floor by the configured distance */
644
697
  private checkDespawnBelowFloor;
@@ -697,4 +750,4 @@ declare function measureText(loadedFont: LoadedFont, text: string, fontSize: num
697
750
  */
698
751
  declare function clearFontCache(): void;
699
752
 
700
- export { type BaseEffectConfig, type Bounds, type BurstEffectConfig, type ClickToFallConfig, type ContainerOptions, type DespawnEffectConfig, type DynamicObject, type EffectConfig, type EffectObjectConfig, type EffectType, type FloorConfig, type FontInfo, type FontManifest, type GlyphData, type LoadedFont, type ObjectConfig, OverlayScene, type OverlaySceneConfig, type PressureThresholdConfig, type RainEffectConfig, type ShadowConfig, type ShapeConfig, type ShapePreset, type StreamEffectConfig, type TTFTextObstacleConfig, type TextObstacleConfig, type TextObstacleResult, type UpdateCallback, type UpdateCallbackData, type WeightConfig, clearFontCache, getGlyphData, getKerning, getLogLevel, loadFont, logger, measureText, setLogLevel };
753
+ export { type BaseEffectConfig, type Bounds, type BurstEffectConfig, type ClickToFallConfig, type ContainerOptions, type DOMObstacleConfig, type DOMObstacleResult, type DespawnEffectConfig, type DynamicObject, type EffectConfig, type EffectObjectConfig, type EffectType, type FloorConfig, type FontInfo, type FontManifest, type GlyphData, type LoadedFont, type ObjectConfig, OverlayScene, type OverlaySceneConfig, type PressureThresholdConfig, type RainEffectConfig, type ShadowConfig, type ShapeConfig, type ShapePreset, type StreamEffectConfig, type TTFTextObstacleConfig, type TextObstacleConfig, type TextObstacleResult, type UpdateCallback, type UpdateCallbackData, type WeightConfig, clearFontCache, getGlyphData, getKerning, getLogLevel, loadFont, logger, measureText, setLogLevel };
package/dist/index.js CHANGED
@@ -1160,6 +1160,9 @@ var OverlayScene = class {
1160
1160
  if (this.config.wrapHorizontal && entry.tags.includes("falling")) {
1161
1161
  wrapHorizontal(entry.body, this.config.bounds);
1162
1162
  }
1163
+ if (entry.domElement && entry.tags.includes("falling")) {
1164
+ this.updateDOMElementTransform(entry);
1165
+ }
1163
1166
  }
1164
1167
  if (!this.config.debug) {
1165
1168
  this.drawTTFGlyphs();
@@ -1194,6 +1197,13 @@ var OverlayScene = class {
1194
1197
  }
1195
1198
  });
1196
1199
  Matter5.Composite.add(this.engine.world, this.mouseConstraint);
1200
+ const wheelHandler = this.mouse.mousewheel;
1201
+ if (wheelHandler) {
1202
+ canvas.removeEventListener("mousewheel", wheelHandler);
1203
+ canvas.removeEventListener("DOMMouseScroll", wheelHandler);
1204
+ canvas.removeEventListener("wheel", wheelHandler);
1205
+ }
1206
+ canvas.style.touchAction = "pan-x pan-y";
1197
1207
  Matter5.Events.on(this.mouseConstraint, "startdrag", this.handleStartDrag);
1198
1208
  canvas.addEventListener("click", this.handleCanvasClick);
1199
1209
  this.render.mouse = this.mouse;
@@ -1447,6 +1457,15 @@ var OverlayScene = class {
1447
1457
  if (!entry.originalPosition) return;
1448
1458
  const opacity = entry.shadow?.opacity ?? 0.3;
1449
1459
  const shadowId = `shadow-${entry.id}`;
1460
+ if (entry.domElement) {
1461
+ const shadowElement = entry.domElement.cloneNode(true);
1462
+ shadowElement.style.opacity = String(opacity);
1463
+ shadowElement.style.pointerEvents = "none";
1464
+ shadowElement.style.transform = entry.domOriginalTransform || "";
1465
+ entry.domElement.parentNode?.insertBefore(shadowElement, entry.domElement);
1466
+ entry.domShadowElement = shadowElement;
1467
+ return;
1468
+ }
1450
1469
  if (entry.ttfGlyph) {
1451
1470
  const body = Matter5.Bodies.circle(entry.originalPosition.x, entry.originalPosition.y, 1, {
1452
1471
  isStatic: true,
@@ -1920,6 +1939,84 @@ var OverlayScene = class {
1920
1939
  areFontsInitialized() {
1921
1940
  return this.fontsInitialized;
1922
1941
  }
1942
+ // ==================== DOM OBSTACLE METHODS ====================
1943
+ /**
1944
+ * Attach a DOM element to physics. The element will follow the physics body
1945
+ * and can have pressure threshold, shadow, and click-to-fall behavior.
1946
+ *
1947
+ * When the element collapses (becomes dynamic), its CSS transform will be
1948
+ * updated each frame to match the physics body position and rotation.
1949
+ *
1950
+ * Shadow creates a cloned DOM element that stays at the original position.
1951
+ */
1952
+ addDOMObstacle(config) {
1953
+ const { element, x, y } = config;
1954
+ const width = config.width ?? element.offsetWidth;
1955
+ const height = config.height ?? element.offsetHeight;
1956
+ const tags = config.tags ?? [];
1957
+ const isStatic = !tags.includes("falling");
1958
+ const body = Matter5.Bodies.rectangle(x, y, width, height, {
1959
+ isStatic,
1960
+ label: `dom-${crypto.randomUUID().slice(0, 8)}`,
1961
+ render: { visible: false }
1962
+ // Don't render the body, DOM element is the visual
1963
+ });
1964
+ const id = body.label;
1965
+ let pressureThreshold;
1966
+ if (config.pressureThreshold) {
1967
+ pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
1968
+ }
1969
+ const shadow = config.shadow ? { opacity: config.shadow.opacity ?? 0.3 } : void 0;
1970
+ const clicksRemaining = config.clickToFall?.clicks;
1971
+ const originalTransform = element.style.transform || "";
1972
+ element.style.position = "absolute";
1973
+ element.style.transformOrigin = "center center";
1974
+ const entry = {
1975
+ id,
1976
+ body,
1977
+ tags,
1978
+ spawnTime: performance.now(),
1979
+ pressureThreshold,
1980
+ weight: config.weight ?? 1,
1981
+ shadow,
1982
+ originalPosition: shadow || clicksRemaining !== void 0 ? { x, y } : void 0,
1983
+ clicksRemaining,
1984
+ domElement: element,
1985
+ domOriginalTransform: originalTransform
1986
+ };
1987
+ this.objects.set(id, entry);
1988
+ Matter5.Composite.add(this.engine.world, body);
1989
+ if (isStatic && pressureThreshold !== void 0) {
1990
+ this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
1991
+ }
1992
+ if (clicksRemaining !== void 0) {
1993
+ const clickHandler = () => {
1994
+ const currentEntry = this.objects.get(id);
1995
+ if (!currentEntry) return;
1996
+ if (currentEntry.tags.includes("falling")) return;
1997
+ if (currentEntry.clicksRemaining === void 0) return;
1998
+ currentEntry.clicksRemaining--;
1999
+ logger.debug("OverlayScene", `Click on DOM element: ${currentEntry.clicksRemaining} clicks remaining`);
2000
+ if (currentEntry.clicksRemaining <= 0) {
2001
+ this.collapseObstacle(currentEntry);
2002
+ element.removeEventListener("click", clickHandler);
2003
+ }
2004
+ };
2005
+ element.addEventListener("click", clickHandler);
2006
+ }
2007
+ return {
2008
+ id,
2009
+ shadowElement: null
2010
+ // Will be populated on collapse
2011
+ };
2012
+ }
2013
+ /**
2014
+ * Get the shadow element for a DOM obstacle (available after collapse).
2015
+ */
2016
+ getDOMObstacleShadow(id) {
2017
+ const entry = this.objects.get(id);
2018
+ return entry?.domShadowElement ?? null;
2019
+ }
1923
2020
  // ==================== TEXT OBSTACLE METHODS ====================
1924
2021
  /**
1925
2022
  * Create text obstacles from a string. Each character becomes an individual obstacle
@@ -2416,6 +2513,22 @@ var OverlayScene = class {
2416
2513
  ctx.restore();
2417
2514
  }
2418
2515
  }
2516
+ /**
2517
+ * Update a DOM element's CSS transform to match its physics body position and rotation.
2518
+ */
2519
+ updateDOMElementTransform(entry) {
2520
+ if (!entry.domElement) return;
2521
+ const body = entry.body;
2522
+ const x = body.position.x;
2523
+ const y = body.position.y;
2524
+ const angle = body.angle;
2525
+ const angleDeg = angle * (180 / Math.PI);
2526
+ const width = entry.domElement.offsetWidth;
2527
+ const height = entry.domElement.offsetHeight;
2528
+ entry.domElement.style.left = `${x - width / 2}px`;
2529
+ entry.domElement.style.top = `${y - height / 2}px`;
2530
+ entry.domElement.style.transform = `rotate(${angleDeg}deg)`;
2531
+ }
2419
2532
  checkTTLExpiration() {
2420
2533
  const now = performance.now();
2421
2534
  const expiredObjects = [];