@moku-labs/game 0.15.13 → 0.15.16

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.
Files changed (33) hide show
  1. package/dist/app/page.d.mts +1 -1
  2. package/dist/app/page.mjs +1 -1
  3. package/dist/app/system.d.mts +3 -3
  4. package/dist/app.d.mts +3 -3
  5. package/dist/app.mjs +1 -1
  6. package/dist/assets.d.mts +1 -1
  7. package/dist/{commands-aLTGwuyk.d.mts → commands-Ay-OfSPt.d.mts} +5 -5
  8. package/dist/control.d.mts +4 -4
  9. package/dist/{define-DbxBzO-G.d.mts → define-BfWdvNHC.d.mts} +2 -2
  10. package/dist/{headless-DHtavbqk.d.mts → headless-ByFLyHxx.d.mts} +1 -1
  11. package/dist/{index-YUceiUFm.d.mts → index-rLpq23uS.d.mts} +7 -6
  12. package/dist/index.d.mts +4 -4
  13. package/dist/index.mjs +2 -2
  14. package/dist/inspect.d.mts +6 -6
  15. package/dist/inspect.mjs +1 -1
  16. package/dist/jsx-dev-runtime.d.mts +1 -1
  17. package/dist/jsx-runtime.d.mts +1 -1
  18. package/dist/{runtime-B5OD9JTT.d.mts → runtime-DkhhsFss.d.mts} +1 -1
  19. package/dist/{sources-Fbq_PFVH.mjs → sources-24TY22DK.mjs} +1 -1
  20. package/dist/{src-3_HjYegY.mjs → src-AvZjWoPX.mjs} +481 -92
  21. package/dist/testing.d.mts +4 -4
  22. package/dist/testing.mjs +1 -1
  23. package/dist/{types-CuEK0Vam.d.mts → types-DOldmhIw.d.mts} +21 -5
  24. package/dist/{types-3bEK86Z1.d.mts → types-De1AZYym.d.mts} +2 -53
  25. package/dist/{types-xmH1X8qd.d.mts → types-DklpXQQk.d.mts} +132 -15
  26. package/dist/{types-Dm4tM5nH.d.mts → types-WqPbnC6B.d.mts} +4 -4
  27. package/dist/{types-_a6-ALzz.d.mts → types-qsE-I59e.d.mts} +2 -2
  28. package/dist/{visual-DExHHx-a.d.mts → visual-B1mq5n-9.d.mts} +6 -6
  29. package/dist/{visual-CbGKzlj0.mjs → visual-BStMZRVc.mjs} +28 -10
  30. package/dist/visual.d.mts +1 -1
  31. package/dist/visual.mjs +1 -1
  32. package/llms.txt +1 -1
  33. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
3
3
  import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze$1, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
4
4
  import { d as graphHash, f as isRestCheckpoint, h as pushEntry, i as servePaused, l as findNode, m as compact, n as captureFrame, o as collectFlows, p as slotNames, r as serveCaptures, s as describeGraph, t as cancelCaptures, u as framePath } from "./capture-D7XvoHdH.mjs";
5
- import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-CbGKzlj0.mjs";
5
+ import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-BStMZRVc.mjs";
6
6
  import { a as isDev } from "./dev-BNndV-hU.mjs";
7
7
  import { n as isComponentDefinition } from "./component-DGg5DqKK.mjs";
8
8
  import { n as flatten } from "./flatten-nUX9PpGo.mjs";
@@ -8798,8 +8798,11 @@ function schemaOf(types) {
8798
8798
  //#region src/plugins/world/ecs/systems.ts
8799
8799
  /** The one phase that runs in every mode, because `renderer.sync` lives in it. */
8800
8800
  const ALWAYS = "sync";
8801
- /** The system that hands the world to Pixi: the last of its phase, so a frame draws what every system wrote. */
8802
- const DRAWS = "renderer.sync";
8801
+ /**
8802
+ * The name of the system that hands the world to Pixi: the last of its phase, so a frame draws what every system
8803
+ * wrote. The renderer registers its system under this name.
8804
+ */
8805
+ const DRAWS_SYSTEM = "renderer.sync";
8803
8806
  /** Rows a timing table holds before it first grows. */
8804
8807
  const TIMING_ROWS = 16;
8805
8808
  /** The four phases, in frame order. */
@@ -8898,8 +8901,9 @@ function storeSlots(count) {
8898
8901
  return slots;
8899
8902
  }
8900
8903
  /**
8901
- * Registers a system at the end of its phase list, before `renderer.sync` in phase `sync`, and returns the remover. Its query and its
8902
- * `scenes` are read here, once: a later change to the definition is not seen. A dev build checks
8904
+ * Registers a system at the end of its phase list and returns the remover. In phase `sync` it goes
8905
+ * before `renderer.sync`, which stays last. Its query and its `scenes` are read here, once: a
8906
+ * later change to the definition is not seen. A dev build checks
8903
8907
  * the `scenes` against the scenes the features declare, once the world started.
8904
8908
  *
8905
8909
  * @param ctx - Domain context of the world plugin.
@@ -8928,7 +8932,7 @@ function registerSystem(ctx, definition) {
8928
8932
  walk: plan.names.length === 0 ? void 0 : new SystemWalk(ctx, plan)
8929
8933
  };
8930
8934
  const list = state.systems[definition.phase];
8931
- const draws = list.findIndex((known) => known.definition.name === DRAWS);
8935
+ const draws = list.findIndex((known) => known.definition.name === DRAWS_SYSTEM);
8932
8936
  if (draws === -1) list.push(entry);
8933
8937
  else list.splice(draws, 0, entry);
8934
8938
  if (state.timing !== void 0) giveRow(state, state.timing, definition.name);
@@ -29055,8 +29059,12 @@ function watchOverlays(sctx) {
29055
29059
  }
29056
29060
  //#endregion
29057
29061
  //#region src/plugins/renderer/sync/system.ts
29058
- /** The name the system is registered under; it shows up in a world error message. */
29059
- const SYNC_SYSTEM_NAME = "renderer.sync";
29062
+ /**
29063
+ * @file renderer/sync — the one world system the renderer registers. It runs in phase `"sync"`,
29064
+ * which `world` runs in every mode, so a fast walk still sets the picture.
29065
+ */
29066
+ /** The name the system is registered under, which `world` keeps last in phase `sync`; it shows up in a world error message. */
29067
+ const SYNC_SYSTEM_NAME = DRAWS_SYSTEM;
29060
29068
  /**
29061
29069
  * Wraps the pass in a world system definition. The system queries nothing: all the work is
29062
29070
  * driven by the change sets of the frame.
@@ -34667,7 +34675,7 @@ function pickVictim(state) {
34667
34675
  const entry = state.manifest.bundles[name];
34668
34676
  if (record.status !== "loaded" || entry === void 0) continue;
34669
34677
  if (state.pinned.has(name) || isPermanent(entry.tier)) continue;
34670
- if (state.sceneNamed.includes(name)) continue;
34678
+ if (state.sceneNamed.includes(name) || state.sceneLeaving.includes(name)) continue;
34671
34679
  if (state.queue?.bundles.includes(name) === true) continue;
34672
34680
  if (record.lastUsed >= smallest) continue;
34673
34681
  smallest = record.lastUsed;
@@ -34822,8 +34830,9 @@ function featuresOnStack(ctx) {
34822
34830
  }
34823
34831
  /**
34824
34832
  * Tells whether a bundle must stay where the game stands now: it is pinned (a bundle of the node
34825
- * entered last), the mounted scene names it (the last list `sceneBundles` received), its feature
34826
- * is in `onStack`, or its tier is permanent.
34833
+ * entered last), the mounted scene names it (the last list `sceneBundles` received), a leaving
34834
+ * scene named it and its elements still play their exit, its feature is in `onStack`, or its tier
34835
+ * is permanent.
34827
34836
  *
34828
34837
  * @param ctx - Domain context of the plugin.
34829
34838
  * @param name - Name of the bundle.
@@ -34833,25 +34842,66 @@ function featuresOnStack(ctx) {
34833
34842
  */
34834
34843
  function isHeld(ctx, name, entry, onStack) {
34835
34844
  const state = ctx.state;
34836
- return state.pinned.has(name) || state.sceneNamed.includes(name) || onStack.has(entry.feature) || isPermanent(entry.tier);
34845
+ return state.pinned.has(name) || state.sceneNamed.includes(name) || state.sceneLeaving.includes(name) || onStack.has(entry.feature) || isPermanent(entry.tier);
34837
34846
  }
34838
34847
  /**
34839
- * Unloads every loaded `"leave"` bundle nobody holds any more, with the reason `"leave"`. A
34840
- * running load is never aborted by it, and a `"budget"` bundle is never touched. Headless it does
34841
- * nothing.
34848
+ * Unloads every loaded `"leave"` bundle nobody holds any more, with the reason `"leave"`; a
34849
+ * leaving scene holds its bundles only while anything carries `Exiting`. A running load is never
34850
+ * aborted by it, and a `"budget"` bundle is never touched. Headless it does nothing.
34842
34851
  *
34843
34852
  * @param ctx - Domain context of the plugin.
34844
34853
  */
34845
34854
  function releaseLeftBundles(ctx) {
34846
34855
  const state = ctx.state;
34847
34856
  if (state.io === void 0) return;
34857
+ if (state.sceneLeaving.length > 0 && state.exiting === 0) state.sceneLeaving = [];
34848
34858
  const onStack = featuresOnStack(ctx);
34849
- for (const [name, record] of state.records) {
34850
- const entry = state.manifest.bundles[name];
34851
- if (record.status !== "loaded" || entry?.release !== "leave") continue;
34852
- if (isHeld(ctx, name, entry, onStack)) continue;
34853
- unloadBundle(ctx, name, "leave");
34854
- }
34859
+ for (const name of state.records.keys()) unloadIfLeft(ctx, name, onStack);
34860
+ }
34861
+ /**
34862
+ * Takes the list the mounted scene names now. A bundle the list named before and names no more
34863
+ * stays held as a leaving scene's, until nothing carries `Exiting` (F51); a name the list takes
34864
+ * again is the mounted scene's once more.
34865
+ *
34866
+ * @param state - The plugin state.
34867
+ * @param named - The bundles the mounted scene names now.
34868
+ */
34869
+ function nameSceneBundles(state, named) {
34870
+ const leaving = state.sceneLeaving.filter((name) => !named.includes(name));
34871
+ for (const name of state.sceneNamed) if (!named.includes(name) && !leaving.includes(name)) leaving.push(name);
34872
+ state.sceneLeaving = leaving;
34873
+ state.sceneNamed = [...named];
34874
+ }
34875
+ /**
34876
+ * Lets the bundles a leaving scene named go once nothing carries `Exiting` any more: each loaded
34877
+ * `"leave"` bundle among them that nothing else holds is unloaded with the reason `"leave"`, at
34878
+ * once, not at the next rest. A bundle of another release stays, as it would have. The world calls
34879
+ * it through `onRemoved(Exiting)`, so the check costs a length read while no scene leaves, and
34880
+ * a count read while one does.
34881
+ *
34882
+ * @param ctx - Domain context of the plugin.
34883
+ */
34884
+ function releaseSceneLeft(ctx) {
34885
+ const state = ctx.state;
34886
+ if (state.sceneLeaving.length === 0 || state.exiting > 0) return;
34887
+ const names = state.sceneLeaving;
34888
+ state.sceneLeaving = [];
34889
+ if (state.io === void 0) return;
34890
+ const onStack = featuresOnStack(ctx);
34891
+ for (const name of names) unloadIfLeft(ctx, name, onStack);
34892
+ }
34893
+ /**
34894
+ * Unloads one bundle with the reason `"leave"` when it is a loaded `"leave"` bundle nobody holds.
34895
+ *
34896
+ * @param ctx - Domain context of the plugin.
34897
+ * @param name - Name of the bundle.
34898
+ * @param onStack - The features on the stack, from `featuresOnStack`.
34899
+ */
34900
+ function unloadIfLeft(ctx, name, onStack) {
34901
+ const entry = ctx.state.manifest.bundles[name];
34902
+ if (ctx.state.records.get(name)?.status !== "loaded" || entry?.release !== "leave") return;
34903
+ if (isHeld(ctx, name, entry, onStack)) return;
34904
+ unloadBundle(ctx, name, "leave");
34855
34905
  }
34856
34906
  //#endregion
34857
34907
  //#region src/plugins/scenes/bundles.ts
@@ -35329,21 +35379,22 @@ function startPreload(ctx) {
35329
35379
  //#endregion
35330
35380
  //#region src/plugins/assets/lifecycle.ts
35331
35381
  /**
35332
- * @file assets plugin — lifecycle: the three registrations of `onInit`, the boot of `onStart`
35382
+ * @file assets plugin — lifecycle: the five registrations of `onInit`, the boot of `onStart`
35333
35383
  * and the teardown that frees every texture and cancels every fetch.
35334
35384
  */
35335
35385
  /**
35336
- * Resolves the dependency APIs `flow`, `renderer`, `time` and `model` with `ctx.require`.
35386
+ * Resolves the dependency APIs `flow`, `renderer`, `time`, `model` and `world` with `ctx.require`.
35337
35387
  *
35338
35388
  * @param ctx - Kernel context of the assets plugin.
35339
- * @returns The four dependency APIs.
35389
+ * @returns The five dependency APIs.
35340
35390
  */
35341
35391
  function resolveDeps$5(ctx) {
35342
35392
  return {
35343
35393
  flow: ctx.require(flowPlugin),
35344
35394
  renderer: ctx.require(rendererPlugin),
35345
35395
  time: ctx.require(timePlugin),
35346
- model: ctx.require(modelPlugin)
35396
+ model: ctx.require(modelPlugin),
35397
+ world: ctx.require(worldPlugin)
35347
35398
  };
35348
35399
  }
35349
35400
  /**
@@ -35443,19 +35494,20 @@ async function enterNode(ctx, node, run) {
35443
35494
  }
35444
35495
  /**
35445
35496
  * Connects the plugin in `onInit`: the `load` stage of `onEnter`, the handler of the `load`
35446
- * effect and the texture provider of `renderer`. The three removers go to the state, because
35447
- * `onStop` has nothing but the state.
35497
+ * effect, the texture provider of `renderer`, and the start and the end of an exit in `world`,
35498
+ * counted so a leaving scene's bundles go when the last exit ends (F51). The five removers go to
35499
+ * the state, because `onStop` has nothing but the state.
35448
35500
  *
35449
35501
  * @param ctx - Kernel context of the assets plugin.
35450
- * @example
35451
- * ```ts
35452
- * connectAssets(ctx);
35453
- * ctx.state.removers.length; // 3
35454
- * ```
35455
35502
  */
35456
35503
  function connectAssets(ctx) {
35457
35504
  const assets = withDeps$6(ctx);
35458
- assets.state.removers.push(assets.deps.flow.onEnter("load", (node, run) => enterNode(assets, node, run)), assets.deps.flow.fx.handle("load", (descriptor, run) => runLoadEffect(assets, descriptor, run), { runInFast: true }), assets.deps.renderer.sync.textures.provide((key) => lookupTexture(assets, key)));
35505
+ assets.state.removers.push(assets.deps.flow.onEnter("load", (node, run) => enterNode(assets, node, run)), assets.deps.flow.fx.handle("load", (descriptor, run) => runLoadEffect(assets, descriptor, run), { runInFast: true }), assets.deps.renderer.sync.textures.provide((key) => lookupTexture(assets, key)), assets.deps.world.ecs.onAdded(Exiting, () => {
35506
+ assets.state.exiting += 1;
35507
+ }), assets.deps.world.ecs.onRemoved(Exiting, () => {
35508
+ assets.state.exiting -= 1;
35509
+ releaseSceneLeft(assets);
35510
+ }));
35459
35511
  }
35460
35512
  /**
35461
35513
  * Picks the I/O seam: the configured one, the browser pair over `renderer.sync.textures` when
@@ -35531,7 +35583,7 @@ async function startAssets(ctx) {
35531
35583
  }
35532
35584
  /**
35533
35585
  * Frees everything in `onStop`: the preload and every running load are aborted, every texture is
35534
- * destroyed, the three registrations are removed and the registries are emptied, the companions
35586
+ * destroyed, the five registrations are removed and the registries are emptied, the companions
35535
35587
  * too. A teardown context carries the state and nothing else, so no other plugin is called here:
35536
35588
  * no companion's `unload` either, its plugin stopped before this one.
35537
35589
  *
@@ -35563,6 +35615,8 @@ function releaseAll(state) {
35563
35615
  state.current = void 0;
35564
35616
  state.sceneId = void 0;
35565
35617
  state.sceneNamed = [];
35618
+ state.sceneLeaving = [];
35619
+ state.exiting = 0;
35566
35620
  state.restPending = false;
35567
35621
  state.stamps = void 0;
35568
35622
  state.swapping = void 0;
@@ -35709,9 +35763,7 @@ function createAssetsApi(ctx) {
35709
35763
  font: (key) => loadedRecord(assets, key)?.fonts.get(key),
35710
35764
  audio: (key) => loadedRecord(assets, key)?.audio.get(key),
35711
35765
  usage: () => usage(assets),
35712
- sceneBundles: (named) => {
35713
- ctx.state.sceneNamed = [...named];
35714
- },
35766
+ sceneBundles: (named) => nameSceneBundles(ctx.state, named),
35715
35767
  has: (bundle) => Object.hasOwn(ctx.state.manifest.bundles, bundle),
35716
35768
  companions: { add: (companion) => addCompanion(ctx.state, companion) }
35717
35769
  };
@@ -36379,6 +36431,8 @@ function createAssetsState() {
36379
36431
  stamps: void 0,
36380
36432
  swapping: void 0,
36381
36433
  sceneNamed: [],
36434
+ sceneLeaving: [],
36435
+ exiting: 0,
36382
36436
  companions: [],
36383
36437
  restPending: false
36384
36438
  };
@@ -36403,7 +36457,8 @@ const assetsPlugin = /*#__PURE__*/ createPlugin$1("assets", {
36403
36457
  flowPlugin,
36404
36458
  rendererPlugin,
36405
36459
  timePlugin,
36406
- modelPlugin
36460
+ modelPlugin,
36461
+ worldPlugin
36407
36462
  ],
36408
36463
  config: {
36409
36464
  manifest: void 0,
@@ -47923,6 +47978,12 @@ const platformPlugin = /*#__PURE__*/ createPlugin$1("platform", {
47923
47978
  const FALLBACK_ADVANCE = .6;
47924
47979
  /** Line height of a style whose font is not loaded, as a share of the style size. */
47925
47980
  const FALLBACK_LINE = 1.2;
47981
+ /**
47982
+ * How much wider than its box a line of a `"box"` style may be. `ui` lays a text out in whole units
47983
+ * and rounds its measure with this tolerance, so a line that adds up to a hair over a whole unit
47984
+ * (ten glyphs of 14.4 are 144.00000000000003) stays on the line the measure kept it on.
47985
+ */
47986
+ const BOX_SLACK = 1e-4;
47926
47987
  /** Splits a run's text into words, runs of spaces and line breaks, separators kept. */
47927
47988
  const TOKENS = /(\n| +)/;
47928
47989
  /**
@@ -48397,13 +48458,33 @@ function emptyKeys() {
48397
48458
  return /* @__PURE__ */ new Set();
48398
48459
  }
48399
48460
  /**
48461
+ * The width a block of a style wraps at: the width of the style, the box for a `"box"` style that
48462
+ * has one, or nothing for one line per `\n` segment.
48463
+ *
48464
+ * @param style - The style of the label.
48465
+ * @param box - The laid-out width of the text element; 0 when it has none.
48466
+ * @returns The wrap width in reference pixels, or `undefined` for no wrapping.
48467
+ * @example
48468
+ * ```ts
48469
+ * wrapOf({ ...bodyStyle, wrap: "box" }, 0); // undefined: no box, one line
48470
+ * ```
48471
+ */
48472
+ function wrapOf(style, box) {
48473
+ if (style.wrap === "none") return void 0;
48474
+ if (style.wrap !== "box") return style.wrap;
48475
+ return box > 0 ? box + BOX_SLACK : void 0;
48476
+ }
48477
+ /**
48400
48478
  * Lays a block of runs out: the lines, the width of the widest one and the total height. The
48401
- * alignment of the style changes nothing here; it moves the lines when they are drawn.
48479
+ * alignment of the style changes nothing here; it moves the lines when they are drawn. A style
48480
+ * with `wrap: "box"` wraps at `box`, the width `ui` laid its text element out at, and is one line
48481
+ * without one; any other style ignores `box`.
48402
48482
  *
48403
48483
  * @param runs - What `parseTags` made of the resolved string.
48404
48484
  * @param style - The style of the label.
48405
48485
  * @param tables - The advance tables that are loaded, by font key.
48406
48486
  * @param options - Where a warning goes.
48487
+ * @param box - The width a `"box"` style wraps at, in reference pixels; 0 for none.
48407
48488
  * @returns The lines and the size of the block in reference pixels.
48408
48489
  * @example
48409
48490
  * ```ts
@@ -48411,13 +48492,14 @@ function emptyKeys() {
48411
48492
  * // { lines: [{ runs: [], width: 0 }], width: 0, height: 38.4 }
48412
48493
  * ```
48413
48494
  */
48414
- function layoutRuns(runs, style, tables, options) {
48495
+ function layoutRuns(runs, style, tables, options, box = 0) {
48415
48496
  const once = onceOnly(options);
48416
- const lines = style.wrap === "none" ? layoutFixed(runs, style, tables, once) : layoutWrapped(runs, {
48497
+ const wrap = wrapOf(style, box);
48498
+ const lines = wrap === void 0 ? layoutFixed(runs, style, tables, once) : layoutWrapped(runs, {
48417
48499
  style,
48418
48500
  tables,
48419
48501
  options: once
48420
- }, style.wrap);
48502
+ }, wrap);
48421
48503
  let width = 0;
48422
48504
  for (const line of lines) width = Math.max(width, line.width);
48423
48505
  return {
@@ -48712,20 +48794,34 @@ function styleOf$1(ctx, name) {
48712
48794
  return ctx.state.styles.get("body") ?? builtInStyles(ctx.config.fonts).body;
48713
48795
  }
48714
48796
  /**
48797
+ * Tells whether a style wraps at the box of its text element. An unknown name is drawn as `body`,
48798
+ * so it answers for `body`. Two map reads, no warning: the frame step asks it.
48799
+ *
48800
+ * @param ctx - Domain context of the text plugin.
48801
+ * @param styleName - The style name a component wrote.
48802
+ * @returns True for a style with `wrap: "box"`.
48803
+ */
48804
+ function wrapsAtBox(ctx, styleName) {
48805
+ return (ctx.state.styles.get(styleName) ?? ctx.state.styles.get("body"))?.wrap === "box";
48806
+ }
48807
+ /**
48715
48808
  * The layout of one resolved string in one style, from the cache or freshly laid out. The cache
48716
- * is what makes `measure` cheap enough for `ui` to ask per invalidation.
48809
+ * is what makes `measure` cheap enough for `ui` to ask per invalidation. A `"box"` style with a
48810
+ * box is cached per width too; any other style ignores the box and keeps one layout.
48717
48811
  *
48718
48812
  * @param ctx - Domain context of the text plugin.
48719
48813
  * @param resolved - The tagged string.
48720
48814
  * @param styleName - The style name.
48815
+ * @param box - The width a `"box"` style wraps at; 0 for one line.
48721
48816
  * @returns The lines and the size of the block.
48722
48817
  */
48723
- function layoutFor(ctx, resolved, styleName) {
48724
- const key = `${styleName}${CACHE_SEPARATOR}${resolved}`;
48818
+ function layoutFor(ctx, resolved, styleName, box = 0) {
48819
+ const width = box > 0 && wrapsAtBox(ctx, styleName) ? box : 0;
48820
+ const key = width > 0 ? `${styleName}${CACHE_SEPARATOR}${width}${CACHE_SEPARATOR}${resolved}` : `${styleName}${CACHE_SEPARATOR}${resolved}`;
48725
48821
  const hit = ctx.state.cache.get(key);
48726
48822
  if (hit !== void 0) return hit;
48727
48823
  const warn = warnFor(ctx);
48728
- const layout = layoutRuns(parseTags(resolved, warn), styleOf$1(ctx, styleName), ctx.state.tables, { warn });
48824
+ const layout = layoutRuns(parseTags(resolved, warn), styleOf$1(ctx, styleName), ctx.state.tables, { warn }, width);
48729
48825
  if (ctx.state.cache.size >= CACHE_LIMIT) {
48730
48826
  const oldest = ctx.state.cache.keys().next().value;
48731
48827
  if (oldest !== void 0) ctx.state.cache.delete(oldest);
@@ -48934,7 +49030,8 @@ function remember(ctx, entity, text, frame, unit) {
48934
49030
  bindRef: text.bind,
48935
49031
  bind: bindKeyOf(text.bind),
48936
49032
  locale: frame.locale,
48937
- unit
49033
+ unit,
49034
+ box: text.box
48938
49035
  });
48939
49036
  return;
48940
49037
  }
@@ -48946,6 +49043,23 @@ function remember(ctx, entity, text, frame, unit) {
48946
49043
  seen.style = text.style;
48947
49044
  seen.locale = frame.locale;
48948
49045
  seen.unit = unit;
49046
+ seen.box = text.box;
49047
+ }
49048
+ /**
49049
+ * Tells whether a label of a `"box"` style was laid out at another box than it carries now. The
49050
+ * box of a label of any other style is only noted: it draws the same at every width, so a resize
49051
+ * resolves nothing. A label whose box stands costs one compare.
49052
+ *
49053
+ * @param ctx - Domain context of the text plugin.
49054
+ * @param seen - What was resolved for the label last.
49055
+ * @param text - Its component value.
49056
+ * @returns True when the label has to be laid out at its new box.
49057
+ */
49058
+ function boxMoved(ctx, seen, text) {
49059
+ if (seen.box === text.box) return false;
49060
+ if (wrapsAtBox(ctx, text.style)) return true;
49061
+ seen.box = text.box;
49062
+ return false;
48949
49063
  }
48950
49064
  /**
48951
49065
  * Keeps the measured size of a label: made once, then overwritten in place.
@@ -48975,7 +49089,7 @@ function keepSize(ctx, entity, layout) {
48975
49089
  * @returns True when nothing has to be formatted or written.
48976
49090
  */
48977
49091
  function isShown(seen, text, unit, format, frame) {
48978
- return seen !== void 0 && seen.unit === unit && seen.style === text.style && isSameBind(seen, text.bind) && (format !== "duration" || seen.locale === frame.locale);
49092
+ return seen.unit === unit && seen.style === text.style && isSameBind(seen, text.bind) && (format !== "duration" || seen.locale === frame.locale);
48979
49093
  }
48980
49094
  /**
48981
49095
  * Writes the new string of a bound label, its measured size, and for a countdown the time left,
@@ -48993,7 +49107,7 @@ function showBound(ctx, entity, text, next, value) {
48993
49107
  ctx.deps.world.ecs.set(entity, Text, { resolved: next });
48994
49108
  if (value !== void 0 && text.bind !== void 0 && isCountdownLeft(text.bind)) ctx.deps.world.ecs.set(entity, Countdown, { left: value });
48995
49109
  }
48996
- if (changed || !ctx.state.measured.has(entity)) keepSize(ctx, entity, layoutFor(ctx, next, text.style));
49110
+ if (changed || !ctx.state.measured.has(entity)) keepSize(ctx, entity, layoutFor(ctx, next, text.style, text.box));
48997
49111
  }
48998
49112
  /**
48999
49113
  * Writes the string a bound label already shows once more, and measures it again. A mark on a
@@ -49007,7 +49121,7 @@ function showBound(ctx, entity, text, next, value) {
49007
49121
  */
49008
49122
  function showAgain(ctx, entity, text) {
49009
49123
  ctx.deps.world.ecs.set(entity, Text, { resolved: text.resolved });
49010
- keepSize(ctx, entity, layoutFor(ctx, text.resolved, text.style));
49124
+ keepSize(ctx, entity, layoutFor(ctx, text.resolved, text.style, text.box));
49011
49125
  }
49012
49126
  /**
49013
49127
  * Reports a bound label whose style is not a digits style, once per style. An unknown style name
@@ -49025,8 +49139,9 @@ function warnWithoutDigits(ctx, styleName) {
49025
49139
  * One frame of a bound label: the value read every frame, the string built and written only
49026
49140
  * when its unit moved. A label that stands still costs a read and a few compares and keeps
49027
49141
  * nothing: no tag is parsed, no key is built. A marked label that stands still is written once
49028
- * with the string it shows. The style is checked for digits only when the label is written, so
49029
- * a label on a wrong style builds no warning key per frame.
49142
+ * with the string it shows; one of a `"box"` style that ui laid out at another box is measured at
49143
+ * it. The style is checked for digits only when the label is written, so a label on a wrong style
49144
+ * builds no warning key per frame.
49030
49145
  *
49031
49146
  * @param ctx - Domain context of the text plugin.
49032
49147
  * @param entity - The entity.
@@ -49039,10 +49154,9 @@ function stepBound(ctx, entity, text, bind, frame) {
49039
49154
  const format = value === void 0 ? "int" : formatOf(ctx, entity, bind, value);
49040
49155
  const unit = value === void 0 ? void 0 : unitOf(value, format);
49041
49156
  const marked = ctx.state.dirty.delete(entity);
49042
- if (isShown(ctx.state.seen.get(entity), text, unit, format, frame)) {
49043
- if (!marked) return;
49044
- warnWithoutDigits(ctx, text.style);
49045
- showAgain(ctx, entity, text);
49157
+ const seen = ctx.state.seen.get(entity);
49158
+ if (seen !== void 0 && isShown(seen, text, unit, format, frame)) {
49159
+ standStill(ctx, entity, text, seen, marked);
49046
49160
  return;
49047
49161
  }
49048
49162
  warnWithoutDigits(ctx, text.style);
@@ -49050,8 +49164,27 @@ function stepBound(ctx, entity, text, bind, frame) {
49050
49164
  remember(ctx, entity, text, frame, unit);
49051
49165
  }
49052
49166
  /**
49053
- * Tells whether a label has to be resolved again. Change sets are cleared every frame and `ui`
49054
- * writes `Text` after this system, so the comparison is against what was seen, not `changed`.
49167
+ * One frame of a bound label whose unit stands still: a marked one is written once as it is, one of
49168
+ * a `"box"` style that ui laid out at another box is measured at it. Either way the box is noted.
49169
+ *
49170
+ * @param ctx - Domain context of the text plugin.
49171
+ * @param entity - The entity.
49172
+ * @param text - Its component value.
49173
+ * @param seen - What was resolved for the label last.
49174
+ * @param marked - Whether the label was marked this frame.
49175
+ */
49176
+ function standStill(ctx, entity, text, seen, marked) {
49177
+ if (marked) {
49178
+ warnWithoutDigits(ctx, text.style);
49179
+ showAgain(ctx, entity, text);
49180
+ } else if (boxMoved(ctx, seen, text)) keepSize(ctx, entity, layoutFor(ctx, text.resolved, text.style, text.box));
49181
+ else return;
49182
+ seen.box = text.box;
49183
+ }
49184
+ /**
49185
+ * Tells whether a label has to be resolved again: its content, style, bind or locale moved, or
49186
+ * ui laid a label of a `"box"` style out at another box. Change sets are cleared every frame and
49187
+ * `ui` writes `Text` after this system, so the comparison is against what was seen, not `changed`.
49055
49188
  *
49056
49189
  * @param ctx - Domain context of the text plugin.
49057
49190
  * @param entity - The entity.
@@ -49063,7 +49196,7 @@ function isStale(ctx, entity, text, frame) {
49063
49196
  if (ctx.state.dirty.has(entity)) return true;
49064
49197
  const seen = ctx.state.seen.get(entity);
49065
49198
  if (seen === void 0) return true;
49066
- return seen.content !== text.content || seen.style !== text.style || !isSameBind(seen, text.bind) || seen.locale !== frame.locale;
49199
+ return seen.content !== text.content || seen.style !== text.style || !isSameBind(seen, text.bind) || seen.locale !== frame.locale || boxMoved(ctx, seen, text);
49067
49200
  }
49068
49201
  /**
49069
49202
  * One frame of one label.
@@ -49079,7 +49212,7 @@ function stepText(ctx, entity, text, frame) {
49079
49212
  return;
49080
49213
  }
49081
49214
  if (!isStale(ctx, entity, text, frame)) return;
49082
- keepSize(ctx, entity, layoutFor(ctx, resolveContent(ctx, entity, text), text.style));
49215
+ keepSize(ctx, entity, layoutFor(ctx, resolveContent(ctx, entity, text), text.style, text.box));
49083
49216
  ctx.state.dirty.delete(entity);
49084
49217
  remember(ctx, entity, text, frame);
49085
49218
  }
@@ -49653,15 +49786,19 @@ function buildRun(ctx, pixi, run, style, at) {
49653
49786
  }
49654
49787
  /**
49655
49788
  * The style and the laid-out block of one component value, under the generation of this moment.
49789
+ * A `"box"` style wraps at the box `ui` wrote; with none (a label outside a ui layout) it is one
49790
+ * line, and a dev warning says so once per style.
49656
49791
  *
49657
49792
  * @param ctx - Domain context of the text plugin.
49658
49793
  * @param value - The component value.
49659
49794
  * @returns What the container is filled from.
49660
49795
  */
49661
49796
  function labelOf(ctx, value) {
49797
+ const style = styleOf$1(ctx, value.style);
49798
+ if (style.wrap === "box" && value.box <= 0 && value.resolved !== "") warnOnce(ctx, `wrap-box:${value.style}`, "text: wrap \"box\" outside layout", { style: value.style });
49662
49799
  return {
49663
- style: styleOf$1(ctx, value.style),
49664
- layout: layoutFor(ctx, value.resolved, value.style),
49800
+ style,
49801
+ layout: layoutFor(ctx, value.resolved, value.style, value.box),
49665
49802
  generation: ctx.state.generation
49666
49803
  };
49667
49804
  }
@@ -49778,8 +49915,9 @@ function samePoint(first, second) {
49778
49915
  /**
49779
49916
  * Tells whether a container already shows a value: the string, the style name and the anchor it
49780
49917
  * was last updated with, drawn with the style object that is current and under the generation
49781
- * that is current. A replaced style is a new object under the same name. A font or an icon a dev
49782
- * hot swap replaced moves the generation, because the objects hold textures that are destroyed.
49918
+ * that is current, and for a `"box"` style at the same box. A replaced style is a new object under
49919
+ * the same name. A font or an icon a dev hot swap replaced moves the generation, because the
49920
+ * objects hold textures that are destroyed. The box of any other style changes nothing drawn.
49783
49921
  *
49784
49922
  * @param ctx - Domain context of the text plugin.
49785
49923
  * @param container - The container of this label.
@@ -49789,7 +49927,8 @@ function samePoint(first, second) {
49789
49927
  */
49790
49928
  function isCurrent(ctx, container, previous, next) {
49791
49929
  const drawn = ctx.state.drawn.get(container);
49792
- return previous.resolved === next.resolved && previous.style === next.style && samePoint(previous.anchor, next.anchor) && drawn?.style === styleOf$1(ctx, next.style) && drawn.generation === ctx.state.generation;
49930
+ const boxStands = previous.box === next.box || drawn?.style.wrap !== "box";
49931
+ return previous.resolved === next.resolved && previous.style === next.style && samePoint(previous.anchor, next.anchor) && drawn?.style === styleOf$1(ctx, next.style) && drawn.generation === ctx.state.generation && boxStands;
49793
49932
  }
49794
49933
  /**
49795
49934
  * Draws a changed value into a container that holds a label already. A ticking counter keeps its
@@ -50046,9 +50185,9 @@ function createTextApi(ctx) {
50046
50185
  return domain;
50047
50186
  };
50048
50187
  return {
50049
- measure: (content, style) => {
50188
+ measure: (content, style, width = 0) => {
50050
50189
  const tctx = text();
50051
- return sizeOf(layoutFor(tctx, sourceOf(tctx, content), style));
50190
+ return sizeOf(layoutFor(tctx, sourceOf(tctx, content), style, width));
50052
50191
  },
50053
50192
  hasGlyph: (char, style) => hasGlyphOf(text(), char, style),
50054
50193
  styles: () => Object.freeze([...ctx.state.styles.keys()]),
@@ -54781,6 +54920,16 @@ function entitySet(entities) {
54781
54920
  return new Set(entities);
54782
54921
  }
54783
54922
  /**
54923
+ * Tells whether an element whose `Box` just came arrives with an enter: new, and not a row a
54924
+ * range change scrolled in.
54925
+ *
54926
+ * @param element - The element, if it is still known.
54927
+ * @returns True when its enter hook is to play.
54928
+ */
54929
+ function arrives(element) {
54930
+ return element !== void 0 && !element.entered && !element.scrolledIn;
54931
+ }
54932
+ /**
54784
54933
  * Builds the reconcile half of the jsx module: the two systems of the frame and the root
54785
54934
  * bookkeeping around them.
54786
54935
  *
@@ -55721,7 +55870,8 @@ function createReconciler(ctx, modules, fields) {
55721
55870
  /**
55722
55871
  * Keeps an element whose spawn has just been applied: the world calls it from the `Box` hook, at
55723
55872
  * the flush of phase `layout`. Its enter hook waits for `playEntered`, so the game's systems see
55724
- * the entity first (F48).
55873
+ * the entity first (F48). The hook itself plays at once, so they see the start pose it writes (F49);
55874
+ * `ui.enter` plays it again when one of them changed what it read.
55725
55875
  *
55726
55876
  * @param entity - The entity the `Box` was attached to.
55727
55877
  */
@@ -55730,6 +55880,8 @@ function createReconciler(ctx, modules, fields) {
55730
55880
  if (entering.count < entering.list.length) entering.list[entering.count] = entity;
55731
55881
  else entering.list.push(entity);
55732
55882
  entering.count += 1;
55883
+ const element = state.elements.get(entity);
55884
+ if (arrives(element)) modules.layout.pose(element, overlayOf(element) === void 0 ? void 0 : createSpan());
55733
55885
  }
55734
55886
  /**
55735
55887
  * Plays the enter hooks queued since the last call, in the order the elements came. The `ui.enter`
@@ -56585,6 +56737,203 @@ function watched(handle, span, watch) {
56585
56737
  };
56586
56738
  }
56587
56739
  /**
56740
+ * A handle that keeps what an enter hook read and wrote: the components it read with `get`, the
56741
+ * components it wrote, and every `set` with the values it wrote over. Everything goes on to the
56742
+ * real handle.
56743
+ *
56744
+ * @param handle - The handle the hook plays on.
56745
+ * @param posed - Where it is kept.
56746
+ * @returns The recording handle.
56747
+ */
56748
+ function recording(handle, posed) {
56749
+ const wrote = (component) => {
56750
+ if (!posed.written.includes(component.componentName)) posed.written.push(component.componentName);
56751
+ };
56752
+ return {
56753
+ entity: handle.entity,
56754
+ key: handle.key,
56755
+ get: (component) => {
56756
+ if (!posed.reads.some((read) => read.component === component)) posed.reads.push({
56757
+ component: asAnyComponent(component),
56758
+ seen: {}
56759
+ });
56760
+ return handle.get(component);
56761
+ },
56762
+ rest: (component) => handle.rest(component),
56763
+ set: (component, patch) => {
56764
+ wrote(component);
56765
+ keepWrite(posed, asAnyComponent(component), patch, handle.get(component));
56766
+ handle.set(component, patch);
56767
+ },
56768
+ tween: (component, to, options) => {
56769
+ wrote(component);
56770
+ return handle.tween(component, to, options);
56771
+ },
56772
+ toRest: (component, options) => {
56773
+ wrote(component);
56774
+ return handle.toRest(component, options);
56775
+ },
56776
+ all: (handles) => handle.all(handles),
56777
+ peer: (key) => handle.peer(key)
56778
+ };
56779
+ }
56780
+ /**
56781
+ * Keeps one `set` of an enter hook with the values its fields had before it. A component the
56782
+ * entity does not carry yet has nothing to go back to: a replay writes it again.
56783
+ *
56784
+ * @param posed - Where the write is kept.
56785
+ * @param component - The component the hook wrote.
56786
+ * @param patch - The fields it wrote.
56787
+ * @param current - The component's value before the write, if the entity carries it.
56788
+ */
56789
+ function keepWrite(posed, component, patch, current) {
56790
+ if (current === void 0) return;
56791
+ const wrote = { ...patch };
56792
+ const was = current;
56793
+ const before = {};
56794
+ for (const field of Object.keys(wrote)) before[field] = was[field];
56795
+ posed.writes.push({
56796
+ component,
56797
+ wrote,
56798
+ before
56799
+ });
56800
+ }
56801
+ /**
56802
+ * A component type as the record of an enter play keeps it.
56803
+ *
56804
+ * @param component - The component type a hook used.
56805
+ * @returns The same type, read as a record of fields.
56806
+ */
56807
+ function asAnyComponent(component) {
56808
+ return component;
56809
+ }
56810
+ /**
56811
+ * Plays the enter hook of an element at its `Box`, before the game's systems of phase `sync` see
56812
+ * the element, so they see its start pose (F49). What the hook read and wrote is kept on the
56813
+ * element: `enterPosed` plays it again in `ui.enter` when a game system changed a component it
56814
+ * read (F48), and keeps this play otherwise.
56815
+ *
56816
+ * @param ctx - Domain context of the ui plugin.
56817
+ * @param element - The element whose `Box` was just attached.
56818
+ * @param span - Notes the longest finite track the hook starts, for the level of an overlay.
56819
+ */
56820
+ function poseEnter(ctx, element, span) {
56821
+ const hook = element.motion?.enter;
56822
+ if (hook === void 0) return;
56823
+ const posed = {
56824
+ motion: void 0,
56825
+ reads: [],
56826
+ written: [],
56827
+ writes: [],
56828
+ span
56829
+ };
56830
+ posed.motion = play(ctx, element, (view) => hook(recording(view, posed), element.node, void 0, element.cause), span);
56831
+ element.posed = posed;
56832
+ const ecs = ctx.deps.world.ecs;
56833
+ for (const read of posed.reads) {
56834
+ if (posed.written.includes(read.component.componentName)) continue;
56835
+ read.seen = { ...ecs.get(element.entity, read.component) };
56836
+ }
56837
+ }
56838
+ /**
56839
+ * Tells whether a game system changed a component the enter hook read at the `Box`.
56840
+ *
56841
+ * @param ctx - Domain context of the ui plugin.
56842
+ * @param element - The element.
56843
+ * @param posed - What the play at the `Box` kept.
56844
+ * @returns True when a field the hook could have read is not what it was.
56845
+ */
56846
+ function readChanged(ctx, element, posed) {
56847
+ const ecs = ctx.deps.world.ecs;
56848
+ for (const read of posed.reads) {
56849
+ if (posed.written.includes(read.component.componentName)) continue;
56850
+ if (fieldsDiffer(read.seen, ecs.get(element.entity, read.component))) return true;
56851
+ }
56852
+ return false;
56853
+ }
56854
+ /**
56855
+ * Tells whether a component's value is not the one a play saw: a field differs, one came or went,
56856
+ * or the component itself went.
56857
+ *
56858
+ * @param seen - The fields as the play saw them.
56859
+ * @param now - The value now, or nothing when the entity lost the component.
56860
+ * @returns True when they differ.
56861
+ * @example
56862
+ * ```ts
56863
+ * fieldsDiffer({ dy: 300 }, { dy: 180 }); // true
56864
+ * ```
56865
+ */
56866
+ function fieldsDiffer(seen, now) {
56867
+ if (now === void 0) return Object.keys(seen).length > 0;
56868
+ const value = now;
56869
+ const fields = /* @__PURE__ */ new Set([...Object.keys(seen), ...Object.keys(value)]);
56870
+ for (const field of fields) if (value[field] !== seen[field]) return true;
56871
+ return false;
56872
+ }
56873
+ /**
56874
+ * Undoes the play at the `Box` before the hook plays again: its motion is cancelled and forgotten,
56875
+ * and a field still holding what a `set` wrote gets its value from before. A field written since
56876
+ * keeps the new value. The later writes go back first, so the first one's value is what stays.
56877
+ *
56878
+ * @param ctx - Domain context of the ui plugin.
56879
+ * @param element - The element.
56880
+ * @param posed - What the play at the `Box` kept.
56881
+ */
56882
+ function undoPosed(ctx, element, posed) {
56883
+ forgetMotion(element, posed.motion);
56884
+ for (const write of posed.writes.toReversed()) revertWrite(ctx, element, write);
56885
+ }
56886
+ /**
56887
+ * Cancels the motion of a play that is undone and takes it out of the element's handles.
56888
+ *
56889
+ * @param element - The element.
56890
+ * @param motion - The motion the play returned, if any.
56891
+ */
56892
+ function forgetMotion(element, motion) {
56893
+ if (motion === void 0) return;
56894
+ motion.cancel();
56895
+ const at = element.handles.indexOf(motion);
56896
+ if (at !== -1) element.handles.splice(at, 1);
56897
+ element.endless?.delete(motion);
56898
+ }
56899
+ /**
56900
+ * Gives one `set` of an undone play back: each field still holding what it wrote gets the value it
56901
+ * had before.
56902
+ *
56903
+ * @param ctx - Domain context of the ui plugin.
56904
+ * @param element - The element.
56905
+ * @param write - The write to take back.
56906
+ */
56907
+ function revertWrite(ctx, element, write) {
56908
+ const ecs = ctx.deps.world.ecs;
56909
+ const current = ecs.get(element.entity, write.component);
56910
+ if (current === void 0) return;
56911
+ const kept = Object.keys(write.wrote).filter((field) => current[field] === write.wrote[field]);
56912
+ if (kept.length === 0) return;
56913
+ ecs.set(element.entity, write.component, Object.fromEntries(kept.map((field) => [field, write.before[field]])));
56914
+ }
56915
+ /**
56916
+ * Settles the enter hook an element played at its `Box`, in `ui.enter`: when a game system of
56917
+ * `sync` changed a component the hook read, the play is undone and the hook plays again on what
56918
+ * the system wrote (F48); otherwise the play stands, and an overlay's span gets what it noted.
56919
+ *
56920
+ * @param ctx - Domain context of the ui plugin.
56921
+ * @param element - The element whose enter plays now.
56922
+ * @param span - Where the longest finite track is noted, for the level of an overlay.
56923
+ * @returns True when the play at the `Box` stands; false when there was none, or it was undone.
56924
+ */
56925
+ function enterPosed(ctx, element, span) {
56926
+ const posed = element.posed;
56927
+ if (posed === void 0) return false;
56928
+ element.posed = void 0;
56929
+ if (readChanged(ctx, element, posed)) {
56930
+ undoPosed(ctx, element, posed);
56931
+ return false;
56932
+ }
56933
+ if (span !== void 0 && posed.span !== void 0) Object.assign(span, posed.span);
56934
+ return true;
56935
+ }
56936
+ /**
56588
56937
  * Runs one motion hook and keeps the handle it returned, so the exit sweep knows when the
56589
56938
  * element is still playing. A motion whose hook started a track that repeats forever is also
56590
56939
  * noted as endless: the sweep never waits for it. Without a hook the value the solve wrote stands;
@@ -57167,23 +57516,38 @@ function shownOf(element, source) {
57167
57516
  }
57168
57517
  /**
57169
57518
  * The size a text or an icon takes before Yoga clamps it: an icon is square on one line of its
57170
- * text style, a bound text measures the string it shows, any other text its content. The size is
57171
- * rounded up to whole units. Yoga rounds a node with a measure function outward but sizes its
57172
- * parent from the unrounded length, so a fractional size would run into the parent's padding.
57519
+ * text style, a bound text measures the string it shows, any other text its content, wrapped at
57520
+ * `width` when its style wraps at its box. The size is rounded up to whole units. Yoga rounds a
57521
+ * node with a measure function outward but sizes its parent from the unrounded length, so a
57522
+ * fractional size would run into the parent's padding.
57173
57523
  *
57174
57524
  * @param element - The text or icon element.
57175
57525
  * @param source - What it is measured through.
57526
+ * @param width - The width Yoga offers, or `undefined` when it bounds none.
57176
57527
  * @returns The width and the height, in whole units.
57177
57528
  */
57178
- function naturalSize(element, source) {
57529
+ function naturalSize(element, source, width) {
57179
57530
  const { content, style } = contentOf$1(element);
57180
- const size = element.type === "icon" ? squareOf(source.measure(LINE_SAMPLE, style)) : source.measure(shownOf(element, source) ?? content, style);
57531
+ const size = element.type === "icon" ? squareOf(source.measure(LINE_SAMPLE, style)) : source.measure(shownOf(element, source) ?? content, style, width);
57181
57532
  return {
57182
57533
  width: Math.ceil(size.width - WHOLE_EPSILON),
57183
57534
  height: Math.ceil(size.height - WHOLE_EPSILON)
57184
57535
  };
57185
57536
  }
57186
57537
  /**
57538
+ * The width Yoga bounds a measure with: what it offers in an exact or an at-most mode, nothing in
57539
+ * an undefined one, where a text is as wide as one line of it.
57540
+ *
57541
+ * @param yoga - The loaded Yoga module, for the measure modes.
57542
+ * @param width - What Yoga offered on the width.
57543
+ * @param mode - How to read that width.
57544
+ * @returns The width, or `undefined` when none is bounded.
57545
+ */
57546
+ function offeredWidth(yoga, width, mode) {
57547
+ if (mode === yoga.MEASURE_MODE_UNDEFINED || !Number.isFinite(width)) return void 0;
57548
+ return width;
57549
+ }
57550
+ /**
57187
57551
  * Clamps one axis of a measured size to what Yoga offered on that axis.
57188
57552
  *
57189
57553
  * @param yoga - The loaded Yoga module, for the three measure modes.
@@ -57219,7 +57583,8 @@ function clampToMode(yoga, measured, asked) {
57219
57583
  }
57220
57584
  /**
57221
57585
  * Installs the measure function of a text element, or drops it when the text is fixed. The
57222
- * function keeps the size it answered on the element, for `remeasureShown`.
57586
+ * function keeps the size it answered and the width it was offered on the element, for
57587
+ * `remeasureShown`.
57223
57588
  *
57224
57589
  * @param state - The layout state, for the Yoga module and the `measured` counter.
57225
57590
  * @param node - The node of the element.
@@ -57234,9 +57599,11 @@ function installMeasure(state, node, element, source) {
57234
57599
  return;
57235
57600
  }
57236
57601
  const measureFunction = (width, widthMode, height, heightMode) => {
57237
- const size = naturalSize(element, source);
57602
+ const offered = offeredWidth(yoga, width, widthMode);
57603
+ const size = naturalSize(element, source, offered);
57238
57604
  state.measured += 1;
57239
57605
  element.measuredSize = size;
57606
+ element.measuredWidth = offered;
57240
57607
  if (element.type === "icon") state.iconLine = lineOf(source);
57241
57608
  return clampToMode(yoga, size, {
57242
57609
  width,
@@ -57258,12 +57625,29 @@ function markMeasured(state, element) {
57258
57625
  state.byEntity.get(element.entity)?.markDirty();
57259
57626
  }
57260
57627
  /**
57628
+ * The width a text that was measured inside a bound width is laid out at now: its node's content
57629
+ * width from the last solve. Yoga may serve a width it measured before from its own cache without
57630
+ * calling the measure function, so the width that function was offered last can be another one.
57631
+ *
57632
+ * @param state - The layout state, for the edge enums of Yoga.
57633
+ * @param node - The node of the text.
57634
+ * @param element - The text element.
57635
+ * @returns The content width, or `undefined` when the text was measured unbounded.
57636
+ */
57637
+ function laidOutWidth(state, node, element) {
57638
+ const yoga = state.yoga;
57639
+ if (element.measuredWidth === void 0 || yoga === void 0) return void 0;
57640
+ const padding = node.getComputedPadding(yoga.EDGE_LEFT) + node.getComputedPadding(yoga.EDGE_RIGHT);
57641
+ return node.getComputedWidth() - padding;
57642
+ }
57643
+ /**
57261
57644
  * Marks the node of a text dirty when what it draws now measures to another size than the node was
57262
57645
  * laid out with. `text` writes `Text` when the shown string of a bound label changes, and for
57263
57646
  * every label it resolves again: a message after a new locale, any label when its font arrives or
57264
57647
  * its style is swapped. So a text without `bind` is measured again too, and a counter that rolls
57265
- * through digits of one width asks for no solve. A text with a fixed size has no measure function
57266
- * and is left alone.
57648
+ * through digits of one width asks for no solve. A text whose width was bounded is measured at
57649
+ * the width it is laid out at now, so one that wraps at its box compares the same lines. A text
57650
+ * with a fixed size has no measure function and is left alone.
57267
57651
  *
57268
57652
  * @param state - The layout state, for the node of the element.
57269
57653
  * @param element - An element whose `Text` changed this frame.
@@ -57275,7 +57659,7 @@ function remeasureShown(state, element, source) {
57275
57659
  const before = element.measuredSize;
57276
57660
  if (node === void 0 || before === void 0) return false;
57277
57661
  if (!needsMeasure(element)) return false;
57278
- const size = naturalSize(element, source);
57662
+ const size = naturalSize(element, source, laidOutWidth(state, node, element));
57279
57663
  if (size.width === before.width && size.height === before.height) return false;
57280
57664
  node.markDirty();
57281
57665
  return true;
@@ -58131,7 +58515,7 @@ async function loadYogaModule() {
58131
58515
  */
58132
58516
  function textSourceOf(ctx) {
58133
58517
  return {
58134
- measure: (content, style) => ctx.deps.text.measure(content, style),
58518
+ measure: (content, style, width) => ctx.deps.text.measure(content, style, width),
58135
58519
  resolved: (entity) => ctx.deps.world.ecs.get(entity, Text)?.resolved,
58136
58520
  duration: (ms) => ctx.deps.i18n.duration(ms)
58137
58521
  };
@@ -58194,9 +58578,11 @@ function createLayoutApi(ctx) {
58194
58578
  },
58195
58579
  enter: (element, span) => {
58196
58580
  const hook = element.motion?.enter;
58197
- play(ctx, element, hook === void 0 ? void 0 : (view) => hook(view, element.node, void 0, element.cause), span);
58581
+ const played = hook === void 0 ? void 0 : (view) => hook(view, element.node, void 0, element.cause);
58582
+ if (!enterPosed(ctx, element, span)) play(ctx, element, played, span);
58198
58583
  startLoop(ctx, element);
58199
58584
  },
58585
+ pose: (element, span) => poseEnter(ctx, element, span),
58200
58586
  loop: (element) => startLoop(ctx, element),
58201
58587
  change: (element, previous, hint) => {
58202
58588
  const hook = element.motion?.change?.Box;
@@ -58768,18 +59154,21 @@ function registerComponents(ctx, jsx) {
58768
59154
  for (const feature of ctx.deps.flow.features.all()) for (const entry of feature.description.ui ?? []) if (isComponent(entry)) jsx.register(entry);
58769
59155
  }
58770
59156
  /**
58771
- * Opens the two systems of phase `layout`, the `ui.enter` system of phase `sync`, which plays the
58772
- * enter hooks after the game's systems (F48), the seven world hooks of `Tree`, `Box`, `Pressed` and
58773
- * `PointerOver`, the two that tell the hosting step a view lost its `Parent` or its `Held` (a
58774
- * view freed so may hang under its host again), the listener that asks for every root to be built again after a restore or a
58775
- * load, the two effect handlers, the hint listener that buffers every released hint for
58776
- * the `change` hooks of the next frame step, the three tap listeners (the `LocalWrite` patch, the
58777
- * pointer tap that clears the keyboard focus, and the tap that starts or ends the editing of a
58778
- * text field), the pointer door that opens the keyboard inside the DOM listener, the key listener
58779
- * of the focus and `LocalWrite` as an input control, so the cursor shows a hand over a local-state
58780
- * button. On an agent page also the tap counter of `DebugTrigger` and its check of a view that takes
58781
- * no press. Every remover goes into the state, so the teardown closes exactly these, and gives every
58782
- * hosted view its layer back before the ui entities go.
59157
+ * Opens the two systems of phase `layout`, the system `ui.enter` of phase `sync`, the seven world
59158
+ * hooks of `Tree`, `Box`, `Pressed` and `PointerOver`, the two that tell the hosting step a view
59159
+ * lost its `Parent` or its `Held` (a view freed so may hang under its host again), the listener
59160
+ * that asks for every root to be built again after a restore or a load, the two effect handlers,
59161
+ * the hint listener that buffers every released hint for the `change` hooks of the next frame
59162
+ * step, the three tap listeners (the `LocalWrite` patch, the pointer tap that clears the keyboard
59163
+ * focus, and the tap that starts or ends the editing of a text field), the pointer door that opens
59164
+ * the keyboard inside the DOM listener, the key listener of the focus and `LocalWrite` as an input
59165
+ * control, so the cursor shows a hand over a local-state button. On an agent page also the tap
59166
+ * counter of `DebugTrigger` and its check of a view that takes no press.
59167
+ *
59168
+ * The `Box` hook plays an element's enter hook and queues it, so the game's systems see its start
59169
+ * pose (F49); `ui.enter` starts its loop after them and before the frame is drawn, and plays the
59170
+ * hook again when a system changed what it read (F48). Every remover goes into the state, so the teardown
59171
+ * closes exactly these, and gives every hosted view its layer back before the ui entities go.
58783
59172
  *
58784
59173
  * @param ctx - Domain context of the ui plugin.
58785
59174
  * @param jsx - The jsx module.