sygnal 7.0.0 → 7.2.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CHANGELOG.md +632 -0
  2. package/dist/agent-ui.cjs.js +592 -0
  3. package/dist/agent-ui.cjs.js.map +1 -0
  4. package/dist/agent-ui.esm.js +582 -0
  5. package/dist/agent-ui.esm.js.map +1 -0
  6. package/dist/api.cjs.js +63 -0
  7. package/dist/api.cjs.js.map +1 -0
  8. package/dist/api.esm.js +2 -0
  9. package/dist/api.esm.js.map +1 -0
  10. package/dist/astro/index.cjs.js +968 -4
  11. package/dist/astro/index.cjs.js.map +1 -1
  12. package/dist/astro/index.mjs +968 -4
  13. package/dist/astro/index.mjs.map +1 -1
  14. package/dist/astro/server.cjs.js +73 -45
  15. package/dist/astro/server.cjs.js.map +1 -1
  16. package/dist/astro/server.mjs +73 -45
  17. package/dist/astro/server.mjs.map +1 -1
  18. package/dist/canvas.cjs.js +2919 -0
  19. package/dist/canvas.cjs.js.map +1 -0
  20. package/dist/canvas.esm.js +2866 -0
  21. package/dist/canvas.esm.js.map +1 -0
  22. package/dist/devtools.cjs.js +38 -6
  23. package/dist/devtools.cjs.js.map +1 -1
  24. package/dist/devtools.esm.js +38 -6
  25. package/dist/devtools.esm.js.map +1 -1
  26. package/dist/diagnostics.cjs.js +224 -20
  27. package/dist/diagnostics.cjs.js.map +1 -1
  28. package/dist/diagnostics.esm.js +224 -20
  29. package/dist/diagnostics.esm.js.map +1 -1
  30. package/dist/guide/accessibility.md +1 -1
  31. package/dist/guide/ai-compose.md +150 -0
  32. package/dist/guide/behaviors.md +2 -1
  33. package/dist/guide/collaborative-state.md +265 -0
  34. package/dist/guide/element-commands.md +36 -2
  35. package/dist/guide/forms-reference.md +5 -5
  36. package/dist/guide/forms.md +3 -2
  37. package/dist/guide/http.md +28 -1
  38. package/dist/guide/islands.md +240 -0
  39. package/dist/guide/recipes/code-editor.md +1 -1
  40. package/dist/guide/renderers/agent-ui.md +138 -0
  41. package/dist/guide/renderers/custom-targets.md +126 -0
  42. package/dist/guide/renderers/overview.md +120 -0
  43. package/dist/guide/renderers/react-native.md +128 -0
  44. package/dist/guide/renderers/terminal.md +306 -0
  45. package/dist/guide/renderers/three.md +332 -0
  46. package/dist/guide/renderers/write-once.md +137 -0
  47. package/dist/guide/resources.md +2 -0
  48. package/dist/guide/sandboxed-components.md +223 -0
  49. package/dist/guide/timers.md +32 -5
  50. package/dist/guide/typed-apis.md +442 -0
  51. package/dist/guide/ui/accordion.md +11 -0
  52. package/dist/guide/ui/combobox.md +112 -8
  53. package/dist/guide/ui/dialog.md +96 -20
  54. package/dist/guide/ui/menu.md +57 -5
  55. package/dist/guide/ui/overview.md +8 -4
  56. package/dist/guide/ui/popover.md +69 -9
  57. package/dist/guide/ui/select.md +54 -4
  58. package/dist/guide/ui/tabs.md +1 -1
  59. package/dist/guide/ui/tooltip.md +7 -5
  60. package/dist/guide/virtual-collections.md +3 -0
  61. package/dist/guide/widgets.md +90 -1
  62. package/dist/host.cjs.js +2181 -0
  63. package/dist/host.cjs.js.map +1 -0
  64. package/dist/host.esm.js +2146 -0
  65. package/dist/host.esm.js.map +1 -0
  66. package/dist/index.cjs.js +3948 -586
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +584 -44
  69. package/dist/index.esm.js +3918 -586
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/primitives.cjs.js +143 -0
  72. package/dist/primitives.cjs.js.map +1 -0
  73. package/dist/primitives.esm.js +133 -0
  74. package/dist/primitives.esm.js.map +1 -0
  75. package/dist/react-native.cjs.js +248 -0
  76. package/dist/react-native.cjs.js.map +1 -0
  77. package/dist/react-native.esm.js +244 -0
  78. package/dist/react-native.esm.js.map +1 -0
  79. package/dist/replay.cjs.js +39 -0
  80. package/dist/replay.cjs.js.map +1 -0
  81. package/dist/replay.esm.js +2 -0
  82. package/dist/replay.esm.js.map +1 -0
  83. package/dist/sandbox-build.cjs.js +140 -0
  84. package/dist/sandbox-build.cjs.js.map +1 -0
  85. package/dist/sandbox-build.esm.js +137 -0
  86. package/dist/sandbox-build.esm.js.map +1 -0
  87. package/dist/sandbox-kernel.esm.js +212 -0
  88. package/dist/sandbox-kernel.esm.js.map +1 -0
  89. package/dist/sandbox-worker.cjs.js +857 -0
  90. package/dist/sandbox-worker.cjs.js.map +1 -0
  91. package/dist/sandbox-worker.esm.js +854 -0
  92. package/dist/sandbox-worker.esm.js.map +1 -0
  93. package/dist/sandbox.cjs.js +1425 -0
  94. package/dist/sandbox.cjs.js.map +1 -0
  95. package/dist/sandbox.esm.js +1406 -0
  96. package/dist/sandbox.esm.js.map +1 -0
  97. package/dist/sygnal.min.js +1 -1
  98. package/dist/sygnal.min.js.map +1 -1
  99. package/dist/terminal.cjs.js +1314 -0
  100. package/dist/terminal.cjs.js.map +1 -0
  101. package/dist/terminal.esm.js +1304 -0
  102. package/dist/terminal.esm.js.map +1 -0
  103. package/dist/three.cjs.js +1111 -0
  104. package/dist/three.cjs.js.map +1 -0
  105. package/dist/three.esm.js +1079 -0
  106. package/dist/three.esm.js.map +1 -0
  107. package/dist/ui-combobox.cjs.js +256 -18
  108. package/dist/ui-combobox.cjs.js.map +1 -1
  109. package/dist/ui-combobox.esm.js +256 -18
  110. package/dist/ui-combobox.esm.js.map +1 -1
  111. package/dist/ui-menu.cjs.js +150 -16
  112. package/dist/ui-menu.cjs.js.map +1 -1
  113. package/dist/ui-menu.esm.js +151 -17
  114. package/dist/ui-menu.esm.js.map +1 -1
  115. package/dist/ui-select.cjs.js +95 -9
  116. package/dist/ui-select.cjs.js.map +1 -1
  117. package/dist/ui-select.esm.js +95 -9
  118. package/dist/ui-select.esm.js.map +1 -1
  119. package/dist/ui.cjs.js +679 -173
  120. package/dist/ui.cjs.js.map +1 -1
  121. package/dist/ui.esm.js +679 -175
  122. package/dist/ui.esm.js.map +1 -1
  123. package/dist/vike/config/package.json +1 -1
  124. package/dist/vite/plugin.cjs.js +968 -4
  125. package/dist/vite/plugin.cjs.js.map +1 -1
  126. package/dist/vite/plugin.mjs +968 -4
  127. package/dist/vite/plugin.mjs.map +1 -1
  128. package/dist/worker-page.cjs.js +669 -0
  129. package/dist/worker-page.cjs.js.map +1 -0
  130. package/dist/worker-page.esm.js +665 -0
  131. package/dist/worker-page.esm.js.map +1 -0
  132. package/dist/worker.cjs.js +377 -0
  133. package/dist/worker.cjs.js.map +1 -0
  134. package/dist/worker.esm.js +374 -0
  135. package/dist/worker.esm.js.map +1 -0
  136. package/dist/yjs-testing.cjs.js +98 -0
  137. package/dist/yjs-testing.cjs.js.map +1 -0
  138. package/dist/yjs-testing.esm.js +77 -0
  139. package/dist/yjs-testing.esm.js.map +1 -0
  140. package/dist/yjs.cjs.js +923 -0
  141. package/dist/yjs.cjs.js.map +1 -0
  142. package/dist/yjs.esm.js +900 -0
  143. package/dist/yjs.esm.js.map +1 -0
  144. package/dist/zag.cjs.js +11 -1
  145. package/dist/zag.cjs.js.map +1 -1
  146. package/dist/zag.esm.js +11 -1
  147. package/dist/zag.esm.js.map +1 -1
  148. package/llms/ai.md +65 -0
  149. package/llms/collaboration.md +5 -0
  150. package/llms/graphics.md +10 -0
  151. package/llms/renderers.md +7 -0
  152. package/llms/replay.md +5 -0
  153. package/llms/sandbox.md +5 -0
  154. package/llms/typed-apis.md +5 -0
  155. package/llms/ui.md +10 -0
  156. package/llms-full.txt +444 -0
  157. package/llms.txt +37 -35
  158. package/package.json +139 -3
  159. package/src/agent-ui/mcp.ts +152 -0
  160. package/src/agent-ui/snapshot.ts +212 -0
  161. package/src/agent-ui.d.ts +106 -0
  162. package/src/agent-ui.ts +249 -0
  163. package/src/ai.d.ts +95 -2
  164. package/src/api.d.ts +303 -0
  165. package/src/api.ts +9 -0
  166. package/src/canvas/Canvas.ts +585 -0
  167. package/src/canvas/backend.ts +627 -0
  168. package/src/canvas/geometry.ts +187 -0
  169. package/src/canvas/gesture.ts +23 -0
  170. package/src/canvas/image.ts +197 -0
  171. package/src/canvas/kit.ts +130 -0
  172. package/src/canvas/paint.ts +260 -0
  173. package/src/canvas/pointer.ts +25 -0
  174. package/src/canvas/snap.ts +68 -0
  175. package/src/canvas/svg.ts +99 -0
  176. package/src/canvas.d.ts +373 -0
  177. package/src/canvas.ts +46 -0
  178. package/src/core/actions.ts +14 -3
  179. package/src/core/cell.ts +12 -4
  180. package/src/core/debug.ts +8 -3
  181. package/src/core/hooks.ts +16 -2
  182. package/src/core/hosts/collection.ts +1 -1
  183. package/src/core/hosts/switchable.ts +26 -8
  184. package/src/core/instance.ts +98 -45
  185. package/src/core/markers/portal.ts +4 -0
  186. package/src/core/registry.ts +4 -0
  187. package/src/core/runtime.ts +34 -25
  188. package/src/core/statics.ts +5 -5
  189. package/src/cycle/dom/EventDelegator.ts +37 -33
  190. package/src/cycle/dom/MainDOMSource.ts +16 -14
  191. package/src/cycle/dom/enrichEventStream.ts +13 -9
  192. package/src/cycle/dom/fromEvent.ts +6 -10
  193. package/src/cycle/dom/index.ts +11 -7
  194. package/src/cycle/dom/isolate.ts +13 -6
  195. package/src/cycle/dom/makeDOMDriver.ts +6 -2
  196. package/src/cycle/dom/utils.ts +9 -2
  197. package/src/cycle/run/adapt.ts +4 -12
  198. package/src/devtools.d.ts +9 -0
  199. package/src/devtools.ts +2 -1
  200. package/src/extra/ai/compose/catalog.ts +2 -1
  201. package/src/extra/ai/compose/decisions.ts +1 -2
  202. package/src/extra/ai/compose/generated.ts +234 -0
  203. package/src/extra/ai/compose/index.ts +6 -2
  204. package/src/extra/ai/compose/layout.ts +3 -1
  205. package/src/extra/ai/compose/llm.ts +7 -3
  206. package/src/extra/ai/compose/types.ts +2 -2
  207. package/src/extra/ai/transports/shared.ts +3 -1
  208. package/src/extra/api/contract.ts +485 -0
  209. package/src/extra/api/index.ts +26 -0
  210. package/src/extra/behaviors.ts +3 -3
  211. package/src/extra/controlled.ts +44 -0
  212. package/src/extra/copyAsTest.ts +22 -0
  213. package/src/extra/devtools.ts +5 -3
  214. package/src/extra/devtoolsActions.ts +8 -0
  215. package/src/extra/devtoolsNext.ts +1 -1
  216. package/src/extra/diagnostics/checks/actionLog.ts +8 -3
  217. package/src/extra/diagnostics/checks/behaviors.ts +25 -0
  218. package/src/extra/diagnostics/checks/elementCommands.ts +17 -5
  219. package/src/extra/diagnostics/checks/index.ts +1 -1
  220. package/src/extra/diagnostics/checks/next.ts +78 -4
  221. package/src/extra/diagnostics/checks/props.ts +6 -1
  222. package/src/extra/diagnostics/checks/replies.ts +11 -7
  223. package/src/extra/diagnostics/checks/timers.ts +8 -2
  224. package/src/extra/diagnostics/checks/virtual.ts +11 -0
  225. package/src/extra/diagnostics/codes.ts +51 -1
  226. package/src/extra/elementCommands.ts +20 -4
  227. package/src/extra/fetchDriver.ts +4 -1
  228. package/src/extra/fetchEventSource.ts +6 -2
  229. package/src/extra/field.ts +29 -0
  230. package/src/extra/form.ts +42 -16
  231. package/src/extra/frame.ts +54 -0
  232. package/src/extra/keymap.ts +41 -0
  233. package/src/extra/pager.ts +2 -0
  234. package/src/extra/persist.ts +2 -1
  235. package/src/extra/pointer.ts +262 -0
  236. package/src/extra/processForm.ts +34 -1
  237. package/src/extra/replay/address.ts +49 -0
  238. package/src/extra/replay/entropy.ts +99 -0
  239. package/src/extra/replay/format.ts +263 -0
  240. package/src/extra/replay/index.ts +8 -0
  241. package/src/extra/replay/inert.ts +22 -0
  242. package/src/extra/replay/recorder.ts +247 -0
  243. package/src/extra/replay/replayer.ts +234 -0
  244. package/src/extra/replay/report.ts +126 -0
  245. package/src/extra/replay/schema.ts +52 -0
  246. package/src/extra/replay/version.ts +3 -0
  247. package/src/extra/router.ts +7 -3
  248. package/src/extra/selection.ts +2 -0
  249. package/src/extra/socketDriver.ts +51 -6
  250. package/src/extra/ssr.ts +128 -42
  251. package/src/extra/testing.ts +295 -39
  252. package/src/extra/timers.ts +96 -7
  253. package/src/extra/undo.ts +2 -0
  254. package/src/extra/virtual.ts +25 -2
  255. package/src/extra/widget.ts +106 -17
  256. package/src/host/actions.ts +383 -0
  257. package/src/host/delegation.ts +101 -0
  258. package/src/host/dom.ts +553 -0
  259. package/src/host/driver.ts +185 -0
  260. package/src/host/selector.ts +119 -0
  261. package/src/host/style.ts +51 -0
  262. package/src/host.d.ts +298 -0
  263. package/src/host.ts +46 -0
  264. package/src/index.d.ts +302 -38
  265. package/src/index.ts +15 -3
  266. package/src/pragma/blocks.ts +211 -0
  267. package/src/pragma/index.ts +44 -10
  268. package/src/primitives.d.ts +54 -0
  269. package/src/primitives.ts +136 -0
  270. package/src/react-native.d.ts +57 -0
  271. package/src/react-native.ts +285 -0
  272. package/src/react-peers.d.ts +21 -3
  273. package/src/replay.d.ts +185 -0
  274. package/src/replay.ts +6 -0
  275. package/src/sandbox/build.ts +119 -0
  276. package/src/sandbox/create.ts +187 -0
  277. package/src/sandbox/engine.ts +267 -0
  278. package/src/sandbox/generated.ts +195 -0
  279. package/src/sandbox/kernel.ts +180 -0
  280. package/src/sandbox/mirror.ts +269 -0
  281. package/src/sandbox/protocol.ts +128 -0
  282. package/src/sandbox/quickjs.ts +74 -0
  283. package/src/sandbox/ssr.ts +41 -0
  284. package/src/sandbox/widget.ts +60 -0
  285. package/src/sandbox/worker-entry.ts +59 -0
  286. package/src/sandbox/worker.ts +137 -0
  287. package/src/sandbox-build.d.ts +12 -0
  288. package/src/sandbox-build.ts +6 -0
  289. package/src/sandbox.d.ts +299 -0
  290. package/src/sandbox.ts +25 -0
  291. package/src/shared.ts +12 -0
  292. package/src/terminal/paint.ts +712 -0
  293. package/src/terminal.d.ts +167 -0
  294. package/src/terminal.ts +530 -0
  295. package/src/three/assets.ts +170 -0
  296. package/src/three/host.ts +36 -0
  297. package/src/three/scene.ts +217 -0
  298. package/src/three/target.ts +252 -0
  299. package/src/three.d.ts +221 -0
  300. package/src/three.ts +30 -0
  301. package/src/ui/accordion.ts +21 -7
  302. package/src/ui/dialog.ts +192 -90
  303. package/src/ui/disclosure.ts +2 -0
  304. package/src/ui/dom.ts +30 -0
  305. package/src/ui/layers.ts +132 -0
  306. package/src/ui/popover.ts +120 -21
  307. package/src/ui/shared.ts +98 -12
  308. package/src/ui/stack.ts +18 -0
  309. package/src/ui/tabs.ts +2 -0
  310. package/src/ui/toaster.ts +16 -15
  311. package/src/ui/tooltip.ts +24 -8
  312. package/src/ui/tooltips.ts +26 -14
  313. package/src/ui/zag/combobox.ts +180 -13
  314. package/src/ui/zag/menu.ts +97 -17
  315. package/src/ui/zag/select.ts +15 -4
  316. package/src/ui/zag/shared.ts +60 -8
  317. package/src/ui-combobox.d.ts +16 -1
  318. package/src/ui-menu.d.ts +22 -7
  319. package/src/ui-zag-types.d.ts +13 -5
  320. package/src/ui.d.ts +62 -7
  321. package/src/ui.ts +2 -2
  322. package/src/vite/api.ts +186 -0
  323. package/src/vite/compile.ts +280 -0
  324. package/src/vite/plugin.d.ts +34 -0
  325. package/src/vite/plugin.ts +78 -4
  326. package/src/vite/sandbox.ts +16 -0
  327. package/src/worker/page.ts +396 -0
  328. package/src/worker/protocol.ts +48 -0
  329. package/src/worker/target.ts +282 -0
  330. package/src/worker-page.d.ts +42 -0
  331. package/src/worker-page.ts +12 -0
  332. package/src/worker.d.ts +65 -0
  333. package/src/worker.ts +16 -0
  334. package/src/yjs/driver.ts +253 -0
  335. package/src/yjs/mapping.ts +389 -0
  336. package/src/yjs/shared.ts +138 -0
  337. package/src/yjs/testing.ts +91 -0
  338. package/src/yjs-testing.d.ts +34 -0
  339. package/src/yjs-testing.ts +7 -0
  340. package/src/yjs.d.ts +109 -0
  341. package/src/yjs.ts +18 -0
  342. package/src/zag.d.ts +2 -0
  343. package/src/zag.ts +8 -1
@@ -0,0 +1,669 @@
1
+ 'use strict';
2
+
3
+ var xs = require('xstream');
4
+
5
+ const cache = new Map();
6
+ const COMPOUND = /([a-zA-Z][\w-]*|\*)|#([\w-]+)|\.([\w-]+)|\[\s*([\w-]+)\s*(?:([~^$*]?=)\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]*))\s*)?\]|:(focus-visible|focus-within|focus|checked|disabled|enabled|open|modal|popover-open)|:not\(([^)]*)\)/y;
7
+ function compound(s, sel) {
8
+ const out = { classes: [], attrs: [], pseudo: [], not: [] };
9
+ let i = 0;
10
+ while (i < s.length) {
11
+ COMPOUND.lastIndex = i;
12
+ const m = COMPOUND.exec(s);
13
+ if (!m)
14
+ throw new Error(`sygnal/host: unsupported selector "${sel}" (at "${s.slice(i)}")`);
15
+ // (before a :not() recursion reuses the sticky regex)
16
+ const next = COMPOUND.lastIndex;
17
+ if (m[1] && i == 0)
18
+ out.tag = m[1] == '*' ? undefined : m[1].toLowerCase();
19
+ else if (m[1])
20
+ throw new Error(`sygnal/host: unsupported selector "${sel}"`);
21
+ else if (m[2])
22
+ out.id = m[2];
23
+ else if (m[3])
24
+ out.classes.push(m[3]);
25
+ else if (m[4])
26
+ out.attrs.push([m[4], m[5] || '', m[6] ?? m[7] ?? m[8]]);
27
+ else if (m[9])
28
+ out.pseudo.push(m[9]);
29
+ else if (m[10] !== undefined)
30
+ out.not.push(compound(m[10].trim(), sel));
31
+ i = next;
32
+ }
33
+ return out;
34
+ }
35
+ function parse(sel) {
36
+ let r = cache.get(sel);
37
+ if (r)
38
+ return r;
39
+ r = sel.split(',').map(part => {
40
+ const steps = [];
41
+ let comb = ' ';
42
+ for (const t of part.trim().replace(/\s*>\s*/g, ' > ').split(/\s+/)) {
43
+ if (!t)
44
+ continue;
45
+ if (t == '>') {
46
+ comb = '>';
47
+ continue;
48
+ }
49
+ steps.push({ comb, simple: compound(t, sel) });
50
+ comb = ' ';
51
+ }
52
+ if (!steps.length)
53
+ throw new Error(`sygnal/host: empty selector "${sel}"`);
54
+ return steps;
55
+ });
56
+ if (cache.size < 1024)
57
+ cache.set(sel, r);
58
+ return r;
59
+ }
60
+ function attrOk(el, [name, op, v]) {
61
+ const has = el.getAttribute(name);
62
+ if (has === null)
63
+ return false;
64
+ if (v === undefined)
65
+ return true;
66
+ switch (op) {
67
+ case '=': return has === v;
68
+ case '^=': return !!v && has.startsWith(v);
69
+ case '$=': return !!v && has.endsWith(v);
70
+ case '*=': return !!v && has.includes(v);
71
+ default: return has.split(/\s+/).includes(v);
72
+ }
73
+ }
74
+ function simpleOk(el, s) {
75
+ if (s.tag && el.localName !== s.tag)
76
+ return false;
77
+ if (s.id && el.getAttribute('id') !== s.id)
78
+ return false;
79
+ for (const c of s.classes)
80
+ if (!el.classList.contains(c))
81
+ return false;
82
+ for (const a of s.attrs)
83
+ if (!attrOk(el, a))
84
+ return false;
85
+ for (const p of s.pseudo)
86
+ if (!pseudoOk(el, p))
87
+ return false;
88
+ for (const n of s.not)
89
+ if (simpleOk(el, n))
90
+ return false;
91
+ return true;
92
+ }
93
+ function pseudoOk(el, p) {
94
+ const a = el.ownerDocument.activeElement;
95
+ switch (p) {
96
+ case 'focus':
97
+ case 'focus-visible': return a === el;
98
+ case 'focus-within': return !!a && el.contains(a);
99
+ case 'checked': return el.localName == 'option' ? !!el.selected || el.parentNode?.value === (el.value || el.getAttribute('value') || el.textContent.trim()) : !!el.checked;
100
+ case 'disabled': return !!el.disabled;
101
+ case 'enabled': return !el.disabled;
102
+ case 'open': return el.localName == 'select' ? el.picker != null : !!el.open || !!el.popoverOpen;
103
+ case 'modal': return !!el.modal;
104
+ default: return !!el.popoverOpen;
105
+ }
106
+ }
107
+ /** right to left; `stop`: an ancestor the match may not reach (an isolation scope's boundary) */
108
+ function stepsOk(el, steps, i, stop) {
109
+ if (!simpleOk(el, steps[i].simple))
110
+ return false;
111
+ if (i === 0)
112
+ return true;
113
+ const child = steps[i].comb === '>';
114
+ for (let p = el.parentNode; p && p !== stop; p = p.parentNode) {
115
+ if (stepsOk(p, steps, i - 1, stop))
116
+ return true;
117
+ if (child)
118
+ return false;
119
+ }
120
+ return false;
121
+ }
122
+ /** whether `el` matches `sel` without reaching `stop` or above for its ancestor parts */
123
+ function matches(el, sel, stop = null) {
124
+ for (const steps of parse(sel))
125
+ if (stepsOk(el, steps, steps.length - 1, stop))
126
+ return true;
127
+ return false;
128
+ }
129
+
130
+ /** a scope's key: its scope names (src/cycle/dom/isolate's {type, scope} list) */
131
+ /** a pattern's fields all equal the event's (nested objects compared the same way), as src/cycle/dom/fromEvent's */
132
+ function matchPattern(pattern, obj) {
133
+ for (const k in pattern) {
134
+ const p = pattern[k], v = obj?.[k];
135
+ if (p && typeof p == 'object' && v && typeof v == 'object' ? !matchPattern(p, v) : p !== v)
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+
141
+ /**
142
+ * sygnal/worker's wire format (research: dev-plans/research/web-worker §2), shared by the worker
143
+ * target (src/worker/target.ts) and the page bridge (src/worker/page.ts). Messages are plain,
144
+ * structured-cloneable objects; node ids are sygnal/host nodeId()s (the host body is the mount).
145
+ *
146
+ * worker → page
147
+ * {root} the host body's id (the page maps it to its mount element)
148
+ * {ops, ack, listen?, prevent?} one per flush: flat op tuples; the last page event handled
149
+ * before they were made; the event types intents listen for and
150
+ * the intents' declared preventDefaults, each when it changed
151
+ * {drv, sink} a value for the page driver `drv`'s sink
152
+ * {sub, drv, method?, args} / {unsub} open / close a page driver source subscription
153
+ * page → worker
154
+ * {ev: [PageEvent]} events, in dispatch order (a field value the page rebased
155
+ * crosses as an input event, inputType 'insertReplacementText')
156
+ * {ping: true} answer with a flush, even an empty one (a held submit waits for its ack)
157
+ * {emit, v} a value from a page driver source subscription
158
+ */
159
+ const OP = {
160
+ EL: 0, // [0, id, tag]
161
+ TEXT: 1, // [1, id, data]
162
+ KIDS: 2, // [2, parentId, childIds]
163
+ ATTR: 3, // [3, id, name, value | null]
164
+ PROP: 4, // [4, id, 'value' | 'checked' | 'selected', value]
165
+ DATA: 5, // [5, id, data]
166
+ STYLE: 6, // [6, id, key, value | null]
167
+ FOCUS: 7, // [7, id | 0]
168
+ FREE: 8, // [8, ids]
169
+ SCOPE: 9, // [9, id, scopeKey | null]: an isolation scope's root element (declared preventDefaults are scoped)
170
+ };
171
+
172
+ /**
173
+ * sygnal/worker/page: the page half (research: dev-plans/research/web-worker). Applies the worker's
174
+ * patch ops to the real DOM, forwards the events the app listens for (with the target's form
175
+ * state), makes the decisions that can't wait for the worker (preventDefault), and hosts the
176
+ * drivers that need the page (localStorage, the router, DOM-only browser APIs). No Sygnal core:
177
+ * xstream for the page drivers' sinks, and sygnal/host's selector matching for declared
178
+ * preventDefaults.
179
+ *
180
+ * startBridge(new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }), root, {
181
+ * drivers: { STORE: localStorageDriver, ROUTER: routerDriver },
182
+ * })
183
+ *
184
+ * Fields: while the user edits a field, the page owns its value. A value the page sent is never
185
+ * echoed back; a worker value about older text is rebased onto what the user typed since (and the
186
+ * caret kept); a submit waits until the worker has acknowledged the form's last edit.
187
+ */
188
+ const INIT_KEYS = ['key', 'code', 'keyCode', 'which', 'charCode', 'location', 'shiftKey', 'ctrlKey', 'altKey', 'metaKey', 'repeat',
189
+ 'button', 'buttons', 'clientX', 'clientY', 'pageX', 'pageY', 'offsetX', 'offsetY', 'screenX', 'screenY', 'movementX', 'movementY',
190
+ 'deltaX', 'deltaY', 'deltaZ', 'deltaMode', 'detail', 'inputType', 'data', 'isComposing', 'pointerId', 'pointerType', 'pressure',
191
+ 'width', 'height', 'isPrimary'];
192
+ // always forwarded: the host tree's copy of field values and focus must follow the page even when
193
+ // no intent listens (processForm reads the worker's form on submit)
194
+ const ALWAYS = ['input', 'change', 'focus', 'blur'];
195
+ const FIELD = /^(INPUT|TEXTAREA|SELECT)$/;
196
+ // text fields whose caret the page can read and set (setSelectionRange throws on the others)
197
+ const CARET = /^(text|search|url|tel|password)$/;
198
+ function startBridge(worker, mount, options = {}) {
199
+ const nodes = new Map();
200
+ const idOf = new WeakMap();
201
+ const scopes = new WeakMap();
202
+ // per field: what the worker had, in order (see prop), and the seq of its last edit the page sent
203
+ const sent = new WeakMap();
204
+ const lastEdit = new WeakMap();
205
+ const composing = new WeakSet();
206
+ const pd = options.preventDefault || [['submit', '*']];
207
+ const coalesce = new Set(options.coalesce || ['pointermove', 'mousemove', 'scroll', 'wheel']);
208
+ const listening = new Set();
209
+ const declared = new Map();
210
+ const stats = { received: 0, sent: 0, ops: 0, held: 0 };
211
+ let seq = 0;
212
+ let ack = 0;
213
+ let applying = false;
214
+ let resolveReady;
215
+ const ready = new Promise((ok) => { resolveReady = ok; });
216
+ const post = (m) => { stats.sent++; worker.postMessage(m); };
217
+ // ---- ops → DOM ----------------------------------------------------------------------------
218
+ function apply(ops) {
219
+ applying = true;
220
+ let n = 0;
221
+ try {
222
+ for (let i = 0; i < ops.length; n++) {
223
+ switch (ops[i]) {
224
+ case OP.EL: {
225
+ const el = document.createElement(ops[i + 2]);
226
+ nodes.set(ops[i + 1], el);
227
+ idOf.set(el, ops[i + 1]);
228
+ i += 3;
229
+ break;
230
+ }
231
+ case OP.TEXT: {
232
+ const t = document.createTextNode(ops[i + 2]);
233
+ nodes.set(ops[i + 1], t);
234
+ i += 3;
235
+ break;
236
+ }
237
+ case OP.KIDS: {
238
+ kids(nodes.get(ops[i + 1]), ops[i + 2]);
239
+ i += 3;
240
+ break;
241
+ }
242
+ case OP.ATTR: {
243
+ const el = nodes.get(ops[i + 1]), v = ops[i + 3];
244
+ v == null ? el.removeAttribute(ops[i + 2]) : el.setAttribute(ops[i + 2], v);
245
+ i += 4;
246
+ break;
247
+ }
248
+ case OP.PROP: {
249
+ prop(nodes.get(ops[i + 1]), ops[i + 2], ops[i + 3]);
250
+ i += 4;
251
+ break;
252
+ }
253
+ case OP.DATA: {
254
+ nodes.get(ops[i + 1]).data = ops[i + 2];
255
+ i += 3;
256
+ break;
257
+ }
258
+ case OP.STYLE: {
259
+ const s = nodes.get(ops[i + 1]).style, k = ops[i + 2], v = ops[i + 3];
260
+ if (k.startsWith('--') || k.includes('-'))
261
+ v == null ? s.removeProperty(k) : s.setProperty(k, v);
262
+ else
263
+ s[k] = v == null ? '' : v;
264
+ i += 4;
265
+ break;
266
+ }
267
+ case OP.FOCUS: {
268
+ const el = nodes.get(ops[i + 1]);
269
+ el ? el.focus() : mount.contains(document.activeElement) && document.activeElement.blur();
270
+ i += 2;
271
+ break;
272
+ }
273
+ case OP.FREE: {
274
+ for (const id of ops[i + 1])
275
+ nodes.delete(id);
276
+ i += 2;
277
+ break;
278
+ }
279
+ case OP.SCOPE: {
280
+ const el = nodes.get(ops[i + 1]);
281
+ ops[i + 2] == null ? scopes.delete(el) : scopes.set(el, ops[i + 2]);
282
+ i += 3;
283
+ break;
284
+ }
285
+ default: throw new Error('sygnal/worker/page: unknown op ' + ops[i]);
286
+ }
287
+ }
288
+ }
289
+ finally {
290
+ applying = false;
291
+ }
292
+ return n;
293
+ }
294
+ /** a parent's children become exactly `ids`: removals first, then one pass of moves/inserts */
295
+ function kids(parent, ids) {
296
+ const want = ids.map((id) => nodes.get(id));
297
+ const keep = new Set(want);
298
+ for (let c = parent.firstChild; c;) {
299
+ const next = c.nextSibling;
300
+ if (!keep.has(c))
301
+ parent.removeChild(c);
302
+ c = next;
303
+ }
304
+ let cur = parent.firstChild;
305
+ for (const n of want) {
306
+ if (n === cur)
307
+ cur = cur.nextSibling;
308
+ else
309
+ parent.insertBefore(n, cur);
310
+ }
311
+ }
312
+ /**
313
+ * A field's value / checked from the worker. When the user has edited the field since the last
314
+ * event the worker had handled (`ack`), the worker's value is a decision about older text: it is
315
+ * rebased (the user's edits since then, applied to the worker's value) instead of dropped
316
+ * (React Native's event-count rule keeps the stale text: "Buy milk" + Enter + "W" would stay
317
+ * "Buy milkW" when the worker's clear arrives) or applied as is (the "W" would be lost).
318
+ * Deferred while an IME composes. The caret follows the text it was in.
319
+ *
320
+ * A field's log holds what the worker had, in order: [seq, value] for each value the page sent,
321
+ * [ack, value] for each value the worker wrote. The base of a rebase is the last entry at `ack`.
322
+ */
323
+ function prop(el, k, v) {
324
+ if (composing.has(el))
325
+ return;
326
+ if (k !== 'value') {
327
+ if (el[k] !== v)
328
+ el[k] = v;
329
+ return;
330
+ }
331
+ let log = sent.get(el);
332
+ if (!log)
333
+ sent.set(el, log = []);
334
+ let b = -1;
335
+ for (let i = 0; i < log.length; i++)
336
+ if (log[i][0] <= ack)
337
+ b = i;
338
+ const base = b >= 0 ? log[b][1] : '';
339
+ const later = log.slice(b + 1);
340
+ const heldEdits = held ? held.filter((ev) => fieldOf.get(ev) === el) : [];
341
+ log.length = 0;
342
+ log.push([ack, v], ...later);
343
+ if (!later.length && !heldEdits.length) {
344
+ if (el.value !== v)
345
+ setValue(el, v);
346
+ return;
347
+ }
348
+ // the events still held back, the screen, and what the worker was last sent follow too
349
+ for (const ev of heldEdits)
350
+ ev.state.value = rebase(base, ev.state.value, v);
351
+ const next = rebase(base, el.value, v);
352
+ if (next !== el.value)
353
+ setValue(el, next);
354
+ if (later.length) {
355
+ const known = later[later.length - 1][1], synced = rebase(base, known, v);
356
+ // the worker's copy and the app's state follow as an input event (its events carried the
357
+ // pre-rebase text), ahead of anything held
358
+ if (synced !== known)
359
+ post_({ seq: 0, type: 'input', id: idOf.get(el), init: { inputType: 'insertReplacementText' }, state: { value: synced } }, el);
360
+ }
361
+ }
362
+ /** a value write that keeps the caret where it was relative to the text around it (Q-WW-4) */
363
+ function setValue(el, next) {
364
+ const old = el.value;
365
+ const keep = el === document.activeElement && (el.tagName === 'TEXTAREA' || CARET.test(el.type));
366
+ const s = keep ? el.selectionStart : null, e = keep ? el.selectionEnd : null;
367
+ el.value = next;
368
+ if (keep && s != null)
369
+ try {
370
+ el.setSelectionRange(caret(old, next, s), caret(old, next, e ?? s));
371
+ }
372
+ catch { /* not a text field after all */ }
373
+ }
374
+ // ---- DOM events → worker ------------------------------------------------------------------
375
+ /** the element carrying `el`'s innermost isolation scope (null: the root component's) */
376
+ const scopeRoot = (el) => {
377
+ for (let e = el; e && e !== mount; e = e.parentNode)
378
+ if (scopes.has(e))
379
+ return e;
380
+ return null;
381
+ };
382
+ /** does an intent's declared preventDefault apply: as sygnal/host's delegation decides, on the page's elements */
383
+ function declaredPrevent(e) {
384
+ const list = declared.get(e.type);
385
+ if (!list)
386
+ return false;
387
+ let sr = scopeRoot(e.target);
388
+ for (let el = e.target; el && el !== mount.parentNode; el = el.parentNode) {
389
+ if (el.nodeType === 1) {
390
+ const key = sr ? scopes.get(sr) : '';
391
+ for (const [, sel, global, scope, pattern] of list) {
392
+ let at = false;
393
+ try {
394
+ at = global ? !sel || matches(el, sel)
395
+ : scope === key && (sel ? matches(el, sel, sr ? sr.parentNode : mount.parentNode) : el === (sr || mount));
396
+ }
397
+ catch { /* a selector sygnal/host doesn't support: the worker reported it */ }
398
+ if (at && (pattern === true || matchPattern(pattern, e)))
399
+ return true;
400
+ }
401
+ }
402
+ if (!e.bubbles || el === mount)
403
+ break;
404
+ if (el === sr)
405
+ sr = scopeRoot(el.parentNode);
406
+ }
407
+ return false;
408
+ }
409
+ // Q-WW-7: per frame, the latest event of a high-frequency type (pending, posted with the next frame or the next other event)
410
+ const pending = new Map();
411
+ let frame = 0;
412
+ function flushPending() {
413
+ if (frame) {
414
+ cancelAnimationFrame(frame);
415
+ frame = 0;
416
+ }
417
+ for (const ev of pending.values())
418
+ send(ev);
419
+ pending.clear();
420
+ }
421
+ // Q-WW-3: a submit waits until the worker has acknowledged the form's last edit, and what follows
422
+ // a submit waits until the worker has handled it (its reaction, a cleared field, rebases what the
423
+ // user typed meanwhile). Sequence numbers are given when an event is posted: the acks only grow
424
+ let held = null;
425
+ let holdUntil = 0;
426
+ let holdTimer = 0;
427
+ // a field event's field (its value is logged when it's posted), a submit's form
428
+ const fieldOf = new WeakMap();
429
+ const formOf = new WeakMap();
430
+ function hold(until) {
431
+ held = [];
432
+ holdUntil = until;
433
+ post({ ping: true });
434
+ // a worker that never answers mustn't swallow the events
435
+ holdTimer = setTimeout(release, 1000);
436
+ }
437
+ function send(ev) {
438
+ if (held) {
439
+ held.push(ev);
440
+ return;
441
+ }
442
+ const form = formOf.get(ev);
443
+ if (form) {
444
+ let until = 0;
445
+ for (const f of Array.from(form.elements || []))
446
+ until = Math.max(until, lastEdit.get(f) ?? 0);
447
+ if (until > ack) {
448
+ stats.held++;
449
+ hold(until);
450
+ held.push(ev);
451
+ return;
452
+ }
453
+ post_(ev);
454
+ hold(ev.seq);
455
+ return;
456
+ }
457
+ post_(ev, fieldOf.get(ev));
458
+ }
459
+ function post_(ev, field) {
460
+ ev.seq = ++seq;
461
+ if (field && ev.state && 'value' in ev.state) {
462
+ let log = sent.get(field);
463
+ if (!log)
464
+ sent.set(field, log = []);
465
+ log.push([ev.seq, ev.state.value]);
466
+ lastEdit.set(field, ev.seq);
467
+ // only the last entry the worker has acknowledged is still a possible base
468
+ if (log.length > 32) {
469
+ let b = 0;
470
+ for (let i = 0; i < log.length; i++)
471
+ if (log[i][0] <= ack)
472
+ b = i;
473
+ log.splice(0, b);
474
+ }
475
+ }
476
+ post({ ev: [ev] });
477
+ }
478
+ function release() {
479
+ clearTimeout(holdTimer);
480
+ const q = held;
481
+ held = null;
482
+ // one message each: the worker reacts to each before it takes the next; a submit holds again
483
+ for (const ev of q)
484
+ send(ev);
485
+ }
486
+ function onEvent(e) {
487
+ // focus moves the bridge itself made (autoFocus in the worker) aren't news to the worker
488
+ if (applying && (e.type === 'focus' || e.type === 'blur'))
489
+ return;
490
+ let el = e.target;
491
+ while (el && !idOf.has(el))
492
+ el = el.parentNode;
493
+ if (!el)
494
+ return;
495
+ const t = e.target;
496
+ if (!e.defaultPrevented) {
497
+ for (const [type, sel] of pd)
498
+ if (type === e.type && (sel === '*' || t.closest?.(sel))) {
499
+ e.preventDefault();
500
+ break;
501
+ }
502
+ if (!e.defaultPrevented && declaredPrevent(e))
503
+ e.preventDefault();
504
+ }
505
+ if (!listening.has(e.type) && !ALWAYS.includes(e.type))
506
+ return;
507
+ const init = {};
508
+ for (const k of INIT_KEYS) {
509
+ const v = e[k];
510
+ if (v !== undefined && typeof v != 'object' && typeof v != 'function')
511
+ init[k] = v;
512
+ }
513
+ const ev = { seq: 0, type: e.type, id: idOf.get(el), init };
514
+ if (FIELD.test(t.tagName) && 'value' in t) {
515
+ ev.state = t.type === 'checkbox' || t.type === 'radio' ? { value: t.value, checked: t.checked } : { value: t.value };
516
+ if (e.type === 'input' || e.type === 'change')
517
+ fieldOf.set(ev, t);
518
+ }
519
+ else if (e.type === 'scroll' && t.nodeType === 1)
520
+ ev.state = { scrollTop: t.scrollTop, scrollLeft: t.scrollLeft };
521
+ if (coalesce.has(e.type)) {
522
+ const prev = pending.get(e.type);
523
+ if (prev && prev.id !== ev.id) {
524
+ pending.delete(e.type);
525
+ send(prev);
526
+ }
527
+ else if (prev && e.type === 'wheel')
528
+ for (const k of ['deltaX', 'deltaY', 'deltaZ'])
529
+ ev.init[k] = (ev.init[k] || 0) + (prev.init[k] || 0);
530
+ pending.set(e.type, ev);
531
+ if (!frame)
532
+ frame = requestAnimationFrame(() => { frame = 0; flushPending(); });
533
+ return;
534
+ }
535
+ flushPending();
536
+ if (e.type === 'submit')
537
+ formOf.set(ev, t);
538
+ send(ev);
539
+ }
540
+ // one capture listener per type: the always-forwarded ones, the bridge's own preventDefaults,
541
+ // then what the worker's intents listen for or declare
542
+ const attached = new Set();
543
+ const attach = (type) => {
544
+ if (attached.has(type))
545
+ return;
546
+ attached.add(type);
547
+ mount.addEventListener(type, onEvent, { capture: true, passive: false });
548
+ };
549
+ for (const t of ALWAYS)
550
+ attach(t);
551
+ for (const [t] of pd)
552
+ attach(t);
553
+ const compStart = (e) => composing.add(e.target);
554
+ const compEnd = (e) => composing.delete(e.target);
555
+ mount.addEventListener('compositionstart', compStart, true);
556
+ mount.addEventListener('compositionend', compEnd, true);
557
+ // ---- drivers on the page ------------------------------------------------------------------
558
+ const sinks = {};
559
+ const sources = {};
560
+ for (const [name, driver] of Object.entries(options.drivers || {})) {
561
+ sinks[name] = xs.create();
562
+ sources[name] = driver(sinks[name]);
563
+ }
564
+ const subs = new Map();
565
+ function onMessage(m) {
566
+ const d = m.data;
567
+ if (!d || typeof d != 'object')
568
+ return;
569
+ stats.received++;
570
+ if (d.ops) {
571
+ ack = d.ack;
572
+ if (d.listen)
573
+ for (const t of d.listen) {
574
+ listening.add(t);
575
+ attach(t);
576
+ }
577
+ if (d.prevent) {
578
+ declared.clear();
579
+ for (const p of d.prevent) {
580
+ const l = declared.get(p[0]);
581
+ l ? l.push(p) : declared.set(p[0], [p]);
582
+ attach(p[0]);
583
+ }
584
+ }
585
+ const n = apply(d.ops);
586
+ stats.ops += n;
587
+ options.onApply?.(n);
588
+ if (held && ack >= holdUntil)
589
+ release();
590
+ resolveReady();
591
+ }
592
+ else if (d.root) {
593
+ nodes.set(d.root, mount);
594
+ idOf.set(mount, d.root);
595
+ }
596
+ else if (d.sink !== undefined)
597
+ sinks[d.drv]?.shamefullySendNext(d.sink);
598
+ else if (d.sub) {
599
+ const src = sources[d.drv];
600
+ if (!src)
601
+ return console.error(`sygnal/worker/page: no page driver named '${d.drv}' (startBridge's drivers)`);
602
+ const stream = d.method ? src[d.method](...d.args) : src;
603
+ const listener = { next: (v) => post({ emit: d.sub, v }), error: (e) => console.error(e), complete: () => { } };
604
+ subs.set(d.sub, { stream, listener });
605
+ stream.addListener(listener);
606
+ }
607
+ else if (d.unsub) {
608
+ const s = subs.get(d.unsub);
609
+ s?.stream.removeListener(s.listener);
610
+ subs.delete(d.unsub);
611
+ }
612
+ }
613
+ worker.addEventListener('message', onMessage);
614
+ worker.start?.();
615
+ return {
616
+ ready,
617
+ stats,
618
+ stop() {
619
+ worker.removeEventListener('message', onMessage);
620
+ for (const t of attached)
621
+ mount.removeEventListener(t, onEvent, true);
622
+ mount.removeEventListener('compositionstart', compStart, true);
623
+ mount.removeEventListener('compositionend', compEnd, true);
624
+ for (const s of subs.values())
625
+ s.stream.removeListener(s.listener);
626
+ subs.clear();
627
+ if (frame)
628
+ cancelAnimationFrame(frame);
629
+ clearTimeout(holdTimer);
630
+ },
631
+ };
632
+ }
633
+ /** the user's edit of `base` (now `mine`) applied to `theirs`: at the end when typed at the end */
634
+ function rebase(base, mine, theirs) {
635
+ if (theirs === base)
636
+ return mine;
637
+ if (mine === base)
638
+ return theirs;
639
+ let p = 0, s = 0;
640
+ while (p < base.length && p < mine.length && base[p] === mine[p])
641
+ p++;
642
+ while (s < base.length - p && s < mine.length - p && base[base.length - 1 - s] === mine[mine.length - 1 - s])
643
+ s++;
644
+ const removed = base.length - p - s, inserted = mine.slice(p, mine.length - s);
645
+ if (s === 0)
646
+ return theirs.slice(0, Math.max(0, theirs.length - removed)) + inserted;
647
+ const at = Math.min(p, theirs.length);
648
+ return theirs.slice(0, at) + inserted + theirs.slice(at + removed);
649
+ }
650
+ /** a caret at `c` in `old`, in `next`: before the changed part it stays, after it keeps its distance from the end */
651
+ function caret(old, next, c) {
652
+ if (old.length === next.length)
653
+ return c;
654
+ let p = 0, s = 0;
655
+ while (p < old.length && p < next.length && old[p] === next[p])
656
+ p++;
657
+ while (s < old.length - p && s < next.length - p && old[old.length - 1 - s] === next[next.length - 1 - s])
658
+ s++;
659
+ if (c <= p)
660
+ return c;
661
+ if (c >= old.length - s)
662
+ return next.length - (old.length - c);
663
+ return next.length - s;
664
+ }
665
+
666
+ exports.caret = caret;
667
+ exports.rebase = rebase;
668
+ exports.startBridge = startBridge;
669
+ //# sourceMappingURL=worker-page.cjs.js.map