@thunderclap-engine/engine 0.0.0-stage → 1.1.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/CHANGELOG.md +390 -0
- package/LICENSE +21 -0
- package/README.md +333 -2
- package/dist/analytics/analytics.d.ts +80 -0
- package/dist/analytics/analytics.js +129 -0
- package/dist/analytics/index.d.ts +1 -0
- package/dist/analytics/index.js +1 -0
- package/dist/anim/aim.d.ts +43 -0
- package/dist/anim/aim.js +157 -0
- package/dist/anim/clip.d.ts +98 -0
- package/dist/anim/clip.js +282 -0
- package/dist/anim/compress.d.ts +65 -0
- package/dist/anim/compress.js +339 -0
- package/dist/anim/foot-ik.d.ts +74 -0
- package/dist/anim/foot-ik.js +241 -0
- package/dist/anim/graph.d.ts +153 -0
- package/dist/anim/graph.js +532 -0
- package/dist/anim/ik.d.ts +105 -0
- package/dist/anim/ik.js +435 -0
- package/dist/anim/index.d.ts +36 -0
- package/dist/anim/index.js +21 -0
- package/dist/anim/match.d.ts +66 -0
- package/dist/anim/match.js +153 -0
- package/dist/anim/mixer.d.ts +113 -0
- package/dist/anim/mixer.js +314 -0
- package/dist/anim/morph.d.ts +41 -0
- package/dist/anim/morph.js +94 -0
- package/dist/anim/pose.d.ts +74 -0
- package/dist/anim/pose.js +254 -0
- package/dist/anim/quat.d.ts +44 -0
- package/dist/anim/quat.js +187 -0
- package/dist/anim/retarget.d.ts +97 -0
- package/dist/anim/retarget.js +366 -0
- package/dist/anim/root-motion.d.ts +44 -0
- package/dist/anim/root-motion.js +224 -0
- package/dist/anim/skin-pose.d.ts +39 -0
- package/dist/anim/skin-pose.js +53 -0
- package/dist/anim/socket.d.ts +47 -0
- package/dist/anim/socket.js +84 -0
- package/dist/anim/spring-chain.d.ts +73 -0
- package/dist/anim/spring-chain.js +294 -0
- package/dist/assets/atlas.d.ts +90 -0
- package/dist/assets/atlas.js +212 -0
- package/dist/assets/baked.d.ts +33 -0
- package/dist/assets/baked.js +38 -0
- package/dist/assets/bundle.d.ts +88 -0
- package/dist/assets/bundle.js +464 -0
- package/dist/assets/cache.d.ts +56 -0
- package/dist/assets/cache.js +123 -0
- package/dist/assets/gltf-load.d.ts +252 -0
- package/dist/assets/gltf-load.js +661 -0
- package/dist/assets/gltf-skin.d.ts +36 -0
- package/dist/assets/gltf-skin.js +109 -0
- package/dist/assets/gltf.d.ts +706 -0
- package/dist/assets/gltf.js +1376 -0
- package/dist/assets/index.d.ts +20 -0
- package/dist/assets/index.js +10 -0
- package/dist/assets/ktx2.d.ts +71 -0
- package/dist/assets/ktx2.js +213 -0
- package/dist/assets/manifest.d.ts +79 -0
- package/dist/assets/manifest.js +7 -0
- package/dist/assets/meshopt.d.ts +23 -0
- package/dist/assets/meshopt.js +403 -0
- package/dist/audio/audio.d.ts +733 -0
- package/dist/audio/audio.js +2013 -0
- package/dist/audio/dsp.d.ts +61 -0
- package/dist/audio/dsp.js +159 -0
- package/dist/audio/duck.d.ts +44 -0
- package/dist/audio/duck.js +101 -0
- package/dist/audio/envelope.d.ts +53 -0
- package/dist/audio/envelope.js +106 -0
- package/dist/audio/index.d.ts +13 -0
- package/dist/audio/index.js +14 -0
- package/dist/audio/music.d.ts +113 -0
- package/dist/audio/music.js +548 -0
- package/dist/audio/occlusion.d.ts +55 -0
- package/dist/audio/occlusion.js +61 -0
- package/dist/audio/persist.d.ts +23 -0
- package/dist/audio/persist.js +27 -0
- package/dist/audio/recipes.d.ts +55 -0
- package/dist/audio/recipes.js +227 -0
- package/dist/audio/send.d.ts +41 -0
- package/dist/audio/send.js +118 -0
- package/dist/audio/sprites.d.ts +59 -0
- package/dist/audio/sprites.js +43 -0
- package/dist/audio/synth.d.ts +103 -0
- package/dist/audio/synth.js +197 -0
- package/dist/audio/variations.d.ts +25 -0
- package/dist/audio/variations.js +55 -0
- package/dist/audio/zones.d.ts +100 -0
- package/dist/audio/zones.js +167 -0
- package/dist/core/detached-surface.d.ts +44 -0
- package/dist/core/detached-surface.js +104 -0
- package/dist/core/events.d.ts +24 -0
- package/dist/core/events.js +43 -0
- package/dist/core/game.d.ts +409 -0
- package/dist/core/game.js +530 -0
- package/dist/core/hash.d.ts +24 -0
- package/dist/core/hash.js +49 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +9 -0
- package/dist/core/loop.d.ts +113 -0
- package/dist/core/loop.js +185 -0
- package/dist/core/math.d.ts +58 -0
- package/dist/core/math.js +210 -0
- package/dist/core/noise.d.ts +40 -0
- package/dist/core/noise.js +247 -0
- package/dist/core/pool.d.ts +42 -0
- package/dist/core/pool.js +85 -0
- package/dist/core/random.d.ts +61 -0
- package/dist/core/random.js +199 -0
- package/dist/core/scene.d.ts +79 -0
- package/dist/core/scene.js +195 -0
- package/dist/core/timer.d.ts +66 -0
- package/dist/core/timer.js +286 -0
- package/dist/fx/billboard-wgsl.d.ts +29 -0
- package/dist/fx/billboard-wgsl.js +158 -0
- package/dist/fx/billboard.d.ts +249 -0
- package/dist/fx/billboard.js +702 -0
- package/dist/fx/debug-draw.d.ts +127 -0
- package/dist/fx/debug-draw.js +523 -0
- package/dist/fx/debug-wgsl.d.ts +7 -0
- package/dist/fx/debug-wgsl.js +32 -0
- package/dist/fx/decals-wgsl.d.ts +15 -0
- package/dist/fx/decals-wgsl.js +173 -0
- package/dist/fx/decals.d.ts +216 -0
- package/dist/fx/decals.js +607 -0
- package/dist/fx/effect.d.ts +129 -0
- package/dist/fx/effect.js +349 -0
- package/dist/fx/effects/hit-spark.json +48 -0
- package/dist/fx/effects/index.d.ts +8 -0
- package/dist/fx/effects/index.js +9 -0
- package/dist/fx/effects/magic-aura.json +47 -0
- package/dist/fx/effects/smoke-puff.json +34 -0
- package/dist/fx/emit-surface.d.ts +40 -0
- package/dist/fx/emit-surface.js +124 -0
- package/dist/fx/flicker.d.ts +30 -0
- package/dist/fx/flicker.js +42 -0
- package/dist/fx/index.d.ts +31 -0
- package/dist/fx/index.js +20 -0
- package/dist/fx/particles-wgsl.d.ts +57 -0
- package/dist/fx/particles-wgsl.js +828 -0
- package/dist/fx/particles.d.ts +566 -0
- package/dist/fx/particles.js +1533 -0
- package/dist/fx/random.d.ts +15 -0
- package/dist/fx/random.js +20 -0
- package/dist/fx/sky-wgsl.d.ts +23 -0
- package/dist/fx/sky-wgsl.js +127 -0
- package/dist/fx/sky.d.ts +96 -0
- package/dist/fx/sky.js +175 -0
- package/dist/fx/text-atlas.d.ts +143 -0
- package/dist/fx/text-atlas.js +327 -0
- package/dist/fx/trail-wgsl.d.ts +11 -0
- package/dist/fx/trail-wgsl.js +42 -0
- package/dist/fx/trail.d.ts +194 -0
- package/dist/fx/trail.js +555 -0
- package/dist/fx/water-wgsl.d.ts +34 -0
- package/dist/fx/water-wgsl.js +197 -0
- package/dist/fx/water.d.ts +176 -0
- package/dist/fx/water.js +371 -0
- package/dist/geom/collide/bvh.d.ts +151 -0
- package/dist/geom/collide/bvh.js +493 -0
- package/dist/geom/collide/debug.d.ts +14 -0
- package/dist/geom/collide/debug.js +74 -0
- package/dist/geom/collide/grid.d.ts +26 -0
- package/dist/geom/collide/grid.js +91 -0
- package/dist/geom/collide/index.d.ts +8 -0
- package/dist/geom/collide/index.js +8 -0
- package/dist/geom/collide/queries.d.ts +70 -0
- package/dist/geom/collide/queries.js +583 -0
- package/dist/geom/collide/serialize.d.ts +14 -0
- package/dist/geom/collide/serialize.js +145 -0
- package/dist/geom/collide/shapes.d.ts +161 -0
- package/dist/geom/collide/shapes.js +686 -0
- package/dist/geom/collide/slide.d.ts +66 -0
- package/dist/geom/collide/slide.js +375 -0
- package/dist/geom/collide/tile-slide.d.ts +88 -0
- package/dist/geom/collide/tile-slide.js +255 -0
- package/dist/geom/geometry.d.ts +95 -0
- package/dist/geom/geometry.js +322 -0
- package/dist/geom/heightfield.d.ts +72 -0
- package/dist/geom/heightfield.js +295 -0
- package/dist/geom/hull.d.ts +27 -0
- package/dist/geom/hull.js +279 -0
- package/dist/geom/index.d.ts +35 -0
- package/dist/geom/index.js +23 -0
- package/dist/geom/iso.d.ts +74 -0
- package/dist/geom/iso.js +89 -0
- package/dist/geom/lod.d.ts +29 -0
- package/dist/geom/lod.js +27 -0
- package/dist/geom/ops.d.ts +47 -0
- package/dist/geom/ops.js +285 -0
- package/dist/geom/primitives.d.ts +97 -0
- package/dist/geom/primitives.js +359 -0
- package/dist/geom/shapes.d.ts +43 -0
- package/dist/geom/shapes.js +88 -0
- package/dist/geom/simplify.d.ts +38 -0
- package/dist/geom/simplify.js +519 -0
- package/dist/geom/split.d.ts +37 -0
- package/dist/geom/split.js +413 -0
- package/dist/geom/sweep.d.ts +83 -0
- package/dist/geom/sweep.js +534 -0
- package/dist/geom/tangents.d.ts +16 -0
- package/dist/geom/tangents.js +124 -0
- package/dist/geom/triangulate.d.ts +19 -0
- package/dist/geom/triangulate.js +147 -0
- package/dist/host/default.d.ts +10 -0
- package/dist/host/default.js +10 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.js +3 -0
- package/dist/host/types.d.ts +144 -0
- package/dist/host/types.js +1 -0
- package/dist/host/web-links.d.ts +38 -0
- package/dist/host/web-links.js +110 -0
- package/dist/host/web.d.ts +30 -0
- package/dist/host/web.js +216 -0
- package/dist/host/xr.d.ts +24 -0
- package/dist/host/xr.js +59 -0
- package/dist/i18n/i18n.d.ts +59 -0
- package/dist/i18n/i18n.js +241 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/message.d.ts +72 -0
- package/dist/i18n/message.js +369 -0
- package/dist/i18n/negotiate.d.ts +17 -0
- package/dist/i18n/negotiate.js +58 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/input/bindings.d.ts +31 -0
- package/dist/input/bindings.js +141 -0
- package/dist/input/clock.d.ts +44 -0
- package/dist/input/clock.js +49 -0
- package/dist/input/follow.d.ts +133 -0
- package/dist/input/follow.js +218 -0
- package/dist/input/gestures.d.ts +281 -0
- package/dist/input/gestures.js +662 -0
- package/dist/input/glyphs.d.ts +35 -0
- package/dist/input/glyphs.js +216 -0
- package/dist/input/gyro.d.ts +85 -0
- package/dist/input/gyro.js +140 -0
- package/dist/input/index.d.ts +19 -0
- package/dist/input/index.js +16 -0
- package/dist/input/input.d.ts +543 -0
- package/dist/input/input.js +1492 -0
- package/dist/input/look.d.ts +327 -0
- package/dist/input/look.js +681 -0
- package/dist/input/magnify.d.ts +177 -0
- package/dist/input/magnify.js +131 -0
- package/dist/input/pointer-dispatch.d.ts +138 -0
- package/dist/input/pointer-dispatch.js +180 -0
- package/dist/input/pointer-router.d.ts +204 -0
- package/dist/input/pointer-router.js +533 -0
- package/dist/input/pointer-sources.d.ts +164 -0
- package/dist/input/pointer-sources.js +306 -0
- package/dist/input/rays.d.ts +138 -0
- package/dist/input/rays.js +615 -0
- package/dist/input/record.d.ts +209 -0
- package/dist/input/record.js +601 -0
- package/dist/input/touch-zones.d.ts +147 -0
- package/dist/input/touch-zones.js +300 -0
- package/dist/input/types.d.ts +158 -0
- package/dist/input/types.js +1 -0
- package/dist/input/xr-source.d.ts +53 -0
- package/dist/input/xr-source.js +164 -0
- package/dist/nav/astar.d.ts +45 -0
- package/dist/nav/astar.js +182 -0
- package/dist/nav/corridor.d.ts +40 -0
- package/dist/nav/corridor.js +95 -0
- package/dist/nav/crowd.d.ts +94 -0
- package/dist/nav/crowd.js +624 -0
- package/dist/nav/flow.d.ts +61 -0
- package/dist/nav/flow.js +193 -0
- package/dist/nav/grid.d.ts +108 -0
- package/dist/nav/grid.js +210 -0
- package/dist/nav/heap.d.ts +22 -0
- package/dist/nav/heap.js +92 -0
- package/dist/nav/index.d.ts +37 -0
- package/dist/nav/index.js +19 -0
- package/dist/nav/mesh/areas.d.ts +44 -0
- package/dist/nav/mesh/areas.js +98 -0
- package/dist/nav/mesh/bake-options.d.ts +33 -0
- package/dist/nav/mesh/bake-options.js +76 -0
- package/dist/nav/mesh/contour.d.ts +22 -0
- package/dist/nav/mesh/contour.js +323 -0
- package/dist/nav/mesh/flow3.d.ts +35 -0
- package/dist/nav/mesh/flow3.js +229 -0
- package/dist/nav/mesh/links.d.ts +38 -0
- package/dist/nav/mesh/links.js +72 -0
- package/dist/nav/mesh/mesh-path.d.ts +152 -0
- package/dist/nav/mesh/mesh-path.js +643 -0
- package/dist/nav/mesh/mesh-reach.d.ts +78 -0
- package/dist/nav/mesh/mesh-reach.js +450 -0
- package/dist/nav/mesh/navmesh.d.ts +117 -0
- package/dist/nav/mesh/navmesh.js +647 -0
- package/dist/nav/mesh/polymesh.d.ts +24 -0
- package/dist/nav/mesh/polymesh.js +384 -0
- package/dist/nav/mesh/regions.d.ts +12 -0
- package/dist/nav/mesh/regions.js +367 -0
- package/dist/nav/mesh/serialize.d.ts +13 -0
- package/dist/nav/mesh/serialize.js +197 -0
- package/dist/nav/mesh/voxelize.d.ts +111 -0
- package/dist/nav/mesh/voxelize.js +552 -0
- package/dist/nav/obstacles.d.ts +75 -0
- package/dist/nav/obstacles.js +294 -0
- package/dist/nav/path.d.ts +59 -0
- package/dist/nav/path.js +177 -0
- package/dist/nav/query-queue.d.ts +46 -0
- package/dist/nav/query-queue.js +308 -0
- package/dist/nav/reach.d.ts +53 -0
- package/dist/nav/reach.js +148 -0
- package/dist/nav/steer.d.ts +36 -0
- package/dist/nav/steer.js +78 -0
- package/dist/platform/boot.d.ts +28 -0
- package/dist/platform/boot.js +43 -0
- package/dist/platform/device-mode.d.ts +40 -0
- package/dist/platform/device-mode.js +104 -0
- package/dist/platform/device.d.ts +31 -0
- package/dist/platform/device.js +91 -0
- package/dist/platform/environment.d.ts +42 -0
- package/dist/platform/environment.js +82 -0
- package/dist/platform/gpu.d.ts +40 -0
- package/dist/platform/gpu.js +57 -0
- package/dist/platform/index.d.ts +18 -0
- package/dist/platform/index.js +10 -0
- package/dist/platform/letterbox.d.ts +43 -0
- package/dist/platform/letterbox.js +57 -0
- package/dist/platform/links.d.ts +20 -0
- package/dist/platform/links.js +43 -0
- package/dist/platform/screens.d.ts +36 -0
- package/dist/platform/screens.js +138 -0
- package/dist/platform/surface.d.ts +72 -0
- package/dist/platform/surface.js +157 -0
- package/dist/platform/xr-fake.d.ts +168 -0
- package/dist/platform/xr-fake.js +434 -0
- package/dist/platform/xr-surface.d.ts +120 -0
- package/dist/platform/xr-surface.js +214 -0
- package/dist/platform/xr-types.d.ts +262 -0
- package/dist/post/ao.d.ts +12 -0
- package/dist/post/ao.js +136 -0
- package/dist/post/blend.d.ts +27 -0
- package/dist/post/blend.js +129 -0
- package/dist/post/depth-pass.d.ts +43 -0
- package/dist/post/depth-pass.js +118 -0
- package/dist/post/dof.d.ts +1 -0
- package/dist/post/dof.js +66 -0
- package/dist/post/exposure.d.ts +80 -0
- package/dist/post/exposure.js +322 -0
- package/dist/post/index.d.ts +12 -0
- package/dist/post/index.js +11 -0
- package/dist/post/lens-dirt.d.ts +13 -0
- package/dist/post/lens-dirt.js +91 -0
- package/dist/post/lut.d.ts +19 -0
- package/dist/post/lut.js +50 -0
- package/dist/post/motion-blur.d.ts +9 -0
- package/dist/post/motion-blur.js +78 -0
- package/dist/post/post-stack.d.ts +302 -0
- package/dist/post/post-stack.js +1137 -0
- package/dist/post/settings.d.ts +485 -0
- package/dist/post/settings.js +416 -0
- package/dist/post/shaders.d.ts +25 -0
- package/dist/post/shaders.js +410 -0
- package/dist/post/ssr.d.ts +16 -0
- package/dist/post/ssr.js +155 -0
- package/dist/post/taa.d.ts +74 -0
- package/dist/post/taa.js +235 -0
- package/dist/post/white-balance.d.ts +25 -0
- package/dist/post/white-balance.js +98 -0
- package/dist/render/bindings.d.ts +98 -0
- package/dist/render/bindings.js +281 -0
- package/dist/render/blend.d.ts +13 -0
- package/dist/render/blend.js +26 -0
- package/dist/render/camera-2d.d.ts +72 -0
- package/dist/render/camera-2d.js +92 -0
- package/dist/render/camera.d.ts +302 -0
- package/dist/render/camera.js +671 -0
- package/dist/render/color.d.ts +16 -0
- package/dist/render/color.js +41 -0
- package/dist/render/compressed.d.ts +104 -0
- package/dist/render/compressed.js +248 -0
- package/dist/render/diagnostics.d.ts +15 -0
- package/dist/render/diagnostics.js +43 -0
- package/dist/render/dynamic-resolution.d.ts +70 -0
- package/dist/render/dynamic-resolution.js +126 -0
- package/dist/render/fake-gpu.d.ts +204 -0
- package/dist/render/fake-gpu.js +426 -0
- package/dist/render/flipbook.d.ts +56 -0
- package/dist/render/flipbook.js +87 -0
- package/dist/render/frame.d.ts +278 -0
- package/dist/render/frame.js +174 -0
- package/dist/render/fullscreen.d.ts +54 -0
- package/dist/render/fullscreen.js +91 -0
- package/dist/render/gpu-timer.d.ts +73 -0
- package/dist/render/gpu-timer.js +186 -0
- package/dist/render/index.d.ts +53 -0
- package/dist/render/index.js +29 -0
- package/dist/render/instance-list.d.ts +185 -0
- package/dist/render/instance-list.js +547 -0
- package/dist/render/lights.d.ts +181 -0
- package/dist/render/lights.js +233 -0
- package/dist/render/lod.d.ts +54 -0
- package/dist/render/lod.js +52 -0
- package/dist/render/material.d.ts +165 -0
- package/dist/render/material.js +292 -0
- package/dist/render/mesh.d.ts +284 -0
- package/dist/render/mesh.js +472 -0
- package/dist/render/mipmap.d.ts +16 -0
- package/dist/render/mipmap.js +121 -0
- package/dist/render/parallax.d.ts +50 -0
- package/dist/render/parallax.js +47 -0
- package/dist/render/pbr-wgsl.d.ts +31 -0
- package/dist/render/pbr-wgsl.js +119 -0
- package/dist/render/ping-pong.d.ts +42 -0
- package/dist/render/ping-pong.js +74 -0
- package/dist/render/renderer.d.ts +741 -0
- package/dist/render/renderer.js +2486 -0
- package/dist/render/scene-pipelines.d.ts +44 -0
- package/dist/render/scene-pipelines.js +92 -0
- package/dist/render/scene-target.d.ts +170 -0
- package/dist/render/scene-target.js +450 -0
- package/dist/render/shadow.d.ts +74 -0
- package/dist/render/shadow.js +208 -0
- package/dist/render/skin.d.ts +112 -0
- package/dist/render/skin.js +240 -0
- package/dist/render/snapshot.d.ts +36 -0
- package/dist/render/snapshot.js +34 -0
- package/dist/render/standard.d.ts +162 -0
- package/dist/render/standard.js +263 -0
- package/dist/render/texture.d.ts +142 -0
- package/dist/render/texture.js +228 -0
- package/dist/render/tile-layer.d.ts +110 -0
- package/dist/render/tile-layer.js +304 -0
- package/dist/render/transform.d.ts +115 -0
- package/dist/render/transform.js +318 -0
- package/dist/render/wgsl.d.ts +131 -0
- package/dist/render/wgsl.js +528 -0
- package/dist/save/codec.d.ts +49 -0
- package/dist/save/codec.js +169 -0
- package/dist/save/index.d.ts +8 -0
- package/dist/save/index.js +8 -0
- package/dist/save/journal.d.ts +24 -0
- package/dist/save/journal.js +102 -0
- package/dist/save/migrate.d.ts +15 -0
- package/dist/save/migrate.js +37 -0
- package/dist/save/schema.d.ts +72 -0
- package/dist/save/schema.js +154 -0
- package/dist/save/settings.d.ts +28 -0
- package/dist/save/settings.js +59 -0
- package/dist/save/slots.d.ts +56 -0
- package/dist/save/slots.js +200 -0
- package/dist/save/storage.d.ts +35 -0
- package/dist/save/storage.js +62 -0
- package/dist/save/store.d.ts +114 -0
- package/dist/save/store.js +269 -0
- package/dist/testing/fake-host.d.ts +103 -0
- package/dist/testing/fake-host.js +163 -0
- package/dist/testing/fixed-clock.d.ts +25 -0
- package/dist/testing/fixed-clock.js +30 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/test-game.d.ts +76 -0
- package/dist/testing/test-game.js +92 -0
- package/dist/tween/ease.d.ts +77 -0
- package/dist/tween/ease.js +192 -0
- package/dist/tween/index.d.ts +8 -0
- package/dist/tween/index.js +7 -0
- package/dist/tween/timeline.d.ts +78 -0
- package/dist/tween/timeline.js +269 -0
- package/dist/tween/tween.d.ts +168 -0
- package/dist/tween/tween.js +320 -0
- package/dist/ui/anchor.d.ts +206 -0
- package/dist/ui/anchor.js +307 -0
- package/dist/ui/caption.d.ts +122 -0
- package/dist/ui/caption.js +286 -0
- package/dist/ui/cursor.d.ts +105 -0
- package/dist/ui/cursor.js +261 -0
- package/dist/ui/dom.d.ts +30 -0
- package/dist/ui/dom.js +92 -0
- package/dist/ui/floats.d.ts +86 -0
- package/dist/ui/floats.js +260 -0
- package/dist/ui/hud.d.ts +198 -0
- package/dist/ui/hud.js +431 -0
- package/dist/ui/index.d.ts +37 -0
- package/dist/ui/index.js +34 -0
- package/dist/ui/inventory-card.d.ts +52 -0
- package/dist/ui/inventory-card.js +173 -0
- package/dist/ui/inventory-context.d.ts +29 -0
- package/dist/ui/inventory-context.js +125 -0
- package/dist/ui/inventory-drag.d.ts +63 -0
- package/dist/ui/inventory-drag.js +394 -0
- package/dist/ui/inventory-grid.d.ts +66 -0
- package/dist/ui/inventory-grid.js +625 -0
- package/dist/ui/inventory-radial.d.ts +39 -0
- package/dist/ui/inventory-radial.js +168 -0
- package/dist/ui/inventory.d.ts +128 -0
- package/dist/ui/inventory.js +154 -0
- package/dist/ui/layout.d.ts +356 -0
- package/dist/ui/layout.js +856 -0
- package/dist/ui/link.d.ts +64 -0
- package/dist/ui/link.js +155 -0
- package/dist/ui/loading.d.ts +40 -0
- package/dist/ui/loading.js +157 -0
- package/dist/ui/loupe.d.ts +74 -0
- package/dist/ui/loupe.js +164 -0
- package/dist/ui/menu-nav.d.ts +33 -0
- package/dist/ui/menu-nav.js +28 -0
- package/dist/ui/minimap-canvas.d.ts +145 -0
- package/dist/ui/minimap-canvas.js +460 -0
- package/dist/ui/notify.d.ts +40 -0
- package/dist/ui/notify.js +141 -0
- package/dist/ui/orb.d.ts +53 -0
- package/dist/ui/orb.js +304 -0
- package/dist/ui/overlay.d.ts +213 -0
- package/dist/ui/overlay.js +394 -0
- package/dist/ui/padnav.d.ts +53 -0
- package/dist/ui/padnav.js +146 -0
- package/dist/ui/perf.d.ts +130 -0
- package/dist/ui/perf.js +329 -0
- package/dist/ui/rotate-card.d.ts +54 -0
- package/dist/ui/rotate-card.js +97 -0
- package/dist/ui/stack.d.ts +128 -0
- package/dist/ui/stack.js +312 -0
- package/dist/ui/styles.d.ts +13 -0
- package/dist/ui/styles.js +200 -0
- package/dist/ui/text-scale.d.ts +67 -0
- package/dist/ui/text-scale.js +102 -0
- package/dist/ui/theme.d.ts +70 -0
- package/dist/ui/theme.js +98 -0
- package/dist/ui/toast.d.ts +23 -0
- package/dist/ui/toast.js +46 -0
- package/dist/ui/tooltip.d.ts +89 -0
- package/dist/ui/tooltip.js +333 -0
- package/dist/ui/widgets-2.d.ts +134 -0
- package/dist/ui/widgets-2.js +399 -0
- package/dist/ui/widgets-3.d.ts +91 -0
- package/dist/ui/widgets-3.js +212 -0
- package/dist/ui/widgets.d.ts +34 -0
- package/dist/ui/widgets.js +50 -0
- package/dist/ui/world-panel/index.d.ts +5 -0
- package/dist/ui/world-panel/index.js +5 -0
- package/dist/ui/world-panel/layout.d.ts +38 -0
- package/dist/ui/world-panel/layout.js +68 -0
- package/dist/ui/world-panel/paint.d.ts +96 -0
- package/dist/ui/world-panel/paint.js +136 -0
- package/dist/ui/world-panel/panel.d.ts +235 -0
- package/dist/ui/world-panel/panel.js +762 -0
- package/dist/ui/world-panel/wgsl.d.ts +10 -0
- package/dist/ui/world-panel/wgsl.js +52 -0
- package/dist/ui/world-panel/widgets.d.ts +207 -0
- package/dist/ui/world-panel/widgets.js +569 -0
- package/dist/voxel/chunks.d.ts +77 -0
- package/dist/voxel/chunks.js +149 -0
- package/dist/voxel/grid.d.ts +84 -0
- package/dist/voxel/grid.js +223 -0
- package/dist/voxel/index.d.ts +22 -0
- package/dist/voxel/index.js +7 -0
- package/dist/voxel/mesher-worker.d.ts +14 -0
- package/dist/voxel/mesher-worker.js +47 -0
- package/dist/voxel/mesher.d.ts +73 -0
- package/dist/voxel/mesher.js +306 -0
- package/dist/voxel/ray.d.ts +32 -0
- package/dist/voxel/ray.js +73 -0
- package/dist/voxel/wgsl.d.ts +16 -0
- package/dist/voxel/wgsl.js +28 -0
- package/dist/voxel/worker-client.d.ts +79 -0
- package/dist/voxel/worker-client.js +122 -0
- package/package.json +177 -3
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { h } from './dom.js';
|
|
2
|
+
import { prefersReducedMotionIn } from './styles.js';
|
|
3
|
+
/** Area (px²) of `box` that lies inside any of `regions` (overlaps between regions count twice). Pure. */
|
|
4
|
+
export function overlapArea(box, regions) {
|
|
5
|
+
let area = 0;
|
|
6
|
+
for (const r of regions) {
|
|
7
|
+
const w = Math.min(box.x + box.width, r.x + r.width) - Math.max(box.x, r.x);
|
|
8
|
+
const h = Math.min(box.y + box.height, r.y + r.height) - Math.max(box.y, r.y);
|
|
9
|
+
if (w > 0 && h > 0)
|
|
10
|
+
area += w * h;
|
|
11
|
+
}
|
|
12
|
+
return area;
|
|
13
|
+
}
|
|
14
|
+
/** The order corners are tried in: the preferred one, its mirror across, then the other edge. Pure. */
|
|
15
|
+
export function cornerOrder(preferred) {
|
|
16
|
+
const [v, side] = preferred.split('-');
|
|
17
|
+
const flipV = v === 'top' ? 'bottom' : 'top';
|
|
18
|
+
const flipS = side === 'left' ? 'right' : 'left';
|
|
19
|
+
return [preferred, `${v}-${flipS}`, `${flipV}-${side}`, `${flipV}-${flipS}`];
|
|
20
|
+
}
|
|
21
|
+
/** How long a line should stay up once fully revealed. Pure. */
|
|
22
|
+
export function readingTimeMs(text, opts = {}) {
|
|
23
|
+
const { baseMs = 1200, perCharMs = 45, minMs = 2000, maxMs = 12000 } = opts;
|
|
24
|
+
const chars = text.trim().length;
|
|
25
|
+
return Math.round(Math.min(maxMs, Math.max(minMs, baseMs + chars * perCharMs)));
|
|
26
|
+
}
|
|
27
|
+
/** Delay before revealing the next char: pauses after punctuation. Pure. */
|
|
28
|
+
export function revealDelayMs(prev, cps) {
|
|
29
|
+
const base = 1000 / cps;
|
|
30
|
+
if (/[.!?…]/.test(prev))
|
|
31
|
+
return base * 8;
|
|
32
|
+
if (/[,;:—–]/.test(prev))
|
|
33
|
+
return base * 4;
|
|
34
|
+
return base;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A corner speech/caption card with a typewriter reveal and a queue. The full line goes into a
|
|
38
|
+
* polite live region at once (screen readers don't hear the typewriter), and the visible text
|
|
39
|
+
* keeps its final layout from the start (unrevealed chars are `visibility:hidden`) so the card
|
|
40
|
+
* never reflows while typing.
|
|
41
|
+
*/
|
|
42
|
+
export class CaptionCard {
|
|
43
|
+
element;
|
|
44
|
+
portraitEl;
|
|
45
|
+
speakerEl;
|
|
46
|
+
textEl;
|
|
47
|
+
shownEl;
|
|
48
|
+
pendingEl;
|
|
49
|
+
liveEl;
|
|
50
|
+
queue = [];
|
|
51
|
+
current = null;
|
|
52
|
+
revealed = 0;
|
|
53
|
+
timer = null;
|
|
54
|
+
phase = 'idle';
|
|
55
|
+
corner;
|
|
56
|
+
sideEl;
|
|
57
|
+
bodyEl;
|
|
58
|
+
placeSubs = new Set();
|
|
59
|
+
homeEl;
|
|
60
|
+
dockHost = null;
|
|
61
|
+
lineCps = 0;
|
|
62
|
+
isSuspended = false;
|
|
63
|
+
reducedMotion;
|
|
64
|
+
cps;
|
|
65
|
+
constructor(container, opts = {}) {
|
|
66
|
+
this.corner = opts.corner ?? 'bottom-right';
|
|
67
|
+
this.reducedMotion = opts.reducedMotion ?? prefersReducedMotionIn();
|
|
68
|
+
this.cps = opts.cps ?? 45;
|
|
69
|
+
this.portraitEl = h('div', { class: 'tc-caption-portrait', 'aria-hidden': 'true' });
|
|
70
|
+
this.speakerEl = h('div', { class: 'tc-caption-speaker', 'aria-hidden': 'true' });
|
|
71
|
+
this.shownEl = h('span');
|
|
72
|
+
this.pendingEl = h('span', { class: 'tc-caption-pending' });
|
|
73
|
+
this.textEl = h('div', { class: 'tc-caption-text', 'aria-hidden': 'true' }, this.shownEl, this.pendingEl);
|
|
74
|
+
this.liveEl = h('div', {
|
|
75
|
+
class: 'tc-sr-only',
|
|
76
|
+
role: 'status',
|
|
77
|
+
'aria-live': 'polite',
|
|
78
|
+
'aria-atomic': 'true',
|
|
79
|
+
});
|
|
80
|
+
// With a portrait, the speaker's name sits under it (a shorter card); without, above the text.
|
|
81
|
+
this.sideEl = h('div', { class: 'tc-caption-side' }, this.portraitEl);
|
|
82
|
+
this.bodyEl = h('div', { class: 'tc-caption-body' }, this.speakerEl, this.textEl);
|
|
83
|
+
this.element = h('div', { class: 'tc-caption', dataset: { corner: this.corner }, hidden: true }, this.sideEl, this.bodyEl);
|
|
84
|
+
if (opts.clickToAdvance !== false)
|
|
85
|
+
this.element.addEventListener('click', () => this.advance());
|
|
86
|
+
this.homeEl = container;
|
|
87
|
+
container.append(this.element, this.liveEl);
|
|
88
|
+
}
|
|
89
|
+
/** The region the card is docked in (see `dock`), or null in its corner. */
|
|
90
|
+
get docked() {
|
|
91
|
+
return this.dockHost;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Region placement (layout regions, `Layout.dockCaption`): move the card into `host` and let
|
|
95
|
+
* it fill that region instead of floating in a corner. While docked, corners and `onPlace` do
|
|
96
|
+
* not apply; the region never overlaps another. `null` returns it to its corner in the
|
|
97
|
+
* original container. The live region stays put.
|
|
98
|
+
*/
|
|
99
|
+
dock(host) {
|
|
100
|
+
if (host === this.dockHost)
|
|
101
|
+
return;
|
|
102
|
+
this.dockHost = host;
|
|
103
|
+
this.element.toggleAttribute('data-docked', !!host);
|
|
104
|
+
this.element.style.maxWidth = '';
|
|
105
|
+
if (host)
|
|
106
|
+
host.append(this.element);
|
|
107
|
+
else
|
|
108
|
+
this.homeEl.insertBefore(this.element, this.liveEl);
|
|
109
|
+
this.place();
|
|
110
|
+
}
|
|
111
|
+
/** True while a line is showing or queued. */
|
|
112
|
+
get busy() {
|
|
113
|
+
return this.current !== null;
|
|
114
|
+
}
|
|
115
|
+
get queued() {
|
|
116
|
+
return this.queue.length;
|
|
117
|
+
}
|
|
118
|
+
/** Text of the line being shown, or null. */
|
|
119
|
+
get currentText() {
|
|
120
|
+
return this.current?.text ?? null;
|
|
121
|
+
}
|
|
122
|
+
/** True while `suspend(true)` holds the card hidden and frozen. */
|
|
123
|
+
get suspended() {
|
|
124
|
+
return this.isSuspended;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Called whenever the card is laid out in its corner (a new line, back from suspension or
|
|
128
|
+
* undocked), after its corner is reset to the line's and its inline `max-width` cleared. A
|
|
129
|
+
* game that must keep the card off parts of its scene moves or narrows it here (`overlapArea`
|
|
130
|
+
* and `cornerOrder` help). Returns unsubscribe.
|
|
131
|
+
*/
|
|
132
|
+
onPlace(fn) {
|
|
133
|
+
this.placeSubs.add(fn);
|
|
134
|
+
return () => this.placeSubs.delete(fn);
|
|
135
|
+
}
|
|
136
|
+
/** Lay the card out again in its corner and run `onPlace` (e.g. after the scene moved). */
|
|
137
|
+
relayout() {
|
|
138
|
+
this.place();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Hide the card and freeze its typing and hold timers (e.g. while a modal panel is open);
|
|
142
|
+
* `suspend(false)` shows it again and picks up where it left off, with a fresh hold. The
|
|
143
|
+
* queue and live region are untouched, and `say()` still queues while suspended.
|
|
144
|
+
*/
|
|
145
|
+
suspend(on) {
|
|
146
|
+
if (on === this.isSuspended)
|
|
147
|
+
return;
|
|
148
|
+
this.isSuspended = on;
|
|
149
|
+
this.element.toggleAttribute('data-suspended', on);
|
|
150
|
+
if (on) {
|
|
151
|
+
this.stopTimer();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.place();
|
|
155
|
+
this.schedule();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Queue a line; resolves when it's dismissed (held time elapsed, advanced, or cleared). */
|
|
159
|
+
say(text, opts = {}) {
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
this.queue.push({ text, opts, resolve });
|
|
162
|
+
if (!this.current)
|
|
163
|
+
this.next();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** Skip the typewriter if typing, else dismiss the current line. */
|
|
167
|
+
advance() {
|
|
168
|
+
if (this.phase === 'typing')
|
|
169
|
+
this.finishReveal();
|
|
170
|
+
else if (this.phase === 'holding')
|
|
171
|
+
this.dismiss();
|
|
172
|
+
}
|
|
173
|
+
/** Drop the queue and hide the card; all pending `say` promises resolve. */
|
|
174
|
+
clear() {
|
|
175
|
+
const pending = this.queue.splice(0);
|
|
176
|
+
this.stopTimer();
|
|
177
|
+
const cur = this.current;
|
|
178
|
+
this.current = null;
|
|
179
|
+
this.phase = 'idle';
|
|
180
|
+
this.element.hidden = true;
|
|
181
|
+
cur?.resolve();
|
|
182
|
+
for (const l of pending)
|
|
183
|
+
l.resolve();
|
|
184
|
+
}
|
|
185
|
+
destroy() {
|
|
186
|
+
this.clear();
|
|
187
|
+
this.placeSubs.clear();
|
|
188
|
+
this.element.remove();
|
|
189
|
+
this.liveEl.remove();
|
|
190
|
+
}
|
|
191
|
+
next() {
|
|
192
|
+
const line = this.queue.shift();
|
|
193
|
+
this.stopTimer();
|
|
194
|
+
if (!line) {
|
|
195
|
+
this.current = null;
|
|
196
|
+
this.phase = 'idle';
|
|
197
|
+
this.element.hidden = true;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.current = line;
|
|
201
|
+
this.revealed = 0;
|
|
202
|
+
this.speakerEl.textContent = line.opts.speaker ?? '';
|
|
203
|
+
this.portraitEl.replaceChildren(...(line.opts.portrait ? [line.opts.portrait] : []));
|
|
204
|
+
this.sideEl.hidden = !line.opts.portrait;
|
|
205
|
+
if (line.opts.portrait)
|
|
206
|
+
this.sideEl.append(this.speakerEl);
|
|
207
|
+
else
|
|
208
|
+
this.bodyEl.prepend(this.speakerEl);
|
|
209
|
+
this.liveEl.textContent = line.opts.speaker ? `${line.opts.speaker}: ${line.text}` : line.text;
|
|
210
|
+
this.element.hidden = false;
|
|
211
|
+
this.lineCps = line.opts.cps ?? this.cps;
|
|
212
|
+
const instant = this.reducedMotion || this.lineCps <= 0;
|
|
213
|
+
if (instant)
|
|
214
|
+
this.revealed = [...line.text].length;
|
|
215
|
+
this.phase = instant ? 'holding' : 'typing';
|
|
216
|
+
// The whole line is laid out from the start (pending chars hidden), so place it once now.
|
|
217
|
+
this.render();
|
|
218
|
+
this.place();
|
|
219
|
+
this.schedule();
|
|
220
|
+
}
|
|
221
|
+
/** (Re)start the timer for the current phase, unless suspended. */
|
|
222
|
+
schedule() {
|
|
223
|
+
this.stopTimer();
|
|
224
|
+
const line = this.current;
|
|
225
|
+
if (!line || this.isSuspended)
|
|
226
|
+
return;
|
|
227
|
+
if (this.phase === 'typing')
|
|
228
|
+
this.tick(this.lineCps);
|
|
229
|
+
else if (this.phase === 'holding') {
|
|
230
|
+
const hold = line.opts.hold ?? readingTimeMs(line.text);
|
|
231
|
+
if (hold > 0)
|
|
232
|
+
this.timer = setTimeout(() => this.dismiss(), hold);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Reset the card to the line's corner and let `onPlace` adjust it. */
|
|
236
|
+
place() {
|
|
237
|
+
const el = this.element;
|
|
238
|
+
const line = this.current;
|
|
239
|
+
if (!line || el.hidden || this.isSuspended || this.dockHost)
|
|
240
|
+
return;
|
|
241
|
+
el.dataset.corner = line.opts.corner ?? this.corner;
|
|
242
|
+
el.style.maxWidth = '';
|
|
243
|
+
for (const fn of this.placeSubs)
|
|
244
|
+
fn(el);
|
|
245
|
+
}
|
|
246
|
+
tick(cps) {
|
|
247
|
+
const line = this.current;
|
|
248
|
+
const chars = [...line.text];
|
|
249
|
+
if (this.revealed >= chars.length) {
|
|
250
|
+
this.finishReveal();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const ch = chars[this.revealed];
|
|
254
|
+
this.revealed++;
|
|
255
|
+
this.render();
|
|
256
|
+
if (ch.trim())
|
|
257
|
+
line.opts.onChar?.(ch, this.revealed - 1);
|
|
258
|
+
this.timer = setTimeout(() => this.tick(cps), revealDelayMs(ch, cps));
|
|
259
|
+
}
|
|
260
|
+
finishReveal() {
|
|
261
|
+
const line = this.current;
|
|
262
|
+
if (!line)
|
|
263
|
+
return;
|
|
264
|
+
this.revealed = [...line.text].length;
|
|
265
|
+
this.render();
|
|
266
|
+
this.phase = 'holding';
|
|
267
|
+
this.schedule();
|
|
268
|
+
}
|
|
269
|
+
dismiss() {
|
|
270
|
+
const line = this.current;
|
|
271
|
+
this.stopTimer();
|
|
272
|
+
this.current = null;
|
|
273
|
+
line?.resolve();
|
|
274
|
+
this.next();
|
|
275
|
+
}
|
|
276
|
+
render() {
|
|
277
|
+
const chars = [...(this.current?.text ?? '')];
|
|
278
|
+
this.shownEl.textContent = chars.slice(0, this.revealed).join('');
|
|
279
|
+
this.pendingEl.textContent = chars.slice(this.revealed).join('');
|
|
280
|
+
}
|
|
281
|
+
stopTimer() {
|
|
282
|
+
if (this.timer)
|
|
283
|
+
clearTimeout(this.timer);
|
|
284
|
+
this.timer = null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World cursor (ws/ui-3): a pad- and keyboard-driven selection cursor over the world. Grid mode
|
|
3
|
+
* steps by (dx, dy) on each fresh d-pad or arrow press, with latch and repeat (hold to keep
|
|
4
|
+
* stepping); targets mode snaps to the nearest target in the pressed direction. A/Enter selects.
|
|
5
|
+
* The cursor element rides `Anchors` on the hud layer when the game says where the cursor is
|
|
6
|
+
* (`at`) or a target is picked. It yields to a modal panel: while the stack owns the pad
|
|
7
|
+
* (`stack.padOwner`, followed through `onPadOwnerChange`, ws/ui-4) nothing is read and the latch
|
|
8
|
+
* is reset, so the pad is the panel's and the cursor resumes afterwards with a fresh latch.
|
|
9
|
+
* Call `update()` once per frame (before or after `padNav.update()`, both latch on their own).
|
|
10
|
+
*/
|
|
11
|
+
import type { Anchors } from './anchor.js';
|
|
12
|
+
import { type PadDir, type PadLike } from './padnav.js';
|
|
13
|
+
import type { PadOwner } from './stack.js';
|
|
14
|
+
/** A thing the cursor can land on in targets mode: a plane position plus its world point. */
|
|
15
|
+
export interface WorldTarget {
|
|
16
|
+
id?: string | number;
|
|
17
|
+
/** Plane coordinates the direction picking reasons in (a cell, a tile, a screen point). */
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
/** The point the cursor element is anchored at. */
|
|
21
|
+
world: ArrayLike<number>;
|
|
22
|
+
}
|
|
23
|
+
export interface CursorRepeat {
|
|
24
|
+
/** ms a direction is held before it repeats. Default 400. */
|
|
25
|
+
delay: number;
|
|
26
|
+
/** ms between repeats. Default 80. */
|
|
27
|
+
interval: number;
|
|
28
|
+
}
|
|
29
|
+
export interface CursorOptions {
|
|
30
|
+
/** `grid` steps by (dx, dy); `targets` snaps between `targets()`. */
|
|
31
|
+
mode: 'grid' | 'targets';
|
|
32
|
+
/** Grid mode: a press (or repeat) in a direction; dy is +1 for down (y down). */
|
|
33
|
+
step?: (dx: number, dy: number) => void;
|
|
34
|
+
/** Grid mode: where the cursor is now, in world units, for its element; null hides it. */
|
|
35
|
+
at?: () => ArrayLike<number> | null;
|
|
36
|
+
/** Targets mode: the targets to pick from, read on each press. */
|
|
37
|
+
targets?: () => readonly WorldTarget[];
|
|
38
|
+
onSelect?: (target: WorldTarget | null) => void;
|
|
39
|
+
onMove?: (target: WorldTarget | null) => void;
|
|
40
|
+
repeat?: Partial<CursorRepeat>;
|
|
41
|
+
/** Perpendicular drift weight for `pickTarget`. Default 2. */
|
|
42
|
+
weight?: number;
|
|
43
|
+
/** Gamepad source. Default `navigator.getGamepads()` (guarded). */
|
|
44
|
+
getGamepads?: () => readonly (PadLike | null)[];
|
|
45
|
+
/** Stick deflection that counts as a d-pad press. Default 0.6. */
|
|
46
|
+
axisThreshold?: number;
|
|
47
|
+
/** Listen for the arrow keys and Enter too. Default true. */
|
|
48
|
+
keys?: boolean;
|
|
49
|
+
/** Start enabled. Default true. */
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface WorldCursor {
|
|
53
|
+
/** The cursor element on the hud layer (`aria-hidden`; a game's own class may style it). */
|
|
54
|
+
readonly element: HTMLElement;
|
|
55
|
+
readonly enabled: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* True while the latch holds a control (a direction or A is held, or a direction repeats), as
|
|
58
|
+
* of the last `update()`. It resets on each pad-owner transition.
|
|
59
|
+
*/
|
|
60
|
+
readonly latched: boolean;
|
|
61
|
+
/** Targets mode: the target under the cursor. */
|
|
62
|
+
readonly target: WorldTarget | null;
|
|
63
|
+
/** The cursor's world position (`at()` in grid mode, the target's `world` in targets mode). */
|
|
64
|
+
readonly position: ArrayLike<number> | null;
|
|
65
|
+
/** Read the pad and move; once per frame. `now` (ms) drives repeat; default `performance.now()`. */
|
|
66
|
+
update(now?: number): void;
|
|
67
|
+
/** Targets mode: land on a target (or none) without a press; fires `onMove`. */
|
|
68
|
+
setTarget(target: WorldTarget | null): void;
|
|
69
|
+
/** A press from the game's own input (a touch d-pad, a binding), latched like the pad. */
|
|
70
|
+
press(dir: PadDir | 'activate'): void;
|
|
71
|
+
enable(): void;
|
|
72
|
+
disable(): void;
|
|
73
|
+
dispose(): void;
|
|
74
|
+
}
|
|
75
|
+
/** What the cursor needs from its overlay (`Overlay` fits). */
|
|
76
|
+
export interface CursorHost {
|
|
77
|
+
readonly layers: {
|
|
78
|
+
readonly hud: HTMLElement;
|
|
79
|
+
};
|
|
80
|
+
readonly stack: {
|
|
81
|
+
/** Read once at creation (and each frame only by a host without `onPadOwnerChange`). */
|
|
82
|
+
readonly hasModal: boolean;
|
|
83
|
+
/** The pad-owner transition (ws/ui-4); a host without it is polled through `hasModal`. */
|
|
84
|
+
onPadOwnerChange?(fn: (owner: PadOwner) => void): () => void;
|
|
85
|
+
};
|
|
86
|
+
/** The overlay's `Anchors`; throws until a camera built it. */
|
|
87
|
+
anchors(): Anchors;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The target nearest `from` in direction `dir` (y down): among the targets with a positive
|
|
91
|
+
* component along the direction, the one with the least `along + weight · perpendicular`, so a
|
|
92
|
+
* neighbour straight ahead beats a nearer one off to the side. Null when none lies that way.
|
|
93
|
+
*/
|
|
94
|
+
export declare function pickTarget(from: {
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
}, targets: readonly WorldTarget[], dir: PadDir, weight?: number): WorldTarget | null;
|
|
98
|
+
/** The cursor (see the module comment). Also reachable as `ui.cursor(opts)`. */
|
|
99
|
+
export declare function createCursor(host: CursorHost, opts: CursorOptions): WorldCursor;
|
|
100
|
+
/**
|
|
101
|
+
* Stylesheet for the cursor element (design px, theme tokens).
|
|
102
|
+
*/
|
|
103
|
+
export declare const CURSOR_CSS: string;
|
|
104
|
+
/** Add `CURSOR_CSS` to `doc` once (the first cursor does it). */
|
|
105
|
+
export declare function ensureCursorStyles(doc?: Document): void;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { consumesArrows, h } from './dom.js';
|
|
2
|
+
import { defaultPads, readPad } from './padnav.js';
|
|
3
|
+
import { toFrameUnits } from './text-scale.js';
|
|
4
|
+
const DIRS = ['up', 'down', 'left', 'right'];
|
|
5
|
+
const DX = { up: 0, down: 0, left: -1, right: 1 };
|
|
6
|
+
const DY = { up: -1, down: 1, left: 0, right: 0 };
|
|
7
|
+
const KEY_DIR = {
|
|
8
|
+
ArrowUp: 'up',
|
|
9
|
+
ArrowDown: 'down',
|
|
10
|
+
ArrowLeft: 'left',
|
|
11
|
+
ArrowRight: 'right',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The target nearest `from` in direction `dir` (y down): among the targets with a positive
|
|
15
|
+
* component along the direction, the one with the least `along + weight · perpendicular`, so a
|
|
16
|
+
* neighbour straight ahead beats a nearer one off to the side. Null when none lies that way.
|
|
17
|
+
*/
|
|
18
|
+
export function pickTarget(from, targets, dir, weight = 2) {
|
|
19
|
+
const dx = DX[dir];
|
|
20
|
+
const dy = DY[dir];
|
|
21
|
+
let best = null;
|
|
22
|
+
let bestCost = Number.POSITIVE_INFINITY;
|
|
23
|
+
for (const t of targets) {
|
|
24
|
+
const vx = t.x - from.x;
|
|
25
|
+
const vy = t.y - from.y;
|
|
26
|
+
const along = vx * dx + vy * dy;
|
|
27
|
+
if (along <= 0)
|
|
28
|
+
continue;
|
|
29
|
+
const perp = Math.abs(vx * dy - vy * dx);
|
|
30
|
+
const cost = along + weight * perp;
|
|
31
|
+
if (cost < bestCost) {
|
|
32
|
+
bestCost = cost;
|
|
33
|
+
best = t;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
}
|
|
38
|
+
/** The cursor (see the module comment). Also reachable as `ui.cursor(opts)`. */
|
|
39
|
+
export function createCursor(host, opts) {
|
|
40
|
+
const layer = host.layers.hud;
|
|
41
|
+
const doc = layer.ownerDocument;
|
|
42
|
+
ensureCursorStyles(doc);
|
|
43
|
+
const element = h('div', { class: 'tc-cursor', 'aria-hidden': 'true', hidden: true });
|
|
44
|
+
const getPads = opts.getGamepads ?? defaultPads;
|
|
45
|
+
const threshold = opts.axisThreshold ?? 0.6;
|
|
46
|
+
const delay = opts.repeat?.delay ?? 400;
|
|
47
|
+
const interval = opts.repeat?.interval ?? 80;
|
|
48
|
+
const weight = opts.weight ?? 2;
|
|
49
|
+
const useKeys = opts.keys ?? true;
|
|
50
|
+
let enabled = opts.enabled ?? true;
|
|
51
|
+
let target = null;
|
|
52
|
+
let handle = null;
|
|
53
|
+
let disposed = false;
|
|
54
|
+
// The latch: what was held last frame (pad and keys together) and the one direction repeating.
|
|
55
|
+
const now = new Set();
|
|
56
|
+
const held = new Set();
|
|
57
|
+
const keys = new Set();
|
|
58
|
+
const pressed = new Set();
|
|
59
|
+
let repeatDir = null;
|
|
60
|
+
let repeatAt = 0;
|
|
61
|
+
// Who owns the pad: a subscribing host tells us on each transition (the latch resets, and
|
|
62
|
+
// nothing is read while the panel has the pad); a polling host is asked each frame.
|
|
63
|
+
const subscribed = typeof host.stack.onPadOwnerChange === 'function';
|
|
64
|
+
let yielded = host.stack.hasModal;
|
|
65
|
+
const resetLatch = () => {
|
|
66
|
+
held.clear();
|
|
67
|
+
pressed.clear();
|
|
68
|
+
repeatDir = null;
|
|
69
|
+
};
|
|
70
|
+
const unwatch = subscribed
|
|
71
|
+
? host.stack.onPadOwnerChange((owner) => {
|
|
72
|
+
yielded = owner === 'stack';
|
|
73
|
+
resetLatch();
|
|
74
|
+
// Getting the pad back: what is still held from the panel is not a fresh press here.
|
|
75
|
+
if (!yielded)
|
|
76
|
+
readPad(getPads(), threshold, held);
|
|
77
|
+
})
|
|
78
|
+
: null;
|
|
79
|
+
const anchorAt = (world) => {
|
|
80
|
+
if (!world) {
|
|
81
|
+
if (handle) {
|
|
82
|
+
handle.remove();
|
|
83
|
+
handle = null;
|
|
84
|
+
}
|
|
85
|
+
element.hidden = true;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (handle)
|
|
89
|
+
handle.set(world);
|
|
90
|
+
else
|
|
91
|
+
handle = host.anchors().add(element, world);
|
|
92
|
+
if (element.hidden)
|
|
93
|
+
element.hidden = false;
|
|
94
|
+
};
|
|
95
|
+
const move = (dir) => {
|
|
96
|
+
if (opts.mode === 'grid') {
|
|
97
|
+
opts.step?.(DX[dir], DY[dir]);
|
|
98
|
+
opts.onMove?.(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const list = opts.targets?.() ?? [];
|
|
102
|
+
const next = target ? pickTarget(target, list, dir, weight) : (list.find(() => true) ?? null);
|
|
103
|
+
if (next && next !== target)
|
|
104
|
+
setTarget(next);
|
|
105
|
+
};
|
|
106
|
+
function setTarget(t) {
|
|
107
|
+
target = t;
|
|
108
|
+
anchorAt(t ? t.world : null);
|
|
109
|
+
opts.onMove?.(t);
|
|
110
|
+
}
|
|
111
|
+
const select = () => opts.onSelect?.(target);
|
|
112
|
+
// A key held adds to `keys` (for the repeat); its first keydown also queues a one-shot in
|
|
113
|
+
// `pressed`, so a tap that starts and ends between two frames still counts as a press.
|
|
114
|
+
const onKeyDown = (e) => {
|
|
115
|
+
const dir = KEY_DIR[e.key];
|
|
116
|
+
const active = doc.activeElement;
|
|
117
|
+
let control;
|
|
118
|
+
if (dir) {
|
|
119
|
+
if (active && active !== doc.body && consumesArrows(active, DX[dir] !== 0))
|
|
120
|
+
return;
|
|
121
|
+
control = dir;
|
|
122
|
+
}
|
|
123
|
+
else if (e.key === 'Enter') {
|
|
124
|
+
if (active && active !== doc.body && active !== layer)
|
|
125
|
+
return;
|
|
126
|
+
control = 'activate';
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
return;
|
|
130
|
+
keys.add(control);
|
|
131
|
+
if (!e.repeat)
|
|
132
|
+
pressed.add(control);
|
|
133
|
+
};
|
|
134
|
+
const onKeyUp = (e) => {
|
|
135
|
+
const dir = KEY_DIR[e.key];
|
|
136
|
+
if (dir)
|
|
137
|
+
keys.delete(dir);
|
|
138
|
+
else if (e.key === 'Enter')
|
|
139
|
+
keys.delete('activate');
|
|
140
|
+
};
|
|
141
|
+
const onBlur = () => keys.clear();
|
|
142
|
+
if (useKeys) {
|
|
143
|
+
doc.addEventListener('keydown', onKeyDown);
|
|
144
|
+
doc.addEventListener('keyup', onKeyUp);
|
|
145
|
+
doc.defaultView?.addEventListener('blur', onBlur);
|
|
146
|
+
}
|
|
147
|
+
const cursor = {
|
|
148
|
+
element,
|
|
149
|
+
get enabled() {
|
|
150
|
+
return enabled;
|
|
151
|
+
},
|
|
152
|
+
get latched() {
|
|
153
|
+
return held.size > 0 || repeatDir !== null;
|
|
154
|
+
},
|
|
155
|
+
get target() {
|
|
156
|
+
return target;
|
|
157
|
+
},
|
|
158
|
+
get position() {
|
|
159
|
+
if (opts.mode === 'targets')
|
|
160
|
+
return target ? target.world : null;
|
|
161
|
+
return opts.at?.() ?? null;
|
|
162
|
+
},
|
|
163
|
+
update(t = typeof performance !== 'undefined' ? performance.now() : Date.now()) {
|
|
164
|
+
if (disposed)
|
|
165
|
+
return;
|
|
166
|
+
if (!subscribed)
|
|
167
|
+
yielded = host.stack.hasModal;
|
|
168
|
+
if (yielded) {
|
|
169
|
+
// The panel has the pad: nothing is read, and the latch stays reset.
|
|
170
|
+
pressed.clear();
|
|
171
|
+
if (opts.mode === 'grid')
|
|
172
|
+
anchorAt(opts.at?.() ?? null);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
readPad(getPads(), threshold, now);
|
|
176
|
+
for (const k of keys)
|
|
177
|
+
now.add(k);
|
|
178
|
+
for (const k of pressed)
|
|
179
|
+
now.add(k);
|
|
180
|
+
pressed.clear();
|
|
181
|
+
if (!enabled) {
|
|
182
|
+
// Disabled: a press that outlives it is not a fresh one.
|
|
183
|
+
held.clear();
|
|
184
|
+
for (const k of now)
|
|
185
|
+
held.add(k);
|
|
186
|
+
repeatDir = null;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
if (now.has('activate') && !held.has('activate'))
|
|
190
|
+
select();
|
|
191
|
+
let fresh = null;
|
|
192
|
+
for (const d of DIRS)
|
|
193
|
+
if (now.has(d) && !held.has(d))
|
|
194
|
+
fresh = d;
|
|
195
|
+
if (fresh) {
|
|
196
|
+
move(fresh);
|
|
197
|
+
repeatDir = fresh;
|
|
198
|
+
repeatAt = t + delay;
|
|
199
|
+
}
|
|
200
|
+
else if (repeatDir && now.has(repeatDir)) {
|
|
201
|
+
if (t >= repeatAt) {
|
|
202
|
+
move(repeatDir);
|
|
203
|
+
repeatAt = t + interval;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
repeatDir = null;
|
|
208
|
+
held.clear();
|
|
209
|
+
for (const k of now)
|
|
210
|
+
held.add(k);
|
|
211
|
+
}
|
|
212
|
+
if (opts.mode === 'grid')
|
|
213
|
+
anchorAt(opts.at?.() ?? null);
|
|
214
|
+
},
|
|
215
|
+
setTarget,
|
|
216
|
+
press(dir) {
|
|
217
|
+
pressed.add(dir);
|
|
218
|
+
},
|
|
219
|
+
enable() {
|
|
220
|
+
enabled = true;
|
|
221
|
+
},
|
|
222
|
+
disable() {
|
|
223
|
+
enabled = false;
|
|
224
|
+
repeatDir = null;
|
|
225
|
+
},
|
|
226
|
+
dispose() {
|
|
227
|
+
disposed = true;
|
|
228
|
+
unwatch?.();
|
|
229
|
+
if (useKeys) {
|
|
230
|
+
doc.removeEventListener('keydown', onKeyDown);
|
|
231
|
+
doc.removeEventListener('keyup', onKeyUp);
|
|
232
|
+
doc.defaultView?.removeEventListener('blur', onBlur);
|
|
233
|
+
}
|
|
234
|
+
anchorAt(null);
|
|
235
|
+
element.remove();
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
return cursor;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Stylesheet for the cursor element (design px, theme tokens).
|
|
242
|
+
*/
|
|
243
|
+
export const CURSOR_CSS = toFrameUnits(`
|
|
244
|
+
.tc-cursor { width: 48px; height: 48px; box-sizing: border-box; pointer-events: none;
|
|
245
|
+
border: 3px solid var(--tc-color-accent); border-radius: var(--tc-radius-s);
|
|
246
|
+
box-shadow: 0 0 0 2px var(--tc-color-surface), var(--tc-shadow);
|
|
247
|
+
animation: tc-cursor-pulse 1s ease-in-out infinite alternate; }
|
|
248
|
+
.tc-cursor[hidden] { display: none; }
|
|
249
|
+
@keyframes tc-cursor-pulse { from { opacity: 0.7; } to { opacity: 1; } }
|
|
250
|
+
@media (prefers-reduced-motion: reduce) { .tc-cursor { animation: none; } }
|
|
251
|
+
`);
|
|
252
|
+
const STYLE_ID = 'tc-ui-cursor';
|
|
253
|
+
/** Add `CURSOR_CSS` to `doc` once (the first cursor does it). */
|
|
254
|
+
export function ensureCursorStyles(doc = document) {
|
|
255
|
+
if (doc.getElementById(STYLE_ID))
|
|
256
|
+
return;
|
|
257
|
+
const style = doc.createElement('style');
|
|
258
|
+
style.id = STYLE_ID;
|
|
259
|
+
style.textContent = CURSOR_CSS;
|
|
260
|
+
doc.head.appendChild(style);
|
|
261
|
+
}
|
package/dist/ui/dom.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Tiny DOM helpers. */
|
|
2
|
+
export type Child = Node | string | number | null | undefined | false;
|
|
3
|
+
type Props = {
|
|
4
|
+
class?: string;
|
|
5
|
+
/** An object of properties (`--custom` ones included) or a `cssText` string. */
|
|
6
|
+
style?: Partial<CSSStyleDeclaration> | Record<string, string> | string;
|
|
7
|
+
dataset?: Record<string, string>;
|
|
8
|
+
/** Event listeners: `on: { click: fn }`. */
|
|
9
|
+
on?: {
|
|
10
|
+
[K in keyof HTMLElementEventMap]?: (e: HTMLElementEventMap[K]) => void;
|
|
11
|
+
};
|
|
12
|
+
[attr: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Hyperscript: `h('div', { class: 'row', 'aria-label': 'x', on: { click } }, 'text', child)`.
|
|
16
|
+
* Unknown props become attributes (`true` → empty attribute, `false`/null → omitted). `style`
|
|
17
|
+
* takes an object (`--custom` properties included) or a `cssText` string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Props | null, ...children: Child[]): HTMLElementTagNameMap[K];
|
|
20
|
+
export declare function append(el: Element, ...children: Child[]): void;
|
|
21
|
+
/** Keyboard-focusable descendants in DOM order (skips `hidden` and `inert` subtrees). */
|
|
22
|
+
export declare function focusableWithin(root: Element): HTMLElement[];
|
|
23
|
+
/**
|
|
24
|
+
* Whether a focused `el` uses the arrow keys of an axis itself, so focus navigation (the panel
|
|
25
|
+
* stack's arrows, `PadNav`'s d-pad) must leave them to it: a text field consumes left/right, a
|
|
26
|
+
* textarea, select, slider (`type=range` or `role=slider`), radio, number, date or other value
|
|
27
|
+
* input and a contenteditable consume both axes; buttons and checkboxes consume none.
|
|
28
|
+
*/
|
|
29
|
+
export declare function consumesArrows(el: Element, horizontal: boolean): boolean;
|
|
30
|
+
export {};
|