@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.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 (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/dist/assistant-ui/entry.d.ts +34 -0
  4. package/dist/assistant-ui/entry.d.ts.map +1 -0
  5. package/dist/assistant-ui/entry.js +50 -0
  6. package/dist/assistant-ui/entry.js.map +1 -0
  7. package/dist/assistant-ui/extension.d.ts +18 -0
  8. package/dist/assistant-ui/extension.d.ts.map +1 -0
  9. package/dist/assistant-ui/extension.js +0 -0
  10. package/dist/assistant-ui/history.d.ts +15 -0
  11. package/dist/assistant-ui/history.d.ts.map +1 -0
  12. package/dist/assistant-ui/history.js +39 -0
  13. package/dist/assistant-ui/history.js.map +1 -0
  14. package/dist/assistant-ui/modelContext.d.ts +45 -0
  15. package/dist/assistant-ui/modelContext.d.ts.map +1 -0
  16. package/dist/assistant-ui/modelContext.js +88 -0
  17. package/dist/assistant-ui/modelContext.js.map +1 -0
  18. package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
  19. package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
  20. package/dist/assistant-ui/snapshotSaver.js +83 -0
  21. package/dist/assistant-ui/snapshotSaver.js.map +1 -0
  22. package/dist/assistant-ui/tool-calls.d.ts +21 -0
  23. package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
  24. package/dist/assistant-ui/tool-calls.js +65 -0
  25. package/dist/assistant-ui/tool-calls.js.map +1 -0
  26. package/dist/assistant-ui/toolkit.d.ts +96 -0
  27. package/dist/assistant-ui/toolkit.d.ts.map +1 -0
  28. package/dist/assistant-ui/toolkit.js +456 -0
  29. package/dist/assistant-ui/toolkit.js.map +1 -0
  30. package/dist/assistant-ui/useSharedContext.d.ts +14 -0
  31. package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
  32. package/dist/assistant-ui/useSharedContext.js +27 -0
  33. package/dist/assistant-ui/useSharedContext.js.map +1 -0
  34. package/dist/assistant-ui.d.ts +3 -0
  35. package/dist/assistant-ui.d.ts.map +1 -0
  36. package/dist/assistant-ui.js +1 -0
  37. package/dist/bootstrap.d.ts +23 -0
  38. package/dist/bootstrap.d.ts.map +1 -0
  39. package/dist/bootstrap.js +78 -0
  40. package/dist/bootstrap.js.map +1 -0
  41. package/dist/csp.d.ts +25 -0
  42. package/dist/csp.d.ts.map +1 -0
  43. package/dist/csp.js +78 -0
  44. package/dist/csp.js.map +1 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +5 -0
  48. package/dist/json-schema.d.ts +70 -0
  49. package/dist/json-schema.d.ts.map +1 -0
  50. package/dist/json-schema.js +163 -0
  51. package/dist/json-schema.js.map +1 -0
  52. package/dist/preview.d.ts +30 -0
  53. package/dist/preview.d.ts.map +1 -0
  54. package/dist/preview.js +60 -0
  55. package/dist/preview.js.map +1 -0
  56. package/dist/prompts/guidance.d.ts +41 -0
  57. package/dist/prompts/guidance.d.ts.map +1 -0
  58. package/dist/prompts/guidance.js +268 -0
  59. package/dist/prompts/guidance.js.map +1 -0
  60. package/dist/prompts.d.ts +2 -0
  61. package/dist/prompts.d.ts.map +1 -0
  62. package/dist/prompts.js +1 -0
  63. package/dist/protocol.d.ts +114 -0
  64. package/dist/protocol.d.ts.map +1 -0
  65. package/dist/protocol.js +49 -0
  66. package/dist/protocol.js.map +1 -0
  67. package/dist/repair/repair.d.ts +72 -0
  68. package/dist/repair/repair.d.ts.map +1 -0
  69. package/dist/repair/repair.js +103 -0
  70. package/dist/repair/repair.js.map +1 -0
  71. package/dist/repair.d.ts +2 -0
  72. package/dist/repair.d.ts.map +1 -0
  73. package/dist/repair.js +1 -0
  74. package/dist/rpc.d.ts +60 -0
  75. package/dist/rpc.d.ts.map +1 -0
  76. package/dist/rpc.js +127 -0
  77. package/dist/rpc.js.map +1 -0
  78. package/dist/runtime/diagnostics.d.ts +17 -0
  79. package/dist/runtime/diagnostics.d.ts.map +1 -0
  80. package/dist/runtime/diagnostics.js +128 -0
  81. package/dist/runtime/diagnostics.js.map +1 -0
  82. package/dist/runtime/generated.d.ts +2 -0
  83. package/dist/runtime/generated.d.ts.map +1 -0
  84. package/dist/runtime/generated.js +5 -0
  85. package/dist/runtime/generated.js.map +1 -0
  86. package/dist/runtime/index.d.ts +7 -0
  87. package/dist/runtime/index.d.ts.map +1 -0
  88. package/dist/runtime/index.js +264 -0
  89. package/dist/runtime/index.js.map +1 -0
  90. package/dist/runtime/morph.d.ts +51 -0
  91. package/dist/runtime/morph.d.ts.map +1 -0
  92. package/dist/runtime/morph.js +251 -0
  93. package/dist/runtime/morph.js.map +1 -0
  94. package/dist/runtime/screenshot.d.ts +23 -0
  95. package/dist/runtime/screenshot.d.ts.map +1 -0
  96. package/dist/runtime/screenshot.js +188 -0
  97. package/dist/runtime/screenshot.js.map +1 -0
  98. package/dist/runtime/size.d.ts +10 -0
  99. package/dist/runtime/size.d.ts.map +1 -0
  100. package/dist/runtime/size.js +21 -0
  101. package/dist/runtime/size.js.map +1 -0
  102. package/dist/runtime/storage.d.ts +4 -0
  103. package/dist/runtime/storage.d.ts.map +1 -0
  104. package/dist/runtime/storage.js +38 -0
  105. package/dist/runtime/storage.js.map +1 -0
  106. package/dist/spec/actions.d.ts +40 -0
  107. package/dist/spec/actions.d.ts.map +1 -0
  108. package/dist/spec/actions.js +53 -0
  109. package/dist/spec/actions.js.map +1 -0
  110. package/dist/spec/assistant-ui/thread.d.ts +66 -0
  111. package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
  112. package/dist/spec/assistant-ui/thread.js +252 -0
  113. package/dist/spec/assistant-ui/thread.js.map +1 -0
  114. package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
  115. package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
  116. package/dist/spec/assistant-ui/toolkit.js +142 -0
  117. package/dist/spec/assistant-ui/toolkit.js.map +1 -0
  118. package/dist/spec/assistant-ui/web.d.ts +6 -0
  119. package/dist/spec/assistant-ui/web.d.ts.map +1 -0
  120. package/dist/spec/assistant-ui/web.js +48 -0
  121. package/dist/spec/assistant-ui/web.js.map +1 -0
  122. package/dist/spec/assistant-ui.d.ts +3 -0
  123. package/dist/spec/assistant-ui.d.ts.map +1 -0
  124. package/dist/spec/assistant-ui.js +1 -0
  125. package/dist/spec/calc.d.ts +8 -0
  126. package/dist/spec/calc.d.ts.map +1 -0
  127. package/dist/spec/calc.js +498 -0
  128. package/dist/spec/calc.js.map +1 -0
  129. package/dist/spec/catalog.d.ts +52 -0
  130. package/dist/spec/catalog.d.ts.map +1 -0
  131. package/dist/spec/catalog.js +104 -0
  132. package/dist/spec/catalog.js.map +1 -0
  133. package/dist/spec/expressions.d.ts +45 -0
  134. package/dist/spec/expressions.d.ts.map +1 -0
  135. package/dist/spec/expressions.js +199 -0
  136. package/dist/spec/expressions.js.map +1 -0
  137. package/dist/spec/patch.d.ts +20 -0
  138. package/dist/spec/patch.d.ts.map +1 -0
  139. package/dist/spec/patch.js +77 -0
  140. package/dist/spec/patch.js.map +1 -0
  141. package/dist/spec/pending.d.ts +15 -0
  142. package/dist/spec/pending.d.ts.map +1 -0
  143. package/dist/spec/pending.js +33 -0
  144. package/dist/spec/pending.js.map +1 -0
  145. package/dist/spec/pointer.d.ts +17 -0
  146. package/dist/spec/pointer.d.ts.map +1 -0
  147. package/dist/spec/pointer.js +88 -0
  148. package/dist/spec/pointer.js.map +1 -0
  149. package/dist/spec/prompt.d.ts +29 -0
  150. package/dist/spec/prompt.d.ts.map +1 -0
  151. package/dist/spec/prompt.js +145 -0
  152. package/dist/spec/prompt.js.map +1 -0
  153. package/dist/spec/render-spec.d.ts +50 -0
  154. package/dist/spec/render-spec.d.ts.map +1 -0
  155. package/dist/spec/render-spec.js +104 -0
  156. package/dist/spec/render-spec.js.map +1 -0
  157. package/dist/spec/resolve.d.ts +39 -0
  158. package/dist/spec/resolve.d.ts.map +1 -0
  159. package/dist/spec/resolve.js +51 -0
  160. package/dist/spec/resolve.js.map +1 -0
  161. package/dist/spec/resolved.d.ts +3 -0
  162. package/dist/spec/resolved.d.ts.map +1 -0
  163. package/dist/spec/resolved.js +36 -0
  164. package/dist/spec/resolved.js.map +1 -0
  165. package/dist/spec/state.d.ts +34 -0
  166. package/dist/spec/state.d.ts.map +1 -0
  167. package/dist/spec/state.js +111 -0
  168. package/dist/spec/state.js.map +1 -0
  169. package/dist/spec/stream.d.ts +50 -0
  170. package/dist/spec/stream.d.ts.map +1 -0
  171. package/dist/spec/stream.js +168 -0
  172. package/dist/spec/stream.js.map +1 -0
  173. package/dist/spec/tools.d.ts +3 -0
  174. package/dist/spec/tools.d.ts.map +1 -0
  175. package/dist/spec/tools.js +3 -0
  176. package/dist/spec/tree.d.ts +21 -0
  177. package/dist/spec/tree.d.ts.map +1 -0
  178. package/dist/spec/tree.js +27 -0
  179. package/dist/spec/tree.js.map +1 -0
  180. package/dist/spec/types.d.ts +66 -0
  181. package/dist/spec/types.d.ts.map +1 -0
  182. package/dist/spec/types.js +9 -0
  183. package/dist/spec/types.js.map +1 -0
  184. package/dist/spec/validate.d.ts +32 -0
  185. package/dist/spec/validate.d.ts.map +1 -0
  186. package/dist/spec/validate.js +264 -0
  187. package/dist/spec/validate.js.map +1 -0
  188. package/dist/spec/vue/SpecRenderer.d.ts +43 -0
  189. package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
  190. package/dist/spec/vue/SpecRenderer.js +357 -0
  191. package/dist/spec/vue/SpecRenderer.js.map +1 -0
  192. package/dist/spec/vue/useSpecStream.d.ts +19 -0
  193. package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
  194. package/dist/spec/vue/useSpecStream.js +115 -0
  195. package/dist/spec/vue/useSpecStream.js.map +1 -0
  196. package/dist/spec/vue.d.ts +3 -0
  197. package/dist/spec/vue.d.ts.map +1 -0
  198. package/dist/spec/vue.js +3 -0
  199. package/dist/spec.d.ts +12 -0
  200. package/dist/spec.d.ts.map +1 -0
  201. package/dist/spec.js +8 -0
  202. package/dist/theme.d.ts +38 -0
  203. package/dist/theme.d.ts.map +1 -0
  204. package/dist/theme.js +370 -0
  205. package/dist/theme.js.map +1 -0
  206. package/dist/tools/define.d.ts +43 -0
  207. package/dist/tools/define.d.ts.map +1 -0
  208. package/dist/tools/define.js +31 -0
  209. package/dist/tools/define.js.map +1 -0
  210. package/dist/tools/edits.d.ts +19 -0
  211. package/dist/tools/edits.d.ts.map +1 -0
  212. package/dist/tools/edits.js +55 -0
  213. package/dist/tools/edits.js.map +1 -0
  214. package/dist/tools/registry.d.ts +19 -0
  215. package/dist/tools/registry.d.ts.map +1 -0
  216. package/dist/tools/registry.js +33 -0
  217. package/dist/tools/registry.js.map +1 -0
  218. package/dist/tools/tools.d.ts +88 -0
  219. package/dist/tools/tools.d.ts.map +1 -0
  220. package/dist/tools/tools.js +235 -0
  221. package/dist/tools/tools.js.map +1 -0
  222. package/dist/tools.d.ts +5 -0
  223. package/dist/tools.d.ts.map +1 -0
  224. package/dist/tools.js +5 -0
  225. package/dist/version.d.ts +2 -0
  226. package/dist/version.d.ts.map +1 -0
  227. package/dist/version.js +5 -0
  228. package/dist/version.js.map +1 -0
  229. package/dist/vue/Widget.d.ts +16 -0
  230. package/dist/vue/Widget.d.ts.map +1 -0
  231. package/dist/vue/Widget.js +58 -0
  232. package/dist/vue/Widget.js.map +1 -0
  233. package/dist/vue/sync.d.ts +19 -0
  234. package/dist/vue/sync.d.ts.map +1 -0
  235. package/dist/vue/sync.js +31 -0
  236. package/dist/vue/sync.js.map +1 -0
  237. package/dist/vue/themeStore.d.ts +23 -0
  238. package/dist/vue/themeStore.d.ts.map +1 -0
  239. package/dist/vue/themeStore.js +115 -0
  240. package/dist/vue/themeStore.js.map +1 -0
  241. package/dist/vue/useThemeTokens.d.ts +6 -0
  242. package/dist/vue/useThemeTokens.d.ts.map +1 -0
  243. package/dist/vue/useThemeTokens.js +49 -0
  244. package/dist/vue/useThemeTokens.js.map +1 -0
  245. package/dist/vue/useWidget.d.ts +10 -0
  246. package/dist/vue/useWidget.d.ts.map +1 -0
  247. package/dist/vue/useWidget.js +81 -0
  248. package/dist/vue/useWidget.js.map +1 -0
  249. package/dist/vue.d.ts +5 -0
  250. package/dist/vue.d.ts.map +1 -0
  251. package/dist/vue.js +4 -0
  252. package/dist/widget.d.ts +129 -0
  253. package/dist/widget.d.ts.map +1 -0
  254. package/dist/widget.js +655 -0
  255. package/dist/widget.js.map +1 -0
  256. package/package.json +125 -3
  257. package/src/assistant-ui/entry.ts +124 -0
  258. package/src/assistant-ui/extension.ts +19 -0
  259. package/src/assistant-ui/history.ts +63 -0
  260. package/src/assistant-ui/modelContext.ts +162 -0
  261. package/src/assistant-ui/snapshotSaver.ts +116 -0
  262. package/src/assistant-ui/tool-calls.ts +115 -0
  263. package/src/assistant-ui/toolkit.tsx +747 -0
  264. package/src/assistant-ui/useSharedContext.ts +40 -0
  265. package/src/assistant-ui.ts +14 -0
  266. package/src/bootstrap.ts +112 -0
  267. package/src/csp.ts +96 -0
  268. package/src/index.ts +44 -0
  269. package/src/json-schema.ts +353 -0
  270. package/src/preview.ts +120 -0
  271. package/src/prompts/guidance.ts +369 -0
  272. package/src/prompts.ts +8 -0
  273. package/src/protocol.ts +141 -0
  274. package/src/repair/repair.ts +202 -0
  275. package/src/repair.ts +11 -0
  276. package/src/rpc.ts +228 -0
  277. package/src/runtime/diagnostics.ts +165 -0
  278. package/src/runtime/generated.ts +2 -0
  279. package/src/runtime/index.ts +343 -0
  280. package/src/runtime/morph.ts +352 -0
  281. package/src/runtime/screenshot.ts +288 -0
  282. package/src/runtime/size.ts +17 -0
  283. package/src/runtime/storage.ts +49 -0
  284. package/src/spec/actions.ts +111 -0
  285. package/src/spec/assistant-ui/thread.ts +381 -0
  286. package/src/spec/assistant-ui/toolkit.tsx +295 -0
  287. package/src/spec/assistant-ui/web.tsx +63 -0
  288. package/src/spec/assistant-ui.ts +2 -0
  289. package/src/spec/calc.ts +598 -0
  290. package/src/spec/catalog.ts +196 -0
  291. package/src/spec/expressions.ts +301 -0
  292. package/src/spec/patch.ts +112 -0
  293. package/src/spec/pending.ts +36 -0
  294. package/src/spec/pointer.ts +170 -0
  295. package/src/spec/prompt.ts +243 -0
  296. package/src/spec/render-spec.ts +144 -0
  297. package/src/spec/resolve.ts +123 -0
  298. package/src/spec/resolved.ts +53 -0
  299. package/src/spec/state.ts +193 -0
  300. package/src/spec/stream.ts +230 -0
  301. package/src/spec/tools.ts +17 -0
  302. package/src/spec/tree.ts +37 -0
  303. package/src/spec/types.ts +51 -0
  304. package/src/spec/validate.ts +429 -0
  305. package/src/spec/vue/SpecRenderer.tsx +544 -0
  306. package/src/spec/vue/__tests__/harness.ts +50 -0
  307. package/src/spec/vue/useSpecStream.ts +162 -0
  308. package/src/spec/vue.ts +13 -0
  309. package/src/spec.ts +55 -0
  310. package/src/theme.ts +423 -0
  311. package/src/tools/define.ts +65 -0
  312. package/src/tools/edits.ts +73 -0
  313. package/src/tools/registry.ts +44 -0
  314. package/src/tools/tools.ts +361 -0
  315. package/src/tools.ts +32 -0
  316. package/src/version.ts +4 -0
  317. package/src/vue/Widget.tsx +74 -0
  318. package/src/vue/sync.ts +36 -0
  319. package/src/vue/themeStore.ts +157 -0
  320. package/src/vue/useThemeTokens.ts +71 -0
  321. package/src/vue/useWidget.ts +117 -0
  322. package/src/vue.ts +8 -0
  323. package/src/widget.ts +1080 -0
@@ -0,0 +1,251 @@
1
+ import { detectWidgetKind } from "../protocol.js";
2
+ // src/runtime/morph.ts
3
+ const SCRIPT_PLACEHOLDER = "gf-script";
4
+ const NON_VISUAL = /* @__PURE__ */ new Set([
5
+ "style",
6
+ "script",
7
+ "meta",
8
+ "link",
9
+ "title",
10
+ "base"
11
+ ]);
12
+ /**
13
+ * Drops a trailing `<style>` element that has not closed yet, so half-written
14
+ * CSS never applies and then snaps when the rule completes.
15
+ */
16
+ function stripUnclosedStyle(source) {
17
+ const open = source.toLowerCase().lastIndexOf("<style");
18
+ if (open === -1) return source;
19
+ const next = source.charAt(open + 6);
20
+ if (next !== "" && !/[\s>/]/.test(next)) return source;
21
+ return source.toLowerCase().includes("</style", open) ? source : source.slice(0, open);
22
+ }
23
+ /**
24
+ * Parses (possibly partial) markup into a detached fragment. The HTML parser
25
+ * closes open elements and drops a tag cut off mid-way, so any prefix of a
26
+ * document parses into a well-formed tree.
27
+ */
28
+ function parseMarkup(doc, source) {
29
+ const template = doc.createElement("template");
30
+ template.innerHTML = source;
31
+ const fragment = template.content;
32
+ const scripts = [];
33
+ for (const script of Array.from(fragment.querySelectorAll("script"))) {
34
+ script.replaceWith(doc.createComment(SCRIPT_PLACEHOLDER));
35
+ scripts.push(script);
36
+ }
37
+ return {
38
+ fragment,
39
+ scripts
40
+ };
41
+ }
42
+ const sameNode = (live, next) => {
43
+ if (live.nodeType !== next.nodeType) return false;
44
+ if (live.nodeType !== Node.ELEMENT_NODE) return true;
45
+ const a = live;
46
+ const b = next;
47
+ return a.localName === b.localName && a.namespaceURI === b.namespaceURI && a.getAttribute("id") === b.getAttribute("id");
48
+ };
49
+ const syncAttributes = (live, next) => {
50
+ for (const attr of Array.from(next.attributes)) {
51
+ if (live.getAttributeNS(attr.namespaceURI, attr.localName) === attr.value) continue;
52
+ try {
53
+ if (attr.namespaceURI) live.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
54
+ else live.setAttribute(attr.name, attr.value);
55
+ } catch {}
56
+ }
57
+ for (const attr of Array.from(live.attributes)) if (!next.hasAttributeNS(attr.namespaceURI, attr.localName)) live.removeAttributeNS(attr.namespaceURI, attr.localName);
58
+ };
59
+ const patchNode = (live, next, ctx) => {
60
+ if (live.nodeType !== Node.ELEMENT_NODE) {
61
+ if (live.nodeValue !== next.nodeValue) live.nodeValue = next.nodeValue;
62
+ return;
63
+ }
64
+ const liveEl = live;
65
+ const nextEl = next;
66
+ syncAttributes(liveEl, nextEl);
67
+ if (liveEl instanceof HTMLTemplateElement && nextEl instanceof HTMLTemplateElement) {
68
+ if (liveEl.innerHTML !== nextEl.innerHTML) liveEl.content.replaceChildren(nextEl.content);
69
+ return;
70
+ }
71
+ morphChildren(liveEl, nextEl, ctx);
72
+ };
73
+ /**
74
+ * Makes `live`'s children match `next`'s, reusing live nodes matched by
75
+ * position and tag. Unmatched nodes move out of `next` into `live`.
76
+ */
77
+ function morphChildren(live, next, ctx) {
78
+ const nextNodes = Array.from(next.childNodes);
79
+ let cursor = live.firstChild;
80
+ for (let i = 0; i < nextNodes.length; i++) {
81
+ const nextChild = nextNodes[i];
82
+ if (!cursor) {
83
+ live.appendChild(nextChild);
84
+ ctx.onAdded(nextChild);
85
+ continue;
86
+ }
87
+ if (sameNode(cursor, nextChild)) {
88
+ patchNode(cursor, nextChild, ctx);
89
+ cursor = cursor.nextSibling;
90
+ continue;
91
+ }
92
+ const following = nextNodes[i + 1];
93
+ if (following && sameNode(cursor, following)) {
94
+ live.insertBefore(nextChild, cursor);
95
+ ctx.onAdded(nextChild);
96
+ continue;
97
+ }
98
+ const afterCursor = cursor.nextSibling;
99
+ if (afterCursor && sameNode(afterCursor, nextChild)) {
100
+ cursor.remove();
101
+ patchNode(afterCursor, nextChild, ctx);
102
+ cursor = afterCursor.nextSibling;
103
+ continue;
104
+ }
105
+ live.replaceChild(nextChild, cursor);
106
+ ctx.onAdded(nextChild);
107
+ cursor = afterCursor;
108
+ }
109
+ while (cursor) {
110
+ const after = cursor.nextSibling;
111
+ cursor.remove();
112
+ cursor = after;
113
+ }
114
+ }
115
+ const SCRIPT_LOAD_TIMEOUT_MS = 15e3;
116
+ const defaultSchedule = (render) => {
117
+ let done = false;
118
+ const run = () => {
119
+ if (done) return;
120
+ done = true;
121
+ cancelAnimationFrame(frame);
122
+ clearTimeout(timer);
123
+ render();
124
+ };
125
+ const frame = requestAnimationFrame(run);
126
+ const timer = setTimeout(run, 50);
127
+ return () => {
128
+ done = true;
129
+ cancelAnimationFrame(frame);
130
+ clearTimeout(timer);
131
+ };
132
+ };
133
+ const createScriptRunner = (onError = () => {}) => (placeholder, original) => {
134
+ const script = placeholder.ownerDocument.createElementNS(original.namespaceURI ?? "http://www.w3.org/1999/xhtml", "script");
135
+ for (const attr of Array.from(original.attributes)) try {
136
+ script.setAttribute(attr.name, attr.value);
137
+ } catch {}
138
+ script.textContent = original.textContent;
139
+ const src = script.getAttribute("src") ?? script.getAttribute("href");
140
+ if (!src) {
141
+ placeholder.replaceWith(script);
142
+ return Promise.resolve();
143
+ }
144
+ return new Promise((resolve) => {
145
+ const timer = setTimeout(() => {
146
+ onError(`Script timed out: ${src}`, src);
147
+ resolve();
148
+ }, SCRIPT_LOAD_TIMEOUT_MS);
149
+ script.addEventListener("load", () => {
150
+ clearTimeout(timer);
151
+ resolve();
152
+ });
153
+ script.addEventListener("error", () => {
154
+ clearTimeout(timer);
155
+ onError(`Script failed to load: ${src}`, src);
156
+ resolve();
157
+ });
158
+ placeholder.replaceWith(script);
159
+ });
160
+ };
161
+ const fadeIn = (node) => {
162
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
163
+ const el = node;
164
+ if (NON_VISUAL.has(el.localName) || typeof el.animate !== "function") return;
165
+ el.animate([{ opacity: 0 }, { opacity: 1 }], {
166
+ duration: 200,
167
+ easing: "ease-out"
168
+ });
169
+ };
170
+ function createStreamRenderer(options) {
171
+ const { root } = options;
172
+ const doc = root.ownerDocument;
173
+ const schedule = options.schedule ?? defaultSchedule;
174
+ const runScript = options.runScript ?? createScriptRunner(options.onScriptError);
175
+ let source = "";
176
+ let kind = "html";
177
+ let ended = false;
178
+ let scriptsRan = false;
179
+ let cancelScheduled;
180
+ let lastRendered;
181
+ let lastScripts = [];
182
+ const ctx = { onAdded(node) {
183
+ if (options.animate?.()) fadeIn(node);
184
+ } };
185
+ const render = (final) => {
186
+ cancelScheduled?.();
187
+ cancelScheduled = void 0;
188
+ const markup = final ? source : stripUnclosedStyle(source);
189
+ if (markup === lastRendered) return;
190
+ lastRendered = markup;
191
+ kind = detectWidgetKind(source);
192
+ root.dataset["kind"] = kind;
193
+ const parsed = parseMarkup(doc, markup);
194
+ lastScripts = parsed.scripts;
195
+ morphChildren(root, parsed.fragment, ctx);
196
+ options.onRender?.(kind);
197
+ };
198
+ const runHeldScripts = async () => {
199
+ const walker = doc.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
200
+ const placeholders = [];
201
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) if (node.nodeValue === "gf-script") placeholders.push(node);
202
+ const scripts = lastScripts;
203
+ for (let i = 0; i < placeholders.length && i < scripts.length; i++) await runScript(placeholders[i], scripts[i]);
204
+ if (scripts.length > 0 && !scriptsRan) {
205
+ scriptsRan = true;
206
+ doc.dispatchEvent(new Event("DOMContentLoaded", { bubbles: true }));
207
+ doc.defaultView?.dispatchEvent(new Event("load"));
208
+ }
209
+ };
210
+ return {
211
+ get source() {
212
+ return source;
213
+ },
214
+ get kind() {
215
+ return kind;
216
+ },
217
+ get ended() {
218
+ return ended;
219
+ },
220
+ get scriptsRan() {
221
+ return scriptsRan;
222
+ },
223
+ write(chunk) {
224
+ if (ended) throw new Error("Cannot write after end; use replace");
225
+ if (!chunk) return;
226
+ source += chunk;
227
+ cancelScheduled ??= schedule(() => {
228
+ cancelScheduled = void 0;
229
+ render(false);
230
+ });
231
+ },
232
+ async end() {
233
+ if (ended) return;
234
+ ended = true;
235
+ render(true);
236
+ await runHeldScripts();
237
+ },
238
+ async replace(code) {
239
+ ended = true;
240
+ source = code;
241
+ render(true);
242
+ await runHeldScripts();
243
+ },
244
+ flush() {
245
+ if (cancelScheduled) render(ended);
246
+ }
247
+ };
248
+ }
249
+ export { SCRIPT_PLACEHOLDER, createScriptRunner, createStreamRenderer, morphChildren, parseMarkup, stripUnclosedStyle };
250
+
251
+ //# sourceMappingURL=morph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"morph.js","names":[],"sources":["../../src/runtime/morph.ts"],"sourcesContent":["import { detectWidgetKind, type WidgetKind } from \"../protocol\";\n\nexport const SCRIPT_PLACEHOLDER = \"gf-script\";\n\nconst NON_VISUAL = new Set([\n \"style\",\n \"script\",\n \"meta\",\n \"link\",\n \"title\",\n \"base\",\n]);\n\nexport type ParsedMarkup = {\n fragment: DocumentFragment;\n /** Scripts in document order; each one's position holds a placeholder comment. */\n scripts: Element[];\n};\n\n/**\n * Drops a trailing `<style>` element that has not closed yet, so half-written\n * CSS never applies and then snaps when the rule completes.\n */\nexport function stripUnclosedStyle(source: string): string {\n const open = source.toLowerCase().lastIndexOf(\"<style\");\n if (open === -1) return source;\n const next = source.charAt(open + 6);\n if (next !== \"\" && !/[\\s>/]/.test(next)) return source;\n return source.toLowerCase().includes(\"</style\", open)\n ? source\n : source.slice(0, open);\n}\n\n/**\n * Parses (possibly partial) markup into a detached fragment. The HTML parser\n * closes open elements and drops a tag cut off mid-way, so any prefix of a\n * document parses into a well-formed tree.\n */\nexport function parseMarkup(doc: Document, source: string): ParsedMarkup {\n const template = doc.createElement(\"template\");\n template.innerHTML = source;\n const fragment = template.content;\n const scripts: Element[] = [];\n for (const script of Array.from(fragment.querySelectorAll(\"script\"))) {\n script.replaceWith(doc.createComment(SCRIPT_PLACEHOLDER));\n scripts.push(script);\n }\n return { fragment, scripts };\n}\n\nexport type MorphContext = {\n onAdded(node: Node): void;\n};\n\nconst sameNode = (live: Node, next: Node): boolean => {\n if (live.nodeType !== next.nodeType) return false;\n if (live.nodeType !== Node.ELEMENT_NODE) return true;\n const a = live as Element;\n const b = next as Element;\n return (\n a.localName === b.localName &&\n a.namespaceURI === b.namespaceURI &&\n a.getAttribute(\"id\") === b.getAttribute(\"id\")\n );\n};\n\nconst syncAttributes = (live: Element, next: Element) => {\n for (const attr of Array.from(next.attributes)) {\n if (live.getAttributeNS(attr.namespaceURI, attr.localName) === attr.value) {\n continue;\n }\n try {\n if (attr.namespaceURI) {\n live.setAttributeNS(attr.namespaceURI, attr.name, attr.value);\n } else {\n live.setAttribute(attr.name, attr.value);\n }\n } catch {\n // The parser accepts attribute names the DOM API rejects; they carry no meaning.\n }\n }\n for (const attr of Array.from(live.attributes)) {\n if (!next.hasAttributeNS(attr.namespaceURI, attr.localName)) {\n live.removeAttributeNS(attr.namespaceURI, attr.localName);\n }\n }\n};\n\nconst patchNode = (live: Node, next: Node, ctx: MorphContext) => {\n if (live.nodeType !== Node.ELEMENT_NODE) {\n if (live.nodeValue !== next.nodeValue) live.nodeValue = next.nodeValue;\n return;\n }\n const liveEl = live as Element;\n const nextEl = next as Element;\n syncAttributes(liveEl, nextEl);\n if (\n liveEl instanceof HTMLTemplateElement &&\n nextEl instanceof HTMLTemplateElement\n ) {\n if (liveEl.innerHTML !== nextEl.innerHTML) {\n liveEl.content.replaceChildren(nextEl.content);\n }\n return;\n }\n morphChildren(liveEl, nextEl, ctx);\n};\n\n/**\n * Makes `live`'s children match `next`'s, reusing live nodes matched by\n * position and tag. Unmatched nodes move out of `next` into `live`.\n */\nexport function morphChildren(\n live: ParentNode & Node,\n next: ParentNode & Node,\n ctx: MorphContext,\n): void {\n const nextNodes = Array.from(next.childNodes);\n let cursor: ChildNode | null = live.firstChild;\n\n for (let i = 0; i < nextNodes.length; i++) {\n const nextChild = nextNodes[i]!;\n if (!cursor) {\n live.appendChild(nextChild);\n ctx.onAdded(nextChild);\n continue;\n }\n if (sameNode(cursor, nextChild)) {\n patchNode(cursor, nextChild, ctx);\n cursor = cursor.nextSibling;\n continue;\n }\n const following = nextNodes[i + 1];\n if (following && sameNode(cursor, following)) {\n live.insertBefore(nextChild, cursor);\n ctx.onAdded(nextChild);\n continue;\n }\n const afterCursor: ChildNode | null = cursor.nextSibling;\n if (afterCursor && sameNode(afterCursor, nextChild)) {\n cursor.remove();\n patchNode(afterCursor, nextChild, ctx);\n cursor = afterCursor.nextSibling;\n continue;\n }\n live.replaceChild(nextChild, cursor);\n ctx.onAdded(nextChild);\n cursor = afterCursor;\n }\n\n while (cursor) {\n const after: ChildNode | null = cursor.nextSibling;\n cursor.remove();\n cursor = after;\n }\n}\n\nexport type ScriptRunner = (\n placeholder: Comment,\n original: Element,\n) => Promise<void>;\n\nexport type StreamRendererOptions = {\n root: HTMLElement;\n /** Whether newly inserted elements fade in. Checked per insertion. */\n animate?: () => boolean;\n /** Schedules a coalesced render; returns a cancel function. */\n schedule?: (render: () => void) => () => void;\n runScript?: ScriptRunner;\n onScriptError?: (message: string, src: string | undefined) => void;\n onRender?: (kind: WidgetKind) => void;\n};\n\nexport type StreamRenderer = {\n readonly source: string;\n readonly kind: WidgetKind;\n readonly ended: boolean;\n readonly scriptsRan: boolean;\n write(chunk: string): void;\n /** Renders the complete source, then runs held scripts in document order. */\n end(): Promise<void>;\n replace(code: string): Promise<void>;\n flush(): void;\n};\n\nconst SCRIPT_LOAD_TIMEOUT_MS = 15_000;\n\nconst defaultSchedule = (render: () => void) => {\n let done = false;\n const run = () => {\n if (done) return;\n done = true;\n cancelAnimationFrame(frame);\n clearTimeout(timer);\n render();\n };\n // rAF alone stalls in hidden or offscreen frames, which previews use.\n const frame = requestAnimationFrame(run);\n const timer = setTimeout(run, 50);\n return () => {\n done = true;\n cancelAnimationFrame(frame);\n clearTimeout(timer);\n };\n};\n\nexport const createScriptRunner =\n (\n onError: (message: string, src: string | undefined) => void = () => {},\n ): ScriptRunner =>\n (placeholder, original) => {\n const doc = placeholder.ownerDocument;\n const script = doc.createElementNS(\n original.namespaceURI ?? \"http://www.w3.org/1999/xhtml\",\n \"script\",\n );\n for (const attr of Array.from(original.attributes)) {\n try {\n script.setAttribute(attr.name, attr.value);\n } catch {\n // See syncAttributes.\n }\n }\n script.textContent = original.textContent;\n const src = script.getAttribute(\"src\") ?? script.getAttribute(\"href\");\n if (!src) {\n placeholder.replaceWith(script);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n onError(`Script timed out: ${src}`, src);\n resolve();\n }, SCRIPT_LOAD_TIMEOUT_MS);\n script.addEventListener(\"load\", () => {\n clearTimeout(timer);\n resolve();\n });\n script.addEventListener(\"error\", () => {\n clearTimeout(timer);\n onError(`Script failed to load: ${src}`, src);\n resolve();\n });\n placeholder.replaceWith(script);\n });\n };\n\nconst fadeIn = (node: Node) => {\n if (node.nodeType !== Node.ELEMENT_NODE) return;\n const el = node as Element;\n if (NON_VISUAL.has(el.localName) || typeof el.animate !== \"function\") return;\n el.animate([{ opacity: 0 }, { opacity: 1 }], {\n duration: 200,\n easing: \"ease-out\",\n });\n};\n\nexport function createStreamRenderer(\n options: StreamRendererOptions,\n): StreamRenderer {\n const { root } = options;\n const doc = root.ownerDocument;\n const schedule = options.schedule ?? defaultSchedule;\n const runScript =\n options.runScript ?? createScriptRunner(options.onScriptError);\n let source = \"\";\n let kind: WidgetKind = \"html\";\n let ended = false;\n let scriptsRan = false;\n let cancelScheduled: (() => void) | undefined;\n let lastRendered: string | undefined;\n let lastScripts: Element[] = [];\n\n const ctx: MorphContext = {\n onAdded(node) {\n if (options.animate?.()) fadeIn(node);\n },\n };\n\n const render = (final: boolean) => {\n cancelScheduled?.();\n cancelScheduled = undefined;\n const markup = final ? source : stripUnclosedStyle(source);\n if (markup === lastRendered) return;\n lastRendered = markup;\n kind = detectWidgetKind(source);\n root.dataset[\"kind\"] = kind;\n const parsed = parseMarkup(doc, markup);\n lastScripts = parsed.scripts;\n morphChildren(root, parsed.fragment, ctx);\n options.onRender?.(kind);\n };\n\n const runHeldScripts = async () => {\n const walker = doc.createTreeWalker(root, NodeFilter.SHOW_COMMENT);\n const placeholders: Comment[] = [];\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeValue === SCRIPT_PLACEHOLDER) {\n placeholders.push(node as Comment);\n }\n }\n const scripts = lastScripts;\n for (let i = 0; i < placeholders.length && i < scripts.length; i++) {\n await runScript(placeholders[i]!, scripts[i]!);\n }\n if (scripts.length > 0 && !scriptsRan) {\n scriptsRan = true;\n // The bootstrap document loaded long ago; widgets written for a fresh page wait for these.\n doc.dispatchEvent(new Event(\"DOMContentLoaded\", { bubbles: true }));\n doc.defaultView?.dispatchEvent(new Event(\"load\"));\n }\n };\n\n return {\n get source() {\n return source;\n },\n get kind() {\n return kind;\n },\n get ended() {\n return ended;\n },\n get scriptsRan() {\n return scriptsRan;\n },\n write(chunk) {\n if (ended) throw new Error(\"Cannot write after end; use replace\");\n if (!chunk) return;\n source += chunk;\n cancelScheduled ??= schedule(() => {\n cancelScheduled = undefined;\n render(false);\n });\n },\n async end() {\n if (ended) return;\n ended = true;\n render(true);\n await runHeldScripts();\n },\n async replace(code) {\n ended = true;\n source = code;\n render(true);\n await runHeldScripts();\n },\n flush() {\n if (cancelScheduled) render(ended);\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ScreenshotOptions } from "../protocol.js";
2
+ export type Screenshot = {
3
+ dataUrl: string;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ /**
8
+ * The capture box: the clone is laid out at the root's own width, and the
9
+ * image grows to the full scroll size so content overflowing the root is
10
+ * not clipped.
11
+ */
12
+ export declare const measureCapture: (root: HTMLElement) => {
13
+ layoutWidth: number;
14
+ width: number;
15
+ height: number;
16
+ };
17
+ /**
18
+ * Captures `root` as a PNG data URL by serializing a styled clone into an SVG
19
+ * `foreignObject` and drawing it onto a canvas. Best effort: web fonts loaded
20
+ * by URL, cross-origin images, and pseudo-elements are not reproduced.
21
+ */
22
+ export declare function captureScreenshot(root: HTMLElement, options?: ScreenshotOptions): Promise<Screenshot>;
23
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAa;AAOpD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,MAAM;IACb,MAAM,EAAE,MAAM;CACf;AAmLD;;;;;AAKA,eAAO,MAAM,cAAc,GAAI,MAAM,WAAW;;;;CAQ/C;AAWD;;;;;AAKA,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,UAAU,CAAC","names":[],"ignoreList":[],"sources":["../../src/runtime/screenshot.ts"],"sourcesContent":[null]}
@@ -0,0 +1,188 @@
1
+ // src/runtime/screenshot.ts
2
+ const XHTML = "http://www.w3.org/1999/xhtml";
3
+ const SVG = "http://www.w3.org/2000/svg";
4
+ const MAX_CANVAS_SIDE = 4096;
5
+ const SKIP = /* @__PURE__ */ new Set([
6
+ "script",
7
+ "noscript",
8
+ "template"
9
+ ]);
10
+ const createDefaultStyles = (doc) => {
11
+ const cache = /* @__PURE__ */ new Map();
12
+ let sandbox;
13
+ let svgSandbox;
14
+ let host;
15
+ const ensureSandbox = () => {
16
+ if (sandbox) return;
17
+ host = doc.createElement("div");
18
+ host.setAttribute("aria-hidden", "true");
19
+ host.style.cssText = "all:initial;position:absolute;left:-99999px;top:0;visibility:hidden;contain:strict;width:0;height:0;";
20
+ const shadow = host.attachShadow({ mode: "closed" });
21
+ sandbox = doc.createElement("div");
22
+ svgSandbox = doc.createElementNS(SVG, "svg");
23
+ sandbox.appendChild(svgSandbox);
24
+ shadow.appendChild(sandbox);
25
+ doc.body.appendChild(host);
26
+ };
27
+ return {
28
+ get(el) {
29
+ const key = `${el.namespaceURI}|${el.localName}`;
30
+ let styles = cache.get(key);
31
+ if (styles) return styles;
32
+ ensureSandbox();
33
+ const probe = doc.createElementNS(el.namespaceURI ?? XHTML, el.localName);
34
+ (el.namespaceURI === SVG ? svgSandbox : sandbox).appendChild(probe);
35
+ const computed = doc.defaultView.getComputedStyle(probe);
36
+ styles = /* @__PURE__ */ new Map();
37
+ for (let i = 0; i < computed.length; i++) {
38
+ const name = computed.item(i);
39
+ styles.set(name, computed.getPropertyValue(name));
40
+ }
41
+ probe.remove();
42
+ cache.set(key, styles);
43
+ return styles;
44
+ },
45
+ dispose() {
46
+ host?.remove();
47
+ }
48
+ };
49
+ };
50
+ const toDataUrl = (source, width, height) => {
51
+ const canvas = document.createElement("canvas");
52
+ canvas.width = Math.max(1, width);
53
+ canvas.height = Math.max(1, height);
54
+ canvas.getContext("2d").drawImage(source, 0, 0, width, height);
55
+ return canvas.toDataURL("image/png");
56
+ };
57
+ /**
58
+ * Clones `node` with every computed style that differs from both the
59
+ * element's default and its parent's value written inline, because a
60
+ * serialized SVG image cannot see the frame's stylesheets.
61
+ */
62
+ const cloneWithStyles = (node, defaults, parentStyle) => {
63
+ if (node.nodeType === Node.TEXT_NODE) return node.cloneNode();
64
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
65
+ const el = node;
66
+ if (SKIP.has(el.localName)) return null;
67
+ const view = el.ownerDocument.defaultView;
68
+ const style = view.getComputedStyle(el);
69
+ let clone;
70
+ if (el instanceof view.HTMLCanvasElement) {
71
+ clone = el.ownerDocument.createElement("img");
72
+ try {
73
+ clone.setAttribute("src", el.toDataURL("image/png"));
74
+ } catch {}
75
+ } else if (el instanceof view.HTMLImageElement && el.complete && el.naturalWidth > 0 && !el.src.startsWith("data:")) {
76
+ clone = el.cloneNode(false);
77
+ try {
78
+ clone.setAttribute("src", toDataUrl(el, el.naturalWidth, el.naturalHeight));
79
+ } catch {}
80
+ } else clone = el.cloneNode(false);
81
+ if (el instanceof view.HTMLInputElement) {
82
+ if (el.type === "checkbox" || el.type === "radio") {
83
+ if (el.checked) clone.setAttribute("checked", "");
84
+ else clone.removeAttribute("checked");
85
+ } else clone.setAttribute("value", el.value);
86
+ } else if (el instanceof view.HTMLTextAreaElement) clone.textContent = el.value;
87
+ else if (el instanceof view.HTMLSelectElement) clone.setAttribute("data-gf-value", el.value);
88
+ const base = defaults.get(el);
89
+ const declarations = [];
90
+ for (let i = 0; i < style.length; i++) {
91
+ const name = style.item(i);
92
+ if (name.startsWith("--")) continue;
93
+ const value = style.getPropertyValue(name);
94
+ if (value !== base.get(name) || parentStyle && value !== parentStyle.getPropertyValue(name)) declarations.push(`${name}:${value}`);
95
+ }
96
+ if (style.animationName !== "none" || style.transitionProperty !== "none") declarations.push("animation:none", "transition:none");
97
+ clone.setAttribute("style", declarations.join(";"));
98
+ if (!(el instanceof view.HTMLTextAreaElement)) for (const child of Array.from(el.childNodes)) {
99
+ const childClone = cloneWithStyles(child, defaults, style);
100
+ if (childClone) clone.appendChild(childClone);
101
+ }
102
+ if (el instanceof view.HTMLSelectElement) {
103
+ for (const option of Array.from(clone.querySelectorAll("option"))) if (option.getAttribute("value") === el.value || option.textContent === el.value) option.setAttribute("selected", "");
104
+ }
105
+ return clone;
106
+ };
107
+ /** Embeds `@font-face` rules that are already self-contained (`data:` sources). */
108
+ const collectInlineFontFaces = (doc) => {
109
+ const rules = [];
110
+ for (const sheet of Array.from(doc.styleSheets)) {
111
+ let cssRules;
112
+ try {
113
+ cssRules = sheet.cssRules;
114
+ } catch {
115
+ continue;
116
+ }
117
+ for (const rule of Array.from(cssRules)) if (rule.constructor.name === "CSSFontFaceRule" && !/url\((?!\s*["']?data:)/i.test(rule.cssText)) rules.push(rule.cssText);
118
+ }
119
+ return rules.join("\n");
120
+ };
121
+ /**
122
+ * The capture box: the clone is laid out at the root's own width, and the
123
+ * image grows to the full scroll size so content overflowing the root is
124
+ * not clipped.
125
+ */
126
+ const measureCapture = (root) => {
127
+ const rect = root.getBoundingClientRect();
128
+ const layoutWidth = Math.max(1, Math.ceil(rect.width));
129
+ return {
130
+ layoutWidth,
131
+ width: Math.max(layoutWidth, Math.ceil(root.scrollWidth)),
132
+ height: Math.max(1, Math.ceil(Math.max(rect.height, root.scrollHeight)))
133
+ };
134
+ };
135
+ const loadImage = (src) => new Promise((resolve, reject) => {
136
+ const img = new Image();
137
+ img.onload = () => resolve(img);
138
+ img.onerror = () => reject(/* @__PURE__ */ new Error("The serialized widget failed to rasterize"));
139
+ img.src = src;
140
+ });
141
+ /**
142
+ * Captures `root` as a PNG data URL by serializing a styled clone into an SVG
143
+ * `foreignObject` and drawing it onto a canvas. Best effort: web fonts loaded
144
+ * by URL, cross-origin images, and pseudo-elements are not reproduced.
145
+ */
146
+ async function captureScreenshot(root, options = {}) {
147
+ const doc = root.ownerDocument;
148
+ const view = doc.defaultView;
149
+ await doc.fonts?.ready;
150
+ const { layoutWidth, width, height } = measureCapture(root);
151
+ const defaults = createDefaultStyles(doc);
152
+ let clone;
153
+ try {
154
+ clone = cloneWithStyles(root, defaults, view.getComputedStyle(doc.body));
155
+ } finally {
156
+ defaults.dispose();
157
+ }
158
+ const wrapper = doc.createElementNS(XHTML, "div");
159
+ wrapper.setAttribute("style", `width:${width}px;height:${height}px;overflow:hidden;`);
160
+ if (clone) wrapper.appendChild(clone);
161
+ if (clone instanceof Element) clone.setAttribute("style", `${clone.getAttribute("style") ?? ""};width:${layoutWidth}px;margin:0;`);
162
+ const fonts = collectInlineFontFaces(doc);
163
+ const markup = new XMLSerializer().serializeToString(wrapper);
164
+ const svg = `<svg xmlns="${SVG}" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><foreignObject x="0" y="0" width="100%" height="100%">` + (fonts ? `<style xmlns="${XHTML}">${fonts.replace(/</g, "\\3c ")}</style>` : "") + markup + `</foreignObject></svg>`;
165
+ const image = await loadImage(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`);
166
+ const requested = options.scale ?? Math.min(view.devicePixelRatio || 1, 2);
167
+ const scale = Math.max(.1, Math.min(requested, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height));
168
+ const canvas = doc.createElement("canvas");
169
+ canvas.width = Math.round(width * scale);
170
+ canvas.height = Math.round(height * scale);
171
+ const context = canvas.getContext("2d");
172
+ if (!context) throw new Error("Canvas 2D context is unavailable");
173
+ const background = options.background ?? view.getComputedStyle(doc.documentElement).getPropertyValue("--color-background").trim();
174
+ if (background) {
175
+ context.fillStyle = background;
176
+ context.fillRect(0, 0, canvas.width, canvas.height);
177
+ }
178
+ context.scale(scale, scale);
179
+ context.drawImage(image, 0, 0, width, height);
180
+ return {
181
+ dataUrl: canvas.toDataURL("image/png"),
182
+ width,
183
+ height
184
+ };
185
+ }
186
+ export { captureScreenshot, measureCapture };
187
+
188
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","names":[],"sources":["../../src/runtime/screenshot.ts"],"sourcesContent":["import type { ScreenshotOptions } from \"../protocol\";\n\nconst XHTML = \"http://www.w3.org/1999/xhtml\";\nconst SVG = \"http://www.w3.org/2000/svg\";\nconst MAX_CANVAS_SIDE = 4096;\nconst SKIP = new Set([\"script\", \"noscript\", \"template\"]);\n\nexport type Screenshot = {\n dataUrl: string;\n width: number;\n height: number;\n};\n\nconst createDefaultStyles = (doc: Document) => {\n const cache = new Map<string, Map<string, string>>();\n let sandbox: HTMLElement | undefined;\n let svgSandbox: SVGSVGElement | undefined;\n\n let host: HTMLElement | undefined;\n\n const ensureSandbox = () => {\n if (sandbox) return;\n host = doc.createElement(\"div\");\n host.setAttribute(\"aria-hidden\", \"true\");\n host.style.cssText =\n \"all:initial;position:absolute;left:-99999px;top:0;visibility:hidden;contain:strict;width:0;height:0;\";\n // Document rules such as `*{box-sizing:border-box}` do not cross a shadow\n // boundary, so probes inside it report the defaults the serialized image\n // will actually render with.\n const shadow = host.attachShadow({ mode: \"closed\" });\n sandbox = doc.createElement(\"div\");\n svgSandbox = doc.createElementNS(SVG, \"svg\");\n sandbox.appendChild(svgSandbox);\n shadow.appendChild(sandbox);\n doc.body.appendChild(host);\n };\n\n return {\n get(el: Element): Map<string, string> {\n const key = `${el.namespaceURI}|${el.localName}`;\n let styles = cache.get(key);\n if (styles) return styles;\n ensureSandbox();\n const probe = doc.createElementNS(el.namespaceURI ?? XHTML, el.localName);\n (el.namespaceURI === SVG ? svgSandbox! : sandbox!).appendChild(probe);\n const computed = doc.defaultView!.getComputedStyle(probe);\n styles = new Map();\n for (let i = 0; i < computed.length; i++) {\n const name = computed.item(i);\n styles.set(name, computed.getPropertyValue(name));\n }\n probe.remove();\n cache.set(key, styles);\n return styles;\n },\n dispose() {\n host?.remove();\n },\n };\n};\n\nconst toDataUrl = (\n source: CanvasImageSource,\n width: number,\n height: number,\n) => {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, width);\n canvas.height = Math.max(1, height);\n canvas.getContext(\"2d\")!.drawImage(source, 0, 0, width, height);\n return canvas.toDataURL(\"image/png\");\n};\n\n/**\n * Clones `node` with every computed style that differs from both the\n * element's default and its parent's value written inline, because a\n * serialized SVG image cannot see the frame's stylesheets.\n */\nconst cloneWithStyles = (\n node: Node,\n defaults: ReturnType<typeof createDefaultStyles>,\n parentStyle: CSSStyleDeclaration | undefined,\n): Node | null => {\n if (node.nodeType === Node.TEXT_NODE) return node.cloneNode();\n if (node.nodeType !== Node.ELEMENT_NODE) return null;\n const el = node as Element;\n if (SKIP.has(el.localName)) return null;\n\n const view = el.ownerDocument.defaultView!;\n const style = view.getComputedStyle(el);\n let clone: Element;\n\n if (el instanceof view.HTMLCanvasElement) {\n clone = el.ownerDocument.createElement(\"img\");\n try {\n clone.setAttribute(\"src\", el.toDataURL(\"image/png\"));\n } catch {\n // A tainted canvas cannot be read; it stays empty in the capture.\n }\n } else if (\n el instanceof view.HTMLImageElement &&\n el.complete &&\n el.naturalWidth > 0 &&\n !el.src.startsWith(\"data:\")\n ) {\n clone = el.cloneNode(false) as Element;\n try {\n clone.setAttribute(\n \"src\",\n toDataUrl(el, el.naturalWidth, el.naturalHeight),\n );\n } catch {\n // Cross-origin images without CORS cannot be inlined.\n }\n } else {\n clone = el.cloneNode(false) as Element;\n }\n\n if (el instanceof view.HTMLInputElement) {\n if (el.type === \"checkbox\" || el.type === \"radio\") {\n if (el.checked) clone.setAttribute(\"checked\", \"\");\n else clone.removeAttribute(\"checked\");\n } else {\n clone.setAttribute(\"value\", el.value);\n }\n } else if (el instanceof view.HTMLTextAreaElement) {\n clone.textContent = el.value;\n } else if (el instanceof view.HTMLSelectElement) {\n clone.setAttribute(\"data-gf-value\", el.value);\n }\n\n const base = defaults.get(el);\n const declarations: string[] = [];\n for (let i = 0; i < style.length; i++) {\n const name = style.item(i);\n if (name.startsWith(\"--\")) continue;\n const value = style.getPropertyValue(name);\n if (\n value !== base.get(name) ||\n (parentStyle && value !== parentStyle.getPropertyValue(name))\n ) {\n declarations.push(`${name}:${value}`);\n }\n }\n if (style.animationName !== \"none\" || style.transitionProperty !== \"none\") {\n declarations.push(\"animation:none\", \"transition:none\");\n }\n clone.setAttribute(\"style\", declarations.join(\";\"));\n\n if (!(el instanceof view.HTMLTextAreaElement)) {\n for (const child of Array.from(el.childNodes)) {\n const childClone = cloneWithStyles(child, defaults, style);\n if (childClone) clone.appendChild(childClone);\n }\n }\n if (el instanceof view.HTMLSelectElement) {\n for (const option of Array.from(clone.querySelectorAll(\"option\"))) {\n if (\n option.getAttribute(\"value\") === el.value ||\n option.textContent === el.value\n ) {\n option.setAttribute(\"selected\", \"\");\n }\n }\n }\n return clone;\n};\n\n/** Embeds `@font-face` rules that are already self-contained (`data:` sources). */\nconst collectInlineFontFaces = (doc: Document): string => {\n const rules: string[] = [];\n for (const sheet of Array.from(doc.styleSheets)) {\n let cssRules: CSSRuleList;\n try {\n cssRules = sheet.cssRules;\n } catch {\n continue;\n }\n for (const rule of Array.from(cssRules)) {\n if (\n rule.constructor.name === \"CSSFontFaceRule\" &&\n !/url\\((?!\\s*[\"']?data:)/i.test(rule.cssText)\n ) {\n rules.push(rule.cssText);\n }\n }\n }\n return rules.join(\"\\n\");\n};\n\n/**\n * The capture box: the clone is laid out at the root's own width, and the\n * image grows to the full scroll size so content overflowing the root is\n * not clipped.\n */\nexport const measureCapture = (root: HTMLElement) => {\n const rect = root.getBoundingClientRect();\n const layoutWidth = Math.max(1, Math.ceil(rect.width));\n return {\n layoutWidth,\n width: Math.max(layoutWidth, Math.ceil(root.scrollWidth)),\n height: Math.max(1, Math.ceil(Math.max(rect.height, root.scrollHeight))),\n };\n};\n\nconst loadImage = (src: string) =>\n new Promise<HTMLImageElement>((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () =>\n reject(new Error(\"The serialized widget failed to rasterize\"));\n img.src = src;\n });\n\n/**\n * Captures `root` as a PNG data URL by serializing a styled clone into an SVG\n * `foreignObject` and drawing it onto a canvas. Best effort: web fonts loaded\n * by URL, cross-origin images, and pseudo-elements are not reproduced.\n */\nexport async function captureScreenshot(\n root: HTMLElement,\n options: ScreenshotOptions = {},\n): Promise<Screenshot> {\n const doc = root.ownerDocument;\n const view = doc.defaultView!;\n await doc.fonts?.ready;\n\n const { layoutWidth, width, height } = measureCapture(root);\n\n const defaults = createDefaultStyles(doc);\n let clone: Node | null;\n try {\n clone = cloneWithStyles(root, defaults, view.getComputedStyle(doc.body));\n } finally {\n defaults.dispose();\n }\n const wrapper = doc.createElementNS(XHTML, \"div\");\n wrapper.setAttribute(\n \"style\",\n `width:${width}px;height:${height}px;overflow:hidden;`,\n );\n if (clone) wrapper.appendChild(clone);\n if (clone instanceof Element) {\n clone.setAttribute(\n \"style\",\n `${clone.getAttribute(\"style\") ?? \"\"};width:${layoutWidth}px;margin:0;`,\n );\n }\n\n const fonts = collectInlineFontFaces(doc);\n const markup = new XMLSerializer().serializeToString(wrapper);\n const svg =\n `<svg xmlns=\"${SVG}\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">` +\n `<foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">` +\n (fonts\n ? `<style xmlns=\"${XHTML}\">${fonts.replace(/</g, \"\\\\3c \")}</style>`\n : \"\") +\n markup +\n `</foreignObject></svg>`;\n\n const image = await loadImage(\n `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,\n );\n\n const requested = options.scale ?? Math.min(view.devicePixelRatio || 1, 2);\n const scale = Math.max(\n 0.1,\n Math.min(requested, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height),\n );\n const canvas = doc.createElement(\"canvas\");\n canvas.width = Math.round(width * scale);\n canvas.height = Math.round(height * scale);\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"Canvas 2D context is unavailable\");\n const background =\n options.background ??\n view\n .getComputedStyle(doc.documentElement)\n .getPropertyValue(\"--color-background\")\n .trim();\n if (background) {\n context.fillStyle = background;\n context.fillRect(0, 0, canvas.width, canvas.height);\n }\n context.scale(scale, scale);\n context.drawImage(image, 0, 0, width, height);\n return { dataUrl: canvas.toDataURL(\"image/png\"), width, height };\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { WidgetSize } from "../protocol.js";
2
+ export declare function measureRoot(root: Element): WidgetSize;
3
+ /**
4
+ * The size to report to the host, or `undefined` while the root has no
5
+ * layout width. A frame laid out at zero width (a collapsed or hidden
6
+ * container, or Chrome's full-page capture) wraps its content one word per
7
+ * line, and its height would size the iframe far past its content.
8
+ */
9
+ export declare function reportableSize(root: Element): WidgetSize | undefined;
10
+ //# sourceMappingURL=size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size.d.ts","mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAa;AAE7C,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU;AAKtD;;;;;;AAMA,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS","names":[],"ignoreList":[],"sources":["../../src/runtime/size.ts"],"sourcesContent":[null]}
@@ -0,0 +1,21 @@
1
+ // src/runtime/size.ts
2
+ function measureRoot(root) {
3
+ const rect = root.getBoundingClientRect();
4
+ return {
5
+ width: Math.ceil(rect.width),
6
+ height: Math.ceil(rect.height)
7
+ };
8
+ }
9
+ /**
10
+ * The size to report to the host, or `undefined` while the root has no
11
+ * layout width. A frame laid out at zero width (a collapsed or hidden
12
+ * container, or Chrome's full-page capture) wraps its content one word per
13
+ * line, and its height would size the iframe far past its content.
14
+ */
15
+ function reportableSize(root) {
16
+ const size = measureRoot(root);
17
+ return size.width > 0 ? size : void 0;
18
+ }
19
+ export { measureRoot, reportableSize };
20
+
21
+ //# sourceMappingURL=size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size.js","names":[],"sources":["../../src/runtime/size.ts"],"sourcesContent":["import type { WidgetSize } from \"../protocol\";\n\nexport function measureRoot(root: Element): WidgetSize {\n const rect = root.getBoundingClientRect();\n return { width: Math.ceil(rect.width), height: Math.ceil(rect.height) };\n}\n\n/**\n * The size to report to the host, or `undefined` while the root has no\n * layout width. A frame laid out at zero width (a collapsed or hidden\n * container, or Chrome's full-page capture) wraps its content one word per\n * line, and its height would size the iframe far past its content.\n */\nexport function reportableSize(root: Element): WidgetSize | undefined {\n const size = measureRoot(root);\n return size.width > 0 ? size : undefined;\n}\n"],"mappings":";AAEA,SAAgB,YAAY,MAA2B;CACrD,MAAM,OAAO,KAAK,sBAAsB;CACxC,OAAO;EAAE,OAAO,KAAK,KAAK,KAAK,KAAK;EAAG,QAAQ,KAAK,KAAK,KAAK,MAAM;CAAE;AACxE;;;;;;;AAQA,SAAgB,eAAe,MAAuC;CACpE,MAAM,OAAO,YAAY,IAAI;CAC7B,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAA;AACjC"}
@@ -0,0 +1,4 @@
1
+ import type { ClearStorageResult } from "../protocol.js";
2
+ /** Clears everything this frame's origin stores in the browser. */
3
+ export declare function clearFrameStorage(win: Window): Promise<ClearStorageResult>;
4
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAa;AAIrD;AACA,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,CAAC","names":[],"ignoreList":[],"sources":["../../src/runtime/storage.ts"],"sourcesContent":[null]}
@@ -0,0 +1,38 @@
1
+ // src/runtime/storage.ts
2
+ const EXPIRED = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
3
+ /** Clears everything this frame's origin stores in the browser. */
4
+ async function clearFrameStorage(win) {
5
+ const cleared = [];
6
+ const attempt = async (kind, clear) => {
7
+ try {
8
+ if (await clear() !== false) cleared.push(kind);
9
+ } catch {}
10
+ };
11
+ await attempt("localStorage", () => win.localStorage.clear());
12
+ await attempt("sessionStorage", () => win.sessionStorage.clear());
13
+ await attempt("indexedDB", async () => {
14
+ const idb = win.indexedDB;
15
+ if (typeof idb?.databases !== "function") return false;
16
+ const databases = await idb.databases();
17
+ await Promise.all(databases.map(({ name }) => name && new Promise((resolve) => {
18
+ const request = idb.deleteDatabase(name);
19
+ request.onsuccess = request.onerror = request.onblocked = () => resolve();
20
+ })));
21
+ return true;
22
+ });
23
+ await attempt("cacheStorage", async () => {
24
+ if (!win.caches) return false;
25
+ for (const key of await win.caches.keys()) await win.caches.delete(key);
26
+ return true;
27
+ });
28
+ await attempt("cookies", () => {
29
+ for (const part of win.document.cookie.split(";")) {
30
+ const name = part.split("=")[0]?.trim();
31
+ if (name) win.document.cookie = `${name}=; ${EXPIRED}; path=/`;
32
+ }
33
+ });
34
+ return { cleared };
35
+ }
36
+ export { clearFrameStorage };
37
+
38
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","names":[],"sources":["../../src/runtime/storage.ts"],"sourcesContent":["import type { ClearStorageResult } from \"../protocol\";\n\nconst EXPIRED = \"expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n\n/** Clears everything this frame's origin stores in the browser. */\nexport async function clearFrameStorage(\n win: Window,\n): Promise<ClearStorageResult> {\n const cleared: string[] = [];\n const attempt = async (kind: string, clear: () => unknown) => {\n try {\n if ((await clear()) !== false) cleared.push(kind);\n } catch {}\n };\n\n await attempt(\"localStorage\", () => win.localStorage.clear());\n await attempt(\"sessionStorage\", () => win.sessionStorage.clear());\n await attempt(\"indexedDB\", async () => {\n const idb = win.indexedDB;\n if (typeof idb?.databases !== \"function\") return false;\n const databases = await idb.databases();\n await Promise.all(\n databases.map(\n ({ name }) =>\n name &&\n new Promise<void>((resolve) => {\n const request = idb.deleteDatabase(name);\n request.onsuccess =\n request.onerror =\n request.onblocked =\n () => resolve();\n }),\n ),\n );\n return true;\n });\n await attempt(\"cacheStorage\", async () => {\n if (!win.caches) return false;\n for (const key of await win.caches.keys()) await win.caches.delete(key);\n return true;\n });\n await attempt(\"cookies\", () => {\n for (const part of win.document.cookie.split(\";\")) {\n const name = part.split(\"=\")[0]?.trim();\n if (name) win.document.cookie = `${name}=; ${EXPIRED}; path=/`;\n }\n });\n return { cleared };\n}\n"],"mappings":";AAEA,MAAM,UAAU;;AAGhB,eAAsB,kBACpB,KAC6B;CAC7B,MAAM,UAAoB,CAAC;CAC3B,MAAM,UAAU,OAAO,MAAc,UAAyB;EAC5D,IAAI;GACF,IAAK,MAAM,MAAM,MAAO,OAAO,QAAQ,KAAK,IAAI;EAClD,QAAQ,CAAC;CACX;CAEA,MAAM,QAAQ,sBAAsB,IAAI,aAAa,MAAM,CAAC;CAC5D,MAAM,QAAQ,wBAAwB,IAAI,eAAe,MAAM,CAAC;CAChE,MAAM,QAAQ,aAAa,YAAY;EACrC,MAAM,MAAM,IAAI;EAChB,IAAI,OAAO,KAAK,cAAc,YAAY,OAAO;EACjD,MAAM,YAAY,MAAM,IAAI,UAAU;EACtC,MAAM,QAAQ,IACZ,UAAU,KACP,EAAE,WACD,QACA,IAAI,SAAe,YAAY;GAC7B,MAAM,UAAU,IAAI,eAAe,IAAI;GACvC,QAAQ,YACN,QAAQ,UACR,QAAQ,kBACA,QAAQ;EACpB,CAAC,CACL,CACF;EACA,OAAO;CACT,CAAC;CACD,MAAM,QAAQ,gBAAgB,YAAY;EACxC,IAAI,CAAC,IAAI,QAAQ,OAAO;EACxB,KAAK,MAAM,OAAO,MAAM,IAAI,OAAO,KAAK,GAAG,MAAM,IAAI,OAAO,OAAO,GAAG;EACtE,OAAO;CACT,CAAC;CACD,MAAM,QAAQ,iBAAiB;EAC7B,KAAK,MAAM,QAAQ,IAAI,SAAS,OAAO,MAAM,GAAG,GAAG;GACjD,MAAM,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK;GACtC,IAAI,MAAM,IAAI,SAAS,SAAS,GAAG,KAAK,KAAK,QAAQ;EACvD;CACF,CAAC;CACD,OAAO,EAAE,QAAQ;AACnB"}