@blotch/angular-lynx 22.2.9 → 22.2.11

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"],
@@ -1989,6 +2590,11 @@ const flushesBefore = (first, second) => {
1989
2590
  const isIOS = () => {
1990
2591
  return ((typeof lynx !== "undefined" ? lynx.SystemInfo : void 0) ?? (typeof SystemInfo !== "undefined" ? SystemInfo : void 0))?.platform === "iOS";
1991
2592
  };
2593
+ const listenForScrollsFromTheStart = (element) => {
2594
+ if (__WEB__ || !isIOS()) return;
2595
+ element.addEventListener("bindscroll", ignoreScroll);
2596
+ };
2597
+ const ignoreScroll = () => {};
1992
2598
  const insertHidesBlurView = (parent, child) => !__WEB__ && child.tagName === "blur-view" && parent.tagName !== "blur-view" && isIOS();
1993
2599
  const isTextish = (el) => el.tagName === "text" || el.tagName === "raw-text";
1994
2600
  var LynxElement = class {
@@ -2014,6 +2620,7 @@ var LynxElement = class {
2014
2620
  #ownTraits = null;
2015
2621
  #addedTraits = [];
2016
2622
  #listeners = [];
2623
+ #heldEvents = /* @__PURE__ */ new Map();
2017
2624
  #webLongPress = null;
2018
2625
  #pointerEvents = null;
2019
2626
  #webPointer = null;
@@ -2029,9 +2636,14 @@ var LynxElement = class {
2029
2636
  static #pendingAttributeRemovals = /* @__PURE__ */ new Set();
2030
2637
  #removedAttributes = /* @__PURE__ */ new Set();
2031
2638
  static #byNativeId = /* @__PURE__ */ new Map();
2639
+ static #pendingReleases = /* @__PURE__ */ new Set();
2640
+ #destroyed = false;
2032
2641
  static #canonicalFor(ref) {
2033
2642
  return _a.#byNativeId.get(__GetElementUniqueID(ref)) ?? new _a(ref);
2034
2643
  }
2644
+ static byUniqueId(uniqueId) {
2645
+ return _a.#byNativeId.get(uniqueId);
2646
+ }
2035
2647
  constructor(element) {
2036
2648
  this.#nativeRef = element;
2037
2649
  _a.#byNativeId.set(__GetElementUniqueID(element), this);
@@ -2040,6 +2652,18 @@ var LynxElement = class {
2040
2652
  const id = __GetElementUniqueID(this.#nativeRef);
2041
2653
  if (_a.#byNativeId.get(id) === this) _a.#byNativeId.delete(id);
2042
2654
  }
2655
+ markDestroyed() {
2656
+ if (this.isRootPageElement) return;
2657
+ this.#destroyed = true;
2658
+ _a.#pendingReleases.add(this);
2659
+ scheduleSettleFlush();
2660
+ }
2661
+ virtualChildren() {
2662
+ return [];
2663
+ }
2664
+ get ownerDocument() {
2665
+ return globalEvents().document;
2666
+ }
2043
2667
  setProperty(name, value) {
2044
2668
  this.setAttribute(name, value);
2045
2669
  }
@@ -2155,6 +2779,7 @@ var LynxElement = class {
2155
2779
  insertBefore(newChild, refChild) {
2156
2780
  if (newChild.isRootPageElement) return;
2157
2781
  newChild.#cancelPendingRemoval();
2782
+ newChild.#keepIfDestroyed();
2158
2783
  newChild.#removeIfMoveWouldBeLost(this);
2159
2784
  newChild.#recreateIfDead();
2160
2785
  if (refChild == null) this.appendChild(newChild);
@@ -2176,6 +2801,7 @@ var LynxElement = class {
2176
2801
  appendChild(newChild) {
2177
2802
  if (newChild.isRootPageElement) return;
2178
2803
  newChild.#cancelPendingRemoval();
2804
+ newChild.#keepIfDestroyed();
2179
2805
  newChild.#removeIfMoveWouldBeLost(this);
2180
2806
  newChild.#recreateIfDead();
2181
2807
  if (wouldFormCycle(this.element, newChild.element)) return;
@@ -2234,10 +2860,18 @@ var LynxElement = class {
2234
2860
  }
2235
2861
  }
2236
2862
  static commitPendingRemovals() {
2237
- if (_a.#pendingRemovals.size === 0) return;
2238
- const items = [..._a.#pendingRemovals];
2239
- _a.#pendingRemovals.clear();
2240
- for (const el of items) el.#doRemove();
2863
+ if (_a.#pendingRemovals.size > 0) {
2864
+ const items = [..._a.#pendingRemovals];
2865
+ _a.#pendingRemovals.clear();
2866
+ for (const el of items) el.#doRemove();
2867
+ }
2868
+ _a.#commitPendingReleases();
2869
+ }
2870
+ static #commitPendingReleases() {
2871
+ if (_a.#pendingReleases.size === 0) return;
2872
+ const elements = [..._a.#pendingReleases];
2873
+ _a.#pendingReleases.clear();
2874
+ for (const el of elements) if (el.#destroyed && !el.#jsParent && !el._virtualParent) el.#release();
2241
2875
  }
2242
2876
  static #enqueueTextNormalization(el) {
2243
2877
  _a.#pendingTextNormalization.add(el);
@@ -2289,6 +2923,11 @@ var LynxElement = class {
2289
2923
  #cancelPendingRemoval() {
2290
2924
  _a.#pendingRemovals.delete(this);
2291
2925
  }
2926
+ #keepIfDestroyed() {
2927
+ if (!this.#destroyed) return;
2928
+ this.#destroyed = false;
2929
+ _a.#pendingReleases.delete(this);
2930
+ }
2292
2931
  #removeIfMoveWouldBeLost(newParent) {
2293
2932
  if (__WEB__ || this.#paintingDead || this._virtualParent) return;
2294
2933
  const oldParent = __GetParent(this.element);
@@ -2296,7 +2935,7 @@ var LynxElement = class {
2296
2935
  if (flushesBefore(newParent.element, oldParent)) this.#doRemove();
2297
2936
  }
2298
2937
  #doRemove() {
2299
- const parent = this.parentNode();
2938
+ const parent = __GetParent(this.element);
2300
2939
  if (!parent) return;
2301
2940
  for (const anim of this.#activeAnimations) anim.cancel();
2302
2941
  if (this.#activeAnimations.length > 0) {
@@ -2304,7 +2943,7 @@ var LynxElement = class {
2304
2943
  __AddInlineStyle(this.element, "transform", null);
2305
2944
  this.#activeAnimations.length = 0;
2306
2945
  }
2307
- __RemoveElement(parent.element, this.element);
2946
+ __RemoveElement(parent, this.element);
2308
2947
  const jsParent = this.#jsParent;
2309
2948
  if (jsParent) {
2310
2949
  const i = jsParent.#children.indexOf(this);
@@ -2314,6 +2953,15 @@ var LynxElement = class {
2314
2953
  }
2315
2954
  this.#markPaintingDead();
2316
2955
  if (jsParent && isTextish(this)) _a.#enqueueTextNormalization(jsParent);
2956
+ if (this.#destroyed) this.#release();
2957
+ }
2958
+ #release() {
2959
+ this.#destroyed = false;
2960
+ this.#paintingDead = true;
2961
+ this.deregisterNative();
2962
+ for (const { eventType, eventName } of this.#nativeHandlers.values()) __AddEvent(this.element, eventType, eventName, void 0);
2963
+ for (const child of this.#children) child.#release();
2964
+ for (const child of this.virtualChildren()) child.#release();
2317
2965
  }
2318
2966
  #markPaintingDead() {
2319
2967
  this.#paintingDead = true;
@@ -2384,10 +3032,19 @@ var LynxElement = class {
2384
3032
  const parsed = parseEventName(name);
2385
3033
  if (!parsed) return () => {};
2386
3034
  if (__WEB__ && parsed.eventName === "longpress") this.#listenForWebLongPress();
2387
- const entry = {
3035
+ return this.#add({
2388
3036
  ...parsed,
2389
3037
  listener: cb
2390
- };
3038
+ });
3039
+ }
3040
+ addTrailingEventListener(name, cb) {
3041
+ return this.#add({
3042
+ ...parseEventName(name),
3043
+ listener: cb,
3044
+ trailing: true
3045
+ });
3046
+ }
3047
+ #add(entry) {
2391
3048
  this.#listeners.push(entry);
2392
3049
  this.#syncNativeHandler(entry);
2393
3050
  return () => this.#removeListener(entry);
@@ -2454,7 +3111,7 @@ var LynxElement = class {
2454
3111
  #layoutTracker() {
2455
3112
  this.#layout ??= new LayoutTracker({
2456
3113
  listen: (event, listener) => this.addEventListener(event, listener),
2457
- measure: (callback) => __InvokeUIMethod(this.element, "boundingClientRect", {}, (result) => callback(result?.code === 0 && result.data ? result.data : null))
3114
+ measure: (callback) => measureLayout(this.element, callback)
2458
3115
  });
2459
3116
  return this.#layout;
2460
3117
  }
@@ -2511,14 +3168,18 @@ var LynxElement = class {
2511
3168
  }
2512
3169
  #listenersLike(entry) {
2513
3170
  const key = nativeEventKey(entry.eventType, entry.eventName);
2514
- return this.#listeners.filter((other) => nativeEventKey(other.eventType, other.eventName) === key);
3171
+ const like = this.#listeners.filter((other) => nativeEventKey(other.eventType, other.eventName) === key);
3172
+ return [...like.filter((other) => !other.trailing), ...like.filter((other) => other.trailing)];
2515
3173
  }
2516
3174
  #syncNativeHandler(entry) {
2517
3175
  const key = nativeEventKey(entry.eventType, entry.eventName);
3176
+ if (this.#heldEvents.has(key)) return;
2518
3177
  const listeners = this.#listenersLike(entry);
2519
3178
  const current = this.#nativeHandlers.get(key);
2520
3179
  const eventType = listeners.length ? combinedEventType(listeners) : null;
2521
3180
  if (current?.eventType === eventType) return;
3181
+ scheduleSettleFlush();
3182
+ this.#sendHandlersWithNextFlush();
2522
3183
  if (current) {
2523
3184
  __AddEvent(this.element, current.eventType, entry.eventName, void 0);
2524
3185
  this.#nativeHandlers.delete(key);
@@ -2535,6 +3196,25 @@ var LynxElement = class {
2535
3196
  value: handler.dispatch
2536
3197
  });
2537
3198
  }
3199
+ holdNativeEvent(eventName) {
3200
+ this.#heldEvents.set(eventName, (this.#heldEvents.get(eventName) ?? 0) + 1);
3201
+ this.#nativeHandlers.delete(eventName);
3202
+ }
3203
+ releaseNativeEvent(eventName) {
3204
+ const held = this.#heldEvents.get(eventName);
3205
+ if (held === void 0) return;
3206
+ if (held > 1) {
3207
+ this.#heldEvents.set(eventName, held - 1);
3208
+ return;
3209
+ }
3210
+ this.#heldEvents.delete(eventName);
3211
+ const entry = this.#listeners.find((listener) => listener.eventName === eventName && listener.eventType !== "global-bindEvent");
3212
+ if (entry) this.#syncNativeHandler(entry);
3213
+ }
3214
+ #sendHandlersWithNextFlush() {
3215
+ if (__WEB__) return;
3216
+ __SetDataset(this.element, { ...this.#dataset });
3217
+ }
2538
3218
  #dispatch(entry, event) {
2539
3219
  if (event && typeof event === "object") {
2540
3220
  if (typeof event.stopPropagation !== "function") event.stopPropagation = () => {};
@@ -2542,6 +3222,10 @@ var LynxElement = class {
2542
3222
  if (typeof event.stopImmediatePropagation !== "function") event.stopImmediatePropagation = () => {};
2543
3223
  }
2544
3224
  if (__WEB__ && entry.eventName === "tap" && dropsTap()) return void 0;
3225
+ if (__WEB__ && this.tagName === "video" && event && typeof event === "object") {
3226
+ const detail = webVideoDetail(entry.eventName, this.element);
3227
+ if (detail) event.detail = detail;
3228
+ }
2545
3229
  const pointer = __WEB__ ? this.#webPointer : this.#pointerEvents?.lastOffset;
2546
3230
  if (pointer && entry.eventName === "tap" && event && typeof event === "object" && event.offsetX === void 0) {
2547
3231
  event.offsetX = pointer.offsetX;
@@ -2647,6 +3331,11 @@ var LynxListElement = class extends LynxElement {
2647
3331
  }
2648
3332
  return children;
2649
3333
  }
3334
+ virtualChildren() {
3335
+ const children = [];
3336
+ for (let child = this.#firstVirtualChild; child; child = child._virtualNext) children.push(child);
3337
+ return children;
3338
+ }
2650
3339
  getCommittedUIChildren() {
2651
3340
  return this.#committedUIChildren;
2652
3341
  }
@@ -5315,6 +6004,9 @@ const noHandler = () => ({
5315
6004
  });
5316
6005
  var LynxMainThreadEvent = class LynxMainThreadEvent {
5317
6006
  #el = inject(ElementRef).nativeElement;
6007
+ get #host() {
6008
+ return this.#el;
6009
+ }
5318
6010
  #registrations = /* @__PURE__ */ new Map();
5319
6011
  mainThreadBindtap;
5320
6012
  mainThreadCatchtap;
@@ -5359,9 +6051,13 @@ var LynxMainThreadEvent = class LynxMainThreadEvent {
5359
6051
  __workletMap[workletId] = handle;
5360
6052
  }
5361
6053
  if (workletId === void 0) {
5362
- if (this.#registrations.delete(inputName)) __AddEvent(elementRef, eventType, eventName, noHandler());
6054
+ if (this.#registrations.delete(inputName)) {
6055
+ __AddEvent(elementRef, eventType, eventName, noHandler());
6056
+ this.#host.releaseNativeEvent?.(eventName);
6057
+ }
5363
6058
  continue;
5364
6059
  }
6060
+ if (!this.#registrations.has(inputName)) this.#host.holdNativeEvent?.(eventName);
5365
6061
  __AddEvent(elementRef, eventType, eventName, {
5366
6062
  type: "worklet",
5367
6063
  value: {
@@ -5377,6 +6073,7 @@ var LynxMainThreadEvent = class LynxMainThreadEvent {
5377
6073
  if (!elementRef) return;
5378
6074
  for (const [inputName, { eventType, eventName }] of this.#registrations) {
5379
6075
  __AddEvent(elementRef, eventType, eventName, noHandler());
6076
+ this.#host.releaseNativeEvent?.(eventName);
5380
6077
  this.#release(inputName);
5381
6078
  }
5382
6079
  this.#registrations.clear();
@@ -8971,6 +9668,7 @@ const CONTEXT_EVENTS = /* @__PURE__ */ new Set([
8971
9668
  "webglcontextrestored",
8972
9669
  "webglcontextcreationerror"
8973
9670
  ]);
9671
+ const contextFinalizer = typeof FinalizationRegistry === "function" ? new FinalizationRegistry((free) => free()) : null;
8974
9672
  const kebab = (name) => name.startsWith("--") ? name : name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
8975
9673
  const createStyle = (element) => {
8976
9674
  return new Proxy({}, {
@@ -9080,6 +9778,7 @@ var LynxCanvasElement = class extends LynxElement {
9080
9778
  return null;
9081
9779
  }
9082
9780
  this.#context = new LynxWebGL2RenderingContext(bridge, contextId, this, resolved);
9781
+ contextFinalizer?.register(this, () => bridge.destroy(contextId));
9083
9782
  return this.#context;
9084
9783
  }
9085
9784
  addEventListener(name, cb) {
@@ -9210,6 +9909,7 @@ var LynxDocument = class {
9210
9909
  if (isAndroid()) __SetID(pageElement, ANDROID_BLUR_CAPTURE_TARGET);
9211
9910
  this.#pageId = __GetElementUniqueID(pageElement);
9212
9911
  setPageElementRef(pageElement);
9912
+ globalEvents().attach(this.page, (id) => LynxElement.byUniqueId(id));
9213
9913
  return this.page;
9214
9914
  }
9215
9915
  createElement(name, value) {
@@ -9217,7 +9917,11 @@ var LynxDocument = class {
9217
9917
  /* v8 ignore next */
9218
9918
  if (__PROFILE__) devStats.elementCreated++;
9219
9919
  scheduleSettleFlush();
9220
- if (tag === "list") return createListElement(this.#pageId);
9920
+ if (tag === "list") {
9921
+ const list = createListElement(this.#pageId);
9922
+ listenForScrollsFromTheStart(list);
9923
+ return list;
9924
+ }
9221
9925
  if (tag === "page") {
9222
9926
  const result = createPageElement(this.page, this.#pageElementRequested);
9223
9927
  this.#pageElementRequested = result.pageElementRequested;
@@ -9228,6 +9932,7 @@ var LynxDocument = class {
9228
9932
  el.tagName = tag;
9229
9933
  if (tag === "raw-text") el.setInitialText(value ?? "");
9230
9934
  if (tag === "blur-view") el.updateBlurMaterial();
9935
+ if (tag === "scroll-view") listenForScrollsFromTheStart(el);
9231
9936
  return el;
9232
9937
  }
9233
9938
  createText(value) {
@@ -9278,6 +9983,7 @@ var LynxHydrateDocument = class {
9278
9983
  page.tagName = "page";
9279
9984
  page.isRootPageElement = true;
9280
9985
  this.#page = page;
9986
+ globalEvents().attach(page, (id) => LynxElement.byUniqueId(id));
9281
9987
  return page;
9282
9988
  }
9283
9989
  createElement(tag, value) {
@@ -9306,7 +10012,48 @@ var LynxHydrateDocument = class {
9306
10012
  this.#page?.appendChild(newChild);
9307
10013
  }
9308
10014
  };
10015
+ const LYNX_ELEMENT_TAGS = /* @__PURE__ */ new Set([
10016
+ "page",
10017
+ "view",
10018
+ "text",
10019
+ "raw-text",
10020
+ "inline-truncation",
10021
+ "image",
10022
+ "scroll-view",
10023
+ "list",
10024
+ "list-item",
10025
+ "block",
10026
+ "if",
10027
+ "for",
10028
+ "frame",
10029
+ "input",
10030
+ "textarea",
10031
+ "overlay",
10032
+ "svg",
10033
+ "viewpager",
10034
+ "viewpager-item",
10035
+ "refresh",
10036
+ "refresh-header",
10037
+ "title-bar-view",
10038
+ "scroll-coordinator",
10039
+ "scroll-coordinator-header",
10040
+ "scroll-coordinator-toolbar",
10041
+ "scroll-coordinator-slot",
10042
+ "scroll-coordinator-slot-drag",
10043
+ "blur-view",
10044
+ "webview",
10045
+ "video",
10046
+ "cover-view",
10047
+ "canvas",
10048
+ "comment"
10049
+ ]);
10050
+ const tagClassOf = (tag) => `_ngtag-${tag.toLowerCase()}`;
10051
+ const tagClassFor = (tag) => LYNX_ELEMENT_TAGS.has(tag) ? null : tagClassOf(tag);
9309
10052
  const collapseWhitespace = (value) => value.replace(/[ \t\n\r\f\v]+/g, " ");
10053
+ const markTag = (element, tag) => {
10054
+ const tagClass = tagClassFor(tag);
10055
+ if (tagClass) element.addScopeClass(tagClass);
10056
+ };
9310
10057
  var LynxRenderer = class {
9311
10058
  #document;
9312
10059
  #data = {};
@@ -9318,7 +10065,9 @@ var LynxRenderer = class {
9318
10065
  }
9319
10066
  destroy() {}
9320
10067
  createElement(name, _namespace) {
9321
- return this.#document.createElement(name);
10068
+ const element = this.#document.createElement(name);
10069
+ markTag(element, name);
10070
+ return element;
9322
10071
  }
9323
10072
  createComment(_value) {
9324
10073
  return this.#document.createComment();
@@ -9327,7 +10076,7 @@ var LynxRenderer = class {
9327
10076
  return this.#document.createText(collapseWhitespace(value));
9328
10077
  }
9329
10078
  destroyNode = (node) => {
9330
- node.deregisterNative?.();
10079
+ node.markDestroyed?.();
9331
10080
  node.releaseContext?.();
9332
10081
  };
9333
10082
  appendChild(parent, newChild) {
@@ -9376,10 +10125,18 @@ var LynxRenderer = class {
9376
10125
  setValue(node, value) {
9377
10126
  node.setAttribute("text", collapseWhitespace(value));
9378
10127
  }
9379
- listen(target, eventName, callback, _options) {
9380
- return target.addEventListener(eventName, callback);
10128
+ listen(target, eventName, callback, options) {
10129
+ const resolved = typeof target === "string" ? globalTarget(target, eventName) : target;
10130
+ const remove = resolved.addEventListener(eventName, callback, options);
10131
+ return typeof remove === "function" ? remove : () => resolved.removeEventListener(eventName, callback, options);
9381
10132
  }
9382
10133
  };
10134
+ const globalTarget = (name, eventName) => {
10135
+ const document = globalEvents().document;
10136
+ const target = name === "window" ? document.defaultView : name === "document" ? document : name === "body" ? document.body : null;
10137
+ if (!target) throw new Error(`Unsupported event target ${name} for event ${eventName}`);
10138
+ return target;
10139
+ };
9383
10140
  var EmulatedLynxRenderer = class extends LynxRenderer {
9384
10141
  #contentClass;
9385
10142
  #hostClass;
@@ -9482,10 +10239,7 @@ const provideRenderer = () => {
9482
10239
  },
9483
10240
  {
9484
10241
  provide: DOCUMENT,
9485
- useValue: {
9486
- getElementById: () => null,
9487
- defaultView: globalThis
9488
- }
10242
+ useFactory: () => globalEvents().document
9489
10243
  },
9490
10244
  {
9491
10245
  provide: APP_BASE_HREF,
@@ -10649,7 +11403,7 @@ var LynxImageElement = class {
10649
11403
  for (const listener of Array.from(this.#listeners.get(type) ?? [])) listener.call(this, event);
10650
11404
  }
10651
11405
  };
10652
- const isLynxElement = (value) => typeof value === "object" && value !== null && typeof value.observeLayout === "function" && "element" in value;
11406
+ const isLynxElement$1 = (value) => typeof value === "object" && value !== null && typeof value.observeLayout === "function" && "element" in value;
10653
11407
  const installCanvasGlobals = (target) => {
10654
11408
  const install = (name, value) => {
10655
11409
  if (typeof target[name] === "undefined") target[name] = value;
@@ -10683,7 +11437,7 @@ const installCanvasGlobals = (target) => {
10683
11437
  if (typeof nativeGetComputedStyle !== "function") target["getComputedStyle"] = Object.assign(() => emptyComputedStyle(), { forLynxElements: true });
10684
11438
  else if (!nativeGetComputedStyle.forLynxElements) {
10685
11439
  const wrapped = (element, pseudo) => {
10686
- if (!isLynxElement(element)) return nativeGetComputedStyle.call(target, element, pseudo);
11440
+ if (!isLynxElement$1(element)) return nativeGetComputedStyle.call(target, element, pseudo);
10687
11441
  const node = element.element;
10688
11442
  const view = node.ownerDocument?.defaultView;
10689
11443
  return view ? view.getComputedStyle(node, pseudo) : emptyComputedStyle();
@@ -10758,7 +11512,7 @@ var LynxResizeObserver = class {
10758
11512
  this.#callback = callback;
10759
11513
  }
10760
11514
  observe(target) {
10761
- if (!isLynxElement(target)) throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': the target is not an element");
11515
+ if (!isLynxElement$1(target)) throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': the target is not an element");
10762
11516
  if (this.#observed.has(target)) return;
10763
11517
  let last = null;
10764
11518
  const notify = (rect) => {
@@ -10773,7 +11527,7 @@ var LynxResizeObserver = class {
10773
11527
  });
10774
11528
  }
10775
11529
  unobserve(target) {
10776
- if (!isLynxElement(target)) return;
11530
+ if (!isLynxElement$1(target)) return;
10777
11531
  this.#observed.get(target)?.();
10778
11532
  this.#observed.delete(target);
10779
11533
  }
@@ -10803,12 +11557,12 @@ const wrapResizeObserver = (NativeResizeObserver) => {
10803
11557
  });
10804
11558
  }
10805
11559
  observe(target, options) {
10806
- const node = isLynxElement(target) ? target.element : target;
10807
- if (isLynxElement(target)) this.#targets.set(node, target);
11560
+ const node = isLynxElement$1(target) ? target.element : target;
11561
+ if (isLynxElement$1(target)) this.#targets.set(node, target);
10808
11562
  this.#observer.observe(node, options);
10809
11563
  }
10810
11564
  unobserve(target) {
10811
- const node = isLynxElement(target) ? target.element : target;
11565
+ const node = isLynxElement$1(target) ? target.element : target;
10812
11566
  this.#targets.delete(node);
10813
11567
  this.#observer.unobserve(node);
10814
11568
  }
@@ -10818,6 +11572,401 @@ const wrapResizeObserver = (NativeResizeObserver) => {
10818
11572
  }
10819
11573
  };
10820
11574
  };
11575
+ const isLynxElement = (value) => typeof value === "object" && value !== null && "element" in value && typeof value.addEventListener === "function" && typeof value.observeLayout === "function" && typeof value.parentNode === "function";
11576
+ const SCROLL_EVENTS = /* @__PURE__ */ new Map([
11577
+ ["scroll-view", "bindscroll"],
11578
+ ["list", "bindscroll"],
11579
+ ["viewpager", "bindoffsetchange"],
11580
+ ["scroll-coordinator", "bindoffset"]
11581
+ ]);
11582
+ const measurements = /* @__PURE__ */ new Map();
11583
+ let sampleMeasurements = null;
11584
+ const measure = (element) => {
11585
+ const native = element.element;
11586
+ const shared = sampleMeasurements?.get(native) ?? measurements.get(native);
11587
+ if (shared) return shared;
11588
+ let answered = false;
11589
+ const rect = new Promise((resolve) => measureLayout(native, (box) => {
11590
+ answered = true;
11591
+ measurements.delete(native);
11592
+ resolve(box);
11593
+ }));
11594
+ sampleMeasurements?.set(native, rect);
11595
+ if (!answered) measurements.set(native, rect);
11596
+ return rect;
11597
+ };
11598
+ const measureGeometry = async (target, root) => {
11599
+ const scrollContainers = [];
11600
+ let top = target;
11601
+ let inRoot = false;
11602
+ while (!top.isRootPageElement) {
11603
+ const parent = top.parentNode();
11604
+ if (!parent) break;
11605
+ if (parent === root) {
11606
+ inRoot = true;
11607
+ break;
11608
+ }
11609
+ if (SCROLL_EVENTS.has(parent.tagName)) scrollContainers.push(parent);
11610
+ top = parent;
11611
+ }
11612
+ if (!root) inRoot = top.isRootPageElement === true;
11613
+ const rootElement = root ?? (inRoot ? top : null);
11614
+ const clips = inRoot ? scrollContainers : [];
11615
+ const [box, rootBox, ...scrollBoxes] = await Promise.all([
11616
+ measure(target),
11617
+ rootElement ? measure(rootElement) : null,
11618
+ ...clips.map(measure)
11619
+ ]);
11620
+ return {
11621
+ box: box ?? null,
11622
+ root: rootBox ?? null,
11623
+ inRoot,
11624
+ scrollContainers: scrollBoxes,
11625
+ scrolledBy: root && inRoot && SCROLL_EVENTS.has(root.tagName) ? [...clips, root] : clips
11626
+ };
11627
+ };
11628
+ const intersect = (a, b, touching) => {
11629
+ const left = Math.max(a.left, b.left);
11630
+ const top = Math.max(a.top, b.top);
11631
+ const right = Math.min(a.left + a.width, b.left + b.width);
11632
+ const bottom = Math.min(a.top + a.height, b.top + b.height);
11633
+ return (touching ? right >= left && bottom >= top : right > left && bottom > top) ? {
11634
+ left,
11635
+ top,
11636
+ width: right - left,
11637
+ height: bottom - top
11638
+ } : null;
11639
+ };
11640
+ const visiblePart = ({ box, root, scrollContainers }) => {
11641
+ let visible = box;
11642
+ for (const clip of [root, ...scrollContainers]) visible = visible && clip ? intersect(visible, clip, false) : null;
11643
+ return visible;
11644
+ };
11645
+ const area = (rect) => rect.width * rect.height;
11646
+ const exposureEntryFor = (target, geometry, exposed) => {
11647
+ const visible = visiblePart(geometry);
11648
+ const isIntersecting = exposed ?? visible !== null;
11649
+ const { box } = geometry;
11650
+ let intersectionRatio = 0;
11651
+ if (isIntersecting) intersectionRatio = !box || area(box) === 0 ? 1 : visible ? area(visible) / area(box) : 0;
11652
+ return {
11653
+ target,
11654
+ time: performance.now(),
11655
+ rootBounds: geometry.root ? toDOMRect(geometry.root) : null,
11656
+ boundingClientRect: toDOMRect(box),
11657
+ intersectionRect: toDOMRect(isIntersecting ? visible : null),
11658
+ intersectionRatio,
11659
+ isIntersecting
11660
+ };
11661
+ };
11662
+ const grow = (rect, margin) => {
11663
+ const [top, right, bottom, left] = margin.map((side, index) => side.percent ? side.value * (index % 2 ? rect.width : rect.height) / 100 : side.value);
11664
+ return {
11665
+ left: rect.left - left,
11666
+ top: rect.top - top,
11667
+ width: Math.max(rect.width + left + right, 0),
11668
+ height: Math.max(rect.height + top + bottom, 0)
11669
+ };
11670
+ };
11671
+ const thresholdIndexOf = (ratio, thresholds) => {
11672
+ let index = 0;
11673
+ while (index < thresholds.length && thresholds[index] <= ratio + 1e-6) index++;
11674
+ return index;
11675
+ };
11676
+ const measuredEntryFor = (target, geometry, margin, thresholds, exposed) => {
11677
+ const { box } = geometry;
11678
+ const rootBounds = geometry.root && grow(geometry.root, margin);
11679
+ let visible = null;
11680
+ let overlaps = false;
11681
+ let ratio = 0;
11682
+ if (!box) {
11683
+ overlaps = exposed === true;
11684
+ ratio = overlaps ? 1 : 0;
11685
+ } else if (geometry.inRoot && rootBounds) {
11686
+ visible = box;
11687
+ for (const clip of [...geometry.scrollContainers, rootBounds]) visible = visible && clip ? intersect(visible, clip, true) : null;
11688
+ if (visible) {
11689
+ overlaps = true;
11690
+ ratio = area(box) === 0 ? 1 : Math.min(area(visible) / area(box), 1);
11691
+ }
11692
+ }
11693
+ const index = overlaps ? thresholdIndexOf(ratio, thresholds) : 0;
11694
+ return {
11695
+ index,
11696
+ entry: {
11697
+ target,
11698
+ time: performance.now(),
11699
+ rootBounds: rootBounds ? toDOMRect(rootBounds) : null,
11700
+ boundingClientRect: toDOMRect(box),
11701
+ intersectionRect: toDOMRect(visible),
11702
+ intersectionRatio: ratio,
11703
+ isIntersecting: index > 0
11704
+ }
11705
+ };
11706
+ };
11707
+ const thresholdsOf = (threshold) => {
11708
+ const given = threshold === void 0 ? [] : Array.isArray(threshold) ? threshold.map(Number) : [Number(threshold)];
11709
+ 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");
11710
+ return Object.freeze(given.length ? given.sort((a, b) => a - b) : [0]);
11711
+ };
11712
+ const rootMarginOf = (margin) => {
11713
+ const tokens = String(margin ?? "").trim().split(/\s+/).filter(Boolean);
11714
+ if (tokens.length > 4) throw invalidRootMargin();
11715
+ const [top = NO_MARGIN, right = top, bottom = top, left = right] = tokens.map((token) => {
11716
+ const match = /^(-?(?:\d+\.?\d*|\.\d+))(px|%)?$/.exec(token);
11717
+ if (!match || !match[2] && Number(match[1]) !== 0) throw invalidRootMargin();
11718
+ return match[2] ? {
11719
+ text: token,
11720
+ value: Number(match[1]),
11721
+ percent: match[2] === "%"
11722
+ } : NO_MARGIN;
11723
+ });
11724
+ return [
11725
+ top,
11726
+ right,
11727
+ bottom,
11728
+ left
11729
+ ];
11730
+ };
11731
+ const NO_MARGIN = {
11732
+ text: "0px",
11733
+ value: 0,
11734
+ percent: false
11735
+ };
11736
+ const invalidRootMargin = () => /* @__PURE__ */ new SyntaxError("Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent.");
11737
+ const SAMPLE_INTERVAL = 50;
11738
+ const SAMPLES_AFTER_SCROLL = 6;
11739
+ const scrolling = /* @__PURE__ */ new Map();
11740
+ let samplingTimer = null;
11741
+ const sampleWhileScrolling = (sample) => {
11742
+ scrolling.set(sample, SAMPLES_AFTER_SCROLL);
11743
+ if (samplingTimer === null) samplingTimer = setTimeout(sampleScrolling, SAMPLE_INTERVAL);
11744
+ };
11745
+ const sampleScrolling = () => {
11746
+ samplingTimer = null;
11747
+ sampleMeasurements = /* @__PURE__ */ new Map();
11748
+ for (const [sample, left] of Array.from(scrolling)) {
11749
+ if (left > 1) scrolling.set(sample, left - 1);
11750
+ else scrolling.delete(sample);
11751
+ sample();
11752
+ }
11753
+ sampleMeasurements = null;
11754
+ if (scrolling.size && samplingTimer === null) samplingTimer = setTimeout(sampleScrolling, SAMPLE_INTERVAL);
11755
+ };
11756
+ const stopSampling = (sample) => {
11757
+ scrolling.delete(sample);
11758
+ if (!scrolling.size && samplingTimer !== null) {
11759
+ clearTimeout(samplingTimer);
11760
+ samplingTimer = null;
11761
+ }
11762
+ };
11763
+ var LynxIntersectionObserver = class {
11764
+ static forLynxElements = true;
11765
+ root;
11766
+ rootMargin;
11767
+ thresholds;
11768
+ #callback;
11769
+ #root;
11770
+ #margin;
11771
+ #measures;
11772
+ #observed = /* @__PURE__ */ new Map();
11773
+ #records = [];
11774
+ #delivering = false;
11775
+ constructor(callback, options = {}) {
11776
+ if (typeof callback !== "function") throw new TypeError("Failed to construct 'IntersectionObserver': parameter 1 is not of type 'Function'.");
11777
+ this.#callback = callback;
11778
+ const root = options.root;
11779
+ if (root === void 0 || root === null || root === globalThis.document) this.#root = null;
11780
+ else if (isLynxElement(root)) this.#root = root;
11781
+ else throw new TypeError("Failed to construct 'IntersectionObserver': Failed to read the 'root' property from 'IntersectionObserverInit': The provided value is not of type '(Document or Element)'.");
11782
+ this.root = this.#root;
11783
+ this.#margin = rootMarginOf(options.rootMargin);
11784
+ this.rootMargin = this.#margin.map((side) => side.text).join(" ");
11785
+ this.thresholds = thresholdsOf(options.threshold);
11786
+ this.#measures = this.#root !== null || this.#margin.some((side) => side.value !== 0) || this.thresholds.length !== 1 || this.thresholds[0] !== 0;
11787
+ }
11788
+ observe(target) {
11789
+ if (!isLynxElement(target)) throw new TypeError("Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.");
11790
+ if (this.#observed.has(target)) return;
11791
+ const stopAppear = target.addEventListener("binduiappear", () => {
11792
+ this.#exposure(target, observation, true);
11793
+ });
11794
+ const stopDisappear = target.addEventListener("binduidisappear", () => {
11795
+ this.#exposure(target, observation, false);
11796
+ });
11797
+ let laidOut = false;
11798
+ const layout = () => {
11799
+ if (this.#measures) {
11800
+ observation.sample();
11801
+ return;
11802
+ }
11803
+ if (laidOut) return;
11804
+ laidOut = true;
11805
+ stopLayout();
11806
+ this.#report(target, observation, null);
11807
+ };
11808
+ const stopLayout = target.observeLayout(layout);
11809
+ const observation = {
11810
+ stop: () => {
11811
+ stopAppear();
11812
+ stopDisappear();
11813
+ stopLayout();
11814
+ for (const stopScroll of observation.scrollListeners.values()) stopScroll();
11815
+ observation.scrollListeners.clear();
11816
+ stopSampling(observation.sample);
11817
+ },
11818
+ index: null,
11819
+ exposed: null,
11820
+ events: 0,
11821
+ measuring: false,
11822
+ again: false,
11823
+ measured: false,
11824
+ scrollListeners: /* @__PURE__ */ new Map(),
11825
+ sample: () => this.#sample(target, observation)
11826
+ };
11827
+ this.#observed.set(target, observation);
11828
+ if (target.layoutRect) queueMicrotask(layout);
11829
+ }
11830
+ unobserve(target) {
11831
+ const observation = this.#observed.get(target);
11832
+ if (!observation) return;
11833
+ observation.stop();
11834
+ this.#observed.delete(target);
11835
+ this.#records = this.#records.filter((entry) => entry.target !== target);
11836
+ }
11837
+ disconnect() {
11838
+ for (const observation of this.#observed.values()) observation.stop();
11839
+ this.#observed.clear();
11840
+ this.#records = [];
11841
+ }
11842
+ takeRecords() {
11843
+ return this.#records.splice(0);
11844
+ }
11845
+ #exposure(target, observation, exposed) {
11846
+ observation.exposed = exposed;
11847
+ if (this.#measures) observation.sample();
11848
+ else this.#report(target, observation, exposed);
11849
+ }
11850
+ async #report(target, observation, exposed) {
11851
+ const event = exposed === null ? 0 : ++observation.events;
11852
+ const geometry = await measureGeometry(target, null);
11853
+ if (this.#observed.get(target) !== observation) return;
11854
+ if (observation.events !== event) return;
11855
+ const entry = exposureEntryFor(target, geometry, exposed);
11856
+ const index = entry.isIntersecting ? 1 : 0;
11857
+ if (index === observation.index) return;
11858
+ observation.index = index;
11859
+ this.#queue(entry);
11860
+ }
11861
+ #sample(target, observation) {
11862
+ if (this.#observed.get(target) !== observation) return;
11863
+ if (observation.measuring) {
11864
+ observation.again = true;
11865
+ return;
11866
+ }
11867
+ observation.measuring = true;
11868
+ measureGeometry(target, this.#root).then((geometry) => {
11869
+ observation.measuring = false;
11870
+ if (this.#observed.get(target) !== observation) return;
11871
+ this.#listenForScrolls(observation, geometry.scrolledBy);
11872
+ if (geometry.box) observation.measured = true;
11873
+ const { entry, index } = measuredEntryFor(target, geometry, this.#margin, this.thresholds, observation.measured ? null : observation.exposed);
11874
+ if (index !== observation.index) {
11875
+ observation.index = index;
11876
+ this.#queue(entry);
11877
+ }
11878
+ if (observation.again) {
11879
+ observation.again = false;
11880
+ observation.sample();
11881
+ }
11882
+ });
11883
+ }
11884
+ #listenForScrolls(observation, scrolledBy) {
11885
+ const current = new Set(scrolledBy);
11886
+ for (const [scroller, stopScroll] of observation.scrollListeners) {
11887
+ if (current.has(scroller)) continue;
11888
+ stopScroll();
11889
+ observation.scrollListeners.delete(scroller);
11890
+ }
11891
+ for (const scroller of scrolledBy) {
11892
+ if (observation.scrollListeners.has(scroller)) continue;
11893
+ const event = SCROLL_EVENTS.get(scroller.tagName);
11894
+ observation.scrollListeners.set(scroller, scroller.addEventListener(event, () => {
11895
+ sampleWhileScrolling(observation.sample);
11896
+ }));
11897
+ }
11898
+ }
11899
+ #queue(entry) {
11900
+ this.#records.push(entry);
11901
+ this.#deliverSoon();
11902
+ }
11903
+ #deliverSoon() {
11904
+ if (this.#delivering) return;
11905
+ this.#delivering = true;
11906
+ queueMicrotask(() => {
11907
+ this.#delivering = false;
11908
+ const records = this.takeRecords();
11909
+ if (records.length) this.#callback.call(this, records, this);
11910
+ });
11911
+ }
11912
+ };
11913
+ const wrapIntersectionObserver = (NativeIntersectionObserver) => {
11914
+ const toNode = (value) => isLynxElement(value) ? value.element : value;
11915
+ return class LynxWebIntersectionObserver {
11916
+ static forLynxElements = true;
11917
+ root;
11918
+ #observer;
11919
+ #targets = /* @__PURE__ */ new Map();
11920
+ constructor(callback, options) {
11921
+ this.root = options?.root ?? null;
11922
+ this.#observer = new NativeIntersectionObserver((entries) => callback(entries.map((entry) => this.#entry(entry)), this), options && {
11923
+ ...options,
11924
+ root: toNode(options.root)
11925
+ });
11926
+ }
11927
+ get rootMargin() {
11928
+ return this.#observer.rootMargin;
11929
+ }
11930
+ get thresholds() {
11931
+ return this.#observer.thresholds;
11932
+ }
11933
+ observe(target) {
11934
+ const node = toNode(target);
11935
+ if (node !== target) this.#targets.set(node, target);
11936
+ this.#observer.observe(node);
11937
+ }
11938
+ unobserve(target) {
11939
+ const node = toNode(target);
11940
+ this.#targets.delete(node);
11941
+ this.#observer.unobserve(node);
11942
+ }
11943
+ disconnect() {
11944
+ this.#targets.clear();
11945
+ this.#observer.disconnect();
11946
+ }
11947
+ takeRecords() {
11948
+ return this.#observer.takeRecords().map((entry) => this.#entry(entry));
11949
+ }
11950
+ #entry(entry) {
11951
+ const target = this.#targets.get(entry.target);
11952
+ if (!target) return entry;
11953
+ return {
11954
+ target,
11955
+ time: entry.time,
11956
+ rootBounds: entry.rootBounds,
11957
+ boundingClientRect: entry.boundingClientRect,
11958
+ intersectionRect: entry.intersectionRect,
11959
+ intersectionRatio: entry.intersectionRatio,
11960
+ isIntersecting: entry.isIntersecting
11961
+ };
11962
+ }
11963
+ };
11964
+ };
11965
+ const installIntersectionObserver = (target) => {
11966
+ const native = target["IntersectionObserver"];
11967
+ if (typeof native === "undefined") target["IntersectionObserver"] = LynxIntersectionObserver;
11968
+ else if (!native.forLynxElements) target["IntersectionObserver"] = wrapIntersectionObserver(native);
11969
+ };
10821
11970
  const readBundledAsset = (url) => {
10822
11971
  const lynxGlobal = globalThis.lynx;
10823
11972
  if (typeof lynxGlobal?.module !== "function") return null;
@@ -11055,8 +12204,19 @@ const parseUrl$1 = (input) => {
11055
12204
  try {
11056
12205
  return new URL(input).href;
11057
12206
  } 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.`);
12207
+ 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.`);
12208
+ }
12209
+ };
12210
+ const isRelative = (url) => !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);
12211
+ const PLACEHOLDER_BASE = "http://placeholder.invalid/";
12212
+ const parseRequestUrl = (input) => {
12213
+ if (!isRelative(input)) return parseUrl$1(input);
12214
+ try {
12215
+ new URL(input, PLACEHOLDER_BASE);
12216
+ } catch {
12217
+ throw new TypeError(`Failed to parse URL from ${input}.`);
11059
12218
  }
12219
+ return input;
11060
12220
  };
11061
12221
  const DEFAULT_OPTIONS = {
11062
12222
  cache: "default",
@@ -11096,7 +12256,7 @@ var LynxRequest = class LynxRequest extends LynxBody {
11096
12256
  };
11097
12257
  if (source !== null && (upper === "GET" || upper === "HEAD")) throw new TypeError("Failed to construct 'Request': Request with GET/HEAD method cannot have body.");
11098
12258
  super(source);
11099
- this.url = base ? base.url : parseUrl$1(String(input));
12259
+ this.url = base ? base.url : parseRequestUrl(String(input));
11100
12260
  this.method = normalizedMethod;
11101
12261
  this.headers = new LynxHeaders(init.headers ?? base?.headers);
11102
12262
  if (type !== null && !this.headers.has("content-type")) this.headers.set("content-type", type);
@@ -11215,21 +12375,18 @@ var LynxResponse = class LynxResponse extends LynxBody {
11215
12375
  }
11216
12376
  };
11217
12377
  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;
12378
+ const bundledResponse = (request) => {
12379
+ const bytes = request.method === "GET" ? readBundledAsset(request.url) : null;
12380
+ 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
12381
  return new LynxResponse(new ExtractedBody(bytes), {
11225
12382
  status: 200,
11226
12383
  statusText: "OK",
11227
12384
  headers: {
11228
- "content-type": typeOfAsset(input),
12385
+ "content-type": typeOfAsset(request.url),
11229
12386
  "content-length": String(bytes.byteLength)
11230
12387
  }
11231
12388
  }, {
11232
- url: input,
12389
+ url: request.url,
11233
12390
  redirected: false,
11234
12391
  type: "basic"
11235
12392
  });
@@ -11267,18 +12424,13 @@ const localResponse = (url) => {
11267
12424
  return null;
11268
12425
  };
11269
12426
  const relayFetch = (input, init) => new Promise((resolve, reject) => {
11270
- const bundled = bundledResponse(input, init);
11271
- if (bundled) {
11272
- resolve(bundled);
11273
- return;
11274
- }
11275
12427
  const request = new LynxRequest(input, init);
11276
12428
  const { signal } = request;
11277
12429
  if (signal.aborted) {
11278
12430
  reject(signal.reason);
11279
12431
  return;
11280
12432
  }
11281
- const local = localResponse(request.url);
12433
+ const local = isRelative(request.url) ? bundledResponse(request) : localResponse(request.url);
11282
12434
  if (local) {
11283
12435
  if (local instanceof LynxResponse) resolve(local);
11284
12436
  else reject(local);
@@ -12327,18 +13479,24 @@ if (typeof Node === "undefined") try {
12327
13479
  });
12328
13480
  globalThis.Node = LynxNode;
12329
13481
  } catch {}
12330
- if (typeof document === "undefined") globalThis.document = {
13482
+ if (isMainThread()) installGlobalDocument(globalThis, globalEvents());
13483
+ else if (typeof document === "undefined") globalThis.document = {
12331
13484
  defaultView: globalThis,
12332
13485
  querySelector: () => null
12333
13486
  };
12334
13487
  try {
12335
13488
  if (typeof window === "undefined") globalThis.window = globalThis;
12336
- } catch {}
13489
+ } catch {
13490
+ if (__WEB__) window = globalThis;
13491
+ }
12337
13492
  try {
12338
13493
  if (typeof navigator === "undefined") globalThis.navigator = { userAgent: "" };
12339
13494
  } catch {}
12340
- if (typeof globalThis.addEventListener !== "function") globalThis.addEventListener = () => {};
12341
- if (typeof globalThis.removeEventListener !== "function") globalThis.removeEventListener = () => {};
13495
+ if (isMainThread()) installGlobalWindow(globalThis, globalEvents());
13496
+ else {
13497
+ if (typeof globalThis.addEventListener !== "function") globalThis.addEventListener = () => {};
13498
+ if (typeof globalThis.removeEventListener !== "function") globalThis.removeEventListener = () => {};
13499
+ }
12342
13500
  if (typeof globalThis.setTimeout !== "function" && typeof lynx !== "undefined") {
12343
13501
  const _lynx = lynx;
12344
13502
  globalThis.setTimeout = _lynx.setTimeout;
@@ -12355,7 +13513,10 @@ if (typeof globalThis.requestIdleCallback !== "function" || typeof globalThis.ca
12355
13513
  }), 0);
12356
13514
  globalThis.cancelIdleCallback = (id) => clearTimeout(id);
12357
13515
  }
12358
- if (isMainThread()) installCanvasGlobals(globalThis);
13516
+ if (isMainThread()) {
13517
+ installCanvasGlobals(globalThis);
13518
+ installIntersectionObserver(globalThis);
13519
+ }
12359
13520
  var Opcode;
12360
13521
  (function(Opcode) {
12361
13522
  Opcode[Opcode["Begin"] = 0] = "Begin";