@mpgd/target-config 0.16.0 → 0.17.1

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.
@@ -227,6 +227,67 @@ export declare function resolveTargetViewportPlan(input: TargetViewportInput, br
227
227
  * render persistent DOM controls or position Phaser HUD elements themselves.
228
228
  */
229
229
  export declare function resolveTargetViewportSnapshot(input: TargetViewportSnapshotInput, breakpoints?: TargetViewportBreakpoints): TargetViewportSnapshot;
230
+ /** A raw viewport size reported by a host surface, before a plan validates it. */
231
+ export interface TargetViewportMeasurement {
232
+ readonly width: number;
233
+ readonly height: number;
234
+ readonly source: TargetViewportMeasurementSource;
235
+ }
236
+ /** A surface that reports its layout box, such as the element that mounts the game. */
237
+ export interface TargetViewportMeasurableElement {
238
+ getBoundingClientRect(): {
239
+ readonly width: number;
240
+ readonly height: number;
241
+ };
242
+ }
243
+ /**
244
+ * Host surfaces for {@link measureTargetViewport}. Structural types keep the helper usable in
245
+ * tests and non-DOM hosts; a browser passes the game container, `window.visualViewport` and
246
+ * `window`.
247
+ */
248
+ export interface TargetViewportMeasurementSources {
249
+ readonly container?: TargetViewportMeasurableElement | null;
250
+ readonly visualViewport?: {
251
+ readonly width: number;
252
+ readonly height: number;
253
+ } | null;
254
+ readonly window?: {
255
+ readonly innerWidth: number;
256
+ readonly innerHeight: number;
257
+ } | null;
258
+ }
259
+ /** Inputs for {@link waitForTargetViewportMeasurement}. */
260
+ export interface TargetViewportMeasurementWait {
261
+ /** Returns a measurement, or `null` while the surface has no usable size. */
262
+ readonly measure: () => TargetViewportMeasurement | null;
263
+ /**
264
+ * Calls the listener whenever the surface may have resized or become visible and returns an
265
+ * unsubscribe function. Browsers typically combine window and `visualViewport` `resize`,
266
+ * document `visibilitychange` and a `ResizeObserver` on the game container.
267
+ */
268
+ readonly subscribe: (listener: () => void) => () => void;
269
+ /** Stops waiting; the promise then rejects with the signal's reason. */
270
+ readonly signal?: AbortSignal;
271
+ }
272
+ /** Whether a size can produce a viewport plan: both sides finite and at least one CSS pixel. */
273
+ export declare function isMeasurableTargetViewport(size: {
274
+ readonly width: number;
275
+ readonly height: number;
276
+ }): boolean;
277
+ /**
278
+ * Measure the first host surface with a usable area: the game container, then the visual
279
+ * viewport, then the window. Returns `null` while every surface is still zero-sized, as in a
280
+ * hidden iframe, a collapsed embed or a background tab before layout. Resolve a viewport plan
281
+ * only from a measurement; {@link waitForTargetViewportMeasurement} waits for the first one.
282
+ */
283
+ export declare function measureTargetViewport(sources: TargetViewportMeasurementSources): TargetViewportMeasurement | null;
284
+ /**
285
+ * Resolve with the first usable measurement, so a game that boots inside a zero-sized surface
286
+ * starts once the host lays it out instead of failing viewport validation. Resolves immediately
287
+ * when the surface is already measurable and unsubscribes as soon as it settles. A measurement
288
+ * that throws, including the first one, rejects the returned promise.
289
+ */
290
+ export declare function waitForTargetViewportMeasurement(input: TargetViewportMeasurementWait): Promise<TargetViewportMeasurement>;
230
291
  /**
231
292
  * Resolve an adaptive game shell inside the snapshot's safe content bounds.
232
293
  *
package/dist/viewport.js CHANGED
@@ -152,6 +152,104 @@ export function resolveTargetViewportSnapshot(input, breakpoints = defaultTarget
152
152
  safeArea: resolveTargetViewportSafeArea(plan.layout, input.safeAreaInsets),
153
153
  };
154
154
  }
155
+ /** Whether a size can produce a viewport plan: both sides finite and at least one CSS pixel. */
156
+ export function isMeasurableTargetViewport(size) {
157
+ return isMeasurableViewportDimension(size.width) && isMeasurableViewportDimension(size.height);
158
+ }
159
+ /**
160
+ * Measure the first host surface with a usable area: the game container, then the visual
161
+ * viewport, then the window. Returns `null` while every surface is still zero-sized, as in a
162
+ * hidden iframe, a collapsed embed or a background tab before layout. Resolve a viewport plan
163
+ * only from a measurement; {@link waitForTargetViewportMeasurement} waits for the first one.
164
+ */
165
+ export function measureTargetViewport(sources) {
166
+ const rect = sources.container?.getBoundingClientRect();
167
+ if (rect !== undefined && isMeasurableTargetViewport(rect)) {
168
+ return { width: rect.width, height: rect.height, source: 'container' };
169
+ }
170
+ const visualViewport = sources.visualViewport;
171
+ if (visualViewport !== undefined &&
172
+ visualViewport !== null &&
173
+ isMeasurableTargetViewport(visualViewport)) {
174
+ return {
175
+ width: visualViewport.width,
176
+ height: visualViewport.height,
177
+ source: 'visual-viewport',
178
+ };
179
+ }
180
+ const host = sources.window;
181
+ if (host !== undefined && host !== null) {
182
+ const size = { width: host.innerWidth, height: host.innerHeight };
183
+ if (isMeasurableTargetViewport(size)) {
184
+ return { ...size, source: 'window' };
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+ /**
190
+ * Resolve with the first usable measurement, so a game that boots inside a zero-sized surface
191
+ * starts once the host lays it out instead of failing viewport validation. Resolves immediately
192
+ * when the surface is already measurable and unsubscribes as soon as it settles. A measurement
193
+ * that throws, including the first one, rejects the returned promise.
194
+ */
195
+ export function waitForTargetViewportMeasurement(input) {
196
+ let initial;
197
+ try {
198
+ initial = input.measure();
199
+ }
200
+ catch (error) {
201
+ return Promise.reject(error);
202
+ }
203
+ if (initial !== null) {
204
+ return Promise.resolve(initial);
205
+ }
206
+ if (input.signal?.aborted === true) {
207
+ return Promise.reject(input.signal.reason);
208
+ }
209
+ return new Promise((resolve, reject) => {
210
+ let settled = false;
211
+ // The listener may settle synchronously inside `subscribe`, before its cleanup is returned.
212
+ const subscription = {};
213
+ const settle = () => {
214
+ settled = true;
215
+ subscription.unsubscribe?.();
216
+ input.signal?.removeEventListener('abort', abort);
217
+ };
218
+ const check = () => {
219
+ if (settled) {
220
+ return;
221
+ }
222
+ let measurement;
223
+ try {
224
+ measurement = input.measure();
225
+ }
226
+ catch (error) {
227
+ settle();
228
+ reject(error);
229
+ return;
230
+ }
231
+ if (measurement !== null) {
232
+ settle();
233
+ resolve(measurement);
234
+ }
235
+ };
236
+ const abort = () => {
237
+ if (!settled) {
238
+ settle();
239
+ reject(input.signal?.reason);
240
+ }
241
+ };
242
+ input.signal?.addEventListener('abort', abort, { once: true });
243
+ const unsubscribe = input.subscribe(check);
244
+ if (settled) {
245
+ unsubscribe();
246
+ return;
247
+ }
248
+ subscription.unsubscribe = unsubscribe;
249
+ // A resize may land between the first measurement and the subscription.
250
+ check();
251
+ });
252
+ }
155
253
  /**
156
254
  * Resolve an adaptive game shell inside the snapshot's safe content bounds.
157
255
  *
@@ -414,6 +512,10 @@ function readTargetViewportCssPixels(style, property) {
414
512
  const match = /^(\d+(?:\.\d+)?)px$/u.exec(value);
415
513
  return match === null ? 0 : Math.round(Number(match[1]));
416
514
  }
515
+ /** Whether a dimension would pass {@link normalizeViewportDimension}. */
516
+ function isMeasurableViewportDimension(value) {
517
+ return Number.isFinite(value) && value > 0 && Math.round(value) > 0;
518
+ }
417
519
  /** Normalize a positive viewport dimension to at least one CSS pixel. */
418
520
  function normalizeViewportDimension(value, name) {
419
521
  if (!Number.isFinite(value) || value <= 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/target-config",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Target-specific platform feature availability and effective config helpers for mpgd.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,9 +37,9 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "typia": "13.0.2",
40
- "@mpgd/catalog": "0.7.5",
41
- "@mpgd/i18n": "0.6.4",
42
- "@mpgd/platform": "0.13.0"
40
+ "@mpgd/catalog": "0.7.7",
41
+ "@mpgd/i18n": "0.6.6",
42
+ "@mpgd/platform": "0.15.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "ttsc": "0.30.4",