@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,856 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout regions (D-041 Phase 2): a `LayoutSpec` names regions per form factor, the engine lays
|
|
3
|
+
* them out with one CSS grid inside the safe area of a CSS-px overlay, and widgets register once
|
|
4
|
+
* with a region per form factor. The layout moves them when the form factor changes (a phone
|
|
5
|
+
* turned upright) and reports `freeRect`, the space the UI leaves, for the scene's camera.
|
|
6
|
+
*
|
|
7
|
+
* Everything is in CSS px relative to the overlay root. The geometry is pure (`resolveLayout`)
|
|
8
|
+
* and the DOM grid uses the same resolved track sizes, so the rects the layout reports are the
|
|
9
|
+
* rects the browser draws.
|
|
10
|
+
*/
|
|
11
|
+
import { h } from './dom.js';
|
|
12
|
+
export const FORM_FACTORS = [
|
|
13
|
+
'phone-landscape',
|
|
14
|
+
'phone-portrait',
|
|
15
|
+
'tablet',
|
|
16
|
+
'desktop',
|
|
17
|
+
];
|
|
18
|
+
export const REGIONS = [
|
|
19
|
+
'top',
|
|
20
|
+
'left',
|
|
21
|
+
'right',
|
|
22
|
+
'bottom',
|
|
23
|
+
'cardLane',
|
|
24
|
+
'captionLane',
|
|
25
|
+
'modal',
|
|
26
|
+
'fill',
|
|
27
|
+
];
|
|
28
|
+
export const NO_INSETS = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
29
|
+
/** `size` in `ff`: the number itself, else the form's entry, `phone` on a phone, `default`. Pure. */
|
|
30
|
+
export function formSize(size, ff) {
|
|
31
|
+
if (typeof size === 'number')
|
|
32
|
+
return size;
|
|
33
|
+
const own = size[ff];
|
|
34
|
+
if (own !== undefined)
|
|
35
|
+
return own;
|
|
36
|
+
if (ff.startsWith('phone') && size.phone !== undefined)
|
|
37
|
+
return size.phone;
|
|
38
|
+
return size.default;
|
|
39
|
+
}
|
|
40
|
+
/** Largest short side (CSS px) a phone has; tablets start around 744. */
|
|
41
|
+
export const PHONE_MAX_SHORT_SIDE = 600;
|
|
42
|
+
/** A window whose short side is under this is phone-sized, whatever the pointer. */
|
|
43
|
+
export const PHONE_MAX_VIEW_SHORT = 480;
|
|
44
|
+
/**
|
|
45
|
+
* Default form-factor choice. A phone is a coarse-pointer screen whose short side is at most
|
|
46
|
+
* `PHONE_MAX_SHORT_SIDE` (the `deviceTraits().handheld` rule), or any window whose short side is
|
|
47
|
+
* under `PHONE_MAX_VIEW_SHORT` (a tiny desktop window gets phone chrome rather than clipped
|
|
48
|
+
* desktop chrome). Phones split by orientation. Other coarse pointers are tablets; the rest are
|
|
49
|
+
* desktops.
|
|
50
|
+
*/
|
|
51
|
+
export function classifyFormFactor(i) {
|
|
52
|
+
const viewShort = Math.min(i.width, i.height);
|
|
53
|
+
const handheld = i.coarsePointer && i.shortSide > 0 && i.shortSide <= PHONE_MAX_SHORT_SIDE;
|
|
54
|
+
if (handheld || (viewShort > 0 && viewShort < PHONE_MAX_VIEW_SHORT)) {
|
|
55
|
+
return i.height > i.width ? 'phone-portrait' : 'phone-landscape';
|
|
56
|
+
}
|
|
57
|
+
return i.coarsePointer ? 'tablet' : 'desktop';
|
|
58
|
+
}
|
|
59
|
+
const WIDE_AREAS = [
|
|
60
|
+
'top top top top',
|
|
61
|
+
'left fill cardLane right',
|
|
62
|
+
'left captionLane cardLane right',
|
|
63
|
+
'bottom bottom bottom bottom',
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* The default regions. They are deliberately sparse: empty tracks collapse, so a phone shows
|
|
67
|
+
* only the chrome a scene registers, and the world gets the rest.
|
|
68
|
+
*
|
|
69
|
+
* - **desktop / tablet**: full-width top and bottom bars; left and right rails; a card lane
|
|
70
|
+
* beside the world on the right; the caption lane under the world.
|
|
71
|
+
* - **phone-landscape**: height is the scarce axis, so left and right are full-height rails
|
|
72
|
+
* (56 px: a 44 px tap target plus padding) and top/bottom sit between them; the card lane is
|
|
73
|
+
* beside the world; the caption lane is a short strip under it.
|
|
74
|
+
* - **phone-portrait**: width is scarce, so the rails are slim and the card lane and caption lane
|
|
75
|
+
* are full-width strips stacked under the world, above the bottom bar.
|
|
76
|
+
*/
|
|
77
|
+
export const defaultForms = {
|
|
78
|
+
desktop: {
|
|
79
|
+
columns: [
|
|
80
|
+
{ frac: 0.16, min: 180, max: 300 },
|
|
81
|
+
'fill',
|
|
82
|
+
{ frac: 0.18, min: 200, max: 320 },
|
|
83
|
+
{ frac: 0.16, min: 180, max: 300 },
|
|
84
|
+
],
|
|
85
|
+
rows: [64, 'fill', { frac: 0.16, min: 96, max: 168 }, 72],
|
|
86
|
+
areas: WIDE_AREAS,
|
|
87
|
+
tapMin: 32,
|
|
88
|
+
textMin: 0,
|
|
89
|
+
pad: 12,
|
|
90
|
+
modalMargin: 24,
|
|
91
|
+
units: 'frame',
|
|
92
|
+
},
|
|
93
|
+
tablet: {
|
|
94
|
+
columns: [
|
|
95
|
+
{ frac: 0.16, min: 160, max: 280 },
|
|
96
|
+
'fill',
|
|
97
|
+
{ frac: 0.2, min: 200, max: 300 },
|
|
98
|
+
{ frac: 0.16, min: 160, max: 280 },
|
|
99
|
+
],
|
|
100
|
+
rows: [68, 'fill', { frac: 0.16, min: 104, max: 168 }, 76],
|
|
101
|
+
areas: WIDE_AREAS,
|
|
102
|
+
tapMin: 44,
|
|
103
|
+
textMin: 0,
|
|
104
|
+
pad: 12,
|
|
105
|
+
modalMargin: 20,
|
|
106
|
+
units: 'frame',
|
|
107
|
+
},
|
|
108
|
+
'phone-landscape': {
|
|
109
|
+
columns: [56, 'fill', { frac: 0.24, min: 140, max: 200 }, 56],
|
|
110
|
+
rows: [52, 'fill', { frac: 0.24, min: 64, max: 96 }, 52],
|
|
111
|
+
areas: [
|
|
112
|
+
'left top top right',
|
|
113
|
+
'left fill cardLane right',
|
|
114
|
+
'left captionLane cardLane right',
|
|
115
|
+
'left bottom bottom right',
|
|
116
|
+
],
|
|
117
|
+
tapMin: 44,
|
|
118
|
+
textMin: 12,
|
|
119
|
+
pad: 4,
|
|
120
|
+
modalMargin: 8,
|
|
121
|
+
units: 'css',
|
|
122
|
+
},
|
|
123
|
+
'phone-portrait': {
|
|
124
|
+
columns: [56, 'fill', 56],
|
|
125
|
+
rows: [56, 'fill', { frac: 0.16, min: 96, max: 140 }, { frac: 0.14, min: 80, max: 120 }, 60],
|
|
126
|
+
areas: [
|
|
127
|
+
'top top top',
|
|
128
|
+
'left fill right',
|
|
129
|
+
'cardLane cardLane cardLane',
|
|
130
|
+
'captionLane captionLane captionLane',
|
|
131
|
+
'bottom bottom bottom',
|
|
132
|
+
],
|
|
133
|
+
tapMin: 44,
|
|
134
|
+
textMin: 12,
|
|
135
|
+
pad: 4,
|
|
136
|
+
modalMargin: 8,
|
|
137
|
+
units: 'css',
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
/** The spec for `ff` with `spec.forms[ff]` merged over the default. */
|
|
141
|
+
export function formSpec(ff, spec = {}) {
|
|
142
|
+
const form = { ...defaultForms[ff], ...spec.forms?.[ff] };
|
|
143
|
+
if (spec.bars)
|
|
144
|
+
form.bars = { ...spec.bars, ...form.bars };
|
|
145
|
+
return form;
|
|
146
|
+
}
|
|
147
|
+
/** Parses `areas` into a cell grid; throws on ragged rows, unknown names or non-rectangles. */
|
|
148
|
+
export function parseAreas(areas) {
|
|
149
|
+
const cells = areas.map((r) => r.trim().split(/\s+/));
|
|
150
|
+
const w = cells[0]?.length ?? 0;
|
|
151
|
+
if (!w || cells.some((r) => r.length !== w))
|
|
152
|
+
throw new Error('layout: ragged areas');
|
|
153
|
+
const boxes = new Map();
|
|
154
|
+
cells.forEach((row, r) => {
|
|
155
|
+
row.forEach((name, c) => {
|
|
156
|
+
if (name === '.')
|
|
157
|
+
return;
|
|
158
|
+
if (!REGIONS.includes(name) || name === 'modal') {
|
|
159
|
+
throw new Error(`layout: unknown grid area "${name}"`);
|
|
160
|
+
}
|
|
161
|
+
const b = boxes.get(name);
|
|
162
|
+
if (!b)
|
|
163
|
+
boxes.set(name, { r0: r, r1: r, c0: c, c1: c, n: 1 });
|
|
164
|
+
else {
|
|
165
|
+
b.r0 = Math.min(b.r0, r);
|
|
166
|
+
b.r1 = Math.max(b.r1, r);
|
|
167
|
+
b.c0 = Math.min(b.c0, c);
|
|
168
|
+
b.c1 = Math.max(b.c1, c);
|
|
169
|
+
b.n++;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
for (const [name, b] of boxes) {
|
|
174
|
+
if ((b.r1 - b.r0 + 1) * (b.c1 - b.c0 + 1) !== b.n) {
|
|
175
|
+
throw new Error(`layout: area "${name}" is not a rectangle`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return cells;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Resolves track sizes on one axis. `collapsed[i]` forces track i to 0. Fixed and fractional
|
|
182
|
+
* tracks take their size; when they overflow, fractional tracks shrink toward `min`, then the
|
|
183
|
+
* fill tracks get what is left (at least 0), shared equally.
|
|
184
|
+
*/
|
|
185
|
+
export function resolveTracks(tracks, length, collapsed = []) {
|
|
186
|
+
const size = tracks.map((t, i) => {
|
|
187
|
+
if (collapsed[i] || t === 'fill')
|
|
188
|
+
return 0;
|
|
189
|
+
if (typeof t === 'number')
|
|
190
|
+
return Math.max(0, t);
|
|
191
|
+
const v = t.frac * length;
|
|
192
|
+
return Math.max(t.min ?? 0, Math.min(t.max ?? Number.POSITIVE_INFINITY, v));
|
|
193
|
+
});
|
|
194
|
+
let over = size.reduce((a, b) => a + b, 0) - length;
|
|
195
|
+
if (over > 0) {
|
|
196
|
+
const slack = tracks.map((t, i) => typeof t === 'object' && !collapsed[i] ? Math.max(0, size[i] - (t.min ?? 0)) : 0);
|
|
197
|
+
const total = slack.reduce((a, b) => a + b, 0);
|
|
198
|
+
if (total > 0) {
|
|
199
|
+
const k = Math.min(1, over / total);
|
|
200
|
+
for (let i = 0; i < size.length; i++)
|
|
201
|
+
size[i] = size[i] - slack[i] * k;
|
|
202
|
+
over -= Math.min(over, total);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const fills = tracks.map((t, i) => t === 'fill' && !collapsed[i]);
|
|
206
|
+
const nFill = fills.filter(Boolean).length;
|
|
207
|
+
const left = Math.max(0, length - size.reduce((a, b) => a + b, 0));
|
|
208
|
+
return size.map((s, i) => Math.round((fills[i] ? left / nFill : s) * 100) / 100);
|
|
209
|
+
}
|
|
210
|
+
/** The insets a rect at `rect` inside a `viewport` of the given size actually touches. */
|
|
211
|
+
export function effectiveInsets(rect, viewport, insets) {
|
|
212
|
+
return {
|
|
213
|
+
top: Math.max(0, insets.top - rect.y),
|
|
214
|
+
left: Math.max(0, insets.left - rect.x),
|
|
215
|
+
bottom: Math.max(0, insets.bottom - (viewport.height - (rect.y + rect.height))),
|
|
216
|
+
right: Math.max(0, insets.right - (viewport.width - (rect.x + rect.width))),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const ROW_BARS = ['top', 'bottom'];
|
|
220
|
+
const COL_BARS = ['left', 'right'];
|
|
221
|
+
const BAR_NAMES = ['top', 'bottom', 'left', 'right'];
|
|
222
|
+
/**
|
|
223
|
+
* The outer track of `bar` in `cells`: the first row for `top`, the last for `bottom`, the first
|
|
224
|
+
* column for `left`, the last for `right`; -1 when the bar is not in the grid.
|
|
225
|
+
*/
|
|
226
|
+
function outerTrack(cells, bar) {
|
|
227
|
+
const rows = bar === 'top' || bar === 'bottom';
|
|
228
|
+
const n = rows ? cells.length : (cells[0]?.length ?? 0);
|
|
229
|
+
let t = -1;
|
|
230
|
+
for (let i = 0; i < n; i++) {
|
|
231
|
+
const has = rows ? cells[i].includes(bar) : cells.some((row) => row[i] === bar);
|
|
232
|
+
if (has && (t < 0 || bar === 'bottom' || bar === 'right'))
|
|
233
|
+
t = i;
|
|
234
|
+
}
|
|
235
|
+
return t;
|
|
236
|
+
}
|
|
237
|
+
/** The track a `content` bar or rail wants (its content plus `pad` each side, clamped by `max`), or 0. */
|
|
238
|
+
function contentWant(form, content, bar) {
|
|
239
|
+
const b = form.bars?.[bar];
|
|
240
|
+
const v = content?.[bar];
|
|
241
|
+
if (b?.size !== 'content' || v === undefined || !(v > 0))
|
|
242
|
+
return 0;
|
|
243
|
+
return Math.min(v + 2 * form.pad, b.max ?? Number.POSITIVE_INFINITY);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* `cells` with the corner rule applied (ws/ui-9, `BarSize`): where a content bar and a content
|
|
247
|
+
* rail meet, the corner cell goes to the one that wants the larger track. A tie, or a swap that
|
|
248
|
+
* would leave a region not a rectangle, keeps the cell. Pure; returns `cells` itself when nothing
|
|
249
|
+
* moves.
|
|
250
|
+
*/
|
|
251
|
+
function cornerCells(form, cells, content) {
|
|
252
|
+
if (!content || !form.bars)
|
|
253
|
+
return cells;
|
|
254
|
+
let out = cells;
|
|
255
|
+
for (const bar of ROW_BARS) {
|
|
256
|
+
const wb = contentWant(form, content, bar);
|
|
257
|
+
if (!wb)
|
|
258
|
+
continue;
|
|
259
|
+
for (const rail of COL_BARS) {
|
|
260
|
+
const wr = contentWant(form, content, rail);
|
|
261
|
+
if (!wr || wr === wb)
|
|
262
|
+
continue;
|
|
263
|
+
const r = outerTrack(out, bar);
|
|
264
|
+
const c = outerTrack(out, rail);
|
|
265
|
+
if (r < 0 || c < 0)
|
|
266
|
+
continue;
|
|
267
|
+
const owner = out[r][c];
|
|
268
|
+
const winner = wb > wr ? bar : rail;
|
|
269
|
+
if ((owner !== bar && owner !== rail) || owner === winner)
|
|
270
|
+
continue;
|
|
271
|
+
const next = out.map((row) => [...row]);
|
|
272
|
+
next[r][c] = winner;
|
|
273
|
+
if (!next.some((row) => row.includes(owner)))
|
|
274
|
+
continue;
|
|
275
|
+
try {
|
|
276
|
+
parseAreas(next.map((row) => row.join(' ')));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
continue; // not a rectangle any more: keep the form's cell
|
|
280
|
+
}
|
|
281
|
+
out = next;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The tracks on one axis of `form` with each `content` bar's (rows: `top`, `bottom`; ws/ui-8) or
|
|
288
|
+
* rail's (columns: `left`, `right`; ws/ui-9) outer track grown to `contentWant`, never below the
|
|
289
|
+
* track's own size. Pure; returns the form's own tracks when nothing grows.
|
|
290
|
+
*/
|
|
291
|
+
function contentTracks(form, cells, content, axis) {
|
|
292
|
+
const base = form[axis];
|
|
293
|
+
if (!content || !form.bars)
|
|
294
|
+
return base;
|
|
295
|
+
let tracks = base;
|
|
296
|
+
for (const bar of axis === 'rows' ? ROW_BARS : COL_BARS) {
|
|
297
|
+
const want = contentWant(form, content, bar);
|
|
298
|
+
if (!want)
|
|
299
|
+
continue;
|
|
300
|
+
const i = outerTrack(cells, bar);
|
|
301
|
+
const t = tracks[i];
|
|
302
|
+
if (t === undefined || t === 'fill')
|
|
303
|
+
continue;
|
|
304
|
+
let next;
|
|
305
|
+
if (typeof t === 'number') {
|
|
306
|
+
if (want <= t)
|
|
307
|
+
continue;
|
|
308
|
+
next = want;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
if (want <= (t.min ?? 0))
|
|
312
|
+
continue;
|
|
313
|
+
next = { ...t, min: want, max: Math.max(t.max ?? Number.POSITIVE_INFINITY, want) };
|
|
314
|
+
}
|
|
315
|
+
if (tracks === base)
|
|
316
|
+
tracks = [...base];
|
|
317
|
+
tracks[i] = next;
|
|
318
|
+
}
|
|
319
|
+
return tracks;
|
|
320
|
+
}
|
|
321
|
+
/** The `grid-template-areas` rows `resolveLayout` lays out for `content` (the corner rule applied). */
|
|
322
|
+
function resolvedAreas(form, content) {
|
|
323
|
+
const cells = parseAreas(form.areas);
|
|
324
|
+
const out = cornerCells(form, cells, content);
|
|
325
|
+
return out === cells ? form.areas : out.map((row) => row.join(' '));
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Lays out one form at one size. `occupied` names the grid regions that hold widgets; with
|
|
329
|
+
* `collapseEmpty`, a track collapses when every region in it is empty or also spans another
|
|
330
|
+
* track on that axis (so a full-width bar never holds a side rail open). `fill` is never empty.
|
|
331
|
+
* `content` is the tallest widget per bar (`top`, `bottom`) and the widest per rail (`left`,
|
|
332
|
+
* `right`), CSS px, for the form's `content` bars and rails (`BarSize`, with its corner rule).
|
|
333
|
+
* Pure.
|
|
334
|
+
*/
|
|
335
|
+
export function resolveLayout(formFactor, form, width, height, safeArea = NO_INSETS, occupied = null, content = null) {
|
|
336
|
+
const cells = cornerCells(form, parseAreas(form.areas), content);
|
|
337
|
+
if (cells.length !== form.rows.length || cells[0].length !== form.columns.length) {
|
|
338
|
+
throw new Error('layout: rows/columns do not match areas');
|
|
339
|
+
}
|
|
340
|
+
const innerW = Math.max(0, width - safeArea.left - safeArea.right);
|
|
341
|
+
const innerH = Math.max(0, height - safeArea.top - safeArea.bottom);
|
|
342
|
+
const spans = new Map();
|
|
343
|
+
cells.forEach((row, r) => {
|
|
344
|
+
row.forEach((n, c) => {
|
|
345
|
+
if (n === '.')
|
|
346
|
+
return;
|
|
347
|
+
const b = spans.get(n);
|
|
348
|
+
if (!b)
|
|
349
|
+
spans.set(n, { r0: r, r1: r, c0: c, c1: c });
|
|
350
|
+
else {
|
|
351
|
+
b.r1 = Math.max(b.r1, r);
|
|
352
|
+
b.c1 = Math.max(b.c1, c);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
const empty = (n) => n !== 'fill' && occupied !== null && !occupied.has(n);
|
|
357
|
+
const collapseAxis = (count, axis) => Array.from({ length: count }, (_, i) => {
|
|
358
|
+
if (occupied === null)
|
|
359
|
+
return false;
|
|
360
|
+
const names = new Set(axis === 'r' ? cells[i] : cells.map((row) => row[i]));
|
|
361
|
+
for (const n of names) {
|
|
362
|
+
if (n === '.')
|
|
363
|
+
continue;
|
|
364
|
+
const s = spans.get(n);
|
|
365
|
+
const wide = axis === 'r' ? s.r1 > s.r0 : s.c1 > s.c0;
|
|
366
|
+
if (n === 'fill' || (!empty(n) && !wide))
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
});
|
|
371
|
+
const colCollapsed = collapseAxis(form.columns.length, 'c');
|
|
372
|
+
const rowCollapsed = collapseAxis(form.rows.length, 'r');
|
|
373
|
+
// An occupied region never ends up with every track collapsed: reopen its first one.
|
|
374
|
+
for (const [n, s] of spans) {
|
|
375
|
+
if (empty(n))
|
|
376
|
+
continue;
|
|
377
|
+
if (colCollapsed.slice(s.c0, s.c1 + 1).every(Boolean))
|
|
378
|
+
colCollapsed[s.c0] = false;
|
|
379
|
+
if (rowCollapsed.slice(s.r0, s.r1 + 1).every(Boolean))
|
|
380
|
+
rowCollapsed[s.r0] = false;
|
|
381
|
+
}
|
|
382
|
+
const columns = resolveTracks(contentTracks(form, cells, content, 'columns'), innerW, colCollapsed);
|
|
383
|
+
const rows = resolveTracks(contentTracks(form, cells, content, 'rows'), innerH, rowCollapsed);
|
|
384
|
+
const offs = (t, i) => t.slice(0, i).reduce((a, b) => a + b, 0);
|
|
385
|
+
const regions = Object.fromEntries(REGIONS.map((n) => [n, null]));
|
|
386
|
+
for (const [n, s] of spans) {
|
|
387
|
+
const x = safeArea.left + offs(columns, s.c0);
|
|
388
|
+
const y = safeArea.top + offs(rows, s.r0);
|
|
389
|
+
regions[n] = {
|
|
390
|
+
x,
|
|
391
|
+
y,
|
|
392
|
+
width: offs(columns, s.c1 + 1) - offs(columns, s.c0),
|
|
393
|
+
height: offs(rows, s.r1 + 1) - offs(rows, s.r0),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const m = form.modalMargin;
|
|
397
|
+
regions.modal = {
|
|
398
|
+
x: safeArea.left + m,
|
|
399
|
+
y: safeArea.top + m,
|
|
400
|
+
width: Math.max(0, innerW - 2 * m),
|
|
401
|
+
height: Math.max(0, innerH - 2 * m),
|
|
402
|
+
};
|
|
403
|
+
const freeRect = regions.fill ?? {
|
|
404
|
+
x: safeArea.left,
|
|
405
|
+
y: safeArea.top,
|
|
406
|
+
width: innerW,
|
|
407
|
+
height: innerH,
|
|
408
|
+
};
|
|
409
|
+
return {
|
|
410
|
+
formFactor,
|
|
411
|
+
units: form.units,
|
|
412
|
+
width,
|
|
413
|
+
height,
|
|
414
|
+
safeArea,
|
|
415
|
+
columns,
|
|
416
|
+
rows,
|
|
417
|
+
regions,
|
|
418
|
+
freeRect: { ...freeRect },
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/** Where `place` puts a widget in `ff`. Pure. */
|
|
422
|
+
export function placementFor(place, ff) {
|
|
423
|
+
return place[ff] ?? place.default ?? 'hidden';
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Pads the layout's regions by a host's safe-area insets (ws/host-mobile): sets
|
|
427
|
+
* `layout.setSafeArea(source.insets())` now and again whenever `source` reports a change, so the
|
|
428
|
+
* `top` region starts below the notch and the `bottom` region ends above the home indicator.
|
|
429
|
+
* `createGame` calls it with `host.safeArea` when the host has one; without it the layout reads
|
|
430
|
+
* `env(safe-area-inset-*)` itself. Returns unsubscribe, which also hands the page's own insets
|
|
431
|
+
* back to the layout.
|
|
432
|
+
*/
|
|
433
|
+
export function followSafeArea(layout, source) {
|
|
434
|
+
const apply = (i) => layout.setSafeArea({ top: i.top, right: i.right, bottom: i.bottom, left: i.left });
|
|
435
|
+
apply(source.insets());
|
|
436
|
+
const off = source.watch(apply);
|
|
437
|
+
return () => {
|
|
438
|
+
off();
|
|
439
|
+
layout.setSafeArea(null);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/** CSS px safe-area insets of `doc`'s viewport (`env(safe-area-inset-*)`, or the `--tc-safe-*` overrides). */
|
|
443
|
+
export function readSafeArea(doc = document) {
|
|
444
|
+
const probe = doc.createElement('div');
|
|
445
|
+
probe.style.cssText =
|
|
446
|
+
'position:fixed;left:0;top:0;width:0;height:0;visibility:hidden;pointer-events:none;' +
|
|
447
|
+
'padding:var(--tc-safe-top,env(safe-area-inset-top,0px)) var(--tc-safe-right,env(safe-area-inset-right,0px)) ' +
|
|
448
|
+
'var(--tc-safe-bottom,env(safe-area-inset-bottom,0px)) var(--tc-safe-left,env(safe-area-inset-left,0px));';
|
|
449
|
+
doc.body.append(probe);
|
|
450
|
+
const cs = doc.defaultView?.getComputedStyle(probe);
|
|
451
|
+
const px = (v) => Number.parseFloat(v ?? '') || 0;
|
|
452
|
+
const out = {
|
|
453
|
+
top: px(cs?.paddingTop),
|
|
454
|
+
right: px(cs?.paddingRight),
|
|
455
|
+
bottom: px(cs?.paddingBottom),
|
|
456
|
+
left: px(cs?.paddingLeft),
|
|
457
|
+
};
|
|
458
|
+
probe.remove();
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
/** Slack before a measured box counts as larger than its region, CSS px (sub-pixel rounding). */
|
|
462
|
+
const OVERFLOW_SLACK = 0.5;
|
|
463
|
+
const r2 = (v) => Math.round(v * 100) / 100;
|
|
464
|
+
const HIDDEN_ATTR = 'data-tc-layout-hidden';
|
|
465
|
+
/**
|
|
466
|
+
* Lays out regions over the overlay (`createOverlay` calls it; read it as `overlay.layout`).
|
|
467
|
+
*
|
|
468
|
+
* The density tokens `--tc-tap-min` and `--tc-text-min` (from the form spec), `data-form-factor`
|
|
469
|
+
* and the form's `units` go on the overlay root, so the base widgets in the panels layer follow
|
|
470
|
+
* them too.
|
|
471
|
+
*/
|
|
472
|
+
export function createLayout(overlay, spec = {}) {
|
|
473
|
+
const root = overlay.root;
|
|
474
|
+
const doc = root.ownerDocument;
|
|
475
|
+
const win = doc.defaultView;
|
|
476
|
+
const classify = spec.classify ?? classifyFormFactor;
|
|
477
|
+
const collapseEmpty = spec.collapseEmpty !== false;
|
|
478
|
+
let insetOverride = spec.safeArea ?? null;
|
|
479
|
+
let formOverride = null;
|
|
480
|
+
const regionEls = Object.fromEntries(REGIONS.map((n) => [
|
|
481
|
+
n,
|
|
482
|
+
h('div', {
|
|
483
|
+
class: 'tc-region',
|
|
484
|
+
dataset: { region: n },
|
|
485
|
+
style: n === 'modal' ? {} : { gridArea: n },
|
|
486
|
+
}),
|
|
487
|
+
]));
|
|
488
|
+
const free = h('div', { class: 'tc-layout-free' });
|
|
489
|
+
const element = h('div', { class: 'tc-layout' }, ...REGIONS.filter((n) => n !== 'modal').map((n) => regionEls[n]));
|
|
490
|
+
overlay.layers.layout.append(element, regionEls.modal, free);
|
|
491
|
+
const entries = [];
|
|
492
|
+
const subs = new Set();
|
|
493
|
+
let seq = 0;
|
|
494
|
+
let state;
|
|
495
|
+
let freeUses = [];
|
|
496
|
+
let overflows = [];
|
|
497
|
+
const measureInsets = () => {
|
|
498
|
+
const vp = {
|
|
499
|
+
width: win?.innerWidth ?? overlay.rect.width,
|
|
500
|
+
height: win?.innerHeight ?? overlay.rect.height,
|
|
501
|
+
};
|
|
502
|
+
const raw = insetOverride ?? readSafeArea(doc);
|
|
503
|
+
// The overlay's rect is relative to its parent; place it in the viewport.
|
|
504
|
+
const pb = root.parentElement?.getBoundingClientRect();
|
|
505
|
+
const r = overlay.rect;
|
|
506
|
+
const inView = {
|
|
507
|
+
x: (pb?.left ?? 0) + r.x,
|
|
508
|
+
y: (pb?.top ?? 0) + r.y,
|
|
509
|
+
width: r.width,
|
|
510
|
+
height: r.height,
|
|
511
|
+
};
|
|
512
|
+
return effectiveInsets(inView, vp, raw);
|
|
513
|
+
};
|
|
514
|
+
const classifyNow = () => {
|
|
515
|
+
const mq = (q) => typeof win?.matchMedia === 'function' && win.matchMedia(q).matches;
|
|
516
|
+
const s = win?.screen;
|
|
517
|
+
return classify({
|
|
518
|
+
width: overlay.rect.width,
|
|
519
|
+
height: overlay.rect.height,
|
|
520
|
+
coarsePointer: mq('(pointer: coarse)'),
|
|
521
|
+
shortSide: s ? Math.min(s.width, s.height) : 0,
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
const apply = () => {
|
|
525
|
+
const ff = classifyNow();
|
|
526
|
+
const form = { ...formSpec(ff, spec), ...formOverride?.[ff] };
|
|
527
|
+
// Which grid regions hold a widget in this form factor.
|
|
528
|
+
const occupied = new Set();
|
|
529
|
+
for (const e of entries) {
|
|
530
|
+
const p = placementFor(e.place, ff);
|
|
531
|
+
if (typeof p === 'string' && p !== 'hidden')
|
|
532
|
+
occupied.add(p);
|
|
533
|
+
}
|
|
534
|
+
const insets = measureInsets();
|
|
535
|
+
const resolve = (content) => resolveLayout(ff, form, overlay.rect.width, overlay.rect.height, insets, collapseEmpty ? occupied : null, content);
|
|
536
|
+
state = resolve(null);
|
|
537
|
+
writeGrid(ff, form, insets, occupied);
|
|
538
|
+
for (const e of entries)
|
|
539
|
+
e.onForm?.(ff);
|
|
540
|
+
// Widgets. A widget already in its region after the one placed before it stays put
|
|
541
|
+
// (ws/ui-11): re-inserting a node cancels its CSS transitions and drops its focus, so a
|
|
542
|
+
// re-layout moves only the elements whose region or order changed, and tells them (`onMove`).
|
|
543
|
+
freeUses = [];
|
|
544
|
+
const sorted = [...entries].sort((a, b) => a.order - b.order || a.seq - b.seq);
|
|
545
|
+
const freed = [];
|
|
546
|
+
const moved = [];
|
|
547
|
+
const lastIn = new Map();
|
|
548
|
+
for (const e of sorted) {
|
|
549
|
+
const was = e.current;
|
|
550
|
+
let p = placementFor(e.place, ff);
|
|
551
|
+
if (typeof p === 'string' && p !== 'hidden' && !state.regions[p]) {
|
|
552
|
+
console.warn(`[layout] ${e.id}: no "${p}" region in ${ff}; hidden`);
|
|
553
|
+
p = 'hidden';
|
|
554
|
+
}
|
|
555
|
+
e.el.toggleAttribute(HIDDEN_ATTR, p === 'hidden');
|
|
556
|
+
e.el.style.order = e.order ? String(e.order) : '';
|
|
557
|
+
if (typeof p === 'object') {
|
|
558
|
+
if (!e.loggedFree) {
|
|
559
|
+
console.info(`[layout] free placement: ${e.id} (${p.reason})`);
|
|
560
|
+
e.loggedFree = true;
|
|
561
|
+
}
|
|
562
|
+
freeUses.push({ id: e.id, reason: p.reason });
|
|
563
|
+
if (e.caption)
|
|
564
|
+
e.caption.dock(null);
|
|
565
|
+
if (e.el.parentNode !== free)
|
|
566
|
+
free.append(e.el);
|
|
567
|
+
freed.push([e, p]);
|
|
568
|
+
e.current = 'free';
|
|
569
|
+
if (was !== 'free')
|
|
570
|
+
moved.push(e);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (e.current === 'free') {
|
|
574
|
+
for (const k of ['position', 'left', 'top', 'width', 'height'])
|
|
575
|
+
e.el.style[k] = '';
|
|
576
|
+
}
|
|
577
|
+
if (p === 'hidden') {
|
|
578
|
+
if (e.caption)
|
|
579
|
+
e.caption.dock(null);
|
|
580
|
+
e.current = 'hidden';
|
|
581
|
+
if (was !== 'hidden')
|
|
582
|
+
moved.push(e);
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
const r = regionEls[p];
|
|
586
|
+
const prev = lastIn.get(r);
|
|
587
|
+
let move = was !== p;
|
|
588
|
+
if (e.caption)
|
|
589
|
+
e.caption.dock(r);
|
|
590
|
+
else if (e.el.parentNode !== r ||
|
|
591
|
+
(prev && !(prev.compareDocumentPosition(e.el) & Node.DOCUMENT_POSITION_FOLLOWING))) {
|
|
592
|
+
if (prev)
|
|
593
|
+
prev.after(e.el);
|
|
594
|
+
else
|
|
595
|
+
r.prepend(e.el);
|
|
596
|
+
move = true;
|
|
597
|
+
}
|
|
598
|
+
lastIn.set(r, e.caption ? e.caption.element : e.el);
|
|
599
|
+
e.current = p;
|
|
600
|
+
if (move)
|
|
601
|
+
moved.push(e);
|
|
602
|
+
}
|
|
603
|
+
// Content bars and rails (ws/ui-8, ws/ui-9): one read of the bar widgets' heights and the
|
|
604
|
+
// rail widgets' widths, then the grid re-flows once if a track or a corner moved. A content
|
|
605
|
+
// rail is measured at `max-content` (ws/ui-10): for the read it is as wide as its widest
|
|
606
|
+
// widget wants and does not stretch them, so a widget reports its intrinsic width rather than
|
|
607
|
+
// the rail's current one, and the rail follows its content both ways.
|
|
608
|
+
const bars = form.bars;
|
|
609
|
+
if (bars && BAR_NAMES.some((b) => bars[b]?.size === 'content')) {
|
|
610
|
+
const content = {};
|
|
611
|
+
const left = bars.left?.size === 'content' ? intrinsic(regionEls.left) : null;
|
|
612
|
+
const right = bars.right?.size === 'content' ? intrinsic(regionEls.right) : null;
|
|
613
|
+
for (const e of sorted) {
|
|
614
|
+
const p = e.current;
|
|
615
|
+
if (p !== 'top' && p !== 'bottom' && p !== 'left' && p !== 'right')
|
|
616
|
+
continue;
|
|
617
|
+
if (bars[p]?.size !== 'content')
|
|
618
|
+
continue;
|
|
619
|
+
const box = e.el.getBoundingClientRect?.();
|
|
620
|
+
const v = (p === 'left' || p === 'right' ? box?.width : box?.height) ?? 0;
|
|
621
|
+
content[p] = Math.max(content[p] ?? 0, v);
|
|
622
|
+
}
|
|
623
|
+
left?.();
|
|
624
|
+
right?.();
|
|
625
|
+
const next = resolve(content);
|
|
626
|
+
const areas = resolvedAreas(form, content);
|
|
627
|
+
if (areas !== form.areas ||
|
|
628
|
+
next.rows.some((r, i) => r !== state.rows[i]) ||
|
|
629
|
+
next.columns.some((c, i) => c !== state.columns[i])) {
|
|
630
|
+
state = next;
|
|
631
|
+
writeGrid(ff, form, insets, occupied, areas);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
for (const [e, p] of freed) {
|
|
635
|
+
const r = typeof p.rect === 'function' ? p.rect(state) : p.rect;
|
|
636
|
+
Object.assign(e.el.style, {
|
|
637
|
+
position: 'absolute',
|
|
638
|
+
left: `${r.x}px`,
|
|
639
|
+
top: `${r.y}px`,
|
|
640
|
+
width: `${r.width}px`,
|
|
641
|
+
height: `${r.height}px`,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
// Overflow: one read pass after the writes above (one reflow per re-layout, none per frame).
|
|
645
|
+
overflows = [];
|
|
646
|
+
for (const e of sorted) {
|
|
647
|
+
const p = e.current;
|
|
648
|
+
if (p === 'free' || p === 'hidden')
|
|
649
|
+
continue;
|
|
650
|
+
const r = state.regions[p];
|
|
651
|
+
const box = e.el.getBoundingClientRect?.();
|
|
652
|
+
if (!r || !box)
|
|
653
|
+
continue;
|
|
654
|
+
const rw = r2(Math.max(0, r.width - 2 * form.pad));
|
|
655
|
+
const rh = r2(Math.max(0, r.height - 2 * form.pad));
|
|
656
|
+
const w = r2(box.width);
|
|
657
|
+
const bh = r2(box.height);
|
|
658
|
+
if (w <= rw + OVERFLOW_SLACK && bh <= rh + OVERFLOW_SLACK)
|
|
659
|
+
continue;
|
|
660
|
+
overflows.push({ id: e.id, region: p, w, h: bh, rw, rh });
|
|
661
|
+
if (!e.warnedOverflow) {
|
|
662
|
+
e.warnedOverflow = true;
|
|
663
|
+
console.warn(`[layout] ${e.id} overflows its "${p}" region in ${ff}: ${w}x${bh} px in ${rw}x${rh} px`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
for (const e of moved)
|
|
667
|
+
e.onMove?.(e.current);
|
|
668
|
+
for (const f of subs)
|
|
669
|
+
f(state);
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* Lay a rail out at `max-content` for a measure (ws/ui-10): the region as wide as its widest
|
|
673
|
+
* widget wants, its widgets at their own width (`align-items: flex-start` instead of the rail's
|
|
674
|
+
* stretch). Returns the restore. Inline values a game set on the region come back.
|
|
675
|
+
*/
|
|
676
|
+
const intrinsic = (el) => {
|
|
677
|
+
const s = el.style;
|
|
678
|
+
const width = s.width;
|
|
679
|
+
const align = s.alignItems;
|
|
680
|
+
s.width = 'max-content';
|
|
681
|
+
s.alignItems = 'flex-start';
|
|
682
|
+
return () => {
|
|
683
|
+
s.width = width;
|
|
684
|
+
s.alignItems = align;
|
|
685
|
+
};
|
|
686
|
+
};
|
|
687
|
+
/** Tokens, grid tracks and region visibility for the current `state`. */
|
|
688
|
+
const writeGrid = (ff, form, insets, occupied,
|
|
689
|
+
/** The grid areas `state` was laid out with (the corner rule may move a cell; ws/ui-9). */
|
|
690
|
+
areas = form.areas) => {
|
|
691
|
+
root.dataset.formFactor = ff;
|
|
692
|
+
root.style.setProperty('--tc-tap-min', `${form.tapMin}px`);
|
|
693
|
+
root.style.setProperty('--tc-text-min', `${form.textMin}px`);
|
|
694
|
+
root.style.setProperty('--tc-region-pad', `${form.pad}px`);
|
|
695
|
+
for (const side of ['top', 'right', 'bottom', 'left']) {
|
|
696
|
+
root.style.setProperty(`--tc-safe-${side}-px`, `${insets[side]}px`);
|
|
697
|
+
}
|
|
698
|
+
const m = state.regions.modal;
|
|
699
|
+
root.style.setProperty('--tc-modal-top', `${m.y}px`);
|
|
700
|
+
root.style.setProperty('--tc-modal-left', `${m.x}px`);
|
|
701
|
+
root.style.setProperty('--tc-modal-right', `${state.width - m.x - m.width}px`);
|
|
702
|
+
root.style.setProperty('--tc-modal-bottom', `${state.height - m.y - m.height}px`);
|
|
703
|
+
Object.assign(element.style, {
|
|
704
|
+
padding: `${insets.top}px ${insets.right}px ${insets.bottom}px ${insets.left}px`,
|
|
705
|
+
gridTemplateColumns: state.columns.map((c) => `${c}px`).join(' '),
|
|
706
|
+
gridTemplateRows: state.rows.map((r) => `${r}px`).join(' '),
|
|
707
|
+
gridTemplateAreas: areas.map((a) => `"${a.trim().split(/\s+/).join(' ')}"`).join(' '),
|
|
708
|
+
});
|
|
709
|
+
for (const n of REGIONS) {
|
|
710
|
+
const r = state.regions[n];
|
|
711
|
+
const el = regionEls[n];
|
|
712
|
+
const absent = !r || r.width <= 0 || r.height <= 0;
|
|
713
|
+
el.toggleAttribute('data-empty', absent || (n !== 'fill' && !occupied.has(n)));
|
|
714
|
+
// A content bar or rail (ws/ui-11): widgets that otherwise shrink to fit their region
|
|
715
|
+
// (the minimap) hold their own size here, and the track grows to them instead.
|
|
716
|
+
el.toggleAttribute('data-content', form.bars?.[n]?.size === 'content');
|
|
717
|
+
el.hidden = absent;
|
|
718
|
+
if (n === 'modal' && r) {
|
|
719
|
+
Object.assign(el.style, {
|
|
720
|
+
left: `${r.x}px`,
|
|
721
|
+
top: `${r.y}px`,
|
|
722
|
+
width: `${r.width}px`,
|
|
723
|
+
height: `${r.height}px`,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const unwatch = overlay.onResize(() => apply());
|
|
729
|
+
const layout = {
|
|
730
|
+
element,
|
|
731
|
+
get formFactor() {
|
|
732
|
+
return state.formFactor;
|
|
733
|
+
},
|
|
734
|
+
get state() {
|
|
735
|
+
return state;
|
|
736
|
+
},
|
|
737
|
+
get freeRect() {
|
|
738
|
+
return state.freeRect;
|
|
739
|
+
},
|
|
740
|
+
get safeArea() {
|
|
741
|
+
return state.safeArea;
|
|
742
|
+
},
|
|
743
|
+
get freeUses() {
|
|
744
|
+
return freeUses;
|
|
745
|
+
},
|
|
746
|
+
region(name) {
|
|
747
|
+
return regionEls[name];
|
|
748
|
+
},
|
|
749
|
+
overflows: () => overflows,
|
|
750
|
+
add(el, opts) {
|
|
751
|
+
const e = {
|
|
752
|
+
id: opts.id ?? (el.id || `widget-${seq + 1}`),
|
|
753
|
+
el,
|
|
754
|
+
place: { ...opts.place },
|
|
755
|
+
order: opts.order ?? 0,
|
|
756
|
+
seq: seq++,
|
|
757
|
+
current: 'hidden',
|
|
758
|
+
home: el.parentNode ? { parent: el.parentNode, next: el.nextSibling } : undefined,
|
|
759
|
+
loggedFree: false,
|
|
760
|
+
warnedOverflow: false,
|
|
761
|
+
onForm: opts.onForm,
|
|
762
|
+
onMove: opts.onMove,
|
|
763
|
+
};
|
|
764
|
+
entries.push(e);
|
|
765
|
+
apply();
|
|
766
|
+
return handle(e);
|
|
767
|
+
},
|
|
768
|
+
dockCaption(caption = overlay.caption, place = { default: 'captionLane' }) {
|
|
769
|
+
const e = {
|
|
770
|
+
id: 'caption',
|
|
771
|
+
el: caption.element,
|
|
772
|
+
place: { ...place },
|
|
773
|
+
order: 0,
|
|
774
|
+
seq: seq++,
|
|
775
|
+
current: 'hidden',
|
|
776
|
+
caption,
|
|
777
|
+
loggedFree: false,
|
|
778
|
+
warnedOverflow: false,
|
|
779
|
+
};
|
|
780
|
+
entries.push(e);
|
|
781
|
+
apply();
|
|
782
|
+
return handle(e);
|
|
783
|
+
},
|
|
784
|
+
setSafeArea(insets) {
|
|
785
|
+
insetOverride = insets ? { ...insets } : null;
|
|
786
|
+
apply();
|
|
787
|
+
},
|
|
788
|
+
setForms(forms) {
|
|
789
|
+
formOverride = forms ? { ...forms } : null;
|
|
790
|
+
apply();
|
|
791
|
+
},
|
|
792
|
+
refresh: () => apply(),
|
|
793
|
+
onChange(fn) {
|
|
794
|
+
subs.add(fn);
|
|
795
|
+
return () => subs.delete(fn);
|
|
796
|
+
},
|
|
797
|
+
destroy() {
|
|
798
|
+
unwatch();
|
|
799
|
+
subs.clear();
|
|
800
|
+
for (const e of entries.splice(0)) {
|
|
801
|
+
if (e.caption)
|
|
802
|
+
e.caption.dock(null);
|
|
803
|
+
else if (e.home)
|
|
804
|
+
goHome(e);
|
|
805
|
+
e.el.removeAttribute(HIDDEN_ATTR);
|
|
806
|
+
}
|
|
807
|
+
element.remove();
|
|
808
|
+
regionEls.modal.remove();
|
|
809
|
+
free.remove();
|
|
810
|
+
delete root.dataset.formFactor;
|
|
811
|
+
},
|
|
812
|
+
};
|
|
813
|
+
/** Back where it was before `add`, or out of the document if it had no parent then. */
|
|
814
|
+
function goHome(e) {
|
|
815
|
+
const home = e.home;
|
|
816
|
+
if (!home?.parent) {
|
|
817
|
+
e.el.remove();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const next = home.next && home.next.parentNode === home.parent ? home.next : null;
|
|
821
|
+
home.parent.insertBefore(e.el, next);
|
|
822
|
+
}
|
|
823
|
+
function handle(e) {
|
|
824
|
+
return {
|
|
825
|
+
element: e.el,
|
|
826
|
+
get region() {
|
|
827
|
+
return e.current;
|
|
828
|
+
},
|
|
829
|
+
setPlace(place) {
|
|
830
|
+
e.place = { ...place };
|
|
831
|
+
e.loggedFree = false;
|
|
832
|
+
e.warnedOverflow = false;
|
|
833
|
+
apply();
|
|
834
|
+
},
|
|
835
|
+
remove() {
|
|
836
|
+
const i = entries.indexOf(e);
|
|
837
|
+
if (i < 0)
|
|
838
|
+
return;
|
|
839
|
+
entries.splice(i, 1);
|
|
840
|
+
if (e.caption)
|
|
841
|
+
e.caption.dock(null);
|
|
842
|
+
else
|
|
843
|
+
goHome(e);
|
|
844
|
+
e.el.removeAttribute(HIDDEN_ATTR);
|
|
845
|
+
e.el.style.order = '';
|
|
846
|
+
if (e.current === 'free') {
|
|
847
|
+
for (const k of ['position', 'left', 'top', 'width', 'height'])
|
|
848
|
+
e.el.style[k] = '';
|
|
849
|
+
}
|
|
850
|
+
apply();
|
|
851
|
+
},
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
apply();
|
|
855
|
+
return layout;
|
|
856
|
+
}
|