@webview-native-ui/engine 0.0.4 → 0.0.5

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/README.md CHANGED
@@ -44,6 +44,7 @@ The current API is pre-1.0. Use matching, pinned Engine versions across npm and
44
44
 
45
45
  ## Further reading
46
46
 
47
+ - [Projection container support](docs/projection-containers.md) — supported HTML and Ionic destinations, sizing conditions and wrapper examples.
47
48
  - [Projection behavior and advanced integration](docs/projection.md) — DOM requirements, lifecycle, fallback policies, optional Ionic support and lower-level APIs.
48
49
  - [Development and releases](docs/maintenance.md) — repository structure, validation and publishing.
49
50
 
@@ -13,6 +13,7 @@ export const getUnprojectedVisibility = (element) => {
13
13
  for (const attribute of Array.from(node.attributes)) {
14
14
  if (attribute.name === 'data-native-ui-snapshot-source' ||
15
15
  attribute.name === 'data-native-ui-snapshot-prepared' ||
16
+ attribute.name === 'data-native-ui-snapshot-content' ||
16
17
  attribute.name.startsWith('data-native-ui-session-'))
17
18
  markers.push({ node, name: attribute.name, value: attribute.value });
18
19
  }
@@ -3,6 +3,6 @@ export const createWebProjectionScope = (element, namespace) => {
3
3
  if (element.nodeType !== 1 || element.namespaceURI !== 'http://www.w3.org/1999/xhtml' || !element.ownerDocument || !('style' in element))
4
4
  throw new TypeError('A Web projection scope requires an HTMLElement fallback');
5
5
  if (element.matches('img, input, textarea, select, video, audio, canvas, iframe, embed, object, br, hr, area, base, link, meta, source, track, wbr'))
6
- throw new TypeError('A Web projection fallback must support ::before; use its existing container element');
6
+ throw new TypeError('A Web projection fallback must be a container element; use the existing fallback container');
7
7
  return Object.freeze({ element, namespace });
8
8
  };
@@ -1,2 +1,2 @@
1
- /** Image presentation only; Native/Web ownership styling stays with the renderer. */
2
- export declare const snapshotStyles = "\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]),\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]) *,\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::after,\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::part(native) { visibility: hidden !important; }\n[data-native-ui-snapshot][data-native-ui-snapshot]::before {\n all:initial!important;content:\"\"!important;display:block!important;position:absolute!important;\n visibility:visible!important;opacity:0!important;will-change:opacity!important;\n pointer-events:none!important;image-rendering:pixelated!important;\n}\n[data-native-ui-snapshot-visible] { opacity:1!important;overflow:visible!important; }\n[data-native-ui-snapshot-visible][data-native-ui-snapshot][data-native-ui-snapshot]::before { opacity:1!important; }\n";
1
+ export declare const snapshotStyles = "\n@layer native-ui-projection {\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]), :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::part(native) { visibility: hidden !important; }\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::after,\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::after {\n visibility: hidden !important;\n}\n}\n[data-native-ui-snapshot-visible] { opacity:1!important;overflow:visible!important; }\n[data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }\n";
2
+ export declare const snapshotShadowStyles = "\n@layer native-ui-projection {\n:host([data-native-ui-snapshot-content]),\n:host([data-native-ui-snapshot-content])::before,\n:host([data-native-ui-snapshot-content])::after,\n:host([data-native-ui-snapshot-content]) *,\n:host([data-native-ui-snapshot-content]) *::before,\n:host([data-native-ui-snapshot-content]) *::after {\n visibility: hidden !important;\n}\n}\n";
@@ -1,14 +1,25 @@
1
1
  /** Image presentation only; Native/Web ownership styling stays with the renderer. */
2
+ const hidden = ':is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])';
2
3
  export const snapshotStyles = `
3
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]),
4
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]) *,
5
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::after,
6
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::part(native) { visibility: hidden !important; }
7
- [data-native-ui-snapshot][data-native-ui-snapshot]::before {
8
- all:initial!important;content:""!important;display:block!important;position:absolute!important;
9
- visibility:visible!important;opacity:0!important;will-change:opacity!important;
10
- pointer-events:none!important;image-rendering:pixelated!important;
4
+ @layer native-ui-projection {
5
+ ${hidden}, ${hidden} *, ${hidden}::part(native) { visibility: hidden !important; }
6
+ ${hidden}::before, ${hidden}::after,
7
+ ${hidden} *::before, ${hidden} *::after {
8
+ visibility: hidden !important;
9
+ }
11
10
  }
12
11
  [data-native-ui-snapshot-visible] { opacity:1!important;overflow:visible!important; }
13
- [data-native-ui-snapshot-visible][data-native-ui-snapshot][data-native-ui-snapshot]::before { opacity:1!important; }
12
+ [data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }
13
+ `;
14
+ export const snapshotShadowStyles = `
15
+ @layer native-ui-projection {
16
+ :host([data-native-ui-snapshot-content]),
17
+ :host([data-native-ui-snapshot-content])::before,
18
+ :host([data-native-ui-snapshot-content])::after,
19
+ :host([data-native-ui-snapshot-content]) *,
20
+ :host([data-native-ui-snapshot-content]) *::before,
21
+ :host([data-native-ui-snapshot-content]) *::after {
22
+ visibility: hidden !important;
23
+ }
24
+ }
14
25
  `;
@@ -1,8 +1,7 @@
1
1
  import { projectionURL } from '../native/resource-url.js';
2
2
  import { bounded, boundedListener } from '../native/bridge.js';
3
3
  import { bindProjectionEvents } from './events.js';
4
- import { projectionAncestors } from '../dom/geometry.js';
5
- import { snapshotStyles } from './styles.js';
4
+ import { snapshotStyles, snapshotShadowStyles } from './styles.js';
6
5
  const layout = (candidates, positionIndependent = false) => JSON.stringify(candidates.map(({ control: c }) => [
7
6
  c.id,
8
7
  c.kind,
@@ -14,41 +13,6 @@ const layout = (candidates, positionIndependent = false) => JSON.stringify(candi
14
13
  c.dark,
15
14
  c.rtl,
16
15
  ]));
17
- // Native frames are viewport pixels; absolute children use the target's local
18
- // padding box. Keep the conversion independent of theme dimensions.
19
- const coordinateSpace = (target, win, style = win.getComputedStyle(target)) => {
20
- const rect = target.getBoundingClientRect();
21
- // offsetWidth/Height round fractional CSS sizes, which would introduce drift.
22
- const borderBox = (size, sides) => {
23
- const value = parseFloat(size);
24
- return style.display !== 'inline' && Number.isFinite(value)
25
- ? value +
26
- (style.boxSizing === 'border-box' ? 0 : sides.reduce((sum, side) => sum + (parseFloat(style.getPropertyValue(side)) || 0), 0))
27
- : NaN;
28
- };
29
- const width = borderBox(style.width, ['padding-left', 'padding-right', 'border-left-width', 'border-right-width']);
30
- const height = borderBox(style.height, ['padding-top', 'padding-bottom', 'border-top-width', 'border-bottom-width']);
31
- // Inline dimensions may be auto (or an ignored specified width). Native element
32
- // projection excludes transformed ancestors; use CSS zoom, not rounded offsets.
33
- let zoom = 1;
34
- if (!(width > 0 && height > 0)) {
35
- for (const element of [target, ...projectionAncestors(target)]) {
36
- const value = (element === target ? style : win.getComputedStyle(element)).getPropertyValue('zoom');
37
- zoom *= (parseFloat(value) || 1) / (value.endsWith('%') ? 100 : 1);
38
- }
39
- }
40
- const scaleX = width > 0 ? rect.width / width : zoom;
41
- const scaleY = height > 0 ? rect.height / height : zoom;
42
- return {
43
- x: rect.x,
44
- y: rect.y,
45
- scaleX: scaleX || 1,
46
- scaleY: scaleY || 1,
47
- left: (parseFloat(style.borderLeftWidth) || 0) - target.scrollLeft,
48
- top: (parseFloat(style.borderTopWidth) || 0) - target.scrollTop,
49
- position: style.position,
50
- };
51
- };
52
16
  /** Images are prepared after accepted native updates, never on the overlay's critical path. */
53
17
  const createProjectionEngine = async (scope, transport, callbacks) => {
54
18
  const captureDelayMs = callbacks.captureDelayMs ?? 50;
@@ -61,6 +25,16 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
61
25
  const imageIdentity = Array.from(win.crypto.getRandomValues(new Uint32Array(4)), (value) => value.toString(16)).join('-');
62
26
  const validSource = (source) => source.isConnected && source.ownerDocument === doc;
63
27
  const validTarget = (target) => !!target?.isConnected && target.ownerDocument === doc;
28
+ // Keep current, preparing and pending-release images leased; reuse idle DOM slots.
29
+ const imagePool = new Map();
30
+ const leasedImages = new Set();
31
+ const retireImage = (image) => {
32
+ leasedImages.delete(image);
33
+ image.style.setProperty('display', 'none', 'important');
34
+ image.removeAttribute('data-native-ui-projection-image');
35
+ image.removeAttribute('src');
36
+ pruneIdleImages();
37
+ };
64
38
  const call = transport.send.bind(transport);
65
39
  const imageMarker = 'data-native-ui-snapshot';
66
40
  const sourcesChangedEvent = `${scope.namespace}:sources-change`;
@@ -78,17 +52,48 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
78
52
  return root.nodeType === 11 && 'host' in root ? root : doc;
79
53
  };
80
54
  const sheetFor = (root) => {
81
- let pair = sheets.get(root);
82
- if (!pair) {
83
- const base = doc.createElement('style'), image = doc.createElement('style');
84
- base.setAttribute('data-native-ui-snapshot-base', '');
85
- image.setAttribute(imageMarker, '');
86
- base.textContent = snapshotStyles;
87
- (root.nodeType === 11 ? root : root.head).append(base, image);
88
- pair = { base, image };
89
- sheets.set(root, pair);
90
- }
91
- return pair.image;
55
+ if (sheets.has(root))
56
+ return;
57
+ const style = doc.createElement('style');
58
+ style.setAttribute('data-native-ui-snapshot-base', '');
59
+ style.textContent = snapshotStyles + (root.nodeType === 11 ? snapshotShadowStyles : '');
60
+ (root.nodeType === 11 ? root : root.head).append(style);
61
+ sheets.set(root, style);
62
+ };
63
+ // Each shadow tree is a separate style scope. Keep these rules alongside the
64
+ // prepared image; only the marker changes during the handoff.
65
+ const finishVisibilityTransitions = (target) => {
66
+ // A visibility transition can keep the old content painted over the image.
67
+ // Do not stop layout, transform or opacity transitions owned by the renderer.
68
+ const animations = [
69
+ ...target.getAnimations({ subtree: true }),
70
+ ...[...(shadowHosts.get(target) ?? [])].flatMap((host) => host.shadowRoot?.getAnimations() ?? []),
71
+ ];
72
+ for (const animation of animations)
73
+ if ('transitionProperty' in animation && animation.transitionProperty === 'visibility')
74
+ animation.cancel();
75
+ };
76
+ const shadowHosts = new Map();
77
+ const syncShadowContent = (target) => {
78
+ const hosts = shadowHosts.get(target) ?? new Set();
79
+ shadowHosts.set(target, hosts);
80
+ const hidden = target.hasAttribute('data-native-ui-snapshot-source') || target.hasAttribute('data-native-ui-snapshot-prepared');
81
+ const visit = (element) => {
82
+ if (element.hasAttribute('data-native-ui-projection-node'))
83
+ return;
84
+ if (element.shadowRoot) {
85
+ hosts.add(element);
86
+ sheetFor(element.shadowRoot);
87
+ for (const child of element.shadowRoot.children)
88
+ visit(child);
89
+ }
90
+ for (const child of element.children)
91
+ visit(child);
92
+ };
93
+ visit(target);
94
+ for (const host of hosts)
95
+ host.toggleAttribute('data-native-ui-snapshot-content', hidden);
96
+ finishVisibilityTransitions(target);
92
97
  };
93
98
  const viewport = () => `${win.innerWidth}:${win.innerHeight}:${win.devicePixelRatio}`;
94
99
  let handoffPending = false;
@@ -109,12 +114,46 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
109
114
  let stopped = false;
110
115
  let flight = false;
111
116
  let latest;
117
+ const pruneIdleImages = () => {
118
+ const targets = new Set(latest?.candidates.map((candidate) => candidate.scope.element));
119
+ for (const [target, pool] of imagePool) {
120
+ const reusable = !stopped && target.isConnected && targets.has(target);
121
+ if (reusable && (active || ending || handoffPending))
122
+ continue;
123
+ const limit = reusable ? 2 * Math.max(1, cached?.scopes.filter((scope) => scope.element === target).length ?? 0) : 0;
124
+ let spare = Math.max(0, limit - pool.filter((image) => leasedImages.has(image)).length);
125
+ const retained = pool.filter((image) => {
126
+ if (leasedImages.has(image) || spare-- > 0)
127
+ return true;
128
+ image.remove();
129
+ return false;
130
+ });
131
+ if (retained.length)
132
+ imagePool.set(target, retained);
133
+ else
134
+ imagePool.delete(target);
135
+ }
136
+ const liveRoots = new Set([
137
+ ...(latest?.candidates.map((candidate) => candidate.scope.element.getRootNode()) ?? []),
138
+ ...(active?.scopes.map((scope) => scope.element.getRootNode()) ?? []),
139
+ ...(cached?.scopes.map((scope) => scope.element.getRootNode()) ?? []),
140
+ ...[...leasedImages].map((image) => image.getRootNode()),
141
+ ...[...shadowHosts.values()].flatMap((hosts) => [...hosts].map((host) => host.shadowRoot)),
142
+ ]);
143
+ for (const [root, pair] of sheets)
144
+ if (root.nodeType === 11 && !liveRoots.has(root)) {
145
+ pair.remove();
146
+ sheets.delete(root);
147
+ }
148
+ };
112
149
  const pendingCleanup = new Set();
113
150
  const cleanup = (action, token, afterPaint) => {
114
151
  const run = () => {
115
152
  if (!pendingCleanup.delete(run))
116
153
  return;
117
154
  void call({ action, token }).catch(() => { });
155
+ if (action === 'restored')
156
+ pruneIdleImages();
118
157
  };
119
158
  pendingCleanup.add(run);
120
159
  if (afterPaint && !stopped)
@@ -131,7 +170,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
131
170
  if (!item || item === cached || item === active)
132
171
  return;
133
172
  item.images.forEach((image) => {
134
- image.src = '';
173
+ retireImage(image);
135
174
  });
136
175
  cleanup('release', item.data.token, afterPaint);
137
176
  };
@@ -207,7 +246,19 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
207
246
  if (result.parts.some((part) => new Set(part.ids.map((id) => selected.find((candidate) => candidate.control.id === id).scope)).size !== 1))
208
247
  return;
209
248
  await bounded(Promise.all(result.parts.map(async (part) => {
210
- const image = doc.createElement('img');
249
+ const target = selected.find((candidate) => part.ids.includes(candidate.control.id)).scope.element;
250
+ let pool = imagePool.get(target);
251
+ if (!pool) {
252
+ pool = [];
253
+ imagePool.set(target, pool);
254
+ }
255
+ let image = pool.find((item) => !leasedImages.has(item));
256
+ if (!image) {
257
+ image = doc.createElement('img');
258
+ image.setAttribute('data-native-ui-projection-node', '');
259
+ pool.push(image);
260
+ }
261
+ leasedImages.add(image);
211
262
  images.push(image);
212
263
  image.src = projectionURL(part.url);
213
264
  await image.decode();
@@ -224,7 +275,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
224
275
  selected.some((candidate) => !validTarget(candidate.scope.element) || ![candidate.element, ...(candidate.sources ?? [])].every(validSource)))
225
276
  return;
226
277
  const previous = cached;
227
- cached = {
278
+ const prepared = {
228
279
  data: result,
229
280
  images,
230
281
  sources: Array.from(new Set(selected.flatMap((item) => [item.element, ...(item.sources ?? [])]))),
@@ -236,12 +287,32 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
236
287
  revision,
237
288
  layout: signature(selected),
238
289
  };
290
+ try {
291
+ if (!render(prepared))
292
+ throw new Error('Projection destination is unavailable');
293
+ }
294
+ catch (error) {
295
+ // Do not publish a token whose DOM could not be installed. Restore the
296
+ // last completed presentation before retiring the failed preparation.
297
+ detach();
298
+ try {
299
+ render();
300
+ cached?.sources.forEach((source) => {
301
+ preparedOwners.add(source);
302
+ preparedSources.add(source);
303
+ });
304
+ }
305
+ catch {
306
+ abandon();
307
+ }
308
+ throw error;
309
+ }
310
+ cached = prepared;
239
311
  clearPrepared();
240
312
  cached.sources.forEach((source) => {
241
313
  preparedOwners.add(source);
242
314
  preparedSources.add(source);
243
315
  });
244
- render();
245
316
  if (callbacks.opaqueUnderlay)
246
317
  void call({ action: 'prepared', token: result.token }).catch(() => { });
247
318
  win.dispatchEvent(new Event(sourcesChangedEvent));
@@ -255,7 +326,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
255
326
  finally {
256
327
  if (!adopted)
257
328
  images.forEach((image) => {
258
- image.src = '';
329
+ retireImage(image);
259
330
  });
260
331
  if (result?.token && !adopted)
261
332
  void call({ action: 'release', token: result.token }).catch(() => { });
@@ -291,6 +362,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
291
362
  if (latest?.revision !== revision)
292
363
  generation++;
293
364
  latest = { revision, candidates };
365
+ pruneIdleImages();
294
366
  if (cached?.revision !== revision || preparedVisualRevision !== visualRevision)
295
367
  schedule();
296
368
  };
@@ -304,7 +376,6 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
304
376
  invalidate();
305
377
  if (active) {
306
378
  updateOffsets();
307
- followBorderAnimation();
308
379
  }
309
380
  visualRevision++;
310
381
  generation++;
@@ -321,17 +392,21 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
321
392
  }));
322
393
  const preparedTargets = new Set();
323
394
  const clearTarget = (target) => {
395
+ shadowHosts.get(target)?.forEach((host) => host.removeAttribute('data-native-ui-snapshot-content'));
324
396
  if (preparedTargets.delete(target))
325
397
  target.removeAttribute('data-native-ui-snapshot-prepared');
326
398
  target.removeAttribute('data-native-ui-snapshot-visible');
327
399
  target.removeAttribute(imageMarker);
400
+ target.removeAttribute('data-native-ui-snapshot-positioned');
328
401
  target.removeAttribute('data-native-ui-snapshot-source');
402
+ finishVisibilityTransitions(target);
403
+ shadowHosts.delete(target);
329
404
  };
330
405
  const detach = () => {
331
406
  presentation++;
332
407
  win.cancelAnimationFrame(paintFrame);
333
- win.cancelAnimationFrame(offsetFrame);
334
- offsetFrame = 0;
408
+ for (const element of offsets.keys())
409
+ element.style.setProperty('opacity', '0', 'important');
335
410
  offsets.clear();
336
411
  paintFrame = 0;
337
412
  handoffPending = false;
@@ -343,11 +418,12 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
343
418
  });
344
419
  rendered.forEach(clearTarget);
345
420
  rendered.clear();
346
- sheets.forEach(({ base, image }) => {
347
- base.remove();
348
- image.remove();
421
+ sheets.forEach((style, root) => {
422
+ if (stopped || (root.nodeType === 11 && !root.host.isConnected)) {
423
+ style.remove();
424
+ sheets.delete(root);
425
+ }
349
426
  });
350
- sheets.clear();
351
427
  };
352
428
  const abandon = (expected = active, afterPaint = ending) => {
353
429
  if (active !== expected)
@@ -371,7 +447,16 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
371
447
  return;
372
448
  }
373
449
  const image = active;
374
- detach();
450
+ presentation++;
451
+ win.cancelAnimationFrame(paintFrame);
452
+ paintFrame = 0;
453
+ handoffPending = presented = false;
454
+ image.sources.forEach((source) => {
455
+ owners.delete(source);
456
+ visibleOwners.delete(source);
457
+ });
458
+ rendered.forEach((target) => target.removeAttribute('data-native-ui-snapshot-source'));
459
+ showImages(image, !!callbacks.opaqueUnderlay);
375
460
  active = undefined;
376
461
  ending = false;
377
462
  win.clearTimeout(restoreTimeout);
@@ -385,7 +470,6 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
385
470
  preparedOwners.add(source);
386
471
  preparedSources.add(source);
387
472
  });
388
- render();
389
473
  callbacks.wake();
390
474
  };
391
475
  const retireRestoration = () => {
@@ -395,51 +479,60 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
395
479
  };
396
480
  const targetsFor = (image) => image.scopes.map((scope) => (validTarget(scope.element) ? scope.element : undefined));
397
481
  const rendered = new Set();
482
+ const showImages = (image, visible) => {
483
+ for (const target of rendered) {
484
+ target.toggleAttribute('data-native-ui-snapshot-visible', visible);
485
+ syncShadowContent(target);
486
+ }
487
+ image.images.forEach((element) => element.style.setProperty('opacity', visible ? '1' : '0', 'important'));
488
+ };
398
489
  const preparedKeys = new Map();
399
490
  const offsets = new Map();
400
- let offsetFrame = 0;
401
491
  const updateOffsets = () => {
402
- for (const [target, offset] of offsets) {
403
- if (!target.isConnected)
492
+ const writes = [];
493
+ // Read all geometry before writing positions; never reset a painted image to zero to measure it.
494
+ for (const [element, placement] of offsets) {
495
+ const { target, frame } = placement;
496
+ if (!target.isConnected) {
497
+ element.remove();
404
498
  continue;
405
- const style = win.getComputedStyle(target);
406
- const left = `${offset.x - (parseFloat(style.borderLeftWidth) || 0) + target.scrollLeft}px`;
407
- const top = `${offset.y - (parseFloat(style.borderTopWidth) || 0) + target.scrollTop}px`;
408
- if (offset.style.left !== left)
409
- offset.style.setProperty('left', left, 'important');
410
- if (offset.style.top !== top)
411
- offset.style.setProperty('top', top, 'important');
412
- }
413
- };
414
- const followBorderAnimation = () => {
415
- if (offsetFrame || !active)
416
- return;
417
- const movingBorder = [...offsets.keys()].some((target) => target
418
- .getAnimations?.()
419
- .some((animation) => animation.playState === 'running' &&
420
- animation.effect
421
- ?.getKeyframes()
422
- .some((frame) => ['borderLeftWidth', 'borderTopWidth', 'borderWidth'].some((property) => property in frame))));
423
- if (movingBorder)
424
- offsetFrame = win.requestAnimationFrame(() => {
425
- offsetFrame = 0;
426
- updateOffsets();
427
- followBorderAnimation();
499
+ }
500
+ const parent = target.shadowRoot ?? target;
501
+ if (element.parentNode !== parent)
502
+ parent.insertBefore(element, null);
503
+ const box = target.getBoundingClientRect();
504
+ const rect = element.getBoundingClientRect();
505
+ const width = parseFloat(element.style.width) || frame.width;
506
+ const height = parseFloat(element.style.height) || frame.height;
507
+ const sx = rect.width / width || 1, sy = rect.height / height || 1;
508
+ const initial = placement.scale ?? (placement.scale = { x: sx, y: sy });
509
+ writes.push({
510
+ element,
511
+ left: (parseFloat(element.style.left) || 0) + (box.x + (frame.x * sx) / initial.x - rect.x) / sx,
512
+ top: (parseFloat(element.style.top) || 0) + (box.y + (frame.y * sy) / initial.y - rect.y) / sy,
513
+ width: frame.width / initial.x,
514
+ height: frame.height / initial.y,
428
515
  });
516
+ }
517
+ for (const { element, ...values } of writes)
518
+ for (const [property, value] of Object.entries(values)) {
519
+ const next = `${value}px`;
520
+ if (element.style.getPropertyValue(property) !== next)
521
+ element.style.setProperty(property, next, 'important');
522
+ }
429
523
  };
430
- const render = () => {
431
- const image = active ?? cached;
524
+ const render = (image = active ?? cached) => {
432
525
  if (!image || ending)
433
- return;
526
+ return false;
434
527
  if (!image.sources.every(validSource) || targetsFor(image).some((target) => !target)) {
435
528
  invalidate();
436
- return;
529
+ return false;
437
530
  }
438
531
  const targets = targetsFor(image);
439
532
  if (active) {
440
533
  if (targets.some((target) => !target?.isConnected || !sheets.has(rootFor(target)))) {
441
534
  abandon(active, false);
442
- return;
535
+ return false;
443
536
  }
444
537
  const next = new Set();
445
538
  for (const [index, source] of image.targets.entries()) {
@@ -449,7 +542,6 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
449
542
  continue;
450
543
  target.setAttribute(imageMarker, key);
451
544
  target.setAttribute('data-native-ui-snapshot-source', '');
452
- target.toggleAttribute('data-native-ui-snapshot-visible', presented);
453
545
  next.add(target);
454
546
  }
455
547
  for (const target of rendered)
@@ -457,83 +549,65 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
457
549
  clearTarget(target);
458
550
  rendered.clear();
459
551
  next.forEach((target) => rendered.add(target));
460
- return;
552
+ showImages(image, presented);
553
+ return true;
461
554
  }
555
+ imagePool.forEach((pool) => pool.forEach((element) => {
556
+ if (!image.images.includes(element)) {
557
+ element.style.setProperty('display', 'none', 'important');
558
+ element.removeAttribute('data-native-ui-projection-image');
559
+ }
560
+ }));
462
561
  preparedKeys.clear();
463
562
  offsets.clear();
464
563
  rendered.forEach(clearTarget);
465
564
  rendered.clear();
466
- // Reuse the image stylesheet. Presentation states belong to the runtime's
467
- // permanent CSS, which survives cache replacement and image retirement.
468
- const groups = new Map();
469
565
  for (const target of new Set(targets)) {
470
566
  if (!target)
471
567
  continue;
472
568
  rendered.add(target);
473
- const sheet = sheetFor(rootFor(target));
474
- let group = groups.get(sheet);
475
- if (!group) {
476
- group = { rules: [], positions: [] };
477
- groups.set(sheet, group);
478
- }
479
- const { rules, positions } = group;
480
- // A decoded clone stays concealed even if this cache is invalidated before
481
- // the projection adapter publishes its next Native/Web ownership state.
482
- if (target !== image.targets[targets.indexOf(target)] && !target.hasAttribute('data-native-ui-snapshot-prepared')) {
569
+ sheetFor(rootFor(target));
570
+ const indices = targets.flatMap((owner, index) => (owner === target ? [index] : []));
571
+ const key = `${imageIdentity}-${indices[0]}`;
572
+ target.setAttribute(imageMarker, key);
573
+ if (target !== image.targets[indices[0]]) {
483
574
  target.setAttribute('data-native-ui-snapshot-prepared', '');
484
575
  preparedTargets.add(target);
485
576
  }
486
- target.toggleAttribute('data-native-ui-snapshot-visible', !!active || !!callbacks.opaqueUnderlay);
487
- if (active)
488
- target.setAttribute('data-native-ui-snapshot-source', '');
489
- const indices = targets.flatMap((owner, index) => (owner === target ? [index] : []));
490
- const style = win.getComputedStyle(target);
491
- {
492
- const space = coordinateSpace(target, win, style);
493
- indices.forEach((index) => {
494
- const part = image.data.parts[index];
495
- if (callbacks.opaqueUnderlay && image.frames[index])
496
- return;
497
- image.frames[index] = {
498
- x: callbacks.opaqueUnderlay ? 0 : (part.x - space.x) / space.scaleX,
499
- y: callbacks.opaqueUnderlay ? 0 : (part.y - space.y) / space.scaleY,
500
- width: part.width / space.scaleX,
501
- height: part.height / space.scaleY,
502
- };
503
- });
577
+ target.toggleAttribute('data-native-ui-snapshot-visible', !!callbacks.opaqueUnderlay);
578
+ syncShadowContent(target);
579
+ const box = target.getBoundingClientRect();
580
+ target.toggleAttribute('data-native-ui-snapshot-positioned', win.getComputedStyle(target).position === 'static');
581
+ const parent = target.shadowRoot ?? target;
582
+ for (const index of [...indices].reverse()) {
583
+ const element = image.images[index];
584
+ const part = image.data.parts[index];
585
+ const frame = image.frames[index] ?? {
586
+ x: callbacks.opaqueUnderlay ? 0 : part.x - box.x,
587
+ y: callbacks.opaqueUnderlay ? 0 : part.y - box.y,
588
+ width: part.width,
589
+ height: part.height,
590
+ };
591
+ image.frames[index] = frame;
592
+ preparedKeys.set(image.targets[index], key);
593
+ element.inert = true;
594
+ element.setAttribute('aria-hidden', 'true');
595
+ element.setAttribute('data-native-ui-projection-image', key);
596
+ element.style.cssText =
597
+ 'all:initial!important;position:absolute!important;display:block!important;visibility:visible!important;pointer-events:none!important;image-rendering:pixelated!important;will-change:opacity!important;opacity:0!important;';
598
+ element.style.setProperty('width', `${frame.width}px`, 'important');
599
+ element.style.setProperty('height', `${frame.height}px`, 'important');
600
+ element.style.setProperty('left', '0px', 'important');
601
+ element.style.setProperty('top', '0px', 'important');
602
+ element.style.setProperty('opacity', callbacks.opaqueUnderlay ? '1' : '0', 'important');
603
+ if (element.parentNode !== parent)
604
+ parent.insertBefore(element, null);
605
+ offsets.set(element, { target, frame });
504
606
  }
505
- const frames = indices.map((index) => image.frames[index]);
506
- const x = Math.min(...frames.map((frame) => frame.x));
507
- const y = Math.min(...frames.map((frame) => frame.y));
508
- const right = Math.max(...frames.map((frame) => frame.x + frame.width));
509
- const bottom = Math.max(...frames.map((frame) => frame.y + frame.height));
510
- const left = (parseFloat(style.borderLeftWidth) || 0) - target.scrollLeft;
511
- const top = (parseFloat(style.borderTopWidth) || 0) - target.scrollTop;
512
- const key = `${imageIdentity}-${indices[0]}`;
513
- indices.forEach((index) => preparedKeys.set(image.targets[index], key));
514
- target.setAttribute(imageMarker, key);
515
- rules.push(`[${imageMarker}="${key}"][data-native-ui-snapshot-visible] { ${style.position === 'static' ? 'position:relative!important;' : ''} }`);
516
- const backgrounds = indices.map((index) => {
517
- const frame = image.frames[index];
518
- return `url(${JSON.stringify(image.images[index].src)}) ${frame.x - x}px ${frame.y - y}px / ${frame.width}px ${frame.height}px no-repeat`;
519
- });
520
- const declaration = doc.createElement('span').style;
521
- declaration.cssText = `left:${x - left}px;top:${y - top}px;width:${right - x}px;height:${bottom - y}px;background:${backgrounds.join(',')};`;
522
- for (const property of Array.from(declaration))
523
- declaration.setProperty(property, declaration.getPropertyValue(property), 'important');
524
- // Outrank every manager's shared reset, even when another manager starts later.
525
- rules.push(`[${imageMarker}="${key}"][${imageMarker}][${imageMarker}]::before { ${declaration.cssText} }`);
526
- positions.push({ target, x, y });
527
607
  }
528
608
  image.parents = image.scopes.map((scope) => scope.element.parentNode);
529
- for (const [sheet, { rules, positions }] of groups) {
530
- sheet.textContent = rules.join('\n');
531
- positions.forEach(({ target, x, y }, index) => offsets.set(target, {
532
- x,
533
- y,
534
- style: sheet.sheet.cssRules[index * 2 + 1].style,
535
- }));
536
- }
609
+ updateOffsets();
610
+ return true;
537
611
  };
538
612
  const begin = () => {
539
613
  retireRestoration();
@@ -565,10 +639,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
565
639
  image.sources.forEach((source) => {
566
640
  owners.add(source);
567
641
  });
568
- rendered.forEach((target) => {
569
- target.setAttribute('data-native-ui-snapshot-source', '');
570
- target.toggleAttribute('data-native-ui-snapshot-visible', presented);
571
- });
642
+ rendered.forEach((target) => target.setAttribute('data-native-ui-snapshot-source', ''));
643
+ showImages(image, presented);
572
644
  // Reconciliation may continue after one frame even if the optional bridge reply
573
645
  // is delayed. The ordered native transport processes cover before that update.
574
646
  paintFrame = win.requestAnimationFrame(() => {
@@ -594,7 +666,6 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
594
666
  image.sources.forEach((source) => visibleOwners.add(source));
595
667
  render();
596
668
  updateOffsets();
597
- followBorderAnimation();
598
669
  }
599
670
  win.cancelAnimationFrame(paintFrame);
600
671
  paintFrame = win.requestAnimationFrame(() => {
@@ -625,6 +696,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
625
696
  }
626
697
  if (callbacks.blocked())
627
698
  return;
699
+ updateOffsets();
628
700
  if (callbacks.transient) {
629
701
  const image = active;
630
702
  const targets = targetsFor(image);
@@ -633,15 +705,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
633
705
  return;
634
706
  }
635
707
  const parts = targets.map((target, index) => {
636
- const space = coordinateSpace(target, win);
637
- const frame = image.frames[index];
638
- return {
639
- ...image.data.parts[index],
640
- x: space.x + frame.x * space.scaleX,
641
- y: space.y + frame.y * space.scaleY,
642
- width: frame.width * space.scaleX,
643
- height: frame.height * space.scaleY,
644
- };
708
+ const rect = image.images[index].getBoundingClientRect();
709
+ return { ...image.data.parts[index], x: rect.x, y: rect.y, width: rect.width, height: rect.height };
645
710
  });
646
711
  if (parts.some((part, index) => ['x', 'y', 'width', 'height'].some((key) => part[key] !== image.data.parts[index][key]))) {
647
712
  if (callbacks.opaqueUnderlay)
@@ -689,7 +754,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
689
754
  .catch(() => { })
690
755
  .finally(() => {
691
756
  previous.images.forEach((image) => {
692
- image.src = '';
757
+ retireImage(image);
693
758
  });
694
759
  if (retiring !== pending)
695
760
  return;
@@ -762,10 +827,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
762
827
  },
763
828
  consider,
764
829
  changed,
765
- moved: () => {
766
- updateOffsets();
767
- followBorderAnimation();
768
- },
830
+ moved: updateOffsets,
769
831
  get token() {
770
832
  return ending ? undefined : active?.data.token;
771
833
  },
@@ -783,6 +845,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
783
845
  async destroy() {
784
846
  stopped = true;
785
847
  invalidate();
848
+ imagePool.forEach((pool) => pool.forEach((image) => image.remove()));
849
+ imagePool.clear();
786
850
  await Promise.allSettled([
787
851
  bounded(Promise.resolve().then(() => listener?.remove())),
788
852
  bounded(Promise.resolve().then(() => eventListener?.remove())),
@@ -277,3 +277,7 @@ If your control continues animating after its update notification, set `captureD
277
277
  on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
278
278
  This changes background image preparation, not overlay presentation timing.
279
279
  See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
280
+
281
+ ### Projection DOM
282
+
283
+ The engine keeps a reusable image inside the fallback container, using its open ShadowRoot when available. No placement option is needed. If the renderer inspects child elements, exclude nodes marked `data-native-ui-projection-node`. See [DOM requirements](projection.md#dom-and-native-surface-requirements).
@@ -196,3 +196,7 @@ If your control continues animating after its update notification, set `captureD
196
196
  on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
197
197
  This changes background image preparation, not overlay presentation timing.
198
198
  See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
199
+
200
+ ### Projection DOM
201
+
202
+ The engine keeps a reusable image inside the fallback container, using its open ShadowRoot when available. No placement option is needed. If the renderer inspects child elements, exclude nodes marked `data-native-ui-projection-node`. See [DOM requirements](projection.md#dom-and-native-surface-requirements).
@@ -0,0 +1,195 @@
1
+ # Projection container support
2
+
3
+ Choose the existing DOM element that occupies the native surface's intended location and Web layer. These tables apply to `projectionSelector` and the element passed to `createWebProjectionScope`, for both Capacitor and Cordova.
4
+
5
+ **⭕️** means the element can contain the Engine's projected image under the stated conditions. **❌** means it is not a supported direct destination; select a suitable surrounding container instead. This is a DOM presentation contract, not a promise that the Engine implements that component's native control, interactions or animations. Native capture eligibility is a separate requirement.
6
+
7
+ ## Shared requirements
8
+
9
+ - Provide one connected HTML element with a nonzero, unfragmented layout box. Its size, placement and layer belong to the application. For inline hosts, use `display: inline-block` or `block` and give them the intended dimensions.
10
+ - `projectionSelector` uses document CSS selectors (ID, class, attribute or descendant). It must match exactly one element and does not enter Shadow DOM. A missing element can be created later; zero or multiple matches keep Native hidden.
11
+ - The retained image is inserted in the element's open ShadowRoot, or as a light-DOM child otherwise. Closed shadow content must inherit the host's visibility and expose a slot that can display the image.
12
+ - An overlay, collapsed item or inactive page is eligible only while it has a visible layout box. Do not force hidden configuration elements to render just to use them as destinations.
13
+ - Host/ancestor clipping, opacity, transforms and filters still affect the image. The image is noninteractive and freezes the captured appearance. See [projection behavior](projection.md#dom-and-native-surface-requirements) for lifecycle, mutation-observer exclusions and native capture limits.
14
+
15
+ ## HTML containers
16
+
17
+ | Container element | Example | Support | Notes |
18
+ | --------------------------------------------------------- | -------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | Block container | `div`, `section`, `nav`, `article`, `header`, `footer` | ⭕️ | Give the container the intended size and Web layer. |
20
+ | Inline container | `span`, `a`, `label` | ⭕️ | Use `inline-block` or `block`; wrapped inline text is not a single projection surface. |
21
+ | Button | `button` | ⭕️ | The original button, listeners and values remain; the image does not receive clicks. |
22
+ | Custom element without Shadow DOM | `native-ad` | ⭕️ | Provide a layout box and allow an image child. |
23
+ | Custom element with open Shadow DOM | `native-control` | ⭕️ | The Engine inserts the image directly in the open root and hides fallback content during projection. |
24
+ | Custom element with closed Shadow DOM and a suitable slot | `native-control` with a default slot | ⭕️ | The image must be slotted visibly; internal content must inherit host visibility. The Engine cannot enforce rules inside the closed root. |
25
+ | Custom element with inaccessible rendering content | Closed root without a suitable slot | ❌ | Use an outer container; a light-DOM image would not be rendered. |
26
+ | Native form control | `select`, `input`, `textarea` | ❌ | Direct scopes are rejected. Wrap the control; its value and listeners are preserved. |
27
+ | Replaced media or drawing surface | `img`, `canvas`, `video`, `audio`, `iframe`, `embed`, `object` | ❌ | Direct scopes are rejected. Use a container around the content. |
28
+ | Void element | `br`, `hr`, `area`, `base`, `link`, `meta`, `source`, `track`, `wbr` | ❌ | Direct scopes are rejected; these cannot contain a projected image. |
29
+ | Configuration or special child-rendering element | `option`, `optgroup`, `template` | ❌ | Acceptance by an HTMLElement check does not make an image child renderable. Use a normal container. |
30
+ | Non-HTML element | `svg`, SVG `g`, MathML `math` | ❌ | Use an HTML wrapper, not the SVG/MathML node itself. |
31
+ | Element without a usable box | `display: none`, `display: contents`, zero size | ❌ | Choose a visible container with the intended dimensions. |
32
+ | Pseudo-element | `::before`, `::after` | ❌ | Not DOM elements and cannot be selected as a scope. Both remain available to the application's CSS. |
33
+
34
+ ## Ionic elements
35
+
36
+ The following inventory covers every component in the [Ionic API index](https://ionicframework.com/docs/api), with one row per element. It was checked against `@ionic/core` **9.0.9**; `ion-icon` is supplied by Ionicons. The final table separately lists the additional internal component shipped in that version.
37
+
38
+ Ionic custom elements are not the HTML elements with similar names: `ion-select`, `ion-input`, `ion-textarea` and `ion-img` can be image containers, while HTML `select`, `input`, `textarea` and `img` cannot. Every ⭕️ below still requires the shared layout conditions above and the component's normal Ionic parent/context.
39
+
40
+ ### Buttons and toolbar
41
+
42
+ | Container element | Example | Support | Notes |
43
+ | ------------------- | ------------------------------- | ------- | ------------------------------------------------------------------------------------ |
44
+ | `ion-button` | `ion-button#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
45
+ | `ion-buttons` | `ion-buttons#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
46
+ | `ion-back-button` | `ion-back-button#native-slot` | ⭕️ | Requires a back destination or defaultHref to remain visible. |
47
+ | `ion-menu-button` | `ion-menu-button#native-slot` | ⭕️ | Requires an available menu, or autoHide=false. |
48
+ | `ion-toolbar` | `ion-toolbar#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
49
+ | `ion-header` | `ion-header#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
50
+ | `ion-footer` | `ion-footer#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
51
+ | `ion-title` | `ion-title#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
52
+ | `ion-ripple-effect` | `ion-ripple-effect#native-slot` | ⭕️ | A captured decorative surface, not a button. Prefer the owning button for a control. |
53
+
54
+ ### Forms and selection
55
+
56
+ | Container element | Example | Support | Notes |
57
+ | --------------------------- | --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------- |
58
+ | `ion-checkbox` | `ion-checkbox#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
59
+ | `ion-input` | `ion-input#native-slot` | ⭕️ | The host is supported; its internal HTML input is not a destination. |
60
+ | `ion-input-otp` | `ion-input-otp#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
61
+ | `ion-input-password-toggle` | `ion-input-password-toggle#native-slot` | ⭕️ | Use inside ion-input; give the host an inline-block box to avoid inline baseline offsets. |
62
+ | `ion-textarea` | `ion-textarea#native-slot` | ⭕️ | The host is supported; its internal HTML textarea is not a destination. |
63
+ | `ion-radio` | `ion-radio#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
64
+ | `ion-radio-group` | `ion-radio-group#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
65
+ | `ion-range` | `ion-range#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
66
+ | `ion-searchbar` | `ion-searchbar#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
67
+ | `ion-select` | `ion-select#native-slot` | ⭕️ | The host is supported. Its separately presented options overlay is not part of the host image. |
68
+ | `ion-select-option` | `ion-select-option#native-slot` | ❌ | Configuration for ion-select; no standalone rendered box. Select ion-select instead. |
69
+ | `ion-toggle` | `ion-toggle#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
70
+ | `ion-datetime` | `ion-datetime#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
71
+ | `ion-datetime-button` | `ion-datetime-button#native-slot` | ⭕️ | Connect the intended ion-datetime; the opened picker is a separate surface. |
72
+ | `ion-picker` | `ion-picker#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
73
+ | `ion-picker-column` | `ion-picker-column#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
74
+ | `ion-picker-column-option` | `ion-picker-column-option#native-slot` | ⭕️ | A rendered picker option, unlike the non-rendered ion-select-option. Keep its picker-column parent. |
75
+
76
+ ### Content and layout
77
+
78
+ | Container element | Example | Support | Notes |
79
+ | ----------------- | ---------------------------- | ------- | ---------------------------------------------------------------------------------------------- |
80
+ | `ion-app` | `ion-app#native-slot` | ⭕️ | Targets the whole app-sized box, not one nested control. |
81
+ | `ion-content` | `ion-content#native-slot` | ⭕️ | Targets the outer content surface; for one scrolled control, select its own container instead. |
82
+ | `ion-grid` | `ion-grid#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
83
+ | `ion-row` | `ion-row#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
84
+ | `ion-col` | `ion-col#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
85
+ | `ion-split-pane` | `ion-split-pane#native-slot` | ⭕️ | Targets the full split layout; select a child container for one pane. |
86
+
87
+ ### Cards, text and media
88
+
89
+ | Container element | Example | Support | Notes |
90
+ | ------------------- | ------------------------------- | ------- | ------------------------------------------------------------------------------------- |
91
+ | `ion-card` | `ion-card#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
92
+ | `ion-card-content` | `ion-card-content#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
93
+ | `ion-card-header` | `ion-card-header#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
94
+ | `ion-card-title` | `ion-card-title#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
95
+ | `ion-card-subtitle` | `ion-card-subtitle#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
96
+ | `ion-text` | `ion-text#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
97
+ | `ion-label` | `ion-label#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
98
+ | `ion-note` | `ion-note#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
99
+ | `ion-badge` | `ion-badge#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
100
+ | `ion-chip` | `ion-chip#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
101
+ | `ion-avatar` | `ion-avatar#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
102
+ | `ion-thumbnail` | `ion-thumbnail#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
103
+ | `ion-img` | `ion-img#native-slot` | ⭕️ | Select the Ionic host, not the internal HTML img. Wait for the intended image size. |
104
+ | `ion-icon` | `ion-icon#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
105
+ | `ion-skeleton-text` | `ion-skeleton-text#native-slot` | ⭕️ | The image preserves one captured state; it does not continue the indicator animation. |
106
+ | `ion-spinner` | `ion-spinner#native-slot` | ⭕️ | The image preserves one captured state; it does not continue the indicator animation. |
107
+ | `ion-progress-bar` | `ion-progress-bar#native-slot` | ⭕️ | The image preserves one captured state; it does not continue the indicator animation. |
108
+
109
+ ### Lists and gestures
110
+
111
+ | Container element | Example | Support | Notes |
112
+ | ----------------------------- | ----------------------------------------- | ------- | --------------------------------------------------------------------------------------- |
113
+ | `ion-accordion` | `ion-accordion#native-slot` | ⭕️ | Targets the current accordion box; expansion changes require a geometry update. |
114
+ | `ion-accordion-group` | `ion-accordion-group#native-slot` | ⭕️ | Targets the group box, including its current expansion state. |
115
+ | `ion-list` | `ion-list#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
116
+ | `ion-list-header` | `ion-list-header#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
117
+ | `ion-item` | `ion-item#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
118
+ | `ion-item-divider` | `ion-item-divider#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
119
+ | `ion-item-group` | `ion-item-group#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
120
+ | `ion-item-sliding` | `ion-item-sliding#native-slot` | ⭕️ | Targets the whole sliding row; the image does not implement the swipe gesture. |
121
+ | `ion-item-options` | `ion-item-options#native-slot` | ⭕️ | Only while the options are revealed by the parent sliding item. |
122
+ | `ion-item-option` | `ion-item-option#native-slot` | ⭕️ | Only while the parent item-options is revealed. |
123
+ | `ion-infinite-scroll` | `ion-infinite-scroll#native-slot` | ⭕️ | Requires a visible, nonzero box; loading and scroll behavior stay with Ionic. |
124
+ | `ion-infinite-scroll-content` | `ion-infinite-scroll-content#native-slot` | ⭕️ | The image freezes the current loading indicator. |
125
+ | `ion-refresher` | `ion-refresher#native-slot` | ⭕️ | Only in its visible active state inside ion-content; hidden idle state is not eligible. |
126
+ | `ion-refresher-content` | `ion-refresher-content#native-slot` | ⭕️ | Use within an active refresher; the image freezes the current indicator. |
127
+ | `ion-reorder` | `ion-reorder#native-slot` | ⭕️ | Keep the item/reorder-group context; the image does not implement dragging. |
128
+ | `ion-reorder-group` | `ion-reorder-group#native-slot` | ⭕️ | Targets the whole group; ordering and drag behavior remain with Ionic. |
129
+
130
+ ### Navigation and tabs
131
+
132
+ | Container element | Example | Support | Notes |
133
+ | --------------------- | --------------------------------- | ------- | -------------------------------------------------------------------------------------------- |
134
+ | `ion-breadcrumb` | `ion-breadcrumb#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
135
+ | `ion-breadcrumbs` | `ion-breadcrumbs#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
136
+ | `ion-nav` | `ion-nav#native-slot` | ⭕️ | Targets the navigation viewport; use the current page container for a page-specific surface. |
137
+ | `ion-nav-link` | `ion-nav-link#native-slot` | ⭕️ | Give this action wrapper an inline-block/block box. |
138
+ | `ion-router` | `ion-router#native-slot` | ❌ | Routing configuration, not a visible surface. Select the rendered page container. |
139
+ | `ion-router-link` | `ion-router-link#native-slot` | ⭕️ | Give this action wrapper an inline-block/block box. |
140
+ | `ion-router-outlet` | `ion-router-outlet#native-slot` | ⭕️ | Targets the outlet viewport; use the active page container for page-specific placement. |
141
+ | `ion-route` | `ion-route#native-slot` | ❌ | Route definition, not a visible surface. Select the rendered page container. |
142
+ | `ion-route-redirect` | `ion-route-redirect#native-slot` | ❌ | Redirect definition, not a visible surface. Select the rendered page container. |
143
+ | `ion-tabs` | `ion-tabs#native-slot` | ⭕️ | Targets the tabs viewport. Use ion-tab-bar for only the bar. |
144
+ | `ion-tab` | `ion-tab#native-slot` | ⭕️ | Only the active, visible tab is eligible. |
145
+ | `ion-tab-bar` | `ion-tab-bar#native-slot` | ⭕️ | The bar is one surface; use ion-tab-button for individual tabs. |
146
+ | `ion-tab-button` | `ion-tab-button#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
147
+ | `ion-segment` | `ion-segment#native-slot` | ⭕️ | The segment is one surface; use ion-segment-button for an individual control. |
148
+ | `ion-segment-button` | `ion-segment-button#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
149
+ | `ion-segment-view` | `ion-segment-view#native-slot` | ⭕️ | Targets the view viewport; individual pages use ion-segment-content. |
150
+ | `ion-segment-content` | `ion-segment-content#native-slot` | ⭕️ | Use the visible segment page, not an offscreen page. |
151
+
152
+ ### Overlays, menus and FAB
153
+
154
+ | Container element | Example | Support | Notes |
155
+ | ------------------ | ------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
156
+ | `ion-action-sheet` | `ion-action-sheet#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
157
+ | `ion-alert` | `ion-alert#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
158
+ | `ion-modal` | `ion-modal#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
159
+ | `ion-popover` | `ion-popover#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
160
+ | `ion-loading` | `ion-loading#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
161
+ | `ion-toast` | `ion-toast#native-slot` | ⭕️ | Only while presented. Targets the overlay host box, not automatically its inner panel; prefer a container inside the overlay for one control. |
162
+ | `ion-backdrop` | `ion-backdrop#native-slot` | ⭕️ | A decorative covering surface. This does not configure overlay detection or native z-order. |
163
+ | `ion-menu` | `ion-menu#native-slot` | ⭕️ | Only while presented with a visible box; its open/close behavior remains with Ionic. |
164
+ | `ion-menu-toggle` | `ion-menu-toggle#native-slot` | ⭕️ | Requires visible content; use inline-block/block and account for autoHide. |
165
+ | `ion-fab` | `ion-fab#native-slot` | ⭕️ | Use the visible host with its intended layout box. |
166
+ | `ion-fab-button` | `ion-fab-button#native-slot` | ⭕️ | The button is a surface independently of its parent FAB group. |
167
+ | `ion-fab-list` | `ion-fab-list#native-slot` | ⭕️ | Only while expanded; retain its ion-fab parent and side placement. |
168
+
169
+ ### Internal component
170
+
171
+ | Container element | Example | Support | Notes |
172
+ | ------------------ | ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
173
+ | `ion-select-modal` | Internal select overlay content | ❌ | Shipped by Ionic 9.0.9 but not a public destination contract. Select the public ion-select host or an application-owned container. |
174
+
175
+ ## Wrapping an unsupported element
176
+
177
+ Select `#seat-projection`, not the HTML `select`:
178
+
179
+ ```html
180
+ <span id="seat-projection" style="display: inline-block; width: 12rem; height: 2.75rem">
181
+ <select aria-label="Seat" style="width: 100%; height: 100%">
182
+ <option>Any seat</option>
183
+ </select>
184
+ </span>
185
+ ```
186
+
187
+ The control's DOM, value and event listeners survive handoff. The plugin still owns native behavior and value synchronization; the prepared image does not accept input.
188
+
189
+ ## Verification scope
190
+
191
+ Temporary Chromium and WebKit probes exercise the Ionic inventory with an explicit layout box, required parents and visible states. A solid-color prepared image checks placement and visible pixel coverage; the probes also check original child identity/value preservation and removal of Engine images on teardown. Configuration elements are checked for the absence of a renderable box. The refresher probe selects its active CSS state; it does not validate the pull gesture. Animated loading indicators can change between the before/after captures.
192
+
193
+ In both browsers, all 90 supported public Ionic elements had zero measured image-frame difference, full solid-color coverage in the inspected interior, preserved original children/values and no retained Engine image after destruction. The four public configuration elements had no renderable box. The internal component was also probed but remains outside the supported public contract. HTML probes cover 41 element/layout combinations, including open and closed shadow roots.
194
+
195
+ These are DOM-container checks, not native capture, SDK eligibility or complete Ionic interaction tests. They do not guarantee every theme, closed-shadow implementation or application stylesheet. The probes are not part of the library's permanent test suite.
@@ -30,15 +30,19 @@ The Engine owns image readiness and restoration acknowledgement. It must not hid
30
30
 
31
31
  Registration and notification forwarding retain the latest intent across startup and reconnection; they do not replay business events. Destruction retires the registration and its cached images. Notifications are intent, not completion promises: an API that promises completed hiding must await the explicit retirement acknowledgement described under DOM and native surface requirements.
32
32
 
33
- An existing renderer that checks CSS visibility can call `getUnprojectedVisibility(element)` from the npm package. It reads current authored/inherited visibility without the Engine's hiding rules, and restores projection ownership before returning. Do not inspect or mutate Engine marker attributes in a consuming plugin. This helper performs a style read and can trigger attribute observers; it does not eliminate measurement or transport costs.
33
+ An existing renderer that checks CSS visibility can call `getUnprojectedVisibility(element)` from the npm package. It reads current authored/inherited visibility without the Engine's hiding rules, and restores projection ownership before returning. Do not use Engine marker attributes to infer projection ownership or mutate them in a consuming plugin. Excluding Engine-owned DOM mutations from renderer invalidation is described below. This helper performs a style read and can trigger attribute observers; it does not eliminate measurement or transport costs.
34
34
 
35
35
  ## DOM and native surface requirements
36
36
 
37
- `projectionSelector` is required and must resolve to **one existing container when projection starts**, including one retrieved by the plugin's own `querySelector`. The engine does not create a DOM or choose its size, placement, layer or z-index. Keep the container's normal layout and any interactive Web controls; an empty zero-size placeholder is not a usable fallback. It is a fixed scope, not a discovery root.
37
+ `projectionSelector` is required and must resolve to **one existing container when projection starts**, using a valid CSS selector. ID, class, attribute and descendant selectors are supported; zero or multiple matches leave Native hidden. The engine does not create the fallback container or choose its size, placement, layer or z-index. Keep the container's normal layout and any interactive Web controls; an empty zero-size placeholder is not a usable fallback. It is a fixed scope, not a discovery root.
38
38
 
39
39
  The container does not have to exist at registration time. A missing DOM waits without showing native content; replacing it creates a new binding and retires old images. The engine preserves the container's children, listeners and values. Pass the native view itself through `view`; a group of controls can use their common native container. The engine manages the internal identifier, so no ID list or view dictionary is required. Independent surfaces use separate registrations.
40
40
 
41
- A Web Component host can be the destination, for example `projectionSelector: 'native-ad[placement="home"]'`. Give the host a layout box (`display: block` with the intended dimensions); the engine observes insertion, removal and selector attribute changes. The selector runs in the document and does not search inside shadow roots. The fallback content, including shadow content, must inherit the host's `visibility`: do not override it with `visibility: visible`, and reserve the host's `::before` for the projection image. These requirements apply to open and closed shadow roots.
41
+ A Web Component host can be the destination, for example `projectionSelector: 'native-ad[placement="home"]'`. Give the host a layout box (`display: block` with the intended dimensions); the engine observes insertion, removal and selector attribute changes. The selector runs in the document and does not search inside shadow roots. The engine suppresses fallback content and pseudo-elements in the light DOM and open shadow roots during projection. Closed shadow content cannot be inspected and must inherit the host's `visibility`; provide a container whose shadow/slot structure can display the inserted image.
42
+
43
+ See [Projection container support](projection-containers.md) for the HTML and complete Ionic element tables, sizing conditions and a wrapper example.
44
+
45
+ ### Native surface requirements
42
46
 
43
47
  For independently moving AdMob slots, retain one registration per loaded native view/surface, use a slot-specific selector and filter the plugin's notifications through `receiveListeners.match` (`match: ["slotKey": slotKey, "loadId": loadId]` / `match = mapOf("slotKey" to slotKey, "loadId" to loadId)`). Dispose the registration when a loaded ad is replaced, even if its host view is reused. Keep SDK loading, impression and click handling in the ad plugin. Supply `canCapture` with its existing capture eligibility checks (for example, test-ad / opaque-background / no-video restrictions); the engine does not grant permission to snapshot an SDK view.
44
48
 
@@ -178,7 +182,13 @@ The container, bridge and update callbacks above belong to the consuming plugin.
178
182
  5. Call `projection.end()`, send the normal native update, then call `projection.updated(snapshotToken, acceptedIDs, revision)`. A newer acknowledgement containing all captured IDs retires the image.
179
183
  6. Call `projection.destroy()` when disposing the surface. It releases only its own images and listener; the plugin owns final Native reset and its DOM.
180
184
 
181
- The engine uses a noninteractive, empty CSS `::before` pseudo-element for the prepared image. Supply a container that supports it, such as an existing `div`, `button` or custom element. Replaced/void elements such as `img`, `input` and `video` are rejected when creating the scope; use their existing fallback container. It does not clone, replace, remove or set `inert`/`aria-hidden` on the existing fallback or its children. Removing its temporary image styles restores the same live DOM, including application changes made during projection. Event listeners and form values are preserved. The renderer may move that same element between parents; it must not replace the scope's element or let two managers control it simultaneously.
185
+ The engine prepares a noninteractive `img` inside the fallback container (directly inside its open ShadowRoot when present) while Native is visible. It reuses the decoded image and DOM node across handoffs, and releases them when the scope is retired or destroyed. The image is `inert`, hidden from accessibility and excluded from hit testing. The existing fallback and its children are never cloned, replaced, removed or assigned `inert`/`aria-hidden`; their listeners, values and application changes survive projection. Both `::before` and `::after` remain available to the application.
186
+
187
+ Supply an existing container such as `div`, `button` or a custom element. Replaced/void elements such as `img`, `input` and `video` remain unsupported as scopes; use their existing fallback container. The renderer may move the same container between parents, but must not replace a low-level scope's element or let two managers control it simultaneously. Notify the manager through `moved()` when changing its placement during projection.
188
+
189
+ The image is absolutely positioned within the fallback, which retains its layout box. As with the previous pseudo-element presentation, static hosts become positioned while the image is visible. Host and ancestor clipping, transforms and filters still apply. Open ShadowRoot insertion bypasses internal slot wrappers, not the host's own clipping. Closed shadow roots cannot be inspected; provide a fallback container that can render the image.
190
+
191
+ Engine-owned nodes carry `data-native-ui-projection-node` for their entire pooled lifetime. Renderers that inspect fallback children must exclude these nodes from application content discovery. A renderer that uses a MutationObserver to invalidate captures must also ignore insertion/removal and attribute changes of these nodes, changes inside them, and Engine-owned `data-native-ui-snapshot-*` attributes. Continue observing authored content, dimensions and disabled-state changes; ignoring all mutations on a projection host would hide real updates. Otherwise image preparation can invalidate itself and cause repeated capture/release cycles. The engine removes only its own nodes on disposal. Child insertion can affect structural selectors within the fallback, but does not add items alongside it in flex, grid or virtual lists.
182
192
 
183
193
  ### Notifications from the existing plugin
184
194
 
@@ -1,14 +1,22 @@
1
- "use strict";var WebViewNativeUIEngineRuntime=(()=>{var Ve=Object.defineProperty;var st=Object.getOwnPropertyDescriptor;var at=Object.getOwnPropertyNames;var ct=Object.prototype.hasOwnProperty;var lt=(e,o)=>{for(var n in o)Ve(e,n,{get:o[n],enumerable:!0})},dt=(e,o,n,p)=>{if(o&&typeof o=="object"||typeof o=="function")for(let v of at(o))!ct.call(e,v)&&v!==n&&Ve(e,v,{get:()=>o[v],enumerable:!(p=st(o,v))||p.enumerable});return e};var ut=e=>dt(Ve({},"__esModule",{value:!0}),e);var xt={};lt(xt,{hide:()=>Mt,notify:()=>bt,receive:()=>Tt,show:()=>Lt,start:()=>jt,stop:()=>St});var ft=Symbol.for("webview-native-ui-engine.projection-placement");var De=e=>e?.[ft];var pt=(e,o)=>{if(e.matches("ion-toast, ion-menu"))return!1;let n=e;if(n.showBackdrop===!1)return!1;if(!e.matches("ion-modal"))return!0;let p=o??n.initialBreakpoint;return p===void 0||p>(n.backdropBreakpoint??0)},mt=Symbol.for("@webview-native-ui/engine/overlays/v1"),vt=e=>{let o=new Set,n=new Set,p=new Set,v=new WeakMap,l=new WeakSet,d=new Map,E=[],u={},S=new Map,M,T=h=>{for(let c of[...p])try{c(h)}catch(y){console.error("Native UI overlay listener failed",y)}},N=(h,c,y)=>{c==="willPresent"||c==="didPresent"?(l.delete(h),o.add(h)):c==="didDismiss"&&(l.add(h),o.delete(h),n.delete(h),v.delete(h));let a=y?.detail?.breakpoint;typeof a=="number"&&a>0&&v.set(h,a),T({element:h,phase:c,event:y})},O=(h=!1)=>{let c=!1;for(let y of o)(!y.isConnected||y.ownerDocument!==e||!E.some(({selector:a})=>y.matches(a)))&&(o.delete(y),n.delete(y),c=!0);for(let y of e.querySelectorAll(E.map(({selector:a})=>a).join(",")))!o.has(y)&&!l.has(y)&&(h||!y.matches("ion-popover"))&&(y.presented||y.classList.contains("show-menu"))&&(o.add(y),c=!0);c&&T({phase:"change"})};return{get active(){return o},subscribe(h){return p.add(h),()=>{p.delete(h)}},setNative(h,c){n.has(h)!==c&&(c?n.add(h):n.delete(h),T({element:h,phase:"change"}))},isExcluded(h){return!!u.exclude&&h.matches(u.exclude)},isNative(h){return n.has(h)},get stack(){let h=[...o].filter(y=>y.isConnected&&y.ownerDocument===e),c=y=>{let a=Number.parseFloat(e.defaultView.getComputedStyle(y).zIndex);return Number.isFinite(a)?a:y.overlayIndex??0};return h.sort((y,a)=>c(y)-c(a)),h},isCovered(h){let c=this.stack,y=c.reduce((a,U,j)=>U.contains(h)?j:a,-1);return c.some((a,U)=>{if(n.has(a)||u.exclude&&a.matches(u.exclude))return!1;let j=u.covers?.(a,h);return j!==void 0?j:U>y&&!a.contains(h)&&(a.matches("ion-menu")||pt(a,v.get(a)))})},enable(h,c){let y={...u,...c},a=[...h,...y.additional??[]];y.exclude&&e.querySelector(y.exclude);let U=new Map;for(let m of a){e.querySelector(m.selector);for(let[A,I]of Object.entries(m.events)){let F=U.get(I);if(F&&F.phase!==A)throw new TypeError(`Overlay event "${I}" has conflicting phases`);U.set(I,{phase:A,selector:F?`${F.selector}, ${m.selector}`:m.selector})}}u=y,E=a,S=U;for(let m of S.keys()){if(d.has(m))continue;let A=I=>{let F=S.get(m),q=I.composedPath().find(W=>W instanceof e.defaultView.HTMLElement&&W.matches(F.selector));q&&N(q,F.phase,I)};e.addEventListener(m,A,!0),d.set(m,()=>e.removeEventListener(m,A,!0))}for(let[m,A]of d)S.has(m)||(A(),d.delete(m));let j=!M;M||(M=new MutationObserver(()=>O()),M.observe(e.documentElement,{childList:!0,subtree:!0}),e.addEventListener("nativeUIOverlayAdopted",m=>{let A=m.detail;A?.ownerDocument===e&&N(A,"willPresent",m)})),O(j),(j||c)&&T({phase:"change"})}}},Ke=e=>{let o=e;return o[mt]??=vt(e)};function*fe(e){let o=e;for(;o;){let n=o.getRootNode();o=o.assignedSlot??o.parentElement??(n instanceof ShadowRoot?n.host:null),o&&(yield o)}}var ht=({x:e,y:o,width:n,height:p})=>({x:e,y:o,width:n,height:p}),Ge=e=>{let o=e.getBoundingClientRect(),n=getComputedStyle(e),p=parseFloat(n.borderLeftWidth)||0,v=parseFloat(n.borderTopWidth)||0;return{x:o.left+p,y:o.top+v,width:o.width-p-(parseFloat(n.borderRightWidth)||0),height:o.height-v-(parseFloat(n.borderBottomWidth)||0)}},Xe=(e,o,n)=>{let p=[e,...fe(e)],v=p.indexOf(o);if(v<=0||o===e.ownerDocument.scrollingElement)return;let l=e.ownerDocument.defaultView;if(l.visualViewport&&(l.visualViewport.scale!==1||l.visualViewport.offsetLeft||l.visualViewport.offsetTop))return;let d=0,E=0,u=l.innerWidth,S=l.innerHeight;for(let[O,h]of p.entries()){let c=getComputedStyle(h);if(c.transform!=="none"&&!new DOMMatrixReadOnly(c.transform).isIdentity||c.zoom&&c.zoom!=="normal"&&Number(c.zoom)!==1||[c.translate,c.rotate,c.scale,c.perspective].some(j=>j&&j!=="none")||c.writingMode&&c.writingMode!=="horizontal-tb"||O<v&&/^(fixed|sticky)$/.test(c.position))return;if(O===0)continue;if(c.scrollbarGutter&&c.scrollbarGutter!=="auto")return;let y=/^(auto|scroll|hidden|clip)$/.test(c.overflowX),a=/^(auto|scroll|hidden|clip)$/.test(c.overflowY);if(O<v&&(y||a))return;let U=h===e.ownerDocument.scrollingElement;if(O>v&&((y||U)&&h.scrollWidth>h.clientWidth||(a||U)&&h.scrollHeight>h.clientHeight)||(y||a)&&[c.borderTopLeftRadius,c.borderTopRightRadius,c.borderBottomLeftRadius,c.borderBottomRightRadius].some(j=>parseFloat(j)>0)||c.clipPath&&c.clipPath!=="none"||c.maskImage&&c.maskImage!=="none")return;if(O>=v){let j=Ge(h);y&&(d=Math.max(d,j.x),u=Math.min(u,j.x+j.width)),a&&(E=Math.max(E,j.y),S=Math.min(S,j.y+j.height))}}let M=getComputedStyle(o);if(!/^(auto|scroll)$/.test(M.overflowX)&&!/^(auto|scroll)$/.test(M.overflowY))return;let T=Ge(o);if(u<=d||S<=E||!T.width||!T.height)return;let N=ht(e.getBoundingClientRect());return{id:n(o),offsetX:o.scrollLeft,offsetY:o.scrollTop,viewport:T,clip:{x:d,y:E,width:u-d,height:S-E},contentWidth:o.scrollWidth,contentHeight:o.scrollHeight,frame:{...N,x:N.x-T.x+o.scrollLeft,y:N.y-T.y+o.scrollTop}}};var Je=e=>{let o=globalThis;if(o.Capacitor?.convertFileSrc)return o.Capacitor.convertFileSrc(e);if(!e.startsWith("file:"))return e;if(o.WkWebView?.convertFilePath)return o.WkWebView.convertFilePath(e);throw new Error("Native UI file URL requires a host resource handler")};var ce=e=>new Promise((o,n)=>{let p=setTimeout(()=>n(new Error("Native UI bridge timed out")),5e3);e.then(o,n).finally(()=>clearTimeout(p))}),Ue=async e=>{try{return await ce(e)}catch(o){throw e.then(n=>n.remove()).catch(()=>{}),o}};var Ie=async(e,o)=>{let n=new Map;for(let E of e.update??[])n.set(E,{updated:!0});for(let E of["native","web","none"])for(let u of e.projection?.[E]??[]){let S=n.get(u)??{updated:!1};if(S.destination&&S.destination!==E)throw new TypeError(`Projection event "${u}" has conflicting destinations`);n.set(u,{...S,destination:E})}let p=!1,v=[],l=async()=>{p=!0;let u=(await Promise.allSettled(v.splice(0).map(S=>Promise.resolve().then(()=>S.remove())))).find(S=>S.status==="rejected");if(u?.status==="rejected")throw u.reason},d=[...n].map(async([E,u])=>{let S=await Ue(Promise.resolve().then(()=>e.addListener(E,M=>{!p&&(!e.accepts||e.accepts(M))&&o(u.destination,u.updated)})));p?await S.remove():v.push(S)});try{return await Promise.all(d),{remove:l}}catch(E){throw await l().catch(()=>{}),E}};var Ze=`
2
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]),
3
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared]) *,
4
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::after,
5
- :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared])::part(native) { visibility: hidden !important; }
6
- [data-native-ui-snapshot][data-native-ui-snapshot]::before {
7
- all:initial!important;content:""!important;display:block!important;position:absolute!important;
8
- visibility:visible!important;opacity:0!important;will-change:opacity!important;
9
- pointer-events:none!important;image-rendering:pixelated!important;
1
+ "use strict";var WebViewNativeUIEngineRuntime=(()=>{var _e=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var dt=Object.getOwnPropertyNames;var ut=Object.prototype.hasOwnProperty;var ft=(t,r)=>{for(var o in r)_e(t,o,{get:r[o],enumerable:!0})},pt=(t,r,o,v)=>{if(r&&typeof r=="object"||typeof r=="function")for(let h of dt(r))!ut.call(t,h)&&h!==o&&_e(t,h,{get:()=>r[h],enumerable:!(v=lt(r,h))||v.enumerable});return t};var mt=t=>pt(_e({},"__esModule",{value:!0}),t);var Ct={};ft(Ct,{hide:()=>Ht,notify:()=>Tt,receive:()=>jt,show:()=>St,start:()=>kt,stop:()=>xt});var vt=Symbol.for("webview-native-ui-engine.projection-placement");var Ne=t=>t?.[vt];var ht=(t,r)=>{if(t.matches("ion-toast, ion-menu"))return!1;let o=t;if(o.showBackdrop===!1)return!1;if(!t.matches("ion-modal"))return!0;let v=r??o.initialBreakpoint;return v===void 0||v>(o.backdropBreakpoint??0)},yt=Symbol.for("@webview-native-ui/engine/overlays/v1"),gt=t=>{let r=new Set,o=new Set,v=new Set,h=new WeakMap,d=new WeakSet,p=new Map,T=[],f={},M=new Map,C,j=y=>{for(let l of[...v])try{l(y)}catch(g){console.error("Native UI overlay listener failed",g)}},B=(y,l,g)=>{l==="willPresent"||l==="didPresent"?(d.delete(y),r.add(y)):l==="didDismiss"&&(d.add(y),r.delete(y),o.delete(y),h.delete(y));let c=g?.detail?.breakpoint;typeof c=="number"&&c>0&&h.set(y,c),j({element:y,phase:l,event:g})},S=(y=!1)=>{let l=!1;for(let g of r)(!g.isConnected||g.ownerDocument!==t||!T.some(({selector:c})=>g.matches(c)))&&(r.delete(g),o.delete(g),l=!0);for(let g of t.querySelectorAll(T.map(({selector:c})=>c).join(",")))!r.has(g)&&!d.has(g)&&(y||!g.matches("ion-popover"))&&(g.presented||g.classList.contains("show-menu"))&&(r.add(g),l=!0);l&&j({phase:"change"})};return{get active(){return r},subscribe(y){return v.add(y),()=>{v.delete(y)}},setNative(y,l){o.has(y)!==l&&(l?o.add(y):o.delete(y),j({element:y,phase:"change"}))},isExcluded(y){return!!f.exclude&&y.matches(f.exclude)},isNative(y){return o.has(y)},get stack(){let y=[...r].filter(g=>g.isConnected&&g.ownerDocument===t),l=g=>{let c=Number.parseFloat(t.defaultView.getComputedStyle(g).zIndex);return Number.isFinite(c)?c:g.overlayIndex??0};return y.sort((g,c)=>l(g)-l(c)),y},isCovered(y){let l=this.stack,g=l.reduce((c,D,H)=>D.contains(y)?H:c,-1);return l.some((c,D)=>{if(o.has(c)||f.exclude&&c.matches(f.exclude))return!1;let H=f.covers?.(c,y);return H!==void 0?H:D>g&&!c.contains(y)&&(c.matches("ion-menu")||ht(c,h.get(c)))})},enable(y,l){let g={...f,...l},c=[...y,...g.additional??[]];g.exclude&&t.querySelector(g.exclude);let D=new Map;for(let m of c){t.querySelector(m.selector);for(let[W,A]of Object.entries(m.events)){let N=D.get(A);if(N&&N.phase!==W)throw new TypeError(`Overlay event "${A}" has conflicting phases`);D.set(A,{phase:W,selector:N?`${N.selector}, ${m.selector}`:m.selector})}}f=g,T=c,M=D;for(let m of M.keys()){if(p.has(m))continue;let W=A=>{let N=M.get(m),oe=A.composedPath().find(F=>F instanceof t.defaultView.HTMLElement&&F.matches(N.selector));oe&&B(oe,N.phase,A)};t.addEventListener(m,W,!0),p.set(m,()=>t.removeEventListener(m,W,!0))}for(let[m,W]of p)M.has(m)||(W(),p.delete(m));let H=!C;C||(C=new MutationObserver(()=>S()),C.observe(t.documentElement,{childList:!0,subtree:!0}),t.addEventListener("nativeUIOverlayAdopted",m=>{let W=m.detail;W?.ownerDocument===t&&B(W,"willPresent",m)})),S(H),(H||l)&&j({phase:"change"})}}},et=t=>{let r=t;return r[yt]??=gt(t)};function*Oe(t){let r=t;for(;r;){let o=r.getRootNode();r=r.assignedSlot??r.parentElement??(o instanceof ShadowRoot?o.host:null),r&&(yield r)}}var wt=({x:t,y:r,width:o,height:v})=>({x:t,y:r,width:o,height:v}),tt=t=>{let r=t.getBoundingClientRect(),o=getComputedStyle(t),v=parseFloat(o.borderLeftWidth)||0,h=parseFloat(o.borderTopWidth)||0;return{x:r.left+v,y:r.top+h,width:r.width-v-(parseFloat(o.borderRightWidth)||0),height:r.height-h-(parseFloat(o.borderBottomWidth)||0)}},Ge=(t,r,o)=>{let v=[t,...Oe(t)],h=v.indexOf(r);if(h<=0||r===t.ownerDocument.scrollingElement)return;let d=t.ownerDocument.defaultView;if(d.visualViewport&&(d.visualViewport.scale!==1||d.visualViewport.offsetLeft||d.visualViewport.offsetTop))return;let p=0,T=0,f=d.innerWidth,M=d.innerHeight;for(let[S,y]of v.entries()){let l=getComputedStyle(y);if(l.transform!=="none"&&!new DOMMatrixReadOnly(l.transform).isIdentity||l.zoom&&l.zoom!=="normal"&&Number(l.zoom)!==1||[l.translate,l.rotate,l.scale,l.perspective].some(H=>H&&H!=="none")||l.writingMode&&l.writingMode!=="horizontal-tb"||S<h&&/^(fixed|sticky)$/.test(l.position))return;if(S===0)continue;if(l.scrollbarGutter&&l.scrollbarGutter!=="auto")return;let g=/^(auto|scroll|hidden|clip)$/.test(l.overflowX),c=/^(auto|scroll|hidden|clip)$/.test(l.overflowY);if(S<h&&(g||c))return;let D=y===t.ownerDocument.scrollingElement;if(S>h&&((g||D)&&y.scrollWidth>y.clientWidth||(c||D)&&y.scrollHeight>y.clientHeight)||(g||c)&&[l.borderTopLeftRadius,l.borderTopRightRadius,l.borderBottomLeftRadius,l.borderBottomRightRadius].some(H=>parseFloat(H)>0)||l.clipPath&&l.clipPath!=="none"||l.maskImage&&l.maskImage!=="none")return;if(S>=h){let H=tt(y);g&&(p=Math.max(p,H.x),f=Math.min(f,H.x+H.width)),c&&(T=Math.max(T,H.y),M=Math.min(M,H.y+H.height))}}let C=getComputedStyle(r);if(!/^(auto|scroll)$/.test(C.overflowX)&&!/^(auto|scroll)$/.test(C.overflowY))return;let j=tt(r);if(f<=p||M<=T||!j.width||!j.height)return;let B=wt(t.getBoundingClientRect());return{id:o(r),offsetX:r.scrollLeft,offsetY:r.scrollTop,viewport:j,clip:{x:p,y:T,width:f-p,height:M-T},contentWidth:r.scrollWidth,contentHeight:r.scrollHeight,frame:{...B,x:B.x-j.x+r.scrollLeft,y:B.y-j.y+r.scrollTop}}};var nt=t=>{let r=globalThis;if(r.Capacitor?.convertFileSrc)return r.Capacitor.convertFileSrc(t);if(!t.startsWith("file:"))return t;if(r.WkWebView?.convertFilePath)return r.WkWebView.convertFilePath(t);throw new Error("Native UI file URL requires a host resource handler")};var pe=t=>new Promise((r,o)=>{let v=setTimeout(()=>o(new Error("Native UI bridge timed out")),5e3);t.then(r,o).finally(()=>clearTimeout(v))}),Be=async t=>{try{return await pe(t)}catch(r){throw t.then(o=>o.remove()).catch(()=>{}),r}};var ze=async(t,r)=>{let o=new Map;for(let T of t.update??[])o.set(T,{updated:!0});for(let T of["native","web","none"])for(let f of t.projection?.[T]??[]){let M=o.get(f)??{updated:!1};if(M.destination&&M.destination!==T)throw new TypeError(`Projection event "${f}" has conflicting destinations`);o.set(f,{...M,destination:T})}let v=!1,h=[],d=async()=>{v=!0;let f=(await Promise.allSettled(h.splice(0).map(M=>Promise.resolve().then(()=>M.remove())))).find(M=>M.status==="rejected");if(f?.status==="rejected")throw f.reason},p=[...o].map(async([T,f])=>{let M=await Be(Promise.resolve().then(()=>t.addListener(T,C=>{!v&&(!t.accepts||t.accepts(C))&&r(f.destination,f.updated)})));v?await M.remove():h.push(M)});try{return await Promise.all(p),{remove:d}}catch(T){throw await d().catch(()=>{}),T}};var je=":is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])",ot=`
2
+ @layer native-ui-projection {
3
+ ${je}, ${je} *, ${je}::part(native) { visibility: hidden !important; }
4
+ ${je}::before, ${je}::after,
5
+ ${je} *::before, ${je} *::after {
6
+ visibility: hidden !important;
7
+ }
10
8
  }
11
9
  [data-native-ui-snapshot-visible] { opacity:1!important;overflow:visible!important; }
12
- [data-native-ui-snapshot-visible][data-native-ui-snapshot][data-native-ui-snapshot]::before { opacity:1!important; }
13
- `;var gt=(e,o=!1)=>JSON.stringify(e.map(({control:n})=>[n.id,n.kind,n.placement,o?0:n.x,o?0:n.y,n.width,n.height,n.dark,n.rtl])),Qe=(e,o,n=o.getComputedStyle(e))=>{let p=e.getBoundingClientRect(),v=(M,T)=>{let N=parseFloat(M);return n.display!=="inline"&&Number.isFinite(N)?N+(n.boxSizing==="border-box"?0:T.reduce((O,h)=>O+(parseFloat(n.getPropertyValue(h))||0),0)):NaN},l=v(n.width,["padding-left","padding-right","border-left-width","border-right-width"]),d=v(n.height,["padding-top","padding-bottom","border-top-width","border-bottom-width"]),E=1;if(!(l>0&&d>0))for(let M of[e,...fe(e)]){let T=(M===e?n:o.getComputedStyle(M)).getPropertyValue("zoom");E*=(parseFloat(T)||1)/(T.endsWith("%")?100:1)}let u=l>0?p.width/l:E,S=d>0?p.height/d:E;return{x:p.x,y:p.y,scaleX:u||1,scaleY:S||1,left:(parseFloat(n.borderLeftWidth)||0)-e.scrollLeft,top:(parseFloat(n.borderTopWidth)||0)-e.scrollTop,position:n.position}},yt=async(e,o,n)=>{let p=n.captureDelayMs??50;if(!Number.isFinite(p)||p<0||p>2147483647)throw new RangeError("captureDelayMs must be a finite, nonnegative timer interval");let v="native",l=e.element.ownerDocument,d=l.defaultView,E=Array.from(d.crypto.getRandomValues(new Uint32Array(4)),t=>t.toString(16)).join("-"),u=t=>t.isConnected&&t.ownerDocument===l,S=t=>!!t?.isConnected&&t.ownerDocument===l,M=o.send.bind(o),T="data-native-ui-snapshot",N=`${e.namespace}:sources-change`,O=new Set,h=new Set,c=new Set,y=new WeakMap,a=0,U=t=>JSON.stringify(t.map(s=>(y.has(s.scope)||y.set(s.scope,++a),[y.get(s.scope),gt([s],!!n.opaqueUnderlay)]))),j=new Map,m=t=>{let s=t.getRootNode();return s.nodeType===11&&"host"in s?s:l},A=t=>{let s=j.get(t);if(!s){let g=l.createElement("style"),r=l.createElement("style");g.setAttribute("data-native-ui-snapshot-base",""),r.setAttribute(T,""),g.textContent=Ze,(t.nodeType===11?t:t.head).append(g,r),s={base:g,image:r},j.set(t,s)}return s.image},I=()=>`${d.innerWidth}:${d.innerHeight}:${d.devicePixelRatio}`,F=!1,q=!1,W=0,ve=0,R,k,w,le=!1,z=!1,he=0,Pe=0,$=0,se=0,B=0,xe=-1,X=!1,Te=!1,_,Le=new Set,Z=(t,s,g)=>{let r=()=>{Le.delete(r)&&M({action:t,token:s}).catch(()=>{})};Le.add(r),g&&!X?d.requestAnimationFrame(()=>d.requestAnimationFrame(r)):r()},de=new Set,Me=()=>{de.forEach(t=>c.delete(t)),de.clear()},ee=(t,s=!1)=>{!t||t===R||t===w||(t.images.forEach(g=>{g.src=""}),Z("release",t.data.token,s))},He=t=>t.filter(s=>{if(!S(s.scope.element)||![s.element,...s.sources??[]].every(u))return!1;if(s.control.scroll){let g=s.element.getBoundingClientRect(),r=s.control.scroll.clip;if(g.right<=r.x||g.bottom<=r.y||g.left>=r.x+r.width||g.top>=r.y+r.height)return!1}return n.eligible?.(s)??!0}),ge=async()=>{if(se=0,X||v!=="native"||Te||le||w||n.blocked()||!_||R?.revision===_.revision&&xe===B)return;let{revision:t,candidates:s}=_,g=$,r=B,L=I(),x=He(s);if(!x.length)return;Te=!0;let C,Q=!1,ne=[];try{let oe=!1,be=M({action:"prepare",revision:n.captureRevision?.()??t,viewportWidth:d.innerWidth,ids:x.map(P=>P.control.id)});be.then(P=>{oe&&P.token&&M({action:"release",token:P.token}).catch(()=>{})}).catch(()=>{});try{C=await ce(be)}finally{oe=!0}if(!C.token||!C.parts?.length)return;let Ee=new Set(x.map(P=>P.control.id));if(C.parts.some(P=>!P.url.startsWith("file:")&&!/^\/__native_ui_engine__\/nativeUI[a-fA-F0-9]+\/[a-fA-F0-9-]+-[0-9]+\.png$/.test(P.url)||!P.ids.length||P.ids.some(V=>!Ee.has(V))||![P.x,P.y,P.width,P.height].every(Number.isFinite)||P.width<=0||P.height<=0)||new Set(C.parts.flatMap(P=>P.ids)).size!==Ee.size||C.parts.some(P=>new Set(P.ids.map(V=>x.find(Be=>Be.control.id===V).scope)).size!==1)||(await ce(Promise.all(C.parts.map(async P=>{let V=l.createElement("img");return ne.push(V),V.src=Je(P.url),await V.decode(),V}))),X||v!=="native"||le||w||n.blocked()||$!==g||I()!==L||_?.revision!==t||x.some(P=>!S(P.scope.element)||![P.element,...P.sources??[]].every(u))))return;let je=R;R={data:C,images:ne,sources:Array.from(new Set(x.flatMap(P=>[P.element,...P.sources??[]]))),targets:C.parts.map(P=>x.find(V=>P.ids.includes(V.control.id)).element),scopes:C.parts.map(P=>x.find(V=>P.ids.includes(V.control.id)).scope),parents:C.parts.map(P=>x.find(V=>P.ids.includes(V.control.id)).scope.element.parentNode),frames:[],viewport:L,revision:t,layout:U(x)},Me(),R.sources.forEach(P=>{c.add(P),de.add(P)}),Fe(),n.opaqueUnderlay&&M({action:"prepared",token:C.token}).catch(()=>{}),d.dispatchEvent(new Event(N)),xe=r,Q=!0,ee(je)}catch{}finally{Q||ne.forEach(oe=>{oe.src=""}),C?.token&&!Q&&M({action:"release",token:C.token}).catch(()=>{}),Te=!1,_&&(_.revision!==t||B!==r)&&re()}},re=()=>{d.clearTimeout(se),!X&&v==="native"&&!w&&!le&&!n.blocked()&&(se=d.setTimeout(()=>{ge()},p))},Ae,ke=(t,s)=>{if(s.some(r=>r.scope.element.ownerDocument!==l||[r.element,...r.sources??[]].some(L=>L.ownerDocument!==l)))throw new TypeError("All projection sources and fallback scopes must share one Document");if(n.blocked())return;!w&&R&&R.layout!==U(He(s))&&we();let g=U(s);Ae!==g&&(Ae=g,B++,$++),_?.revision!==t&&$++,_={revision:t,candidates:s},(R?.revision!==t||xe!==B)&&re()},Se=t=>{!w&&R&&t&&_?.candidates.some(s=>s.scrollElement===t&&R.sources.includes(s.element))&&we(),w&&(Y(),ae()),B++,$++,re()},Ce=await Ue(o.listen(({active:t})=>{le=t,$++,d.clearTimeout(se),t||re()})),ue=new Set,We=t=>{ue.delete(t)&&t.removeAttribute("data-native-ui-snapshot-prepared"),t.removeAttribute("data-native-ui-snapshot-visible"),t.removeAttribute(T),t.removeAttribute("data-native-ui-snapshot-source")},ye=()=>{ve++,d.cancelAnimationFrame(W),d.cancelAnimationFrame(J),J=0,G.clear(),W=0,F=!1,q=!1,Me(),w?.sources.forEach(t=>{O.delete(t),h.delete(t)}),D.forEach(We),D.clear(),j.forEach(({base:t,image:s})=>{t.remove(),s.remove()}),j.clear()},te=(t=w,s=z)=>{if(w!==t)return;let g=new Set([w,R]);ye(),w=void 0,R=void 0,z=!1,d.clearTimeout(he),g.forEach(r=>ee(r,s)),n.wake()},i=()=>{if(!w)return;if(R!==w){te();return}let t=w;ye(),w=void 0,z=!1,d.clearTimeout(he),Z("restored",t.data.token,!0),t.sources.forEach(s=>{c.add(s),de.add(s)}),Fe(),n.wake()},f=()=>{z&&i(),Le.forEach(t=>t())},b=t=>t.scopes.map(s=>S(s.element)?s.element:void 0),D=new Set,K=new Map,G=new Map,J=0,Y=()=>{for(let[t,s]of G){if(!t.isConnected)continue;let g=d.getComputedStyle(t),r=`${s.x-(parseFloat(g.borderLeftWidth)||0)+t.scrollLeft}px`,L=`${s.y-(parseFloat(g.borderTopWidth)||0)+t.scrollTop}px`;s.style.left!==r&&s.style.setProperty("left",r,"important"),s.style.top!==L&&s.style.setProperty("top",L,"important")}},ae=()=>{if(J||!w)return;[...G.keys()].some(s=>s.getAnimations?.().some(g=>g.playState==="running"&&g.effect?.getKeyframes().some(r=>["borderLeftWidth","borderTopWidth","borderWidth"].some(L=>L in r))))&&(J=d.requestAnimationFrame(()=>{J=0,Y(),ae()}))},Fe=()=>{let t=w??R;if(!t||z)return;if(!t.sources.every(u)||b(t).some(r=>!r)){we();return}let s=b(t);if(w){if(s.some(L=>!L?.isConnected||!j.has(m(L)))){te(w,!1);return}let r=new Set;for(let[L,x]of t.targets.entries()){let C=K.get(x),Q=s[L];!C||!Q?.isConnected||(Q.setAttribute(T,C),Q.setAttribute("data-native-ui-snapshot-source",""),Q.toggleAttribute("data-native-ui-snapshot-visible",q),r.add(Q))}for(let L of D)r.has(L)||We(L);D.clear(),r.forEach(L=>D.add(L));return}K.clear(),G.clear(),D.forEach(We),D.clear();let g=new Map;for(let r of new Set(s)){if(!r)continue;D.add(r);let L=A(m(r)),x=g.get(L);x||(x={rules:[],positions:[]},g.set(L,x));let{rules:C,positions:Q}=x;r!==t.targets[s.indexOf(r)]&&!r.hasAttribute("data-native-ui-snapshot-prepared")&&(r.setAttribute("data-native-ui-snapshot-prepared",""),ue.add(r)),r.toggleAttribute("data-native-ui-snapshot-visible",!!w||!!n.opaqueUnderlay),w&&r.setAttribute("data-native-ui-snapshot-source","");let ne=s.flatMap((H,ie)=>H===r?[ie]:[]),oe=d.getComputedStyle(r);{let H=Qe(r,d,oe);ne.forEach(ie=>{let Ne=t.data.parts[ie];n.opaqueUnderlay&&t.frames[ie]||(t.frames[ie]={x:n.opaqueUnderlay?0:(Ne.x-H.x)/H.scaleX,y:n.opaqueUnderlay?0:(Ne.y-H.y)/H.scaleY,width:Ne.width/H.scaleX,height:Ne.height/H.scaleY})})}let be=ne.map(H=>t.frames[H]),Ee=Math.min(...be.map(H=>H.x)),je=Math.min(...be.map(H=>H.y)),P=Math.max(...be.map(H=>H.x+H.width)),V=Math.max(...be.map(H=>H.y+H.height)),Be=(parseFloat(oe.borderLeftWidth)||0)-r.scrollLeft,rt=(parseFloat(oe.borderTopWidth)||0)-r.scrollTop,$e=`${E}-${ne[0]}`;ne.forEach(H=>K.set(t.targets[H],$e)),r.setAttribute(T,$e),C.push(`[${T}="${$e}"][data-native-ui-snapshot-visible] { ${oe.position==="static"?"position:relative!important;":""} }`);let it=ne.map(H=>{let ie=t.frames[H];return`url(${JSON.stringify(t.images[H].src)}) ${ie.x-Ee}px ${ie.y-je}px / ${ie.width}px ${ie.height}px no-repeat`}),Oe=l.createElement("span").style;Oe.cssText=`left:${Ee-Be}px;top:${je-rt}px;width:${P-Ee}px;height:${V-je}px;background:${it.join(",")};`;for(let H of Array.from(Oe))Oe.setProperty(H,Oe.getPropertyValue(H),"important");C.push(`[${T}="${$e}"][${T}][${T}]::before { ${Oe.cssText} }`),Q.push({target:r,x:Ee,y:je})}t.parents=t.scopes.map(r=>r.element.parentNode);for(let[r,{rules:L,positions:x}]of g)r.textContent=L.join(`
14
- `),x.forEach(({target:C,x:Q,y:ne},oe)=>G.set(C,{x:Q,y:ne,style:r.sheet.cssRules[oe*2+1].style}))},Ye=()=>{if(f(),X||v==="none"||w)return;let t=R;if(!t||t.viewport!==I()||!t.sources.every(u)||n.current&&!n.current(t.targets)||t.targets.some(r=>!K.has(r))||b(t).some(r=>!r?.isConnected)||[...D].some(r=>!r.isConnected))return;t.scopes.some((r,L)=>r.element.parentNode!==t.parents[L])&&Fe(),Y();let s=++ve,g=()=>ve===s;F=!0,q=!!n.opaqueUnderlay,w=t,$++,d.clearTimeout(se),t.sources.forEach(r=>{O.add(r)}),D.forEach(r=>{r.setAttribute("data-native-ui-snapshot-source",""),r.toggleAttribute("data-native-ui-snapshot-visible",q)}),W=d.requestAnimationFrame(()=>{W=0,g()&&(F=!1,n.wake())}),ce(M({action:"cover",token:t.data.token,...n.transient?{transient:!0}:{}})).then(r=>{if(g()){if(F=!1,r?.shown!==!0){te(t,!1);return}z||(q=!0,t.sources.forEach(L=>h.add(L)),Fe(),Y(),ae()),d.cancelAnimationFrame(W),W=d.requestAnimationFrame(()=>{W=0,g()&&M({action:"painted",token:t.data.token}).catch(()=>{g()&&te(t,!1)})}),n.wake()}}).catch(()=>{g()&&te(t,!1)})},qe=()=>{if(!(!w||X||z)){if(!w.sources.every(u)||b(w).some(t=>!t)||n.current&&!n.current(w.targets)){te(w,!1);return}if(!n.blocked()){if(n.transient){let t=w,s=b(t);if(s.some(r=>!r?.isConnected)){te(t,!1);return}let g=s.map((r,L)=>{let x=Qe(r,d),C=t.frames[L];return{...t.data.parts[L],x:x.x+C.x*x.scaleX,y:x.y+C.y*x.scaleY,width:C.width*x.scaleX,height:C.height*x.scaleY}});g.some((r,L)=>["x","y","width","height"].some(x=>r[x]!==t.data.parts[L][x]))&&(n.opaqueUnderlay?t.data={...t.data,parts:g}:R=void 0),M({action:"restore",token:t.data.token,viewportWidth:d.innerWidth,parts:g}).catch(()=>{})}Pe=n.revision?.()??_?.revision??0,z=!0,w.sources.forEach(t=>O.delete(t)),he=d.setTimeout(()=>{z&&te(w,!1)},250),n.wake()}}},ot=(t,s,g)=>{!z||!w||g===void 0||g<=Pe||!n.transient&&t||w.data.parts.every(r=>r.ids.every(L=>s?.includes(L)))&&(i(),re())},we=(t=!1)=>{Le.forEach(g=>g()),$++,d.clearTimeout(se);let s=R;if(R=void 0,(t||X)&&(k=void 0),n.retainPreparedUntilRelease&&!w&&!t&&!X){if(s){let g={};k=g,ce(M({action:"release",token:s.data.token})).catch(()=>{}).finally(()=>{if(s.images.forEach(r=>{r.src=""}),k===g&&(k=void 0,!(R||w))){for(let r of D)r.getAttribute(T)!==K.get(r)&&(D.delete(r),ue.delete(r));ye(),K.clear(),d.dispatchEvent(new Event(N))}})}if(k)return}ee(s),w?te(w,!1):(ye(),K.clear(),d.dispatchEvent(new Event(N)))},_e;try{n.events&&(_e=await Ie(n.events,(t,s)=>{if(!X){if(t&&(v=t),v==="none"){we();return}t==="web"&&Ye(),t==="native"&&(qe(),re()),s&&Se()}}))}catch(t){throw X=!0,we(),await ce(Promise.resolve(Ce.remove())).catch(()=>{}),t}return{get owners(){return[...O]},get visibleOwners(){return[...h]},get preparedOwners(){return[...c]},consider:ke,changed:Se,moved:()=>{Y(),ae()},get token(){return z?void 0:w?.data.token},begin:Ye,get restoring(){return z},get handoffPending(){return F},retireRestoration:f,end:qe,updated:ot,invalidate:we,async destroy(){X=!0,we(),await Promise.allSettled([ce(Promise.resolve().then(()=>Ce?.remove())),ce(Promise.resolve().then(()=>_e?.remove()))])}}},et=async(e,o,n)=>{let p=await yt(e,o,n),v=p.consider;return Object.assign(p,{consider:(l,d)=>v(l,d.map(E=>({...E,scope:e})))})};var tt=(e,o)=>{if(e.nodeType!==1||e.namespaceURI!=="http://www.w3.org/1999/xhtml"||!e.ownerDocument||!("style"in e))throw new TypeError("A Web projection scope requires an HTMLElement fallback");if(e.matches("img, input, textarea, select, video, audio, canvas, iframe, embed, object, br, hr, area, base, link, meta, source, track, wbr"))throw new TypeError("A Web projection fallback must support ::before; use its existing container element");return Object.freeze({element:e,namespace:o})};var nt=async(e,o,n={addListener:async(p,v)=>globalThis.Capacitor.addListener(e.plugin,p,v)})=>{let p="view",v=e.receiveListeners,l=!1,d=!1,E="native",u="native",S=Ke(document),M=()=>E==="native"&&m&&S.isCovered(m)?"web":E,T=!1,N=!1,O=!1,h=!1,c,y,a,U=!1,j=()=>{let i=U;U=!0;try{a?.begin()}finally{U=i}},m,A={},I,F=0,q=()=>{F++,I=void 0;let i=De(m);i?.owner===A&&(i.projected=!1,i.owner=void 0)},W={listen:i=>o.listen(f=>{i(f),!f.active&&N&&(N=!1,re()),f.precovered&&Ae(f.precovered)}),send:i=>{if(!e.placementHandoff)return o.send(i);let f=De(m);if(i.action==="cover"){let J=I===i.token&&f?.owner===A&&f.projected,Y=++F;return I=i.token,f&&(f.owner=A,f.projected=!!J),o.send(i).then(ae=>(Y===F&&f===De(m)&&f?.owner===A&&(f.projected=ae?.shown===!0),ae)).catch(ae=>{throw Y===F&&f?.owner===A&&(q(),f.invalidate()),ae})}let b=i.action==="release"&&i.token===I,D=I&&(i.action==="restore"||b||i.action==="reset"||i.action==="visibility"&&i.visible);D&&F++;let K=F,G=()=>{K!==F||f?.owner!==A||(q(),f.invalidate())};try{let J=D&&f?.owner===A?{...i,token:"token"in i?i.token:I,placement:f.measure()}:i;return o.send(J).then(async Y=>(i.action==="release"&&Y?.requiresPlacement&&(f&&(!f.owner||f.owner===A)?Y=await o.send({...i,placement:f.measure()}):(await o.send({action:"visibility",visible:!1}),Y=await o.send(i))),K===F&&(i.action==="resume"||b||i.action==="reset"||i.action==="visibility")&&q(),Y)).catch(Y=>{throw G(),Y})}catch(J){return G(),Promise.reject(J)}}},ve=0,R=0,k=0,w=!1,le=!1,z=!1,he=!1,Pe=document.createElement("style"),$=`data-native-ui-session-${e.id.replace(/[^a-zA-Z0-9-]/g,"")}`;Pe.textContent=`[${$}="native"], [${$}="native"] *, [${$}="none"], [${$}="none"] * {visibility:hidden!important}`,document.documentElement.append(Pe);let se=()=>{let i=document.querySelectorAll(e.projectionSelector);return i.length===1&&i[0]instanceof HTMLElement?i[0]:void 0},B=()=>{m&&(u==="web"||u==="native"&&O?m.removeAttribute($):m.setAttribute($,u))},xe=()=>{if(!m?.isConnected)return[];let i=m.getBoundingClientRect(),f={x:i.x,y:i.y,width:i.width,height:i.height},b=c&&Xe(m,c,()=>e.id);return[{element:m,scrollElement:c,control:{id:p,...f,...b?{scroll:b}:{}}}]},X=async()=>{if(l)return;let i=k,f=e.failedFallback==="native"&&M()==="native"&&!!m?.isConnected;await W.send({action:"visibility",visible:f}).catch(()=>{}),!(l||i!==k)&&(f||u==="none"?B():m?.removeAttribute($))},Te=()=>{if(!T||u!=="native"||!m?.isConnected||a?.token||a?.restoring)return;let i=e.failedFallback==="native";W.send({action:"visibility",visible:i}).catch(()=>{}),i?B():m.removeAttribute($)},_=()=>{if(l||U)return;if(Te(),u==="web"&&a&&!a.token&&!a.restoring){W.send({action:"visibility",visible:!1}).catch(()=>{}),m?.removeAttribute($);return}if(w||!a?.restoring||u!=="native")return;w=!0;let i=k,f=a;W.send({action:"resume"}).then(()=>{l||i!==k||f!==a||(O=!1,f.updated(void 0,[p],++ve),B(),Z())}).catch(()=>{i!==k||l||(f.invalidate(),X())}).finally(()=>{w=!1,_()})},Le=async()=>{if(l||le||T)return;if(a?.restoring){_();return}let i=se();if(i!==m){if(k++,q(),m?.removeAttribute($),await a?.destroy(),a=void 0,l)return;m=i,O=!1,Me.disconnect(),m&&Me.observe(m)}if(ke(),u=M(),!m){await W.send({action:"visibility",visible:!1});return}if(!a){le=!0;let f=k;try{let b=await et(tt(m,e.id),W,{wake:_,captureDelayMs:e.captureDelayMs,retainPreparedUntilRelease:e.nativeDragHandoff,blocked:()=>u!=="native"||T||O&&!a?.token,revision:()=>ve,captureRevision:e.captureMode==="view"?()=>R:void 0,transient:!0,opaqueUnderlay:e.captureMode==="view"});if(l||f!==k){await b.destroy();return}a=b}finally{le=!1}}if(!l){if(u==="native"&&O){await W.send({action:"visibility",visible:!1}),B();return}if(u==="native"){let f=k;if(await W.send({action:"visibility",visible:!0}),l||f!==k||u!=="native")return;B(),c=[...fe(m)].find(G=>{let J=getComputedStyle(G);return/auto|scroll/.test(J.overflowX+J.overflowY)&&(G.scrollHeight>G.clientHeight||G.scrollWidth>G.clientWidth)});let b=c&&Xe(m,c,()=>e.id),D=e.nativeDragHandoff&&e.captureMode==="view"&&c?{viewport:c.getBoundingClientRect(),offsetX:c.scrollLeft,offsetY:c.scrollTop,rangeX:Math.max(0,c.scrollWidth-c.clientWidth),rangeY:Math.max(0,c.scrollHeight-c.clientHeight)}:void 0,K=await W.send({action:"track",viewportWidth:innerWidth,...b?{scroll:b}:{},...D?{gesture:D}:{}});if(l||f!==k)return;h=K?.tracked===!0,a.consider(e.captureMode==="view"?R:++ve,xe())}else await W.send({action:"visibility",visible:!1}),B()}},Z=()=>{l||(z=!0,!(!d||he)&&(he=!0,Promise.resolve().then(async()=>{try{for(;z&&!l;)z=!1,await Le()}catch{await X()}finally{he=!1,z&&Z()}})))},de=()=>{a?.invalidate(),Z()},Me=new ResizeObserver(de);window.addEventListener("resize",de);let ee=new Set,He=new WeakSet,ge=i=>{let f=i.composedPath()[0]??i.target;if(!(l||u!=="native"||!m||He.has(i)||f!==document&&![...fe(m)].includes(f))){if(He.add(i),h&&f===c){a?.changed(f);return}T?e.captureMode==="view"?a?.moved():a?.changed():((w||a?.restoring)&&k++,T=!0,j(),O=e.failedFallback!=="native",a?.token?m.removeAttribute($):Te()),re()}};function re(){clearTimeout(y),y=setTimeout(()=>{N||(T=!1,!(l||u!=="native")&&(a?.end(),a?.restoring||Z()))},100)}function Ae(i){if(l||u!=="native"||!m?.isConnected||!a||(N=!0,a.token))return;(w||a.restoring)&&k++,I=i;let f=De(m);if(f&&(f.owner=A,f.projected=!0),T=!0,j(),!a.token){T=!1,X();return}re()}let ke=()=>{let i=new Set(m?fe(m):[]),f=i.size!==ee.size||[...i].some(b=>!ee.has(b));for(let b of ee)i.has(b)||(b.removeEventListener("scroll",ge),b.removeEventListener("slotchange",Se),ee.delete(b));for(let b of i)ee.has(b)||(b.addEventListener("scroll",ge,{passive:!0}),b.addEventListener("slotchange",Se),ee.add(b));f&&(h=!1,c=void 0,a?.invalidate(),Z())},Se=()=>{ke(),Z()};document.addEventListener("scroll",ge,!0);let Ce=new MutationObserver(i=>{ke(),se()!==m?Z():m&&i.some(f=>f.type==="childList")&&ue(M())});Ce.observe(document.documentElement,{childList:!0,attributes:!0,subtree:!0});let ue=(i,f=!1)=>{if(l||!f&&i===u)return;T=!1,N=!1,clearTimeout(y),u=i,k++;let b=k;u==="none"?W.send({action:"visibility",visible:!1}).then(()=>{u!=="none"||b!==k||l||(a?.invalidate(!0),B())}).catch(()=>{u!=="none"||b!==k||l||(a?.invalidate(!0),B())}):u==="web"?(j(),a?.token||W.send({action:"visibility",visible:!1}).catch(()=>{}),B()):u==="native"&&(a?.end(),a?.restoring||Z())},We=S.subscribe(()=>queueMicrotask(()=>ue(M()))),ye,te=async()=>{if(!l){l=!0,q(),k++,We(),Ce.disconnect(),Me.disconnect(),window.removeEventListener("resize",de),document.removeEventListener("scroll",ge,!0);for(let i of ee)i.removeEventListener("scroll",ge),i.removeEventListener("slotchange",Se);ee.clear(),clearTimeout(y),m?.removeAttribute($),Pe.remove(),await Promise.allSettled([Promise.resolve(ye?.remove()),a?.destroy()])}};try{let i=k;ye=await Ie({addListener:(b,D)=>n.addListener(b,D),update:v?.update,projection:v?.projection,accepts:b=>!v?.match||Object.entries(v?.match).every(([D,K])=>typeof b=="object"&&b!==null&&b[D]===K)},(b,D)=>{l||(b&&(b==="native"&&(O=!1),E=b,W.send({action:"destination",destination:b}).catch(()=>{}),ue(M(),!0)),D&&(R++,a?.changed(),u==="native"&&Z()))});let f=await W.send({action:"connect"});return k===i&&["native","web","none"].includes(f?.destination)&&(E=f.destination),u=M(),d=!0,Z(),{destroy:te,show:()=>{O=!1,E="native",ue(M(),!0)},hide:()=>{E=u="none",k++,T=!1,N=!1,clearTimeout(y),a?.invalidate(!0),B()}}}catch(i){throw await te(),i}};var pe=new Map,me=new Map,Re=new Map,wt=Array.from(crypto.getRandomValues(new Uint32Array(4)),e=>e.toString(16)).join("-"),ze=new Map,bt=(e,o,n)=>ze.get(e)?.get(o)?.(n),Et=0,Pt=(e,o)=>new Promise((n,p)=>{let v=`${e}:${wt}:${++Et}`,l=setTimeout(()=>{me.delete(v),p(new Error("Native UI session timed out"))},5e3);me.set(v,{resolve:n,reject:p,timer:l});let d=JSON.stringify({key:v,request:o}),E=globalThis,u=E.webkit?.messageHandlers?.[e]??E[e]??E.nativeUIEngine;if(!u){clearTimeout(l),me.delete(v),p(new Error("Native UI session unavailable"));return}try{u.postMessage(d)}catch(S){clearTimeout(l),me.delete(v),p(S)}}),Tt=(e,o,n,p)=>{if(o===null){Re.get(e)?.(n);return}let v=me.get(o);!v||!o.startsWith(`${e}:`)||(me.delete(o),clearTimeout(v.timer),p?v.reject(new Error(p)):v.resolve(n))},Lt=e=>{pe.get(e)?.show()},Mt=e=>(pe.get(e)?.hide(),!0),St=async e=>{let o=pe.get(e);pe.delete(e),ze.delete(e),Re.delete(e);for(let[n,p]of me)n.startsWith(`${e}:`)&&(me.delete(n),clearTimeout(p.timer),p.reject(new Error("Native UI session closed")));await o?.destroy()},jt=async e=>{if(pe.has(e.id))return;let o,n=!1,p,v={show:()=>{p="native",o?.show()},hide:()=>{p="none",o?.hide()},destroy:async()=>{n=!0,await o?.destroy()}};pe.set(e.id,v);try{if(document.readyState==="loading"&&await new Promise(d=>document.addEventListener("DOMContentLoaded",()=>d(),{once:!0})),n)return;let l=new Map;e.runtime==="cordova"&&ze.set(e.id,l),o=await nt(e,{send:d=>Pt(e.id,d),listen:async d=>(Re.set(e.id,d),{remove:()=>{Re.get(e.id)===d&&Re.delete(e.id)}})},e.runtime==="cordova"?{addListener:async(d,E)=>(l.set(d,E),{remove:()=>{l.delete(d)}})}:void 0),p==="none"?o.hide():p==="native"&&o.show(),n&&await o.destroy()}catch(l){throw pe.get(e.id)===v&&(pe.delete(e.id),ze.delete(e.id)),l}};return ut(xt);})();
10
+ [data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }
11
+ `,it=`
12
+ @layer native-ui-projection {
13
+ :host([data-native-ui-snapshot-content]),
14
+ :host([data-native-ui-snapshot-content])::before,
15
+ :host([data-native-ui-snapshot-content])::after,
16
+ :host([data-native-ui-snapshot-content]) *,
17
+ :host([data-native-ui-snapshot-content]) *::before,
18
+ :host([data-native-ui-snapshot-content]) *::after {
19
+ visibility: hidden !important;
20
+ }
21
+ }
22
+ `;var bt=(t,r=!1)=>JSON.stringify(t.map(({control:o})=>[o.id,o.kind,o.placement,r?0:o.x,r?0:o.y,o.width,o.height,o.dark,o.rtl])),Et=async(t,r,o)=>{let v=o.captureDelayMs??50;if(!Number.isFinite(v)||v<0||v>2147483647)throw new RangeError("captureDelayMs must be a finite, nonnegative timer interval");let h="native",d=t.element.ownerDocument,p=d.defaultView,T=Array.from(p.crypto.getRandomValues(new Uint32Array(4)),e=>e.toString(16)).join("-"),f=e=>e.isConnected&&e.ownerDocument===d,M=e=>!!e?.isConnected&&e.ownerDocument===d,C=new Map,j=new Set,B=e=>{j.delete(e),e.style.setProperty("display","none","important"),e.removeAttribute("data-native-ui-projection-image"),e.removeAttribute("src"),le()},S=r.send.bind(r),y="data-native-ui-snapshot",l=`${t.namespace}:sources-change`,g=new Set,c=new Set,D=new Set,H=new WeakMap,m=0,W=e=>JSON.stringify(e.map(i=>(H.has(i.scope)||H.set(i.scope,++m),[H.get(i.scope),bt([i],!!o.opaqueUnderlay)]))),A=new Map,N=e=>{let i=e.getRootNode();return i.nodeType===11&&"host"in i?i:d},oe=e=>{if(A.has(e))return;let i=d.createElement("style");i.setAttribute("data-native-ui-snapshot-base",""),i.textContent=ot+(e.nodeType===11?it:""),(e.nodeType===11?e:e.head).append(i),A.set(e,i)},F=e=>{let i=[...e.getAnimations({subtree:!0}),...[...se.get(e)??[]].flatMap(n=>n.shadowRoot?.getAnimations()??[])];for(let n of i)"transitionProperty"in n&&n.transitionProperty==="visibility"&&n.cancel()},se=new Map,Se=e=>{let i=se.get(e)??new Set;se.set(e,i);let n=e.hasAttribute("data-native-ui-snapshot-source")||e.hasAttribute("data-native-ui-snapshot-prepared"),s=w=>{if(!w.hasAttribute("data-native-ui-projection-node")){if(w.shadowRoot){i.add(w),oe(w.shadowRoot);for(let L of w.shadowRoot.children)s(L)}for(let L of w.children)s(L)}};s(e);for(let w of i)w.toggleAttribute("data-native-ui-snapshot-content",n);F(e)},O=()=>`${p.innerWidth}:${p.innerHeight}:${p.devicePixelRatio}`,Z=!1,ae=!1,J=0,me=0,R,U,E,X=!1,Y=!1,we=0,We=0,ee=0,ve=0,z=0,be=-1,V=!1,te=!1,q,le=()=>{let e=new Set(q?.candidates.map(n=>n.scope.element));for(let[n,s]of C){let w=!V&&n.isConnected&&e.has(n);if(w&&(E||Y||Z))continue;let L=w?2*Math.max(1,R?.scopes.filter(k=>k.element===n).length??0):0,x=Math.max(0,L-s.filter(k=>j.has(k)).length),I=s.filter(k=>j.has(k)||x-- >0?!0:(k.remove(),!1));I.length?C.set(n,I):C.delete(n)}let i=new Set([...q?.candidates.map(n=>n.scope.element.getRootNode())??[],...E?.scopes.map(n=>n.element.getRootNode())??[],...R?.scopes.map(n=>n.element.getRootNode())??[],...[...j].map(n=>n.getRootNode()),...[...se.values()].flatMap(n=>[...n].map(s=>s.shadowRoot))]);for(let[n,s]of A)n.nodeType===11&&!i.has(n)&&(s.remove(),A.delete(n))},he=new Set,Ie=(e,i,n)=>{let s=()=>{he.delete(s)&&(S({action:e,token:i}).catch(()=>{}),e==="restored"&&le())};he.add(s),n&&!V?p.requestAnimationFrame(()=>p.requestAnimationFrame(s)):s()},de=new Set,He=()=>{de.forEach(e=>D.delete(e)),de.clear()},xe=(e,i=!1)=>{!e||e===R||e===E||(e.images.forEach(n=>{B(n)}),Ie("release",e.data.token,i))},Ee=e=>e.filter(i=>{if(!M(i.scope.element)||![i.element,...i.sources??[]].every(f))return!1;if(i.control.scroll){let n=i.element.getBoundingClientRect(),s=i.control.scroll.clip;if(n.right<=s.x||n.bottom<=s.y||n.left>=s.x+s.width||n.top>=s.y+s.height)return!1}return o.eligible?.(i)??!0}),Xe=async()=>{if(ve=0,V||h!=="native"||te||X||E||o.blocked()||!q||R?.revision===q.revision&&be===z)return;let{revision:e,candidates:i}=q,n=ee,s=z,w=O(),L=Ee(i);if(!L.length)return;te=!0;let x,I=!1,k=[];try{let ie=!1,re=S({action:"prepare",revision:o.captureRevision?.()??e,viewportWidth:p.innerWidth,ids:L.map(b=>b.control.id)});re.then(b=>{ie&&b.token&&S({action:"release",token:b.token}).catch(()=>{})}).catch(()=>{});try{x=await pe(re)}finally{ie=!0}if(!x.token||!x.parts?.length)return;let Ce=new Set(L.map(b=>b.control.id));if(x.parts.some(b=>!b.url.startsWith("file:")&&!/^\/__native_ui_engine__\/nativeUI[a-fA-F0-9]+\/[a-fA-F0-9-]+-[0-9]+\.png$/.test(b.url)||!b.ids.length||b.ids.some(G=>!Ce.has(G))||![b.x,b.y,b.width,b.height].every(Number.isFinite)||b.width<=0||b.height<=0)||new Set(x.parts.flatMap(b=>b.ids)).size!==Ce.size||x.parts.some(b=>new Set(b.ids.map(G=>L.find(Me=>Me.control.id===G).scope)).size!==1)||(await pe(Promise.all(x.parts.map(async b=>{let G=L.find(qe=>b.ids.includes(qe.control.id)).scope.element,Me=C.get(G);Me||(Me=[],C.set(G,Me));let fe=Me.find(qe=>!j.has(qe));return fe||(fe=d.createElement("img"),fe.setAttribute("data-native-ui-projection-node",""),Me.push(fe)),j.add(fe),k.push(fe),fe.src=nt(b.url),await fe.decode(),fe}))),V||h!=="native"||X||E||o.blocked()||ee!==n||O()!==w||q?.revision!==e||L.some(b=>!M(b.scope.element)||![b.element,...b.sources??[]].every(f))))return;let Re=R,Qe={data:x,images:k,sources:Array.from(new Set(L.flatMap(b=>[b.element,...b.sources??[]]))),targets:x.parts.map(b=>L.find(G=>b.ids.includes(G.control.id)).element),scopes:x.parts.map(b=>L.find(G=>b.ids.includes(G.control.id)).scope),parents:x.parts.map(b=>L.find(G=>b.ids.includes(G.control.id)).scope.element.parentNode),frames:[],viewport:w,revision:e,layout:W(L)};try{if(!Ue(Qe))throw new Error("Projection destination is unavailable")}catch(b){Q();try{Ue(),R?.sources.forEach(G=>{D.add(G),de.add(G)})}catch{$()}throw b}R=Qe,He(),R.sources.forEach(b=>{D.add(b),de.add(b)}),o.opaqueUnderlay&&S({action:"prepared",token:x.token}).catch(()=>{}),p.dispatchEvent(new Event(l)),be=s,I=!0,xe(Re)}catch{}finally{I||k.forEach(ie=>{B(ie)}),x?.token&&!I&&S({action:"release",token:x.token}).catch(()=>{}),te=!1,q&&(q.revision!==e||z!==s)&&ue()}},ue=()=>{p.clearTimeout(ve),!V&&h==="native"&&!E&&!X&&!o.blocked()&&(ve=p.setTimeout(()=>{Xe()},v))},Ae,a=(e,i)=>{if(i.some(s=>s.scope.element.ownerDocument!==d||[s.element,...s.sources??[]].some(w=>w.ownerDocument!==d)))throw new TypeError("All projection sources and fallback scopes must share one Document");if(o.blocked())return;!E&&R&&R.layout!==W(Ee(i))&&Le();let n=W(i);Ae!==n&&(Ae=n,z++,ee++),q?.revision!==e&&ee++,q={revision:e,candidates:i},le(),(R?.revision!==e||be!==z)&&ue()},u=e=>{!E&&R&&e&&q?.candidates.some(i=>i.scrollElement===e&&R.sources.includes(i.element))&&Le(),E&&ke(),z++,ee++,ue()},P=await Be(r.listen(({active:e})=>{X=e,ee++,p.clearTimeout(ve),e||ue()})),_=new Set,ce=e=>{se.get(e)?.forEach(i=>i.removeAttribute("data-native-ui-snapshot-content")),_.delete(e)&&e.removeAttribute("data-native-ui-snapshot-prepared"),e.removeAttribute("data-native-ui-snapshot-visible"),e.removeAttribute(y),e.removeAttribute("data-native-ui-snapshot-positioned"),e.removeAttribute("data-native-ui-snapshot-source"),F(e),se.delete(e)},Q=()=>{me++,p.cancelAnimationFrame(J);for(let e of Fe.keys())e.style.setProperty("opacity","0","important");Fe.clear(),J=0,Z=!1,ae=!1,He(),E?.sources.forEach(e=>{g.delete(e),c.delete(e)}),K.forEach(ce),K.clear(),A.forEach((e,i)=>{(V||i.nodeType===11&&!i.host.isConnected)&&(e.remove(),A.delete(i))})},$=(e=E,i=Y)=>{if(E!==e)return;let n=new Set([E,R]);Q(),E=void 0,R=void 0,Y=!1,p.clearTimeout(we),n.forEach(s=>xe(s,i)),o.wake()},ne=()=>{if(!E)return;if(R!==E){$();return}let e=E;me++,p.cancelAnimationFrame(J),J=0,Z=ae=!1,e.sources.forEach(i=>{g.delete(i),c.delete(i)}),K.forEach(i=>i.removeAttribute("data-native-ui-snapshot-source")),Ye(e,!!o.opaqueUnderlay),E=void 0,Y=!1,p.clearTimeout(we),Ie("restored",e.data.token,!0),e.sources.forEach(i=>{D.add(i),de.add(i)}),o.wake()},Pe=()=>{Y&&ne(),he.forEach(e=>e())},De=e=>e.scopes.map(i=>M(i.element)?i.element:void 0),K=new Set,Ye=(e,i)=>{for(let n of K)n.toggleAttribute("data-native-ui-snapshot-visible",i),Se(n);e.images.forEach(n=>n.style.setProperty("opacity",i?"1":"0","important"))},Te=new Map,Fe=new Map,ke=()=>{let e=[];for(let[i,n]of Fe){let{target:s,frame:w}=n;if(!s.isConnected){i.remove();continue}let L=s.shadowRoot??s;i.parentNode!==L&&L.insertBefore(i,null);let x=s.getBoundingClientRect(),I=i.getBoundingClientRect(),k=parseFloat(i.style.width)||w.width,ie=parseFloat(i.style.height)||w.height,re=I.width/k||1,Ce=I.height/ie||1,Re=n.scale??(n.scale={x:re,y:Ce});e.push({element:i,left:(parseFloat(i.style.left)||0)+(x.x+w.x*re/Re.x-I.x)/re,top:(parseFloat(i.style.top)||0)+(x.y+w.y*Ce/Re.y-I.y)/Ce,width:w.width/Re.x,height:w.height/Re.y})}for(let{element:i,...n}of e)for(let[s,w]of Object.entries(n)){let L=`${w}px`;i.style.getPropertyValue(s)!==L&&i.style.setProperty(s,L,"important")}},Ue=(e=E??R)=>{if(!e||Y)return!1;if(!e.sources.every(f)||De(e).some(n=>!n))return Le(),!1;let i=De(e);if(E){if(i.some(s=>!s?.isConnected||!A.has(N(s))))return $(E,!1),!1;let n=new Set;for(let[s,w]of e.targets.entries()){let L=Te.get(w),x=i[s];!L||!x?.isConnected||(x.setAttribute(y,L),x.setAttribute("data-native-ui-snapshot-source",""),n.add(x))}for(let s of K)n.has(s)||ce(s);return K.clear(),n.forEach(s=>K.add(s)),Ye(e,ae),!0}C.forEach(n=>n.forEach(s=>{e.images.includes(s)||(s.style.setProperty("display","none","important"),s.removeAttribute("data-native-ui-projection-image"))})),Te.clear(),Fe.clear(),K.forEach(ce),K.clear();for(let n of new Set(i)){if(!n)continue;K.add(n),oe(N(n));let s=i.flatMap((I,k)=>I===n?[k]:[]),w=`${T}-${s[0]}`;n.setAttribute(y,w),n!==e.targets[s[0]]&&(n.setAttribute("data-native-ui-snapshot-prepared",""),_.add(n)),n.toggleAttribute("data-native-ui-snapshot-visible",!!o.opaqueUnderlay),Se(n);let L=n.getBoundingClientRect();n.toggleAttribute("data-native-ui-snapshot-positioned",p.getComputedStyle(n).position==="static");let x=n.shadowRoot??n;for(let I of[...s].reverse()){let k=e.images[I],ie=e.data.parts[I],re=e.frames[I]??{x:o.opaqueUnderlay?0:ie.x-L.x,y:o.opaqueUnderlay?0:ie.y-L.y,width:ie.width,height:ie.height};e.frames[I]=re,Te.set(e.targets[I],w),k.inert=!0,k.setAttribute("aria-hidden","true"),k.setAttribute("data-native-ui-projection-image",w),k.style.cssText="all:initial!important;position:absolute!important;display:block!important;visibility:visible!important;pointer-events:none!important;image-rendering:pixelated!important;will-change:opacity!important;opacity:0!important;",k.style.setProperty("width",`${re.width}px`,"important"),k.style.setProperty("height",`${re.height}px`,"important"),k.style.setProperty("left","0px","important"),k.style.setProperty("top","0px","important"),k.style.setProperty("opacity",o.opaqueUnderlay?"1":"0","important"),k.parentNode!==x&&x.insertBefore(k,null),Fe.set(k,{target:n,frame:re})}}return e.parents=e.scopes.map(n=>n.element.parentNode),ke(),!0},Je=()=>{if(Pe(),V||h==="none"||E)return;let e=R;if(!e||e.viewport!==O()||!e.sources.every(f)||o.current&&!o.current(e.targets)||e.targets.some(s=>!Te.has(s))||De(e).some(s=>!s?.isConnected)||[...K].some(s=>!s.isConnected))return;e.scopes.some((s,w)=>s.element.parentNode!==e.parents[w])&&Ue(),ke();let i=++me,n=()=>me===i;Z=!0,ae=!!o.opaqueUnderlay,E=e,ee++,p.clearTimeout(ve),e.sources.forEach(s=>{g.add(s)}),K.forEach(s=>s.setAttribute("data-native-ui-snapshot-source","")),Ye(e,ae),J=p.requestAnimationFrame(()=>{J=0,n()&&(Z=!1,o.wake())}),pe(S({action:"cover",token:e.data.token,...o.transient?{transient:!0}:{}})).then(s=>{if(n()){if(Z=!1,s?.shown!==!0){$(e,!1);return}Y||(ae=!0,e.sources.forEach(w=>c.add(w)),Ue(),ke()),p.cancelAnimationFrame(J),J=p.requestAnimationFrame(()=>{J=0,n()&&S({action:"painted",token:e.data.token}).catch(()=>{n()&&$(e,!1)})}),o.wake()}}).catch(()=>{n()&&$(e,!1)})},Ke=()=>{if(!(!E||V||Y)){if(!E.sources.every(f)||De(E).some(e=>!e)||o.current&&!o.current(E.targets)){$(E,!1);return}if(!o.blocked()){if(ke(),o.transient){let e=E,i=De(e);if(i.some(s=>!s?.isConnected)){$(e,!1);return}let n=i.map((s,w)=>{let L=e.images[w].getBoundingClientRect();return{...e.data.parts[w],x:L.x,y:L.y,width:L.width,height:L.height}});n.some((s,w)=>["x","y","width","height"].some(L=>s[L]!==e.data.parts[w][L]))&&(o.opaqueUnderlay?e.data={...e.data,parts:n}:R=void 0),S({action:"restore",token:e.data.token,viewportWidth:p.innerWidth,parts:n}).catch(()=>{})}We=o.revision?.()??q?.revision??0,Y=!0,E.sources.forEach(e=>g.delete(e)),we=p.setTimeout(()=>{Y&&$(E,!1)},250),o.wake()}}},ct=(e,i,n)=>{!Y||!E||n===void 0||n<=We||!o.transient&&e||E.data.parts.every(s=>s.ids.every(w=>i?.includes(w)))&&(ne(),ue())},Le=(e=!1)=>{he.forEach(n=>n()),ee++,p.clearTimeout(ve);let i=R;if(R=void 0,(e||V)&&(U=void 0),o.retainPreparedUntilRelease&&!E&&!e&&!V){if(i){let n={};U=n,pe(S({action:"release",token:i.data.token})).catch(()=>{}).finally(()=>{if(i.images.forEach(s=>{B(s)}),U===n&&(U=void 0,!(R||E))){for(let s of K)s.getAttribute(y)!==Te.get(s)&&(K.delete(s),_.delete(s));Q(),Te.clear(),p.dispatchEvent(new Event(l))}})}if(U)return}xe(i),E?$(E,!1):(Q(),Te.clear(),p.dispatchEvent(new Event(l)))},Ze;try{o.events&&(Ze=await ze(o.events,(e,i)=>{if(!V){if(e&&(h=e),h==="none"){Le();return}e==="web"&&Je(),e==="native"&&(Ke(),ue()),i&&u()}}))}catch(e){throw V=!0,Le(),await pe(Promise.resolve(P.remove())).catch(()=>{}),e}return{get owners(){return[...g]},get visibleOwners(){return[...c]},get preparedOwners(){return[...D]},consider:a,changed:u,moved:ke,get token(){return Y?void 0:E?.data.token},begin:Je,get restoring(){return Y},get handoffPending(){return Z},retireRestoration:Pe,end:Ke,updated:ct,invalidate:Le,async destroy(){V=!0,Le(),C.forEach(e=>e.forEach(i=>i.remove())),C.clear(),await Promise.allSettled([pe(Promise.resolve().then(()=>P?.remove())),pe(Promise.resolve().then(()=>Ze?.remove()))])}}},rt=async(t,r,o)=>{let v=await Et(t,r,o),h=v.consider;return Object.assign(v,{consider:(d,p)=>h(d,p.map(T=>({...T,scope:t})))})};var st=(t,r)=>{if(t.nodeType!==1||t.namespaceURI!=="http://www.w3.org/1999/xhtml"||!t.ownerDocument||!("style"in t))throw new TypeError("A Web projection scope requires an HTMLElement fallback");if(t.matches("img, input, textarea, select, video, audio, canvas, iframe, embed, object, br, hr, area, base, link, meta, source, track, wbr"))throw new TypeError("A Web projection fallback must be a container element; use the existing fallback container");return Object.freeze({element:t,namespace:r})};var at=async(t,r,o={addListener:async(v,h)=>globalThis.Capacitor.addListener(t.plugin,v,h)})=>{let v="view",h=t.receiveListeners,d=!1,p=!1,T="native",f="native",M=et(document),C=()=>T==="native"&&m&&M.isCovered(m)?"web":T,j=!1,B=!1,S=!1,y=!1,l,g,c,D=!1,H=()=>{let a=D;D=!0;try{c?.begin()}finally{D=a}},m,W={},A,N=0,oe=()=>{N++,A=void 0;let a=Ne(m);a?.owner===W&&(a.projected=!1,a.owner=void 0)},F={listen:a=>r.listen(u=>{a(u),!u.active&&B&&(B=!1,he()),u.precovered&&Ie(u.precovered)}),send:a=>{if(!t.placementHandoff)return r.send(a);let u=Ne(m);if(a.action==="cover"){let $=A===a.token&&u?.owner===W&&u.projected,ne=++N;return A=a.token,u&&(u.owner=W,u.projected=!!$),r.send(a).then(Pe=>(ne===N&&u===Ne(m)&&u?.owner===W&&(u.projected=Pe?.shown===!0),Pe)).catch(Pe=>{throw ne===N&&u?.owner===W&&(oe(),u.invalidate()),Pe})}let P=a.action==="release"&&a.token===A,_=A&&(a.action==="restore"||P||a.action==="reset"||a.action==="visibility"&&a.visible);_&&N++;let ce=N,Q=()=>{ce!==N||u?.owner!==W||(oe(),u.invalidate())};try{let $=_&&u?.owner===W?{...a,token:"token"in a?a.token:A,placement:u.measure()}:a;return r.send($).then(async ne=>(a.action==="release"&&ne?.requiresPlacement&&(u&&(!u.owner||u.owner===W)?ne=await r.send({...a,placement:u.measure()}):(await r.send({action:"visibility",visible:!1}),ne=await r.send(a))),ce===N&&(a.action==="resume"||P||a.action==="reset"||a.action==="visibility")&&oe(),ne)).catch(ne=>{throw Q(),ne})}catch($){return Q(),Promise.reject($)}}},se=0,Se=0,O=0,Z=!1,ae=!1,J=!1,me=!1,R=document.createElement("style"),U=`data-native-ui-session-${t.id.replace(/[^a-zA-Z0-9-]/g,"")}`;R.textContent=`[${U}="native"], [${U}="native"] *, [${U}="none"], [${U}="none"] * {visibility:hidden!important}`,document.documentElement.append(R);let E=()=>{let a=document.querySelectorAll(t.projectionSelector);return a.length===1&&a[0]instanceof HTMLElement?a[0]:void 0},X=()=>{m&&(f==="web"||f==="native"&&S?m.removeAttribute(U):m.setAttribute(U,f))},Y=()=>{if(!m?.isConnected)return[];let a=m.getBoundingClientRect(),u={x:a.x,y:a.y,width:a.width,height:a.height},P=l&&Ge(m,l,()=>t.id);return[{element:m,scrollElement:l,control:{id:v,...u,...P?{scroll:P}:{}}}]},we=async()=>{if(d)return;let a=O,u=t.failedFallback==="native"&&C()==="native"&&!!m?.isConnected;await F.send({action:"visibility",visible:u}).catch(()=>{}),!(d||a!==O)&&(u||f==="none"?X():m?.removeAttribute(U))},We=()=>{if(!j||f!=="native"||!m?.isConnected||c?.token||c?.restoring)return;let a=t.failedFallback==="native";F.send({action:"visibility",visible:a}).catch(()=>{}),a?X():m.removeAttribute(U)},ee=()=>{if(d||D)return;if(We(),f==="web"&&c&&!c.token&&!c.restoring){F.send({action:"visibility",visible:!1}).catch(()=>{}),m?.removeAttribute(U);return}if(Z||!c?.restoring||f!=="native")return;Z=!0;let a=O,u=c;F.send({action:"resume"}).then(()=>{d||a!==O||u!==c||(S=!1,u.updated(void 0,[v],++se),X(),z())}).catch(()=>{a!==O||d||(u.invalidate(),we())}).finally(()=>{Z=!1,ee()})},ve=async()=>{if(d||ae||j)return;if(c?.restoring){ee();return}let a=E();if(a!==m){if(O++,oe(),m?.removeAttribute(U),await c?.destroy(),c=void 0,d)return;m=a,S=!1,V.disconnect(),m&&V.observe(m)}if(de(),f=C(),!m){await F.send({action:"visibility",visible:!1});return}if(!c){ae=!0;let u=O;try{let P=await rt(st(m,t.id),F,{wake:ee,captureDelayMs:t.captureDelayMs,retainPreparedUntilRelease:t.nativeDragHandoff,blocked:()=>f!=="native"||j||S&&!c?.token,revision:()=>se,captureRevision:t.captureMode==="view"?()=>Se:void 0,transient:!0,opaqueUnderlay:t.captureMode==="view"});if(d||u!==O){await P.destroy();return}c=P}finally{ae=!1}}if(!d){if(f==="native"&&S){await F.send({action:"visibility",visible:!1}),X();return}if(f==="native"){let u=O;if(await F.send({action:"visibility",visible:!0}),d||u!==O||f!=="native")return;X(),l=[...Oe(m)].find(Q=>{let $=getComputedStyle(Q);return/auto|scroll/.test($.overflowX+$.overflowY)&&(Q.scrollHeight>Q.clientHeight||Q.scrollWidth>Q.clientWidth)});let P=l&&Ge(m,l,()=>t.id),_=t.nativeDragHandoff&&t.captureMode==="view"&&l?{viewport:l.getBoundingClientRect(),offsetX:l.scrollLeft,offsetY:l.scrollTop,rangeX:Math.max(0,l.scrollWidth-l.clientWidth),rangeY:Math.max(0,l.scrollHeight-l.clientHeight)}:void 0,ce=await F.send({action:"track",viewportWidth:innerWidth,...P?{scroll:P}:{},..._?{gesture:_}:{}});if(d||u!==O)return;y=ce?.tracked===!0,c.consider(t.captureMode==="view"?Se:++se,Y())}else await F.send({action:"visibility",visible:!1}),X()}},z=()=>{d||(J=!0,!(!p||me)&&(me=!0,Promise.resolve().then(async()=>{try{for(;J&&!d;)J=!1,await ve()}catch{await we()}finally{me=!1,J&&z()}})))},be=()=>{c?.invalidate(),z()},V=new ResizeObserver(be);window.addEventListener("resize",be);let te=new Set,q=new WeakSet,le=a=>{let u=a.composedPath()[0]??a.target;if(!(d||f!=="native"||!m||q.has(a)||u!==document&&![...Oe(m)].includes(u))){if(q.add(a),y&&u===l){c?.changed(u);return}j?t.captureMode==="view"?c?.moved():c?.changed():((Z||c?.restoring)&&O++,j=!0,H(),S=t.failedFallback!=="native",c?.token?m.removeAttribute(U):We()),he()}};function he(){clearTimeout(g),g=setTimeout(()=>{B||(j=!1,!(d||f!=="native")&&(c?.end(),c?.restoring||z()))},100)}function Ie(a){if(d||f!=="native"||!m?.isConnected||!c||(B=!0,c.token))return;(Z||c.restoring)&&O++,A=a;let u=Ne(m);if(u&&(u.owner=W,u.projected=!0),j=!0,H(),!c.token){j=!1,we();return}he()}let de=()=>{let a=new Set(m?Oe(m):[]),u=a.size!==te.size||[...a].some(P=>!te.has(P));for(let P of te)a.has(P)||(P.removeEventListener("scroll",le),P.removeEventListener("slotchange",He),te.delete(P));for(let P of a)te.has(P)||(P.addEventListener("scroll",le,{passive:!0}),P.addEventListener("slotchange",He),te.add(P));u&&(y=!1,l=void 0,c?.invalidate(),z())},He=()=>{de(),z()};document.addEventListener("scroll",le,!0);let xe=new MutationObserver(a=>{de(),E()!==m?z():m&&a.some(u=>u.type==="childList")&&Ee(C())});xe.observe(document.documentElement,{childList:!0,attributes:!0,subtree:!0});let Ee=(a,u=!1)=>{if(d||!u&&a===f)return;j=!1,B=!1,clearTimeout(g),f=a,O++;let P=O;f==="none"?F.send({action:"visibility",visible:!1}).then(()=>{f!=="none"||P!==O||d||(c?.invalidate(!0),X())}).catch(()=>{f!=="none"||P!==O||d||(c?.invalidate(!0),X())}):f==="web"?(H(),c?.token||F.send({action:"visibility",visible:!1}).catch(()=>{}),X()):f==="native"&&(c?.end(),c?.restoring||z())},Xe=M.subscribe(()=>queueMicrotask(()=>Ee(C()))),ue,Ae=async()=>{if(!d){d=!0,oe(),O++,Xe(),xe.disconnect(),V.disconnect(),window.removeEventListener("resize",be),document.removeEventListener("scroll",le,!0);for(let a of te)a.removeEventListener("scroll",le),a.removeEventListener("slotchange",He);te.clear(),clearTimeout(g),m?.removeAttribute(U),R.remove(),await Promise.allSettled([Promise.resolve(ue?.remove()),c?.destroy()])}};try{let a=O;ue=await ze({addListener:(P,_)=>o.addListener(P,_),update:h?.update,projection:h?.projection,accepts:P=>!h?.match||Object.entries(h?.match).every(([_,ce])=>typeof P=="object"&&P!==null&&P[_]===ce)},(P,_)=>{d||(P&&(P==="native"&&(S=!1),T=P,F.send({action:"destination",destination:P}).catch(()=>{}),Ee(C(),!0)),_&&(Se++,c?.changed(),f==="native"&&z()))});let u=await F.send({action:"connect"});return O===a&&["native","web","none"].includes(u?.destination)&&(T=u.destination),f=C(),p=!0,z(),{destroy:Ae,show:()=>{S=!1,T="native",Ee(C(),!0)},hide:()=>{T=f="none",O++,j=!1,B=!1,clearTimeout(g),c?.invalidate(!0),X()}}}catch(a){throw await Ae(),a}};var ye=new Map,ge=new Map,$e=new Map,Pt=Array.from(crypto.getRandomValues(new Uint32Array(4)),t=>t.toString(16)).join("-"),Ve=new Map,Tt=(t,r,o)=>Ve.get(t)?.get(r)?.(o),Lt=0,Mt=(t,r)=>new Promise((o,v)=>{let h=`${t}:${Pt}:${++Lt}`,d=setTimeout(()=>{ge.delete(h),v(new Error("Native UI session timed out"))},5e3);ge.set(h,{resolve:o,reject:v,timer:d});let p=JSON.stringify({key:h,request:r}),T=globalThis,f=T.webkit?.messageHandlers?.[t]??T[t]??T.nativeUIEngine;if(!f){clearTimeout(d),ge.delete(h),v(new Error("Native UI session unavailable"));return}try{f.postMessage(p)}catch(M){clearTimeout(d),ge.delete(h),v(M)}}),jt=(t,r,o,v)=>{if(r===null){$e.get(t)?.(o);return}let h=ge.get(r);!h||!r.startsWith(`${t}:`)||(ge.delete(r),clearTimeout(h.timer),v?h.reject(new Error(v)):h.resolve(o))},St=t=>{ye.get(t)?.show()},Ht=t=>(ye.get(t)?.hide(),!0),xt=async t=>{let r=ye.get(t);ye.delete(t),Ve.delete(t),$e.delete(t);for(let[o,v]of ge)o.startsWith(`${t}:`)&&(ge.delete(o),clearTimeout(v.timer),v.reject(new Error("Native UI session closed")));await r?.destroy()},kt=async t=>{if(ye.has(t.id))return;let r,o=!1,v,h={show:()=>{v="native",r?.show()},hide:()=>{v="none",r?.hide()},destroy:async()=>{o=!0,await r?.destroy()}};ye.set(t.id,h);try{if(document.readyState==="loading"&&await new Promise(p=>document.addEventListener("DOMContentLoaded",()=>p(),{once:!0})),o)return;let d=new Map;t.runtime==="cordova"&&Ve.set(t.id,d),r=await at(t,{send:p=>Mt(t.id,p),listen:async p=>($e.set(t.id,p),{remove:()=>{$e.get(t.id)===p&&$e.delete(t.id)}})},t.runtime==="cordova"?{addListener:async(p,T)=>(d.set(p,T),{remove:()=>{d.delete(p)}})}:void 0),v==="none"?r.hide():v==="native"&&r.show(),o&&await r.destroy()}catch(d){throw ye.get(t.id)===h&&(ye.delete(t.id),Ve.delete(t.id)),d}};return mt(Ct);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webview-native-ui/engine",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Native UI placement, scroll tracking, and Web projection for Capacitor and Cordova plugin maintainers.",
5
5
  "type": "module",
6
6
  "license": "MIT",