@blotch/angular-lynx 22.2.9 → 22.2.10

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.
@@ -1571,99 +1571,323 @@ const takePageHandOver = () => {
1571
1571
  handingOverPage = false;
1572
1572
  return handing;
1573
1573
  };
1574
- let firstRenderPending = true;
1575
- const deferredCallbacks = [];
1576
- const isFirstRenderPending = () => firstRenderPending;
1577
- const markFirstRenderComplete = () => {
1578
- firstRenderPending = false;
1579
- if (deferredCallbacks.length === 0) return;
1580
- const callbacks = deferredCallbacks.splice(0);
1581
- setTimeout(() => {
1582
- for (const callback of callbacks) callback();
1583
- }, 0);
1574
+ var LynxEvent = class {
1575
+ static NONE = 0;
1576
+ static CAPTURING_PHASE = 1;
1577
+ static AT_TARGET = 2;
1578
+ static BUBBLING_PHASE = 3;
1579
+ type;
1580
+ bubbles;
1581
+ cancelable;
1582
+ composed;
1583
+ timeStamp = Date.now();
1584
+ isTrusted = false;
1585
+ target = null;
1586
+ currentTarget = null;
1587
+ eventPhase = 0;
1588
+ defaultPrevented = false;
1589
+ cancelBubble = false;
1590
+ constructor(type, init = {}) {
1591
+ this.type = String(type);
1592
+ this.bubbles = !!init.bubbles;
1593
+ this.cancelable = !!init.cancelable;
1594
+ this.composed = !!init.composed;
1595
+ }
1596
+ get srcElement() {
1597
+ return this.target;
1598
+ }
1599
+ preventDefault() {
1600
+ if (this.cancelable) this.defaultPrevented = true;
1601
+ }
1602
+ stopPropagation() {
1603
+ this.cancelBubble = true;
1604
+ }
1605
+ stopImmediatePropagation() {
1606
+ this.cancelBubble = true;
1607
+ }
1608
+ composedPath() {
1609
+ return this.currentTarget ? [this.currentTarget] : [];
1610
+ }
1584
1611
  };
1585
- const runAfterFirstRender = (callback) => {
1586
- if (!firstRenderPending) {
1587
- callback();
1588
- return;
1612
+ var LynxCustomEvent = class extends LynxEvent {
1613
+ detail;
1614
+ constructor(type, init = {}) {
1615
+ super(type, init);
1616
+ this.detail = init.detail ?? null;
1589
1617
  }
1590
- deferredCallbacks.push(callback);
1591
1618
  };
1592
- let insideChangeDetection = false;
1593
- const setInsideChangeDetection = (value) => {
1594
- insideChangeDetection = value;
1619
+ const installEventClasses = (target) => {
1620
+ if (typeof target["Event"] === "undefined") target["Event"] = LynxEvent;
1621
+ if (typeof target["CustomEvent"] === "undefined") target["CustomEvent"] = LynxCustomEvent;
1595
1622
  };
1596
- const isInsideChangeDetection = () => insideChangeDetection;
1597
- const DASHARRAY_ATTRIBUTE = /(\s)stroke-dasharray\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
1598
- const DASHARRAY_DECLARATION = /(^|[;"'\s])stroke-dasharray\s*:\s*([^;"']*);?/g;
1599
- const DASH_LENGTH = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(?:px)?$/i;
1600
- const normalizeDashArray = (value) => {
1601
- const tokens = value.trim().split(/[\s,]+/).filter((token) => token.length > 0);
1602
- if (tokens.length === 0 || !tokens.every((t) => DASH_LENGTH.test(t))) return null;
1603
- return tokens.map((token) => token.replace(/px$/i, "")).join(" ");
1623
+ const createEvent = (type) => {
1624
+ return new (globalThis.Event ?? LynxEvent)(type);
1604
1625
  };
1605
- const sanitizeSvgContent = (content) => {
1606
- if (!content.includes("stroke-dasharray")) return content;
1607
- return content.replace(DASHARRAY_ATTRIBUTE, (_match, space, double, single) => {
1608
- const normalized = normalizeDashArray(double ?? single);
1609
- return normalized === null ? "" : `${space}stroke-dasharray="${normalized}"`;
1610
- }).replace(DASHARRAY_DECLARATION, (_match, before, value) => {
1611
- const normalized = normalizeDashArray(value);
1612
- return normalized === null ? before : `${before}stroke-dasharray:${normalized};`;
1626
+ const captureOf = (options) => typeof options === "boolean" ? options : !!options?.capture;
1627
+ const setEventProperty = (event, name, value) => {
1628
+ Object.defineProperty(event, name, {
1629
+ value,
1630
+ writable: true,
1631
+ configurable: true,
1632
+ enumerable: true
1613
1633
  });
1614
1634
  };
1615
- const toDOMRect = (rect) => {
1616
- const { left = 0, top = 0, width = 0, height = 0 } = rect ?? {};
1617
- const box = {
1618
- x: left,
1619
- y: top,
1620
- width,
1621
- height,
1622
- top,
1623
- left,
1624
- right: left + width,
1625
- bottom: top + height
1626
- };
1627
- return {
1628
- ...box,
1629
- toJSON: () => ({ ...box })
1630
- };
1635
+ const withProperties = (event, values, run) => {
1636
+ const saved = Object.keys(values).map((name) => [name, Object.getOwnPropertyDescriptor(event, name)]);
1637
+ for (const [name, value] of Object.entries(values)) setEventProperty(event, name, value);
1638
+ try {
1639
+ run();
1640
+ } finally {
1641
+ for (const [name, descriptor] of saved) if (descriptor) Object.defineProperty(event, name, descriptor);
1642
+ else delete event[name];
1643
+ }
1631
1644
  };
1632
- var LayoutTracker = class {
1633
- #host;
1634
- #observers = /* @__PURE__ */ new Set();
1635
- #listening = false;
1636
- rect = null;
1637
- constructor(host) {
1638
- this.#host = host;
1645
+ var GlobalEventTarget = class {
1646
+ #entries = /* @__PURE__ */ new Map();
1647
+ #owner;
1648
+ #onTypesChange = () => {};
1649
+ constructor(owner) {
1650
+ this.#owner = owner ?? this;
1651
+ }
1652
+ set onTypesChange(callback) {
1653
+ this.#onTypesChange = callback;
1654
+ }
1655
+ addEventListener(type, callback, options) {
1656
+ if (!callback) return;
1657
+ const capture = captureOf(options);
1658
+ const signal = typeof options === "object" ? options.signal : void 0;
1659
+ if (signal?.aborted) return;
1660
+ const list = this.#entries.get(type) ?? [];
1661
+ if (list.some((e) => e.callback === callback && e.capture === capture)) return;
1662
+ const entry = {
1663
+ callback,
1664
+ capture,
1665
+ once: typeof options === "object" && !!options.once,
1666
+ unlinkSignal: null
1667
+ };
1668
+ if (signal) {
1669
+ const abort = () => this.#remove(type, entry);
1670
+ signal.addEventListener("abort", abort);
1671
+ entry.unlinkSignal = () => signal.removeEventListener?.("abort", abort);
1672
+ }
1673
+ list.push(entry);
1674
+ this.#entries.set(type, list);
1675
+ if (list.length === 1) this.#onTypesChange();
1639
1676
  }
1640
- start() {
1641
- if (this.#listening) return;
1642
- this.#listening = true;
1643
- this.#host.listen("bindlayoutchange", (event) => {
1644
- const detail = event?.detail;
1645
- if (detail && typeof detail.width === "number") this.#update(detail);
1677
+ removeEventListener(type, callback, options) {
1678
+ const capture = captureOf(options);
1679
+ const entry = this.#entries.get(type)?.find((e) => e.callback === callback && e.capture === capture);
1680
+ if (entry) this.#remove(type, entry);
1681
+ }
1682
+ dispatchEvent(event) {
1683
+ return dispatchThrough(event, this.#owner, [this]);
1684
+ }
1685
+ get types() {
1686
+ return [...this.#entries.keys()];
1687
+ }
1688
+ hasListeners(type) {
1689
+ return this.#entries.has(type);
1690
+ }
1691
+ get owner() {
1692
+ return this.#owner;
1693
+ }
1694
+ invoke(type, event, phase, state) {
1695
+ const list = this.#entries.get(type);
1696
+ if (!list) return;
1697
+ const ordered = phase === 2 ? [...list.filter((e) => e.capture), ...list.filter((e) => !e.capture)] : list.filter((e) => e.capture === (phase === 1));
1698
+ withProperties(event, {
1699
+ currentTarget: this.#owner,
1700
+ eventPhase: phase
1701
+ }, () => this.#run(type, ordered, event, state));
1702
+ }
1703
+ #run(type, entries, event, state) {
1704
+ for (const entry of entries) {
1705
+ if (!this.#entries.get(type)?.includes(entry)) continue;
1706
+ if (entry.once) this.#remove(type, entry);
1707
+ try {
1708
+ if (typeof entry.callback === "function") entry.callback.call(this.#owner, event);
1709
+ else entry.callback.handleEvent(event);
1710
+ } catch (error) {
1711
+ queueMicrotask(() => {
1712
+ throw error;
1713
+ });
1714
+ }
1715
+ if (state.stoppedImmediately) break;
1716
+ }
1717
+ }
1718
+ #remove(type, entry) {
1719
+ const list = this.#entries.get(type);
1720
+ const index = list?.indexOf(entry) ?? -1;
1721
+ if (!list || index < 0) return;
1722
+ list.splice(index, 1);
1723
+ entry.unlinkSignal?.();
1724
+ if (list.length > 0) return;
1725
+ this.#entries.delete(type);
1726
+ this.#onTypesChange();
1727
+ }
1728
+ };
1729
+ var PathDispatch = class {
1730
+ #event;
1731
+ #target;
1732
+ #path;
1733
+ #type;
1734
+ #state;
1735
+ constructor(event, target, path, type = event.type) {
1736
+ this.#event = event;
1737
+ this.#target = target;
1738
+ this.#path = path;
1739
+ this.#type = type;
1740
+ if (event.target == null) setEventProperty(event, "target", target);
1741
+ this.#state = {
1742
+ stopped: false,
1743
+ stoppedImmediately: false
1744
+ };
1745
+ }
1746
+ #tracking(run) {
1747
+ const event = this.#event;
1748
+ const state = this.#state;
1749
+ const stop = event.stopPropagation;
1750
+ const stopImmediately = event.stopImmediatePropagation;
1751
+ withProperties(event, {
1752
+ stopPropagation: () => {
1753
+ state.stopped = true;
1754
+ stop.call(event);
1755
+ },
1756
+ stopImmediatePropagation: () => {
1757
+ state.stopped = true;
1758
+ state.stoppedImmediately = true;
1759
+ stopImmediately.call(event);
1760
+ }
1761
+ }, run);
1762
+ }
1763
+ #phaseOf(index, outerPhase) {
1764
+ return this.#path[index].owner === this.#target ? 2 : outerPhase;
1765
+ }
1766
+ capture() {
1767
+ this.#tracking(() => {
1768
+ for (let i = this.#path.length - 1; i >= 0; i--) {
1769
+ if (this.#state.stopped) return;
1770
+ const phase = this.#phaseOf(i, 1);
1771
+ if (phase === 2) continue;
1772
+ this.#path[i].invoke(this.#type, this.#event, phase, this.#state);
1773
+ }
1646
1774
  });
1647
- this.#host.measure((rect) => {
1648
- if (rect && !this.rect) this.#update(rect);
1775
+ }
1776
+ bubble() {
1777
+ const bubbles = this.#event.bubbles !== false;
1778
+ this.#tracking(() => {
1779
+ for (let i = 0; i < this.#path.length; i++) {
1780
+ if (this.#state.stopped) return;
1781
+ const phase = this.#phaseOf(i, 3);
1782
+ if (phase === 2 || bubbles) this.#path[i].invoke(this.#type, this.#event, phase, this.#state);
1783
+ }
1649
1784
  });
1785
+ return !this.#event.defaultPrevented;
1650
1786
  }
1651
- observe(observer) {
1652
- this.start();
1653
- this.#observers.add(observer);
1654
- return () => this.#observers.delete(observer);
1787
+ };
1788
+ const dispatchThrough = (event, target, path) => {
1789
+ const dispatch = new PathDispatch(event, target, path);
1790
+ dispatch.capture();
1791
+ return dispatch.bubble();
1792
+ };
1793
+ var LynxGlobalDocument = class extends GlobalEventTarget {
1794
+ #events;
1795
+ constructor(events) {
1796
+ super();
1797
+ this.#events = events;
1655
1798
  }
1656
- #update(detail) {
1657
- const rect = {
1658
- left: detail.left,
1659
- top: detail.top,
1660
- width: detail.width,
1661
- height: detail.height
1799
+ get defaultView() {
1800
+ return this.#events.window.owner;
1801
+ }
1802
+ get body() {
1803
+ return this.#events.page;
1804
+ }
1805
+ getElementById(_id) {
1806
+ return null;
1807
+ }
1808
+ querySelector(_selector) {
1809
+ return null;
1810
+ }
1811
+ querySelectorAll(_selector) {
1812
+ return [];
1813
+ }
1814
+ dispatchEvent(event) {
1815
+ return dispatchThrough(event, this, [this, this.#events.window]);
1816
+ }
1817
+ };
1818
+ const screenViewport = () => {
1819
+ const info = globalThis.lynx?.SystemInfo ?? globalThis.SystemInfo;
1820
+ const ratio = info?.pixelRatio ?? 1;
1821
+ return {
1822
+ width: (info?.pixelWidth ?? 0) / ratio,
1823
+ height: (info?.pixelHeight ?? 0) / ratio
1824
+ };
1825
+ };
1826
+ var GlobalEvents = class {
1827
+ window;
1828
+ document;
1829
+ #source;
1830
+ #page = null;
1831
+ #lookup = () => void 0;
1832
+ #size = null;
1833
+ #pending = [];
1834
+ constructor(windowObject, createSource) {
1835
+ this.window = new GlobalEventTarget(windowObject);
1836
+ this.document = new LynxGlobalDocument(this);
1837
+ this.#source = createSource(this);
1838
+ this.window.onTypesChange = () => this.#sync();
1839
+ this.document.onTypesChange = () => this.#sync();
1840
+ }
1841
+ attach(page, lookup) {
1842
+ this.#page = page;
1843
+ this.#lookup = lookup;
1844
+ this.#sync();
1845
+ }
1846
+ get page() {
1847
+ return this.#page;
1848
+ }
1849
+ get viewport() {
1850
+ return this.#source.viewport?.() ?? this.#size;
1851
+ }
1852
+ hasListeners(type) {
1853
+ return this.document.hasListeners(type) || this.window.hasListeners(type);
1854
+ }
1855
+ targetFor(uniqueId) {
1856
+ return (uniqueId === void 0 ? void 0 : this.#lookup(uniqueId)) ?? this.#page ?? this.document;
1857
+ }
1858
+ early(key, deliveries) {
1859
+ const dispatches = deliveries.map(({ type, event, target }) => new PathDispatch(event, target, [this.document, this.window], type));
1860
+ for (const dispatch of dispatches) dispatch.capture();
1861
+ const pending = {
1862
+ key,
1863
+ dispatches
1662
1864
  };
1663
- const old = this.rect;
1664
- this.rect = rect;
1665
- if (old && old.left === rect.left && old.top === rect.top && old.width === rect.width && old.height === rect.height) return;
1666
- for (const observer of Array.from(this.#observers)) observer(rect);
1865
+ this.#pending.push(pending);
1866
+ setTimeout(() => this.#finish(pending), 0);
1867
+ }
1868
+ late(key) {
1869
+ const pending = this.#pending.find((p) => p.key === key);
1870
+ if (pending) this.#finish(pending);
1871
+ }
1872
+ finishPending() {
1873
+ for (const pending of this.#pending.slice()) this.#finish(pending);
1874
+ }
1875
+ resized(size) {
1876
+ const before = this.#size ?? screenViewport();
1877
+ this.#size = size;
1878
+ if (before.width === size.width && before.height === size.height) return;
1879
+ if (!this.window.hasListeners("resize")) return;
1880
+ dispatchThrough(createEvent("resize"), this.window.owner, [this.window]);
1881
+ }
1882
+ #finish(pending) {
1883
+ const index = this.#pending.indexOf(pending);
1884
+ if (index < 0) return;
1885
+ this.#pending.splice(index, 1);
1886
+ for (const dispatch of pending.dispatches) dispatch.bubble();
1887
+ }
1888
+ #sync() {
1889
+ const types = /* @__PURE__ */ new Set([...this.document.types, ...this.window.types]);
1890
+ this.#source.sync(this.#page, types);
1667
1891
  }
1668
1892
  };
1669
1893
  const POINTER_EVENT_NAMES = /* @__PURE__ */ new Set([
@@ -1678,6 +1902,8 @@ const POINTER_EVENT_NAMES = /* @__PURE__ */ new Set([
1678
1902
  "gotpointercapture",
1679
1903
  "lostpointercapture"
1680
1904
  ]);
1905
+ const MAX_TOUCH_NUMBER = 65535;
1906
+ const pointerIdOf = (touch) => touch.identifier >= 0 && touch.identifier <= MAX_TOUCH_NUMBER ? touch.identifier : 0;
1681
1907
  var LynxPointerEvent = class {
1682
1908
  type;
1683
1909
  pointerId;
@@ -1829,49 +2055,410 @@ var PointerEventSource = class {
1829
2055
  }
1830
2056
  #onStart(event) {
1831
2057
  for (const touch of event.changedTouches ?? []) {
2058
+ const id = pointerIdOf(touch);
1832
2059
  const offset = this.#offset(event, touch);
1833
- this.#down.set(touch.identifier, offset);
1834
- this.#primary ??= touch.identifier;
2060
+ this.#down.set(id, offset);
2061
+ this.#primary ??= id;
1835
2062
  this.lastOffset = offset;
1836
- this.#fire("pointerover", touch.identifier, touch, event, offset);
1837
- this.#fire("pointerenter", touch.identifier, touch, event, offset);
1838
- this.#fire("pointerdown", touch.identifier, touch, event, offset);
2063
+ this.#fire("pointerover", id, touch, event, offset);
2064
+ this.#fire("pointerenter", id, touch, event, offset);
2065
+ this.#fire("pointerdown", id, touch, event, offset);
1839
2066
  }
1840
2067
  }
1841
2068
  #onMove(event) {
1842
2069
  for (const touch of event.changedTouches ?? []) {
1843
- if (!this.#down.has(touch.identifier)) continue;
2070
+ const id = pointerIdOf(touch);
2071
+ if (!this.#down.has(id)) continue;
1844
2072
  const offset = this.#offset(event, touch);
1845
- this.#down.set(touch.identifier, offset);
2073
+ this.#down.set(id, offset);
1846
2074
  this.lastOffset = offset;
1847
- this.#fire("pointermove", touch.identifier, touch, event, offset);
2075
+ this.#fire("pointermove", id, touch, event, offset);
1848
2076
  }
1849
2077
  }
1850
2078
  #onEnd(event, canceled) {
1851
2079
  for (const touch of event.changedTouches ?? []) {
1852
- if (!this.#down.has(touch.identifier)) continue;
1853
- const offset = canceled ? this.#down.get(touch.identifier) : this.#offset(event, touch);
2080
+ const id = pointerIdOf(touch);
2081
+ if (!this.#down.has(id)) continue;
2082
+ const offset = canceled ? this.#down.get(id) : this.#offset(event, touch);
1854
2083
  this.lastOffset = offset;
1855
- this.#fire(canceled ? "pointercancel" : "pointerup", touch.identifier, touch, event, offset);
1856
- if (this.#captured.delete(touch.identifier)) this.#fire("lostpointercapture", touch.identifier, touch, event, offset);
1857
- this.#fire("pointerout", touch.identifier, touch, event, offset);
1858
- this.#fire("pointerleave", touch.identifier, touch, event, offset);
1859
- this.#down.delete(touch.identifier);
1860
- if (this.#primary === touch.identifier) this.#primary = null;
2084
+ this.#fire(canceled ? "pointercancel" : "pointerup", id, touch, event, offset);
2085
+ if (this.#captured.delete(id)) this.#fire("lostpointercapture", id, touch, event, offset);
2086
+ this.#fire("pointerout", id, touch, event, offset);
2087
+ this.#fire("pointerleave", id, touch, event, offset);
2088
+ this.#down.delete(id);
2089
+ if (this.#primary === id) this.#primary = null;
1861
2090
  }
1862
2091
  }
1863
2092
  #fire(type, pointerId, touch, event, offset) {
1864
2093
  const listeners = this.#listeners.get(type);
1865
2094
  if (!listeners) return;
1866
2095
  const position = offset ?? this.#down.get(pointerId);
1867
- const pressed = type === "pointerdown" || type === "pointermove" || type === "pointerover" ? 1 : 0;
1868
- const pointerEvent = new LynxPointerEvent(type, pointerId, this.#primary === null || this.#primary === pointerId, touch?.clientX ?? touch?.pageX ?? 0, touch?.clientY ?? touch?.pageY ?? 0, touch?.pageX ?? 0, touch?.pageY ?? 0, position.offsetX, position.offsetY, type === "pointermove" ? -1 : 0, pressed, pressed ? .5 : 0, event?.timestamp ?? Date.now(), this.#host.eventTarget);
2096
+ const pointerEvent = pointerEventFor(type, {
2097
+ pointerId,
2098
+ isPrimary: this.#primary === null || this.#primary === pointerId,
2099
+ ...position
2100
+ }, touch, event?.timestamp, this.#host.eventTarget);
1869
2101
  for (const listener of listeners.slice()) {
1870
2102
  listener(pointerEvent);
1871
2103
  if (pointerEvent.immediatePropagationStopped) break;
1872
2104
  }
1873
2105
  }
1874
2106
  };
2107
+ const pointerEventFor = (type, pointer, touch, timeStamp, target) => {
2108
+ const pressed = type === "pointerdown" || type === "pointermove" || type === "pointerover" ? 1 : 0;
2109
+ return new LynxPointerEvent(type, pointer.pointerId, pointer.isPrimary, touch?.clientX ?? touch?.pageX ?? 0, touch?.clientY ?? touch?.pageY ?? 0, touch?.pageX ?? 0, touch?.pageY ?? 0, pointer.offsetX, pointer.offsetY, type === "pointermove" ? -1 : 0, pressed, pressed ? .5 : 0, timeStamp ?? Date.now(), target);
2110
+ };
2111
+ const TOUCH_EVENTS = [
2112
+ "touchstart",
2113
+ "touchmove",
2114
+ "touchend",
2115
+ "touchcancel"
2116
+ ];
2117
+ const POINTER_EVENTS = /* @__PURE__ */ new Set([
2118
+ "pointerover",
2119
+ "pointerdown",
2120
+ "pointermove",
2121
+ "pointerup",
2122
+ "pointercancel",
2123
+ "pointerout"
2124
+ ]);
2125
+ const POINTERS_FOR = {
2126
+ touchstart: ["pointerover", "pointerdown"],
2127
+ touchmove: ["pointermove"],
2128
+ touchend: ["pointerup", "pointerout"],
2129
+ touchcancel: ["pointercancel", "pointerout"]
2130
+ };
2131
+ const sizeOf$1 = (event) => {
2132
+ const size = Array.isArray(event.detail) ? event.detail[1] : void 0;
2133
+ if (!Array.isArray(size) || size.length < 2) return null;
2134
+ return {
2135
+ width: Number(size[0]),
2136
+ height: Number(size[1])
2137
+ };
2138
+ };
2139
+ var NativeScreenSource = class {
2140
+ #events;
2141
+ #page = null;
2142
+ #listening = /* @__PURE__ */ new Map();
2143
+ #down = /* @__PURE__ */ new Set();
2144
+ #primary = null;
2145
+ constructor(events) {
2146
+ this.#events = events;
2147
+ }
2148
+ sync(page, types) {
2149
+ if (page !== this.#page) {
2150
+ this.#stop([...this.#listening.keys()]);
2151
+ this.#page = page;
2152
+ }
2153
+ if (!page) return;
2154
+ const wanted = /* @__PURE__ */ new Set(["resize"]);
2155
+ if ([...types].some((t) => POINTER_EVENTS.has(t) || TOUCH_EVENTS.includes(t))) for (const name of TOUCH_EVENTS) wanted.add(name);
2156
+ if (types.has("click")) wanted.add("tap");
2157
+ this.#stop([...this.#listening.keys()].filter((n) => !wanted.has(n)));
2158
+ for (const name of wanted) if (!this.#listening.has(name)) this.#listening.set(name, this.#listen(page, name));
2159
+ }
2160
+ #listen(page, name) {
2161
+ if (name === "resize") return page.addEventListener("bindresize", (event) => {
2162
+ const size = sizeOf$1(event);
2163
+ if (size) this.#events.resized(size);
2164
+ });
2165
+ const stopEarly = page.addEventListener(`global-bind${name}`, (event) => this.#begin(name, event));
2166
+ const stopLate = page.addTrailingEventListener(`bind${name}`, () => this.#events.late(name));
2167
+ return () => {
2168
+ stopEarly();
2169
+ stopLate();
2170
+ };
2171
+ }
2172
+ #stop(names) {
2173
+ for (const name of names) {
2174
+ this.#listening.get(name)?.();
2175
+ this.#listening.delete(name);
2176
+ if (name === "touchstart") {
2177
+ this.#down.clear();
2178
+ this.#primary = null;
2179
+ }
2180
+ }
2181
+ }
2182
+ #begin(name, event) {
2183
+ this.#events.finishPending();
2184
+ const target = this.#events.targetFor(event.target?.uid);
2185
+ const deliveries = name === "tap" ? [{
2186
+ type: "click",
2187
+ event,
2188
+ target
2189
+ }] : [...this.#pointers(name, event, target), {
2190
+ type: name,
2191
+ event,
2192
+ target
2193
+ }];
2194
+ this.#events.early(name, deliveries.filter((d) => this.#events.hasListeners(d.type)));
2195
+ }
2196
+ #pointers(name, event, target) {
2197
+ const deliveries = [];
2198
+ for (const touch of event.changedTouches ?? []) {
2199
+ const id = pointerIdOf(touch);
2200
+ if (name === "touchstart") {
2201
+ this.#down.add(id);
2202
+ this.#primary ??= id;
2203
+ } else if (!this.#down.has(id)) continue;
2204
+ for (const type of POINTERS_FOR[name]) deliveries.push({
2205
+ type,
2206
+ event: this.#pointerEvent(type, touch, event, target),
2207
+ target
2208
+ });
2209
+ if (name === "touchend" || name === "touchcancel") {
2210
+ this.#down.delete(id);
2211
+ if (this.#primary === id) this.#primary = null;
2212
+ }
2213
+ }
2214
+ return deliveries;
2215
+ }
2216
+ #pointerEvent(type, touch, event, target) {
2217
+ return pointerEventFor(type, {
2218
+ pointerId: pointerIdOf(touch),
2219
+ isPrimary: this.#primary === pointerIdOf(touch),
2220
+ offsetX: touch.x ?? 0,
2221
+ offsetY: touch.y ?? 0
2222
+ }, touch, event.timestamp, target);
2223
+ }
2224
+ };
2225
+ const KEY_EVENTS = /* @__PURE__ */ new Set([
2226
+ "keydown",
2227
+ "keyup",
2228
+ "keypress"
2229
+ ]);
2230
+ var WebScreenSource = class {
2231
+ #events;
2232
+ #page = null;
2233
+ #listening = /* @__PURE__ */ new Map();
2234
+ constructor(events) {
2235
+ this.#events = events;
2236
+ }
2237
+ sync(page, types) {
2238
+ if (page !== this.#page) {
2239
+ this.#stop([...this.#listening.keys()]);
2240
+ this.#page = page;
2241
+ }
2242
+ const scope = this.#scope();
2243
+ if (!scope) return;
2244
+ this.#stop([...this.#listening.keys()].filter((t) => !types.has(t)));
2245
+ for (const type of types) {
2246
+ if (this.#listening.has(type)) continue;
2247
+ this.#listening.set(type, type === "resize" ? this.#observeSize(scope.box) : this.#listen(scope, type));
2248
+ }
2249
+ }
2250
+ viewport() {
2251
+ const rect = this.#scope()?.box.getBoundingClientRect();
2252
+ return rect ? {
2253
+ width: rect.width,
2254
+ height: rect.height
2255
+ } : null;
2256
+ }
2257
+ #scope() {
2258
+ const inside = globalThis.frameElement ?? this.#page?.element;
2259
+ if (!inside) return null;
2260
+ const root = inside.getRootNode();
2261
+ return {
2262
+ root,
2263
+ box: root.host ?? inside
2264
+ };
2265
+ }
2266
+ #listen({ root, box }, type) {
2267
+ const target = KEY_EVENTS.has(type) ? box.ownerDocument : root;
2268
+ const begin = (event) => this.#events.early(event, [{
2269
+ type,
2270
+ event,
2271
+ target: event.target
2272
+ }]);
2273
+ const end = (event) => this.#events.late(event);
2274
+ target.addEventListener(type, begin, true);
2275
+ target.addEventListener(type, end);
2276
+ return () => {
2277
+ target.removeEventListener(type, begin, true);
2278
+ target.removeEventListener(type, end);
2279
+ };
2280
+ }
2281
+ #observeSize(box) {
2282
+ const view = box.ownerDocument.defaultView;
2283
+ let last = null;
2284
+ const observer = new view.ResizeObserver(() => {
2285
+ const size = this.viewport();
2286
+ if (last && last.width === size.width && last.height === size.height) return;
2287
+ const first = last === null;
2288
+ last = size;
2289
+ if (!first) this.#events.resized(size);
2290
+ });
2291
+ observer.observe(box);
2292
+ return () => observer.disconnect();
2293
+ }
2294
+ #stop(types) {
2295
+ for (const type of types) {
2296
+ this.#listening.get(type)();
2297
+ this.#listening.delete(type);
2298
+ }
2299
+ }
2300
+ };
2301
+ const SCREEN_EVENTS = /* @__PURE__ */ new Set([
2302
+ "pointerover",
2303
+ "pointerdown",
2304
+ "pointermove",
2305
+ "pointerup",
2306
+ "pointercancel",
2307
+ "pointerout",
2308
+ "touchstart",
2309
+ "touchmove",
2310
+ "touchend",
2311
+ "touchcancel",
2312
+ "mousedown",
2313
+ "mousemove",
2314
+ "mouseup",
2315
+ "click",
2316
+ "dblclick",
2317
+ "contextmenu",
2318
+ "wheel",
2319
+ "keydown",
2320
+ "keyup",
2321
+ "keypress",
2322
+ "resize"
2323
+ ]);
2324
+ const INSTALLED = Symbol.for("@blotch/angular-lynx/global-window");
2325
+ const isBrowserWindow = (target) => typeof target["document"]?.nodeType === "number";
2326
+ const installGlobalWindow = (target, events) => {
2327
+ if (target[INSTALLED]) return;
2328
+ target[INSTALLED] = true;
2329
+ installEventClasses(target);
2330
+ const own = events.window;
2331
+ const browser = isBrowserWindow(target);
2332
+ const original = {
2333
+ add: target["addEventListener"],
2334
+ remove: target["removeEventListener"],
2335
+ dispatch: target["dispatchEvent"]
2336
+ };
2337
+ const ours = (type) => !browser || SCREEN_EVENTS.has(type);
2338
+ target["addEventListener"] = (type, listener, options) => {
2339
+ if (ours(type)) own.addEventListener(type, listener, options);
2340
+ else original.add.call(target, type, listener, options);
2341
+ };
2342
+ target["removeEventListener"] = (type, listener, options) => {
2343
+ if (ours(type)) own.removeEventListener(type, listener, options);
2344
+ else original.remove.call(target, type, listener, options);
2345
+ };
2346
+ target["dispatchEvent"] = (event) => ours(event.type) ? dispatchThrough(event, target, [own]) : original.dispatch.call(target, event);
2347
+ if (browser && !__WEB__) return;
2348
+ for (const [name, axis] of [["innerWidth", "width"], ["innerHeight", "height"]]) Object.defineProperty(target, name, {
2349
+ get: () => (events.viewport ?? screenViewport())[axis],
2350
+ configurable: true,
2351
+ enumerable: true
2352
+ });
2353
+ };
2354
+ const installGlobalDocument = (target, events) => {
2355
+ if (typeof target["document"]?.nodeType === "number") return;
2356
+ target["document"] = events.document;
2357
+ };
2358
+ const KEY = Symbol.for("@blotch/angular-lynx/global-events");
2359
+ const globalEvents = () => {
2360
+ const store = globalThis;
2361
+ store[KEY] ??= new GlobalEvents(globalThis, (events) => __WEB__ ? new WebScreenSource(events) : new NativeScreenSource(events));
2362
+ return store[KEY];
2363
+ };
2364
+ let firstRenderPending = true;
2365
+ const deferredCallbacks = [];
2366
+ const isFirstRenderPending = () => firstRenderPending;
2367
+ const markFirstRenderComplete = () => {
2368
+ firstRenderPending = false;
2369
+ if (deferredCallbacks.length === 0) return;
2370
+ const callbacks = deferredCallbacks.splice(0);
2371
+ setTimeout(() => {
2372
+ for (const callback of callbacks) callback();
2373
+ }, 0);
2374
+ };
2375
+ const runAfterFirstRender = (callback) => {
2376
+ if (!firstRenderPending) {
2377
+ callback();
2378
+ return;
2379
+ }
2380
+ deferredCallbacks.push(callback);
2381
+ };
2382
+ let insideChangeDetection = false;
2383
+ const setInsideChangeDetection = (value) => {
2384
+ insideChangeDetection = value;
2385
+ };
2386
+ const isInsideChangeDetection = () => insideChangeDetection;
2387
+ const DASHARRAY_ATTRIBUTE = /(\s)stroke-dasharray\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
2388
+ const DASHARRAY_DECLARATION = /(^|[;"'\s])stroke-dasharray\s*:\s*([^;"']*);?/g;
2389
+ const DASH_LENGTH = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(?:px)?$/i;
2390
+ const normalizeDashArray = (value) => {
2391
+ const tokens = value.trim().split(/[\s,]+/).filter((token) => token.length > 0);
2392
+ if (tokens.length === 0 || !tokens.every((t) => DASH_LENGTH.test(t))) return null;
2393
+ return tokens.map((token) => token.replace(/px$/i, "")).join(" ");
2394
+ };
2395
+ const sanitizeSvgContent = (content) => {
2396
+ if (!content.includes("stroke-dasharray")) return content;
2397
+ return content.replace(DASHARRAY_ATTRIBUTE, (_match, space, double, single) => {
2398
+ const normalized = normalizeDashArray(double ?? single);
2399
+ return normalized === null ? "" : `${space}stroke-dasharray="${normalized}"`;
2400
+ }).replace(DASHARRAY_DECLARATION, (_match, before, value) => {
2401
+ const normalized = normalizeDashArray(value);
2402
+ return normalized === null ? before : `${before}stroke-dasharray:${normalized};`;
2403
+ });
2404
+ };
2405
+ const toDOMRect = (rect) => {
2406
+ const { left = 0, top = 0, width = 0, height = 0 } = rect ?? {};
2407
+ const box = {
2408
+ x: left,
2409
+ y: top,
2410
+ width,
2411
+ height,
2412
+ top,
2413
+ left,
2414
+ right: left + width,
2415
+ bottom: top + height
2416
+ };
2417
+ return {
2418
+ ...box,
2419
+ toJSON: () => ({ ...box })
2420
+ };
2421
+ };
2422
+ const measureLayout = (element, callback) => {
2423
+ __InvokeUIMethod(element, "boundingClientRect", {}, (result) => callback(result?.code === 0 && result.data ? result.data : null));
2424
+ };
2425
+ var LayoutTracker = class {
2426
+ #host;
2427
+ #observers = /* @__PURE__ */ new Set();
2428
+ #listening = false;
2429
+ rect = null;
2430
+ constructor(host) {
2431
+ this.#host = host;
2432
+ }
2433
+ start() {
2434
+ if (this.#listening) return;
2435
+ this.#listening = true;
2436
+ this.#host.listen("bindlayoutchange", (event) => {
2437
+ const detail = event?.detail;
2438
+ if (detail && typeof detail.width === "number") this.#update(detail);
2439
+ });
2440
+ this.#host.measure((rect) => {
2441
+ if (rect && !this.rect) this.#update(rect);
2442
+ });
2443
+ }
2444
+ observe(observer) {
2445
+ this.start();
2446
+ this.#observers.add(observer);
2447
+ return () => this.#observers.delete(observer);
2448
+ }
2449
+ #update(detail) {
2450
+ const rect = {
2451
+ left: detail.left,
2452
+ top: detail.top,
2453
+ width: detail.width,
2454
+ height: detail.height
2455
+ };
2456
+ const old = this.rect;
2457
+ this.rect = rect;
2458
+ if (old && old.left === rect.left && old.top === rect.top && old.width === rect.width && old.height === rect.height) return;
2459
+ for (const observer of Array.from(this.#observers)) observer(rect);
2460
+ }
2461
+ };
1875
2462
  const LONG_PRESS_SLOP = 10;
1876
2463
  const TAP_GRACE = 300;
1877
2464
  let tapsDropped = false;
@@ -1927,6 +2514,20 @@ var WebLongPress = class {
1927
2514
  this.#timer = void 0;
1928
2515
  }
1929
2516
  };
2517
+ const seconds = (value) => Number.isFinite(value) ? value : 0;
2518
+ const webVideoDetail = (eventName, video) => {
2519
+ switch (eventName) {
2520
+ case "timeupdate": return {
2521
+ current: seconds(video.currentTime),
2522
+ duration: seconds(video.duration)
2523
+ };
2524
+ case "error": return {
2525
+ errorCode: video.error?.code ?? 0,
2526
+ errorMsg: video.error?.message ?? ""
2527
+ };
2528
+ default: return;
2529
+ }
2530
+ };
1930
2531
  const EVENT_PREFIXES = [
1931
2532
  ["capture-bind", "capture-bindEvent"],
1932
2533
  ["capture-catch", "capture-catchEvent"],
@@ -2032,6 +2633,9 @@ var LynxElement = class {
2032
2633
  static #canonicalFor(ref) {
2033
2634
  return _a.#byNativeId.get(__GetElementUniqueID(ref)) ?? new _a(ref);
2034
2635
  }
2636
+ static byUniqueId(uniqueId) {
2637
+ return _a.#byNativeId.get(uniqueId);
2638
+ }
2035
2639
  constructor(element) {
2036
2640
  this.#nativeRef = element;
2037
2641
  _a.#byNativeId.set(__GetElementUniqueID(element), this);
@@ -2040,6 +2644,9 @@ var LynxElement = class {
2040
2644
  const id = __GetElementUniqueID(this.#nativeRef);
2041
2645
  if (_a.#byNativeId.get(id) === this) _a.#byNativeId.delete(id);
2042
2646
  }
2647
+ get ownerDocument() {
2648
+ return globalEvents().document;
2649
+ }
2043
2650
  setProperty(name, value) {
2044
2651
  this.setAttribute(name, value);
2045
2652
  }
@@ -2384,10 +2991,19 @@ var LynxElement = class {
2384
2991
  const parsed = parseEventName(name);
2385
2992
  if (!parsed) return () => {};
2386
2993
  if (__WEB__ && parsed.eventName === "longpress") this.#listenForWebLongPress();
2387
- const entry = {
2994
+ return this.#add({
2388
2995
  ...parsed,
2389
2996
  listener: cb
2390
- };
2997
+ });
2998
+ }
2999
+ addTrailingEventListener(name, cb) {
3000
+ return this.#add({
3001
+ ...parseEventName(name),
3002
+ listener: cb,
3003
+ trailing: true
3004
+ });
3005
+ }
3006
+ #add(entry) {
2391
3007
  this.#listeners.push(entry);
2392
3008
  this.#syncNativeHandler(entry);
2393
3009
  return () => this.#removeListener(entry);
@@ -2454,7 +3070,7 @@ var LynxElement = class {
2454
3070
  #layoutTracker() {
2455
3071
  this.#layout ??= new LayoutTracker({
2456
3072
  listen: (event, listener) => this.addEventListener(event, listener),
2457
- measure: (callback) => __InvokeUIMethod(this.element, "boundingClientRect", {}, (result) => callback(result?.code === 0 && result.data ? result.data : null))
3073
+ measure: (callback) => measureLayout(this.element, callback)
2458
3074
  });
2459
3075
  return this.#layout;
2460
3076
  }
@@ -2511,7 +3127,8 @@ var LynxElement = class {
2511
3127
  }
2512
3128
  #listenersLike(entry) {
2513
3129
  const key = nativeEventKey(entry.eventType, entry.eventName);
2514
- return this.#listeners.filter((other) => nativeEventKey(other.eventType, other.eventName) === key);
3130
+ const like = this.#listeners.filter((other) => nativeEventKey(other.eventType, other.eventName) === key);
3131
+ return [...like.filter((other) => !other.trailing), ...like.filter((other) => other.trailing)];
2515
3132
  }
2516
3133
  #syncNativeHandler(entry) {
2517
3134
  const key = nativeEventKey(entry.eventType, entry.eventName);
@@ -2519,6 +3136,7 @@ var LynxElement = class {
2519
3136
  const current = this.#nativeHandlers.get(key);
2520
3137
  const eventType = listeners.length ? combinedEventType(listeners) : null;
2521
3138
  if (current?.eventType === eventType) return;
3139
+ scheduleSettleFlush();
2522
3140
  if (current) {
2523
3141
  __AddEvent(this.element, current.eventType, entry.eventName, void 0);
2524
3142
  this.#nativeHandlers.delete(key);
@@ -2542,6 +3160,10 @@ var LynxElement = class {
2542
3160
  if (typeof event.stopImmediatePropagation !== "function") event.stopImmediatePropagation = () => {};
2543
3161
  }
2544
3162
  if (__WEB__ && entry.eventName === "tap" && dropsTap()) return void 0;
3163
+ if (__WEB__ && this.tagName === "video" && event && typeof event === "object") {
3164
+ const detail = webVideoDetail(entry.eventName, this.element);
3165
+ if (detail) event.detail = detail;
3166
+ }
2545
3167
  const pointer = __WEB__ ? this.#webPointer : this.#pointerEvents?.lastOffset;
2546
3168
  if (pointer && entry.eventName === "tap" && event && typeof event === "object" && event.offsetX === void 0) {
2547
3169
  event.offsetX = pointer.offsetX;
@@ -9210,6 +9832,7 @@ var LynxDocument = class {
9210
9832
  if (isAndroid()) __SetID(pageElement, ANDROID_BLUR_CAPTURE_TARGET);
9211
9833
  this.#pageId = __GetElementUniqueID(pageElement);
9212
9834
  setPageElementRef(pageElement);
9835
+ globalEvents().attach(this.page, (id) => LynxElement.byUniqueId(id));
9213
9836
  return this.page;
9214
9837
  }
9215
9838
  createElement(name, value) {
@@ -9278,6 +9901,7 @@ var LynxHydrateDocument = class {
9278
9901
  page.tagName = "page";
9279
9902
  page.isRootPageElement = true;
9280
9903
  this.#page = page;
9904
+ globalEvents().attach(page, (id) => LynxElement.byUniqueId(id));
9281
9905
  return page;
9282
9906
  }
9283
9907
  createElement(tag, value) {
@@ -9306,7 +9930,48 @@ var LynxHydrateDocument = class {
9306
9930
  this.#page?.appendChild(newChild);
9307
9931
  }
9308
9932
  };
9933
+ const LYNX_ELEMENT_TAGS = /* @__PURE__ */ new Set([
9934
+ "page",
9935
+ "view",
9936
+ "text",
9937
+ "raw-text",
9938
+ "inline-truncation",
9939
+ "image",
9940
+ "scroll-view",
9941
+ "list",
9942
+ "list-item",
9943
+ "block",
9944
+ "if",
9945
+ "for",
9946
+ "frame",
9947
+ "input",
9948
+ "textarea",
9949
+ "overlay",
9950
+ "svg",
9951
+ "viewpager",
9952
+ "viewpager-item",
9953
+ "refresh",
9954
+ "refresh-header",
9955
+ "title-bar-view",
9956
+ "scroll-coordinator",
9957
+ "scroll-coordinator-header",
9958
+ "scroll-coordinator-toolbar",
9959
+ "scroll-coordinator-slot",
9960
+ "scroll-coordinator-slot-drag",
9961
+ "blur-view",
9962
+ "webview",
9963
+ "video",
9964
+ "cover-view",
9965
+ "canvas",
9966
+ "comment"
9967
+ ]);
9968
+ const tagClassOf = (tag) => `_ngtag-${tag.toLowerCase()}`;
9969
+ const tagClassFor = (tag) => LYNX_ELEMENT_TAGS.has(tag) ? null : tagClassOf(tag);
9309
9970
  const collapseWhitespace = (value) => value.replace(/[ \t\n\r\f\v]+/g, " ");
9971
+ const markTag = (element, tag) => {
9972
+ const tagClass = tagClassFor(tag);
9973
+ if (tagClass) element.addScopeClass(tagClass);
9974
+ };
9310
9975
  var LynxRenderer = class {
9311
9976
  #document;
9312
9977
  #data = {};
@@ -9318,7 +9983,9 @@ var LynxRenderer = class {
9318
9983
  }
9319
9984
  destroy() {}
9320
9985
  createElement(name, _namespace) {
9321
- return this.#document.createElement(name);
9986
+ const element = this.#document.createElement(name);
9987
+ markTag(element, name);
9988
+ return element;
9322
9989
  }
9323
9990
  createComment(_value) {
9324
9991
  return this.#document.createComment();
@@ -9376,10 +10043,18 @@ var LynxRenderer = class {
9376
10043
  setValue(node, value) {
9377
10044
  node.setAttribute("text", collapseWhitespace(value));
9378
10045
  }
9379
- listen(target, eventName, callback, _options) {
9380
- return target.addEventListener(eventName, callback);
10046
+ listen(target, eventName, callback, options) {
10047
+ const resolved = typeof target === "string" ? globalTarget(target, eventName) : target;
10048
+ const remove = resolved.addEventListener(eventName, callback, options);
10049
+ return typeof remove === "function" ? remove : () => resolved.removeEventListener(eventName, callback, options);
9381
10050
  }
9382
10051
  };
10052
+ const globalTarget = (name, eventName) => {
10053
+ const document = globalEvents().document;
10054
+ const target = name === "window" ? document.defaultView : name === "document" ? document : name === "body" ? document.body : null;
10055
+ if (!target) throw new Error(`Unsupported event target ${name} for event ${eventName}`);
10056
+ return target;
10057
+ };
9383
10058
  var EmulatedLynxRenderer = class extends LynxRenderer {
9384
10059
  #contentClass;
9385
10060
  #hostClass;
@@ -9482,10 +10157,7 @@ const provideRenderer = () => {
9482
10157
  },
9483
10158
  {
9484
10159
  provide: DOCUMENT,
9485
- useValue: {
9486
- getElementById: () => null,
9487
- defaultView: globalThis
9488
- }
10160
+ useFactory: () => globalEvents().document
9489
10161
  },
9490
10162
  {
9491
10163
  provide: APP_BASE_HREF,
@@ -10649,7 +11321,7 @@ var LynxImageElement = class {
10649
11321
  for (const listener of Array.from(this.#listeners.get(type) ?? [])) listener.call(this, event);
10650
11322
  }
10651
11323
  };
10652
- const isLynxElement = (value) => typeof value === "object" && value !== null && typeof value.observeLayout === "function" && "element" in value;
11324
+ const isLynxElement$1 = (value) => typeof value === "object" && value !== null && typeof value.observeLayout === "function" && "element" in value;
10653
11325
  const installCanvasGlobals = (target) => {
10654
11326
  const install = (name, value) => {
10655
11327
  if (typeof target[name] === "undefined") target[name] = value;
@@ -10683,7 +11355,7 @@ const installCanvasGlobals = (target) => {
10683
11355
  if (typeof nativeGetComputedStyle !== "function") target["getComputedStyle"] = Object.assign(() => emptyComputedStyle(), { forLynxElements: true });
10684
11356
  else if (!nativeGetComputedStyle.forLynxElements) {
10685
11357
  const wrapped = (element, pseudo) => {
10686
- if (!isLynxElement(element)) return nativeGetComputedStyle.call(target, element, pseudo);
11358
+ if (!isLynxElement$1(element)) return nativeGetComputedStyle.call(target, element, pseudo);
10687
11359
  const node = element.element;
10688
11360
  const view = node.ownerDocument?.defaultView;
10689
11361
  return view ? view.getComputedStyle(node, pseudo) : emptyComputedStyle();
@@ -10735,7 +11407,7 @@ const sizeOf = (width, height) => [{
10735
11407
  inlineSize: width,
10736
11408
  blockSize: height
10737
11409
  }];
10738
- const entryFor = (target, rect) => {
11410
+ const entryFor$1 = (target, rect) => {
10739
11411
  const ratio = pixelRatio();
10740
11412
  return {
10741
11413
  target,
@@ -10758,13 +11430,13 @@ var LynxResizeObserver = class {
10758
11430
  this.#callback = callback;
10759
11431
  }
10760
11432
  observe(target) {
10761
- if (!isLynxElement(target)) throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': the target is not an element");
11433
+ if (!isLynxElement$1(target)) throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': the target is not an element");
10762
11434
  if (this.#observed.has(target)) return;
10763
11435
  let last = null;
10764
11436
  const notify = (rect) => {
10765
11437
  if (last && last.width === rect.width && last.height === rect.height) return;
10766
11438
  last = rect;
10767
- this.#callback([entryFor(target, rect)], this);
11439
+ this.#callback([entryFor$1(target, rect)], this);
10768
11440
  };
10769
11441
  this.#observed.set(target, target.observeLayout(notify));
10770
11442
  const rect = target.layoutRect;
@@ -10773,7 +11445,7 @@ var LynxResizeObserver = class {
10773
11445
  });
10774
11446
  }
10775
11447
  unobserve(target) {
10776
- if (!isLynxElement(target)) return;
11448
+ if (!isLynxElement$1(target)) return;
10777
11449
  this.#observed.get(target)?.();
10778
11450
  this.#observed.delete(target);
10779
11451
  }
@@ -10803,12 +11475,12 @@ const wrapResizeObserver = (NativeResizeObserver) => {
10803
11475
  });
10804
11476
  }
10805
11477
  observe(target, options) {
10806
- const node = isLynxElement(target) ? target.element : target;
10807
- if (isLynxElement(target)) this.#targets.set(node, target);
11478
+ const node = isLynxElement$1(target) ? target.element : target;
11479
+ if (isLynxElement$1(target)) this.#targets.set(node, target);
10808
11480
  this.#observer.observe(node, options);
10809
11481
  }
10810
11482
  unobserve(target) {
10811
- const node = isLynxElement(target) ? target.element : target;
11483
+ const node = isLynxElement$1(target) ? target.element : target;
10812
11484
  this.#targets.delete(node);
10813
11485
  this.#observer.unobserve(node);
10814
11486
  }
@@ -10818,6 +11490,238 @@ const wrapResizeObserver = (NativeResizeObserver) => {
10818
11490
  }
10819
11491
  };
10820
11492
  };
11493
+ const isLynxElement = (value) => typeof value === "object" && value !== null && "element" in value && typeof value.addEventListener === "function" && typeof value.observeLayout === "function" && typeof value.parentNode === "function";
11494
+ const SCROLL_CONTAINERS = /* @__PURE__ */ new Set([
11495
+ "scroll-view",
11496
+ "list",
11497
+ "viewpager",
11498
+ "scroll-coordinator"
11499
+ ]);
11500
+ const measure = (element) => new Promise((resolve) => measureLayout(element.element, resolve));
11501
+ const measureGeometry = async (target) => {
11502
+ const scrollContainers = [];
11503
+ let top = target;
11504
+ while (!top.isRootPageElement) {
11505
+ const parent = top.parentNode();
11506
+ if (!parent) break;
11507
+ if (SCROLL_CONTAINERS.has(parent.tagName)) scrollContainers.push(parent);
11508
+ top = parent;
11509
+ }
11510
+ const page = top.isRootPageElement ? top : null;
11511
+ const [box, root, ...scrollBoxes] = await Promise.all([
11512
+ measure(target),
11513
+ page ? measure(page) : null,
11514
+ ...scrollContainers.map(measure)
11515
+ ]);
11516
+ return {
11517
+ box: box ?? null,
11518
+ root: root ?? null,
11519
+ scrollContainers: scrollBoxes
11520
+ };
11521
+ };
11522
+ const intersect = (a, b) => {
11523
+ const left = Math.max(a.left, b.left);
11524
+ const top = Math.max(a.top, b.top);
11525
+ const right = Math.min(a.left + a.width, b.left + b.width);
11526
+ const bottom = Math.min(a.top + a.height, b.top + b.height);
11527
+ return right > left && bottom > top ? {
11528
+ left,
11529
+ top,
11530
+ width: right - left,
11531
+ height: bottom - top
11532
+ } : null;
11533
+ };
11534
+ const visiblePart = ({ box, root, scrollContainers }) => {
11535
+ let visible = box;
11536
+ for (const clip of [root, ...scrollContainers]) visible = visible && clip ? intersect(visible, clip) : null;
11537
+ return visible;
11538
+ };
11539
+ const area = (rect) => rect.width * rect.height;
11540
+ const entryFor = (target, geometry, exposed) => {
11541
+ const visible = visiblePart(geometry);
11542
+ const isIntersecting = exposed ?? visible !== null;
11543
+ const { box } = geometry;
11544
+ let intersectionRatio = 0;
11545
+ if (isIntersecting) intersectionRatio = !box || area(box) === 0 ? 1 : visible ? area(visible) / area(box) : 0;
11546
+ return {
11547
+ target,
11548
+ time: performance.now(),
11549
+ rootBounds: geometry.root ? toDOMRect(geometry.root) : null,
11550
+ boundingClientRect: toDOMRect(box),
11551
+ intersectionRect: toDOMRect(isIntersecting ? visible : null),
11552
+ intersectionRatio,
11553
+ isIntersecting
11554
+ };
11555
+ };
11556
+ const thresholdsOf = (threshold) => {
11557
+ const given = threshold === void 0 ? [] : Array.isArray(threshold) ? threshold.map(Number) : [Number(threshold)];
11558
+ for (const value of given) if (!(value >= 0 && value <= 1)) throw new RangeError("Failed to construct 'IntersectionObserver': Threshold values must be numbers between 0 and 1");
11559
+ return Object.freeze(given.length ? given.sort((a, b) => a - b) : [0]);
11560
+ };
11561
+ const rootMarginOf = (margin) => {
11562
+ const tokens = String(margin ?? "").trim().split(/\s+/).filter(Boolean);
11563
+ if (tokens.length > 4) throw invalidRootMargin();
11564
+ const [top = "0px", right = top, bottom = top, left = right] = tokens.map((token) => {
11565
+ const match = /^(-?(?:\d+\.?\d*|\.\d+))(px|%)?$/.exec(token);
11566
+ if (!match || !match[2] && Number(match[1]) !== 0) throw invalidRootMargin();
11567
+ return match[2] ? token : "0px";
11568
+ });
11569
+ return `${top} ${right} ${bottom} ${left}`;
11570
+ };
11571
+ const invalidRootMargin = () => /* @__PURE__ */ new SyntaxError("Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent.");
11572
+ const DEFAULT_ROOT_MARGIN = "0px 0px 0px 0px";
11573
+ let warnedOfOptions = false;
11574
+ const warnOfOptions = (observer) => {
11575
+ if (warnedOfOptions) return;
11576
+ const ignored = [
11577
+ observer.root !== null && "root",
11578
+ observer.rootMargin !== DEFAULT_ROOT_MARGIN && "rootMargin",
11579
+ (observer.thresholds.length !== 1 || observer.thresholds[0] !== 0) && "threshold"
11580
+ ].filter(Boolean);
11581
+ if (!ignored.length) return;
11582
+ warnedOfOptions = true;
11583
+ console.warn(`[angular-lynx] IntersectionObserver on Lynx reports when an element comes into view and when it leaves, inside its scroll containers and the screen. It ignores ${ignored.join(", ")} for now.`);
11584
+ };
11585
+ var LynxIntersectionObserver = class {
11586
+ static forLynxElements = true;
11587
+ root;
11588
+ rootMargin;
11589
+ thresholds;
11590
+ #callback;
11591
+ #observed = /* @__PURE__ */ new Map();
11592
+ #records = [];
11593
+ #delivering = false;
11594
+ constructor(callback, options = {}) {
11595
+ if (typeof callback !== "function") throw new TypeError("Failed to construct 'IntersectionObserver': parameter 1 is not of type 'Function'.");
11596
+ this.#callback = callback;
11597
+ const root = options.root;
11598
+ this.root = root === void 0 || root === globalThis.document ? null : root;
11599
+ this.rootMargin = rootMarginOf(options.rootMargin);
11600
+ this.thresholds = thresholdsOf(options.threshold);
11601
+ warnOfOptions(this);
11602
+ }
11603
+ observe(target) {
11604
+ if (!isLynxElement(target)) throw new TypeError("Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.");
11605
+ if (this.#observed.has(target)) return;
11606
+ const stopAppear = target.addEventListener("binduiappear", () => {
11607
+ this.#report(target, observation, true);
11608
+ });
11609
+ const stopDisappear = target.addEventListener("binduidisappear", () => {
11610
+ this.#report(target, observation, false);
11611
+ });
11612
+ let laidOut = false;
11613
+ const first = () => {
11614
+ if (laidOut) return;
11615
+ laidOut = true;
11616
+ stopLayout();
11617
+ this.#report(target, observation, null);
11618
+ };
11619
+ const stopLayout = target.observeLayout(first);
11620
+ const observation = {
11621
+ stop: () => {
11622
+ stopAppear();
11623
+ stopDisappear();
11624
+ stopLayout();
11625
+ },
11626
+ intersecting: null,
11627
+ events: 0
11628
+ };
11629
+ this.#observed.set(target, observation);
11630
+ if (target.layoutRect) queueMicrotask(first);
11631
+ }
11632
+ unobserve(target) {
11633
+ const observation = this.#observed.get(target);
11634
+ if (!observation) return;
11635
+ observation.stop();
11636
+ this.#observed.delete(target);
11637
+ this.#records = this.#records.filter((entry) => entry.target !== target);
11638
+ }
11639
+ disconnect() {
11640
+ for (const observation of this.#observed.values()) observation.stop();
11641
+ this.#observed.clear();
11642
+ this.#records = [];
11643
+ }
11644
+ takeRecords() {
11645
+ return this.#records.splice(0);
11646
+ }
11647
+ async #report(target, observation, exposed) {
11648
+ const event = exposed === null ? 0 : ++observation.events;
11649
+ const geometry = await measureGeometry(target);
11650
+ if (this.#observed.get(target) !== observation) return;
11651
+ if (observation.events !== event) return;
11652
+ const entry = entryFor(target, geometry, exposed);
11653
+ if (entry.isIntersecting === observation.intersecting) return;
11654
+ observation.intersecting = entry.isIntersecting;
11655
+ this.#records.push(entry);
11656
+ this.#deliverSoon();
11657
+ }
11658
+ #deliverSoon() {
11659
+ if (this.#delivering) return;
11660
+ this.#delivering = true;
11661
+ queueMicrotask(() => {
11662
+ this.#delivering = false;
11663
+ const records = this.takeRecords();
11664
+ if (records.length) this.#callback.call(this, records, this);
11665
+ });
11666
+ }
11667
+ };
11668
+ const wrapIntersectionObserver = (NativeIntersectionObserver) => {
11669
+ const toNode = (value) => isLynxElement(value) ? value.element : value;
11670
+ return class LynxWebIntersectionObserver {
11671
+ static forLynxElements = true;
11672
+ root;
11673
+ #observer;
11674
+ #targets = /* @__PURE__ */ new Map();
11675
+ constructor(callback, options) {
11676
+ this.root = options?.root ?? null;
11677
+ this.#observer = new NativeIntersectionObserver((entries) => callback(entries.map((entry) => this.#entry(entry)), this), options && {
11678
+ ...options,
11679
+ root: toNode(options.root)
11680
+ });
11681
+ }
11682
+ get rootMargin() {
11683
+ return this.#observer.rootMargin;
11684
+ }
11685
+ get thresholds() {
11686
+ return this.#observer.thresholds;
11687
+ }
11688
+ observe(target) {
11689
+ const node = toNode(target);
11690
+ if (node !== target) this.#targets.set(node, target);
11691
+ this.#observer.observe(node);
11692
+ }
11693
+ unobserve(target) {
11694
+ const node = toNode(target);
11695
+ this.#targets.delete(node);
11696
+ this.#observer.unobserve(node);
11697
+ }
11698
+ disconnect() {
11699
+ this.#targets.clear();
11700
+ this.#observer.disconnect();
11701
+ }
11702
+ takeRecords() {
11703
+ return this.#observer.takeRecords().map((entry) => this.#entry(entry));
11704
+ }
11705
+ #entry(entry) {
11706
+ const target = this.#targets.get(entry.target);
11707
+ if (!target) return entry;
11708
+ return {
11709
+ target,
11710
+ time: entry.time,
11711
+ rootBounds: entry.rootBounds,
11712
+ boundingClientRect: entry.boundingClientRect,
11713
+ intersectionRect: entry.intersectionRect,
11714
+ intersectionRatio: entry.intersectionRatio,
11715
+ isIntersecting: entry.isIntersecting
11716
+ };
11717
+ }
11718
+ };
11719
+ };
11720
+ const installIntersectionObserver = (target) => {
11721
+ const native = target["IntersectionObserver"];
11722
+ if (typeof native === "undefined") target["IntersectionObserver"] = LynxIntersectionObserver;
11723
+ else if (!native.forLynxElements) target["IntersectionObserver"] = wrapIntersectionObserver(native);
11724
+ };
10821
11725
  const readBundledAsset = (url) => {
10822
11726
  const lynxGlobal = globalThis.lynx;
10823
11727
  if (typeof lynxGlobal?.module !== "function") return null;
@@ -11055,9 +11959,20 @@ const parseUrl$1 = (input) => {
11055
11959
  try {
11056
11960
  return new URL(input).href;
11057
11961
  } catch {
11058
- throw new TypeError(`Failed to parse URL from ${input}. Lynx has no page URL to resolve a relative URL against, so requests need an absolute URL.`);
11962
+ throw new TypeError(`Failed to parse URL from ${input}. Lynx has no page URL to resolve a relative URL against, so this needs an absolute URL.`);
11059
11963
  }
11060
11964
  };
11965
+ const isRelative = (url) => !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
11966
+ const PLACEHOLDER_BASE = "http://placeholder.invalid/";
11967
+ const parseRequestUrl = (input) => {
11968
+ if (!isRelative(input)) return parseUrl$1(input);
11969
+ try {
11970
+ new URL(input, PLACEHOLDER_BASE);
11971
+ } catch {
11972
+ throw new TypeError(`Failed to parse URL from ${input}.`);
11973
+ }
11974
+ return input;
11975
+ };
11061
11976
  const DEFAULT_OPTIONS = {
11062
11977
  cache: "default",
11063
11978
  credentials: "same-origin",
@@ -11096,7 +12011,7 @@ var LynxRequest = class LynxRequest extends LynxBody {
11096
12011
  };
11097
12012
  if (source !== null && (upper === "GET" || upper === "HEAD")) throw new TypeError("Failed to construct 'Request': Request with GET/HEAD method cannot have body.");
11098
12013
  super(source);
11099
- this.url = base ? base.url : parseUrl$1(String(input));
12014
+ this.url = base ? base.url : parseRequestUrl(String(input));
11100
12015
  this.method = normalizedMethod;
11101
12016
  this.headers = new LynxHeaders(init.headers ?? base?.headers);
11102
12017
  if (type !== null && !this.headers.has("content-type")) this.headers.set("content-type", type);
@@ -11215,21 +12130,18 @@ var LynxResponse = class LynxResponse extends LynxBody {
11215
12130
  }
11216
12131
  };
11217
12132
  let nextFetchId = 0;
11218
- const isRelative = (url) => !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
11219
- const bundledResponse = (input, init) => {
11220
- if (typeof input !== "string" || !isRelative(input)) return null;
11221
- if (init?.method && init.method.toUpperCase() !== "GET") return null;
11222
- const bytes = readBundledAsset(input);
11223
- if (!bytes) return null;
12133
+ const bundledResponse = (request) => {
12134
+ const bytes = request.method === "GET" ? readBundledAsset(request.url) : null;
12135
+ if (!bytes) return /* @__PURE__ */ new TypeError(`Failed to fetch ${request.url}. Lynx has no page URL to resolve a relative URL against: only a GET of a file the app bundles can use one, and other requests need an absolute URL.`);
11224
12136
  return new LynxResponse(new ExtractedBody(bytes), {
11225
12137
  status: 200,
11226
12138
  statusText: "OK",
11227
12139
  headers: {
11228
- "content-type": typeOfAsset(input),
12140
+ "content-type": typeOfAsset(request.url),
11229
12141
  "content-length": String(bytes.byteLength)
11230
12142
  }
11231
12143
  }, {
11232
- url: input,
12144
+ url: request.url,
11233
12145
  redirected: false,
11234
12146
  type: "basic"
11235
12147
  });
@@ -11267,18 +12179,13 @@ const localResponse = (url) => {
11267
12179
  return null;
11268
12180
  };
11269
12181
  const relayFetch = (input, init) => new Promise((resolve, reject) => {
11270
- const bundled = bundledResponse(input, init);
11271
- if (bundled) {
11272
- resolve(bundled);
11273
- return;
11274
- }
11275
12182
  const request = new LynxRequest(input, init);
11276
12183
  const { signal } = request;
11277
12184
  if (signal.aborted) {
11278
12185
  reject(signal.reason);
11279
12186
  return;
11280
12187
  }
11281
- const local = localResponse(request.url);
12188
+ const local = isRelative(request.url) ? bundledResponse(request) : localResponse(request.url);
11282
12189
  if (local) {
11283
12190
  if (local instanceof LynxResponse) resolve(local);
11284
12191
  else reject(local);
@@ -12327,18 +13234,24 @@ if (typeof Node === "undefined") try {
12327
13234
  });
12328
13235
  globalThis.Node = LynxNode;
12329
13236
  } catch {}
12330
- if (typeof document === "undefined") globalThis.document = {
13237
+ if (isMainThread()) installGlobalDocument(globalThis, globalEvents());
13238
+ else if (typeof document === "undefined") globalThis.document = {
12331
13239
  defaultView: globalThis,
12332
13240
  querySelector: () => null
12333
13241
  };
12334
13242
  try {
12335
13243
  if (typeof window === "undefined") globalThis.window = globalThis;
12336
- } catch {}
13244
+ } catch {
13245
+ if (__WEB__) window = globalThis;
13246
+ }
12337
13247
  try {
12338
13248
  if (typeof navigator === "undefined") globalThis.navigator = { userAgent: "" };
12339
13249
  } catch {}
12340
- if (typeof globalThis.addEventListener !== "function") globalThis.addEventListener = () => {};
12341
- if (typeof globalThis.removeEventListener !== "function") globalThis.removeEventListener = () => {};
13250
+ if (isMainThread()) installGlobalWindow(globalThis, globalEvents());
13251
+ else {
13252
+ if (typeof globalThis.addEventListener !== "function") globalThis.addEventListener = () => {};
13253
+ if (typeof globalThis.removeEventListener !== "function") globalThis.removeEventListener = () => {};
13254
+ }
12342
13255
  if (typeof globalThis.setTimeout !== "function" && typeof lynx !== "undefined") {
12343
13256
  const _lynx = lynx;
12344
13257
  globalThis.setTimeout = _lynx.setTimeout;
@@ -12355,7 +13268,10 @@ if (typeof globalThis.requestIdleCallback !== "function" || typeof globalThis.ca
12355
13268
  }), 0);
12356
13269
  globalThis.cancelIdleCallback = (id) => clearTimeout(id);
12357
13270
  }
12358
- if (isMainThread()) installCanvasGlobals(globalThis);
13271
+ if (isMainThread()) {
13272
+ installCanvasGlobals(globalThis);
13273
+ installIntersectionObserver(globalThis);
13274
+ }
12359
13275
  var Opcode;
12360
13276
  (function(Opcode) {
12361
13277
  Opcode[Opcode["Begin"] = 0] = "Begin";