@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,104 @@
1
+ import { describeSchema, toJsonSchema, validateSchema } from "../json-schema.js";
2
+ import { isExpression } from "./expressions.js";
3
+ import { buildSpecPrompt } from "./prompt.js";
4
+ // src/spec/catalog.ts
5
+ /** The built-in action every catalog accepts. */
6
+ const SET_STATE_ACTION = {
7
+ description: "Writes `value` at the JSON Pointer `path` in state.",
8
+ params: {
9
+ type: "object",
10
+ properties: {
11
+ path: {
12
+ type: "string",
13
+ description: "JSON Pointer into state"
14
+ },
15
+ value: { description: "The value to write" }
16
+ },
17
+ required: ["path"]
18
+ }
19
+ };
20
+ /** Built-in actions every catalog accepts; a host handles `sendMessage`, `updateModelContext`, and `copyText` the way it handles its own actions. */
21
+ const BUILT_IN_ACTIONS = {
22
+ setState: SET_STATE_ACTION,
23
+ sendMessage: {
24
+ description: "Sends `text` as the user's next message.",
25
+ params: {
26
+ type: "object",
27
+ properties: { text: {
28
+ type: "string",
29
+ description: "The message"
30
+ } },
31
+ required: ["text"]
32
+ }
33
+ },
34
+ updateModelContext: {
35
+ description: "Replaces what this UI tells the model about the user's choices with `text` and `data`.",
36
+ params: {
37
+ type: "object",
38
+ properties: {
39
+ text: {
40
+ type: "string",
41
+ description: "The choices in words"
42
+ },
43
+ data: { description: "The choices as data" }
44
+ }
45
+ }
46
+ },
47
+ copyText: {
48
+ description: "Copies `text` to the user's clipboard.",
49
+ params: {
50
+ type: "object",
51
+ properties: { text: {
52
+ type: "string",
53
+ description: "The text to copy"
54
+ } },
55
+ required: ["text"]
56
+ }
57
+ }
58
+ };
59
+ const skipExpressions = { skip: isExpression };
60
+ /**
61
+ * Declares the components and actions a model may use in a spec. Props are
62
+ * JSON Schema (or a Standard Schema exposing JSON Schema), so the catalog
63
+ * stays dependency-free and works as model guidance and runtime validation.
64
+ */
65
+ function defineCatalog(definition) {
66
+ const components = definition.components;
67
+ const actions = definition.actions ?? {};
68
+ const schemaCache = /* @__PURE__ */ new Map();
69
+ const component = (type) => Object.hasOwn(components, type) ? components[type] : void 0;
70
+ for (const name of Object.keys(BUILT_IN_ACTIONS)) if (Object.hasOwn(actions, name)) throw new Error(`The action name "${name}" is reserved for the built-in action.`);
71
+ const action = (name) => Object.hasOwn(BUILT_IN_ACTIONS, name) ? BUILT_IN_ACTIONS[name] : Object.hasOwn(actions, name) ? actions[name] : void 0;
72
+ const catalog = {
73
+ components,
74
+ actions,
75
+ component,
76
+ action,
77
+ propsSchema(type) {
78
+ if (!schemaCache.has(type)) schemaCache.set(type, toJsonSchema(component(type)?.props));
79
+ return schemaCache.get(type);
80
+ },
81
+ paramsSchema(name) {
82
+ const key = `action:${name}`;
83
+ if (!schemaCache.has(key)) schemaCache.set(key, toJsonSchema(action(name)?.params));
84
+ return schemaCache.get(key);
85
+ },
86
+ validateProps(type, props) {
87
+ const schema = component(type)?.props;
88
+ if (!schema) return [];
89
+ return validateSchema(schema, props ?? {}, skipExpressions);
90
+ },
91
+ validateParams(name, params) {
92
+ const schema = action(name)?.params;
93
+ if (!schema) return [];
94
+ return validateSchema(schema, params ?? {}, skipExpressions);
95
+ },
96
+ prompt: (options) => buildSpecPrompt(catalog, options)
97
+ };
98
+ return catalog;
99
+ }
100
+ /** One line describing a component's props for prompts and errors. */
101
+ const describeProps = (catalog, type) => describeSchema(catalog.propsSchema(type));
102
+ export { SET_STATE_ACTION, defineCatalog, describeProps };
103
+
104
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","names":[],"sources":["../../src/spec/catalog.ts"],"sourcesContent":["import {\n describeSchema,\n toJsonSchema,\n validateSchema,\n type JsonSchema,\n type SchemaIssue,\n type StandardSchemaLike,\n} from \"../json-schema\";\nimport { isExpression } from \"./expressions\";\nimport { buildSpecPrompt, type SpecPromptOptions } from \"./prompt\";\n\nexport type PropsSchema = JsonSchema | StandardSchemaLike;\n\n/** A component the model may use. Implementations are supplied per framework. */\nexport type ComponentDefinition = {\n description: string;\n /** JSON Schema for the props, or a Standard Schema (e.g. Zod 4) that can export one. */\n props?: PropsSchema;\n /**\n * Child slots. `\"default\"` maps to `children`; other names map to\n * `slots.<name>`. Components without slots take no children.\n */\n slots?: readonly string[];\n /** Events the component emits, which elements bind with `on`. */\n events?: readonly string[];\n};\n\nexport type ActionDefinition = {\n description: string;\n params?: PropsSchema;\n};\n\nexport type CatalogDefinition<\n TComponents extends Record<string, ComponentDefinition> = Record<\n string,\n ComponentDefinition\n >,\n TActions extends Record<string, ActionDefinition> = Record<\n string,\n ActionDefinition\n >,\n> = {\n components: TComponents;\n actions?: TActions;\n};\n\nexport type Catalog<\n TComponents extends Record<string, ComponentDefinition> = Record<\n string,\n ComponentDefinition\n >,\n TActions extends Record<string, ActionDefinition> = Record<\n string,\n ActionDefinition\n >,\n> = {\n readonly components: TComponents;\n readonly actions: TActions;\n /** Component definition by name, or undefined. */\n component(type: string): ComponentDefinition | undefined;\n /** Action definition by name, including the built-in `setState`. */\n action(name: string): ActionDefinition | undefined;\n /** JSON Schema of a component's props, if it has one. */\n propsSchema(type: string): JsonSchema | undefined;\n /** Validates props, treating expression values as valid placeholders. */\n /** JSON Schema of an action's params, if it has one. */\n paramsSchema(action: string): JsonSchema | undefined;\n validateProps(type: string, props: unknown): SchemaIssue[];\n validateParams(action: string, params: unknown): SchemaIssue[];\n /** Model guidance for this catalog: components, actions, protocol, expressions, rules. */\n prompt(options?: SpecPromptOptions): string;\n};\n\n/** The built-in action every catalog accepts. */\nexport const SET_STATE_ACTION: ActionDefinition = {\n description: \"Writes `value` at the JSON Pointer `path` in state.\",\n params: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"JSON Pointer into state\" },\n value: { description: \"The value to write\" },\n },\n required: [\"path\"],\n },\n};\n\nconst SEND_MESSAGE_ACTION: ActionDefinition = {\n description: \"Sends `text` as the user's next message.\",\n params: {\n type: \"object\",\n properties: { text: { type: \"string\", description: \"The message\" } },\n required: [\"text\"],\n },\n};\n\nconst UPDATE_MODEL_CONTEXT_ACTION: ActionDefinition = {\n description:\n \"Replaces what this UI tells the model about the user's choices with `text` and `data`.\",\n params: {\n type: \"object\",\n properties: {\n text: { type: \"string\", description: \"The choices in words\" },\n data: { description: \"The choices as data\" },\n },\n },\n};\n\nconst COPY_TEXT_ACTION: ActionDefinition = {\n description: \"Copies `text` to the user's clipboard.\",\n params: {\n type: \"object\",\n properties: { text: { type: \"string\", description: \"The text to copy\" } },\n required: [\"text\"],\n },\n};\n\n/** Built-in actions every catalog accepts; a host handles `sendMessage`, `updateModelContext`, and `copyText` the way it handles its own actions. */\nconst BUILT_IN_ACTIONS: Readonly<Record<string, ActionDefinition>> = {\n setState: SET_STATE_ACTION,\n sendMessage: SEND_MESSAGE_ACTION,\n updateModelContext: UPDATE_MODEL_CONTEXT_ACTION,\n copyText: COPY_TEXT_ACTION,\n};\n\nconst skipExpressions = { skip: isExpression };\n\n/**\n * Declares the components and actions a model may use in a spec. Props are\n * JSON Schema (or a Standard Schema exposing JSON Schema), so the catalog\n * stays dependency-free and works as model guidance and runtime validation.\n */\nexport function defineCatalog<\n const TComponents extends Record<string, ComponentDefinition>,\n const TActions extends Record<string, ActionDefinition> = Record<\n never,\n ActionDefinition\n >,\n>(\n definition: CatalogDefinition<TComponents, TActions>,\n): Catalog<TComponents, TActions> {\n const components = definition.components;\n const actions = (definition.actions ?? {}) as TActions;\n const schemaCache = new Map<string, JsonSchema | undefined>();\n\n const component = (type: string) =>\n Object.hasOwn(components, type) ? components[type] : undefined;\n for (const name of Object.keys(BUILT_IN_ACTIONS)) {\n if (Object.hasOwn(actions, name)) {\n throw new Error(\n `The action name \"${name}\" is reserved for the built-in action.`,\n );\n }\n }\n const action = (name: string) =>\n Object.hasOwn(BUILT_IN_ACTIONS, name)\n ? BUILT_IN_ACTIONS[name]\n : Object.hasOwn(actions, name)\n ? actions[name]\n : undefined;\n\n const catalog: Catalog<TComponents, TActions> = {\n components,\n actions,\n component,\n action,\n propsSchema(type) {\n if (!schemaCache.has(type)) {\n schemaCache.set(type, toJsonSchema(component(type)?.props));\n }\n return schemaCache.get(type);\n },\n paramsSchema(name) {\n const key = `action:${name}`;\n if (!schemaCache.has(key)) {\n schemaCache.set(key, toJsonSchema(action(name)?.params));\n }\n return schemaCache.get(key);\n },\n validateProps(type, props) {\n const schema = component(type)?.props;\n if (!schema) return [];\n return validateSchema(schema, props ?? {}, skipExpressions);\n },\n validateParams(name, params) {\n const schema = action(name)?.params;\n if (!schema) return [];\n return validateSchema(schema, params ?? {}, skipExpressions);\n },\n prompt: (options) => buildSpecPrompt(catalog, options),\n };\n return catalog;\n}\n\n/** One line describing a component's props for prompts and errors. */\nexport const describeProps = (catalog: Catalog, type: string) =>\n describeSchema(catalog.propsSchema(type));\n"],"mappings":";;;;;AA0EA,MAAa,mBAAqC;CAChD,aAAa;CACb,QAAQ;EACN,MAAM;EACN,YAAY;GACV,MAAM;IAAE,MAAM;IAAU,aAAa;GAA0B;GAC/D,OAAO,EAAE,aAAa,qBAAqB;EAC7C;EACA,UAAU,CAAC,MAAM;CACnB;AACF;;AAiCA,MAAM,mBAA+D;CACnE,UAAU;CACV,aAAa;EAhCb,aAAa;EACb,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,MAAM;IAAE,MAAM;IAAU,aAAa;GAAc,EAAE;GACnE,UAAU,CAAC,MAAM;EACnB;CA2B+B;CAC/B,oBAAoB;EAxBpB,aACE;EACF,QAAQ;GACN,MAAM;GACN,YAAY;IACV,MAAM;KAAE,MAAM;KAAU,aAAa;IAAuB;IAC5D,MAAM,EAAE,aAAa,sBAAsB;GAC7C;EACF;CAgB8C;CAC9C,UAAU;EAbV,aAAa;EACb,QAAQ;GACN,MAAM;GACN,YAAY,EAAE,MAAM;IAAE,MAAM;IAAU,aAAa;GAAmB,EAAE;GACxE,UAAU,CAAC,MAAM;EACnB;CAQyB;AAC3B;AAEA,MAAM,kBAAkB,EAAE,MAAM,aAAa;;;;;;AAO7C,SAAgB,cAOd,YACgC;CAChC,MAAM,aAAa,WAAW;CAC9B,MAAM,UAAW,WAAW,WAAW,CAAC;CACxC,MAAM,8BAAc,IAAI,IAAoC;CAE5D,MAAM,aAAa,SACjB,OAAO,OAAO,YAAY,IAAI,IAAI,WAAW,QAAQ,KAAA;CACvD,KAAK,MAAM,QAAQ,OAAO,KAAK,gBAAgB,GAC7C,IAAI,OAAO,OAAO,SAAS,IAAI,GAC7B,MAAM,IAAI,MACR,oBAAoB,KAAK,uCAC3B;CAGJ,MAAM,UAAU,SACd,OAAO,OAAO,kBAAkB,IAAI,IAChC,iBAAiB,QACjB,OAAO,OAAO,SAAS,IAAI,IACzB,QAAQ,QACR,KAAA;CAER,MAAM,UAA0C;EAC9C;EACA;EACA;EACA;EACA,YAAY,MAAM;GAChB,IAAI,CAAC,YAAY,IAAI,IAAI,GACvB,YAAY,IAAI,MAAM,aAAa,UAAU,IAAI,CAAC,EAAE,KAAK,CAAC;GAE5D,OAAO,YAAY,IAAI,IAAI;EAC7B;EACA,aAAa,MAAM;GACjB,MAAM,MAAM,UAAU;GACtB,IAAI,CAAC,YAAY,IAAI,GAAG,GACtB,YAAY,IAAI,KAAK,aAAa,OAAO,IAAI,CAAC,EAAE,MAAM,CAAC;GAEzD,OAAO,YAAY,IAAI,GAAG;EAC5B;EACA,cAAc,MAAM,OAAO;GACzB,MAAM,SAAS,UAAU,IAAI,CAAC,EAAE;GAChC,IAAI,CAAC,QAAQ,OAAO,CAAC;GACrB,OAAO,eAAe,QAAQ,SAAS,CAAC,GAAG,eAAe;EAC5D;EACA,eAAe,MAAM,QAAQ;GAC3B,MAAM,SAAS,OAAO,IAAI,CAAC,EAAE;GAC7B,IAAI,CAAC,QAAQ,OAAO,CAAC;GACrB,OAAO,eAAe,QAAQ,UAAU,CAAC,GAAG,eAAe;EAC7D;EACA,SAAS,YAAY,gBAAgB,SAAS,OAAO;CACvD;CACA,OAAO;AACT;;AAGA,MAAa,iBAAiB,SAAkB,SAC9C,eAAe,QAAQ,YAAY,IAAI,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { Condition } from "./types.js";
2
+ /** What expressions can read while an element renders or an action runs. */
3
+ export type ExpressionContext = {
4
+ state: unknown;
5
+ /** The current item and index inside a `repeat`. */
6
+ item?: unknown;
7
+ index?: number;
8
+ /** The state path of the current item, so `$bindItem` can write back. */
9
+ itemPath?: string;
10
+ /** The payload an event was emitted with, for action params. */
11
+ event?: unknown;
12
+ };
13
+ /** True for `{ $state }`, `{ $bindState }`, `{ $item }`, `{ $bindItem }`, `{ $index }`, `{ $event }`, `{ $cond }`, `{ $calc }`, and `{ $template }`. */
14
+ export declare const isExpression: (value: unknown) => value is Record<string, unknown>;
15
+ /** True for a string that is only an expression key, such as `"$calc"`. */
16
+ export declare const isExpressionKey: (value: string) => boolean;
17
+ /**
18
+ * Fills `${/path}` (state), `${$item/path}`, `${$item}`, and `${$index}`
19
+ * placeholders in a `$template` string.
20
+ */
21
+ export declare function renderTemplate(template: string, ctx: ExpressionContext): string;
22
+ /** The state paths a `$template` string's placeholders read. */
23
+ export declare const templateStatePaths: (template: string) => string[];
24
+ /** Resolves expressions anywhere inside a value; plain values pass through. */
25
+ export declare function resolveValue(value: unknown, ctx: ExpressionContext): unknown;
26
+ /** The state paths a condition only holds true while they have a value: a `$state` it tests for truthiness or with any comparator but `neq` alone, in every part of `$and` or a list. */
27
+ export declare function conditionGuards(condition: unknown): string[];
28
+ /**
29
+ * Evaluates a visibility condition. Unknown shapes are false, so a
30
+ * half-streamed condition hides its element rather than throwing.
31
+ */
32
+ export declare function evaluateCondition(condition: Condition | undefined, ctx: ExpressionContext): boolean;
33
+ /** Resolved props plus the state path behind each two-way bound prop. */
34
+ export type ResolvedProps = {
35
+ props: Record<string, unknown>;
36
+ bindings: Record<string, string>;
37
+ };
38
+ /**
39
+ * Resolves an element's props. Top-level `$bindState` and `$bindItem` props
40
+ * are also reported in `bindings`, so the component can write the value back.
41
+ */
42
+ export declare function resolveProps(props: Record<string, unknown> | undefined, ctx: ExpressionContext): ResolvedProps;
43
+ /** The state path of item `index` in the array at `path`. */
44
+ export declare const itemPointer: (path: string, index: number) => string;
45
+ //# sourceMappingURL=expressions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expressions.d.ts","mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAS;AAExC;AACA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,OAAO;IACd;IACA,IAAI,CAAC,EAAE,OAAO;IACd,KAAK,CAAC,EAAE,MAAM;IACd;IACA,QAAQ,CAAC,EAAE,MAAM;IACjB;IACA,KAAK,CAAC,EAAE,OAAO;CAChB;AAqBD;AACA,eAAO,MAAM,YAAY,GACvB,OAAO,OAAO,KACb,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAC6C;AAE/E;AACA,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,YACV;AAqCnC;;;;AAIA,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,iBAAiB,GACrB,MAAM;AA8BT;AACA,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAAM,EAGlB;AAE1C;AACA,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO;AAmD7E;AACA,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE;AAc7D;;;;AAIA,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,GAAG,EAAE,iBAAiB,GACrB,OAAO;AA+DV;AACA,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CACjC;AAKD;;;;AAIA,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,GAAG,EAAE,iBAAiB,GACrB,aAAa;AAqBhB;AACA,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,WACa","names":[],"ignoreList":[],"sources":["../../src/spec/expressions.ts"],"sourcesContent":[null]}
@@ -0,0 +1,199 @@
1
+ import { deepEqual } from "../json-schema.js";
2
+ import { getAtPointer, joinPointer, parsePointer } from "./pointer.js";
3
+ import { evaluateFormula } from "./calc.js";
4
+ // src/spec/expressions.ts
5
+ const SOURCE_KEYS = [
6
+ "$state",
7
+ "$bindState",
8
+ "$item",
9
+ "$bindItem",
10
+ "$index",
11
+ "$event"
12
+ ];
13
+ const EXPRESSION_KEYS = /* @__PURE__ */ new Set([
14
+ ...SOURCE_KEYS,
15
+ "$cond",
16
+ "$calc",
17
+ "$template"
18
+ ]);
19
+ const COMPARATORS = [
20
+ "eq",
21
+ "neq",
22
+ "gt",
23
+ "gte",
24
+ "lt",
25
+ "lte"
26
+ ];
27
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
28
+ /** True for `{ $state }`, `{ $bindState }`, `{ $item }`, `{ $bindItem }`, `{ $index }`, `{ $event }`, `{ $cond }`, `{ $calc }`, and `{ $template }`. */
29
+ const isExpression = (value) => isRecord(value) && Object.keys(value).some((key) => EXPRESSION_KEYS.has(key));
30
+ /** True for a string that is only an expression key, such as `"$calc"`. */
31
+ const isExpressionKey = (value) => EXPRESSION_KEYS.has(value.trim());
32
+ const readPath = (base, path) => typeof path === "string" && path !== "" ? getAtPointer(base, safeTokens(path)) : base;
33
+ const safeTokens = (path) => {
34
+ try {
35
+ return parsePointer(path.startsWith("/") ? path : `/${path}`);
36
+ } catch {
37
+ return [];
38
+ }
39
+ };
40
+ const readSource = (expression, ctx) => {
41
+ if ("$state" in expression) return readPath(ctx.state, expression["$state"]);
42
+ if ("$bindState" in expression) return readPath(ctx.state, expression["$bindState"]);
43
+ if ("$item" in expression) return readPath(ctx.item, expression["$item"]);
44
+ if ("$bindItem" in expression) return readPath(ctx.item, expression["$bindItem"]);
45
+ if ("$index" in expression) return ctx.index;
46
+ if ("$event" in expression) return readPath(ctx.event, expression["$event"]);
47
+ };
48
+ const formatTemplateValue = (value) => value === void 0 || value === null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
49
+ /**
50
+ * Fills `${/path}` (state), `${$item/path}`, `${$item}`, and `${$index}`
51
+ * placeholders in a `$template` string.
52
+ */
53
+ function renderTemplate(template, ctx) {
54
+ const fill = (ref) => {
55
+ if (ref === "$index") return formatTemplateValue(ctx.index);
56
+ if (ref === "$item") return formatTemplateValue(ctx.item);
57
+ if (ref.startsWith("$item/")) return formatTemplateValue(readPath(ctx.item, ref.slice(5)));
58
+ return formatTemplateValue(readPath(ctx.state, ref));
59
+ };
60
+ let out = "";
61
+ let from = 0;
62
+ for (const { start, end, ref } of placeholders(template)) {
63
+ out += template.slice(from, start) + fill(ref);
64
+ from = end + 1;
65
+ }
66
+ return out + template.slice(from);
67
+ }
68
+ function* placeholders(template) {
69
+ let from = 0;
70
+ for (;;) {
71
+ const start = template.indexOf("${", from);
72
+ if (start === -1) return;
73
+ const end = template.indexOf("}", start + 2);
74
+ if (end === -1) return;
75
+ yield {
76
+ start,
77
+ end,
78
+ ref: template.slice(start + 2, end).trim()
79
+ };
80
+ from = end + 1;
81
+ }
82
+ }
83
+ /** The state paths a `$template` string's placeholders read. */
84
+ const templateStatePaths = (template) => [...placeholders(template)].map(({ ref }) => ref).filter((ref) => !ref.startsWith("$"));
85
+ /** Resolves expressions anywhere inside a value; plain values pass through. */
86
+ function resolveValue(value, ctx) {
87
+ if (Array.isArray(value)) return value.map((item) => resolveValue(item, ctx));
88
+ if (!isRecord(value)) return value;
89
+ if ("$cond" in value) return evaluateCondition(value["$cond"], ctx) ? resolveValue(value["$then"], ctx) : resolveValue(value["$else"], ctx);
90
+ if ("$calc" in value) return typeof value["$calc"] === "string" ? evaluateFormula(value["$calc"], ctx) : null;
91
+ if ("$template" in value) return typeof value["$template"] === "string" ? renderTemplate(value["$template"], ctx) : "";
92
+ if (SOURCE_KEYS.some((key) => key in value)) return readSource(value, ctx);
93
+ const result = {};
94
+ for (const [key, child] of Object.entries(value)) result[key] = resolveValue(child, ctx);
95
+ return result;
96
+ }
97
+ const compare = (left, operator, right) => {
98
+ switch (operator) {
99
+ case "eq": return deepEqual(left, right);
100
+ case "neq": return !deepEqual(left, right);
101
+ default:
102
+ if (!(typeof left === "number" && typeof right === "number") && !(typeof left === "string" && typeof right === "string")) return false;
103
+ if (operator === "gt") return left > right;
104
+ if (operator === "gte") return left >= right;
105
+ if (operator === "lt") return left < right;
106
+ return left <= right;
107
+ }
108
+ };
109
+ /** The state paths a condition only holds true while they have a value: a `$state` it tests for truthiness or with any comparator but `neq` alone, in every part of `$and` or a list. */
110
+ function conditionGuards(condition) {
111
+ if (Array.isArray(condition)) return condition.flatMap(conditionGuards);
112
+ if (!isRecord(condition)) return [];
113
+ if (Array.isArray(condition["$and"])) return conditionGuards(condition["$and"]);
114
+ if (typeof condition["$state"] !== "string" || condition["not"] === true) return [];
115
+ const comparators = COMPARATORS.filter((operator) => operator in condition);
116
+ return comparators.length === 0 || comparators.some((operator) => operator !== "neq") ? [condition["$state"]] : [];
117
+ }
118
+ /**
119
+ * Evaluates a visibility condition. Unknown shapes are false, so a
120
+ * half-streamed condition hides its element rather than throwing.
121
+ */
122
+ function evaluateCondition(condition, ctx) {
123
+ return evaluateConditionValue(condition, ctx) ?? false;
124
+ }
125
+ function evaluateConditionValue(condition, ctx) {
126
+ if (condition === void 0) return true;
127
+ if (typeof condition === "boolean") return condition;
128
+ if (Array.isArray(condition)) {
129
+ let result = true;
130
+ for (const part of condition) {
131
+ const value = evaluateConditionValue(part, ctx);
132
+ if (value === false) return false;
133
+ if (value === void 0) result = void 0;
134
+ }
135
+ return result;
136
+ }
137
+ if (!isRecord(condition)) return Boolean(condition);
138
+ if (Array.isArray(condition["$and"])) return evaluateConditionValue(condition["$and"], ctx);
139
+ if (Array.isArray(condition["$or"])) {
140
+ let result = false;
141
+ for (const part of condition["$or"]) {
142
+ const value = evaluateConditionValue(part, ctx);
143
+ if (value === true) return true;
144
+ if (value === void 0) result = void 0;
145
+ }
146
+ return result;
147
+ }
148
+ const hasSource = SOURCE_KEYS.some((key) => key in condition);
149
+ if (!hasSource && "not" in condition) {
150
+ const value = evaluateConditionValue(condition["not"], ctx);
151
+ return value === void 0 ? void 0 : !value;
152
+ }
153
+ if (!hasSource) {
154
+ if (!isExpression(condition)) return void 0;
155
+ let expression = condition;
156
+ while (isRecord(expression) && "$cond" in expression) {
157
+ const matches = evaluateConditionValue(expression["$cond"], ctx);
158
+ if (matches === void 0) return void 0;
159
+ expression = expression[matches ? "$then" : "$else"];
160
+ }
161
+ return Boolean(resolveValue(expression, ctx));
162
+ }
163
+ const value = readSource(condition, ctx);
164
+ let result = true;
165
+ let compared = false;
166
+ for (const operator of COMPARATORS) {
167
+ if (!(operator in condition)) continue;
168
+ compared = true;
169
+ result &&= compare(value, operator, resolveValue(condition[operator], ctx));
170
+ }
171
+ if (!compared) result = Boolean(value);
172
+ return condition["not"] === true ? !result : result;
173
+ }
174
+ const normalizePath = (path) => path === "" || path.startsWith("/") ? path : `/${path}`;
175
+ /**
176
+ * Resolves an element's props. Top-level `$bindState` and `$bindItem` props
177
+ * are also reported in `bindings`, so the component can write the value back.
178
+ */
179
+ function resolveProps(props, ctx) {
180
+ const resolved = {};
181
+ const bindings = {};
182
+ for (const [key, value] of Object.entries(props ?? {})) {
183
+ if (isRecord(value) && typeof value["$bindState"] === "string") bindings[key] = normalizePath(value["$bindState"]);
184
+ else if (isRecord(value) && typeof value["$bindItem"] === "string" && ctx.itemPath !== void 0) {
185
+ const field = value["$bindItem"];
186
+ bindings[key] = field ? `${ctx.itemPath}${normalizePath(field)}` : ctx.itemPath;
187
+ }
188
+ resolved[key] = resolveValue(value, ctx);
189
+ }
190
+ return {
191
+ props: resolved,
192
+ bindings
193
+ };
194
+ }
195
+ /** The state path of item `index` in the array at `path`. */
196
+ const itemPointer = (path, index) => `${path === "" ? "" : normalizePath(path)}${joinPointer([index])}`;
197
+ export { conditionGuards, evaluateCondition, isExpression, isExpressionKey, itemPointer, renderTemplate, resolveProps, resolveValue, templateStatePaths };
198
+
199
+ //# sourceMappingURL=expressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expressions.js","names":[],"sources":["../../src/spec/expressions.ts"],"sourcesContent":["import { deepEqual } from \"../json-schema\";\nimport { evaluateFormula } from \"./calc\";\nimport { getAtPointer, joinPointer, parsePointer } from \"./pointer\";\nimport type { Condition } from \"./types\";\n\n/** What expressions can read while an element renders or an action runs. */\nexport type ExpressionContext = {\n state: unknown;\n /** The current item and index inside a `repeat`. */\n item?: unknown;\n index?: number;\n /** The state path of the current item, so `$bindItem` can write back. */\n itemPath?: string;\n /** The payload an event was emitted with, for action params. */\n event?: unknown;\n};\n\nconst SOURCE_KEYS = [\n \"$state\",\n \"$bindState\",\n \"$item\",\n \"$bindItem\",\n \"$index\",\n \"$event\",\n] as const;\nconst EXPRESSION_KEYS = new Set<string>([\n ...SOURCE_KEYS,\n \"$cond\",\n \"$calc\",\n \"$template\",\n]);\nconst COMPARATORS = [\"eq\", \"neq\", \"gt\", \"gte\", \"lt\", \"lte\"] as const;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** True for `{ $state }`, `{ $bindState }`, `{ $item }`, `{ $bindItem }`, `{ $index }`, `{ $event }`, `{ $cond }`, `{ $calc }`, and `{ $template }`. */\nexport const isExpression = (\n value: unknown,\n): value is Record<string, unknown> =>\n isRecord(value) && Object.keys(value).some((key) => EXPRESSION_KEYS.has(key));\n\n/** True for a string that is only an expression key, such as `\"$calc\"`. */\nexport const isExpressionKey = (value: string) =>\n EXPRESSION_KEYS.has(value.trim());\n\nconst readPath = (base: unknown, path: unknown) =>\n typeof path === \"string\" && path !== \"\"\n ? getAtPointer(base, safeTokens(path))\n : base;\n\nconst safeTokens = (path: string) => {\n try {\n return parsePointer(path.startsWith(\"/\") ? path : `/${path}`);\n } catch {\n return [];\n }\n};\n\nconst readSource = (\n expression: Record<string, unknown>,\n ctx: ExpressionContext,\n) => {\n if (\"$state\" in expression) return readPath(ctx.state, expression[\"$state\"]);\n if (\"$bindState\" in expression)\n return readPath(ctx.state, expression[\"$bindState\"]);\n if (\"$item\" in expression) return readPath(ctx.item, expression[\"$item\"]);\n if (\"$bindItem\" in expression)\n return readPath(ctx.item, expression[\"$bindItem\"]);\n if (\"$index\" in expression) return ctx.index;\n if (\"$event\" in expression) return readPath(ctx.event, expression[\"$event\"]);\n return undefined;\n};\n\nconst formatTemplateValue = (value: unknown) =>\n value === undefined || value === null\n ? \"\"\n : typeof value === \"object\"\n ? JSON.stringify(value)\n : String(value);\n\n/**\n * Fills `${/path}` (state), `${$item/path}`, `${$item}`, and `${$index}`\n * placeholders in a `$template` string.\n */\nexport function renderTemplate(\n template: string,\n ctx: ExpressionContext,\n): string {\n const fill = (ref: string) => {\n if (ref === \"$index\") return formatTemplateValue(ctx.index);\n if (ref === \"$item\") return formatTemplateValue(ctx.item);\n if (ref.startsWith(\"$item/\")) {\n return formatTemplateValue(readPath(ctx.item, ref.slice(5)));\n }\n return formatTemplateValue(readPath(ctx.state, ref));\n };\n let out = \"\";\n let from = 0;\n for (const { start, end, ref } of placeholders(template)) {\n out += template.slice(from, start) + fill(ref);\n from = end + 1;\n }\n return out + template.slice(from);\n}\n\nfunction* placeholders(template: string) {\n let from = 0;\n for (;;) {\n const start = template.indexOf(\"${\", from);\n if (start === -1) return;\n const end = template.indexOf(\"}\", start + 2);\n if (end === -1) return;\n yield { start, end, ref: template.slice(start + 2, end).trim() };\n from = end + 1;\n }\n}\n\n/** The state paths a `$template` string's placeholders read. */\nexport const templateStatePaths = (template: string): string[] =>\n [...placeholders(template)]\n .map(({ ref }) => ref)\n .filter((ref) => !ref.startsWith(\"$\"));\n\n/** Resolves expressions anywhere inside a value; plain values pass through. */\nexport function resolveValue(value: unknown, ctx: ExpressionContext): unknown {\n if (Array.isArray(value)) return value.map((item) => resolveValue(item, ctx));\n if (!isRecord(value)) return value;\n if (\"$cond\" in value) {\n return evaluateCondition(value[\"$cond\"] as Condition, ctx)\n ? resolveValue(value[\"$then\"], ctx)\n : resolveValue(value[\"$else\"], ctx);\n }\n if (\"$calc\" in value) {\n return typeof value[\"$calc\"] === \"string\"\n ? evaluateFormula(value[\"$calc\"], ctx)\n : null;\n }\n if (\"$template\" in value) {\n return typeof value[\"$template\"] === \"string\"\n ? renderTemplate(value[\"$template\"], ctx)\n : \"\";\n }\n if (SOURCE_KEYS.some((key) => key in value)) return readSource(value, ctx);\n const result: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n result[key] = resolveValue(child, ctx);\n }\n return result;\n}\n\nconst compare = (\n left: unknown,\n operator: (typeof COMPARATORS)[number],\n right: unknown,\n) => {\n switch (operator) {\n case \"eq\":\n return deepEqual(left, right);\n case \"neq\":\n return !deepEqual(left, right);\n default: {\n if (\n !(typeof left === \"number\" && typeof right === \"number\") &&\n !(typeof left === \"string\" && typeof right === \"string\")\n ) {\n return false;\n }\n if (operator === \"gt\") return left > right;\n if (operator === \"gte\") return left >= right;\n if (operator === \"lt\") return left < right;\n return left <= right;\n }\n }\n};\n\n/** The state paths a condition only holds true while they have a value: a `$state` it tests for truthiness or with any comparator but `neq` alone, in every part of `$and` or a list. */\nexport function conditionGuards(condition: unknown): string[] {\n if (Array.isArray(condition)) return condition.flatMap(conditionGuards);\n if (!isRecord(condition)) return [];\n if (Array.isArray(condition[\"$and\"]))\n return conditionGuards(condition[\"$and\"]);\n if (typeof condition[\"$state\"] !== \"string\" || condition[\"not\"] === true)\n return [];\n const comparators = COMPARATORS.filter((operator) => operator in condition);\n return comparators.length === 0 ||\n comparators.some((operator) => operator !== \"neq\")\n ? [condition[\"$state\"]]\n : [];\n}\n\n/**\n * Evaluates a visibility condition. Unknown shapes are false, so a\n * half-streamed condition hides its element rather than throwing.\n */\nexport function evaluateCondition(\n condition: Condition | undefined,\n ctx: ExpressionContext,\n): boolean {\n return evaluateConditionValue(condition, ctx) ?? false;\n}\n\nfunction evaluateConditionValue(\n condition: Condition | undefined,\n ctx: ExpressionContext,\n): boolean | undefined {\n if (condition === undefined) return true;\n if (typeof condition === \"boolean\") return condition;\n if (Array.isArray(condition)) {\n let result: boolean | undefined = true;\n for (const part of condition) {\n const value = evaluateConditionValue(part as Condition, ctx);\n if (value === false) return false;\n if (value === undefined) result = undefined;\n }\n return result;\n }\n if (!isRecord(condition)) return Boolean(condition);\n if (Array.isArray(condition[\"$and\"])) {\n return evaluateConditionValue(condition[\"$and\"] as Condition[], ctx);\n }\n if (Array.isArray(condition[\"$or\"])) {\n let result: boolean | undefined = false;\n for (const part of condition[\"$or\"] as Condition[]) {\n const value = evaluateConditionValue(part, ctx);\n if (value === true) return true;\n if (value === undefined) result = undefined;\n }\n return result;\n }\n const hasSource = SOURCE_KEYS.some((key) => key in condition);\n if (!hasSource && \"not\" in condition) {\n const value = evaluateConditionValue(condition[\"not\"] as Condition, ctx);\n return value === undefined ? undefined : !value;\n }\n if (!hasSource) {\n if (!isExpression(condition)) return undefined;\n let expression: unknown = condition;\n while (isRecord(expression) && \"$cond\" in expression) {\n const matches = evaluateConditionValue(\n expression[\"$cond\"] as Condition,\n ctx,\n );\n if (matches === undefined) return undefined;\n expression = expression[matches ? \"$then\" : \"$else\"];\n }\n return Boolean(resolveValue(expression, ctx));\n }\n\n const value = readSource(condition, ctx);\n let result = true;\n let compared = false;\n for (const operator of COMPARATORS) {\n if (!(operator in condition)) continue;\n compared = true;\n result &&= compare(value, operator, resolveValue(condition[operator], ctx));\n }\n if (!compared) result = Boolean(value);\n return condition[\"not\"] === true ? !result : result;\n}\n\n/** Resolved props plus the state path behind each two-way bound prop. */\nexport type ResolvedProps = {\n props: Record<string, unknown>;\n bindings: Record<string, string>;\n};\n\nconst normalizePath = (path: string) =>\n path === \"\" || path.startsWith(\"/\") ? path : `/${path}`;\n\n/**\n * Resolves an element's props. Top-level `$bindState` and `$bindItem` props\n * are also reported in `bindings`, so the component can write the value back.\n */\nexport function resolveProps(\n props: Record<string, unknown> | undefined,\n ctx: ExpressionContext,\n): ResolvedProps {\n const resolved: Record<string, unknown> = {};\n const bindings: Record<string, string> = {};\n for (const [key, value] of Object.entries(props ?? {})) {\n if (isRecord(value) && typeof value[\"$bindState\"] === \"string\") {\n bindings[key] = normalizePath(value[\"$bindState\"]);\n } else if (\n isRecord(value) &&\n typeof value[\"$bindItem\"] === \"string\" &&\n ctx.itemPath !== undefined\n ) {\n const field = value[\"$bindItem\"];\n bindings[key] = field\n ? `${ctx.itemPath}${normalizePath(field)}`\n : ctx.itemPath;\n }\n resolved[key] = resolveValue(value, ctx);\n }\n return { props: resolved, bindings };\n}\n\n/** The state path of item `index` in the array at `path`. */\nexport const itemPointer = (path: string, index: number) =>\n `${path === \"\" ? \"\" : normalizePath(path)}${joinPointer([index])}`;\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type WriteOptions } from "./pointer.js";
2
+ import type { PatchOperation } from "./types.js";
3
+ /** Narrows an unknown value to a patch operation, or explains why it is not one. */
4
+ export declare function checkPatchOperation(value: unknown): {
5
+ ok: true;
6
+ op: PatchOperation;
7
+ } | {
8
+ ok: false;
9
+ error: string;
10
+ };
11
+ /**
12
+ * Applies one RFC 6902 operation and returns the new document; the input is
13
+ * not mutated and unchanged branches are shared. Throws when the operation
14
+ * cannot apply. With `createMissing`, `add` and `replace` create missing
15
+ * parent objects, which streaming relies on for paths like `/state/a/b`.
16
+ */
17
+ export declare function applyPatchOperation<T>(doc: T, operation: PatchOperation, options?: WriteOptions): T;
18
+ /** Applies operations in order; throws on the first one that fails. */
19
+ export declare function applyPatch<T>(doc: T, operations: readonly PatchOperation[], options?: WriteOptions): T;
20
+ //# sourceMappingURL=patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.d.ts","mappings":"AACA,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,cAAW;AAClB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAS;AAI7C;AACA,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb;IAAE,EAAE,EAAE,IAAI;IAAE,EAAE,EAAE,cAAc;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK;IAAE,KAAK,EAAE,MAAM;CAAE;AAgClE;;;;;;AAMA,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,EACN,SAAS,EAAE,cAAc,EACzB,OAAO,GAAE,YAAiB,GACzB,CAAC;AA6CJ;AACA,wBAAgB,UAAU,CAAC,CAAC,EAC1B,GAAG,EAAE,CAAC,EACN,UAAU,EAAE,SAAS,cAAc,EAAE,EACrC,OAAO,GAAE,YAAiB,GACzB,CAAC","names":[],"ignoreList":[],"sources":["../../src/spec/patch.ts"],"sourcesContent":[null]}
@@ -0,0 +1,77 @@
1
+ import { deepEqual } from "../json-schema.js";
2
+ import { getAtPointer, parsePointer, writeAtPointer } from "./pointer.js";
3
+ // src/spec/patch.ts
4
+ const OPS = /* @__PURE__ */ new Set([
5
+ "add",
6
+ "replace",
7
+ "remove",
8
+ "move",
9
+ "copy",
10
+ "test"
11
+ ]);
12
+ /** Narrows an unknown value to a patch operation, or explains why it is not one. */
13
+ function checkPatchOperation(value) {
14
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return {
15
+ ok: false,
16
+ error: "a patch operation must be a JSON object"
17
+ };
18
+ const op = value;
19
+ if (typeof op["op"] !== "string" || !OPS.has(op["op"])) return {
20
+ ok: false,
21
+ error: `"op" must be one of ${[...OPS].join(", ")}`
22
+ };
23
+ if (typeof op["path"] !== "string") return {
24
+ ok: false,
25
+ error: "\"path\" must be a string"
26
+ };
27
+ if ((op["op"] === "move" || op["op"] === "copy") && typeof op["from"] !== "string") return {
28
+ ok: false,
29
+ error: `"${op["op"]}" needs a string "from"`
30
+ };
31
+ if ((op["op"] === "add" || op["op"] === "replace" || op["op"] === "test") && !("value" in op)) return {
32
+ ok: false,
33
+ error: `"${op["op"]}" needs a "value"`
34
+ };
35
+ return {
36
+ ok: true,
37
+ op
38
+ };
39
+ }
40
+ const clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
41
+ /**
42
+ * Applies one RFC 6902 operation and returns the new document; the input is
43
+ * not mutated and unchanged branches are shared. Throws when the operation
44
+ * cannot apply. With `createMissing`, `add` and `replace` create missing
45
+ * parent objects, which streaming relies on for paths like `/state/a/b`.
46
+ */
47
+ function applyPatchOperation(doc, operation, options = {}) {
48
+ const path = parsePointer(operation.path);
49
+ switch (operation.op) {
50
+ case "add": return writeAtPointer(doc, path, "add", operation.value, options);
51
+ case "replace": return writeAtPointer(doc, path, "replace", operation.value, options);
52
+ case "remove": return writeAtPointer(doc, path, "remove");
53
+ case "move": {
54
+ const from = parsePointer(operation.from);
55
+ if (from.length < path.length && from.every((token, index) => path[index] === token)) throw new Error("Cannot move a value into one of its own children");
56
+ const value = getAtPointer(doc, from);
57
+ if (value === void 0) throw new Error(`Nothing to move at "${operation.from}"`);
58
+ const removed = writeAtPointer(doc, from, "remove");
59
+ return writeAtPointer(removed, path, "add", value, options);
60
+ }
61
+ case "copy": {
62
+ const value = getAtPointer(doc, operation.from);
63
+ if (value === void 0) throw new Error(`Nothing to copy at "${operation.from}"`);
64
+ return writeAtPointer(doc, path, "add", clone(value), options);
65
+ }
66
+ case "test":
67
+ if (!deepEqual(getAtPointer(doc, path), operation.value)) throw new Error(`Test failed at "${operation.path}"`);
68
+ return doc;
69
+ }
70
+ }
71
+ /** Applies operations in order; throws on the first one that fails. */
72
+ function applyPatch(doc, operations, options = {}) {
73
+ return operations.reduce((current, operation) => applyPatchOperation(current, operation, options), doc);
74
+ }
75
+ export { applyPatch, applyPatchOperation, checkPatchOperation };
76
+
77
+ //# sourceMappingURL=patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.js","names":[],"sources":["../../src/spec/patch.ts"],"sourcesContent":["import { deepEqual } from \"../json-schema\";\nimport {\n getAtPointer,\n parsePointer,\n writeAtPointer,\n type WriteOptions,\n} from \"./pointer\";\nimport type { PatchOperation } from \"./types\";\n\nconst OPS = new Set([\"add\", \"replace\", \"remove\", \"move\", \"copy\", \"test\"]);\n\n/** Narrows an unknown value to a patch operation, or explains why it is not one. */\nexport function checkPatchOperation(\n value: unknown,\n): { ok: true; op: PatchOperation } | { ok: false; error: string } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { ok: false, error: \"a patch operation must be a JSON object\" };\n }\n const op = value as Record<string, unknown>;\n if (typeof op[\"op\"] !== \"string\" || !OPS.has(op[\"op\"])) {\n return {\n ok: false,\n error: `\"op\" must be one of ${[...OPS].join(\", \")}`,\n };\n }\n if (typeof op[\"path\"] !== \"string\") {\n return { ok: false, error: '\"path\" must be a string' };\n }\n if (\n (op[\"op\"] === \"move\" || op[\"op\"] === \"copy\") &&\n typeof op[\"from\"] !== \"string\"\n ) {\n return { ok: false, error: `\"${op[\"op\"]}\" needs a string \"from\"` };\n }\n if (\n (op[\"op\"] === \"add\" || op[\"op\"] === \"replace\" || op[\"op\"] === \"test\") &&\n !(\"value\" in op)\n ) {\n return { ok: false, error: `\"${op[\"op\"]}\" needs a \"value\"` };\n }\n return { ok: true, op: op as PatchOperation };\n}\n\nconst clone = <T>(value: T): T =>\n value === undefined ? value : (JSON.parse(JSON.stringify(value)) as T);\n\n/**\n * Applies one RFC 6902 operation and returns the new document; the input is\n * not mutated and unchanged branches are shared. Throws when the operation\n * cannot apply. With `createMissing`, `add` and `replace` create missing\n * parent objects, which streaming relies on for paths like `/state/a/b`.\n */\nexport function applyPatchOperation<T>(\n doc: T,\n operation: PatchOperation,\n options: WriteOptions = {},\n): T {\n const path = parsePointer(operation.path);\n switch (operation.op) {\n case \"add\":\n return writeAtPointer(doc, path, \"add\", operation.value, options) as T;\n case \"replace\":\n return writeAtPointer(\n doc,\n path,\n \"replace\",\n operation.value,\n options,\n ) as T;\n case \"remove\":\n return writeAtPointer(doc, path, \"remove\") as T;\n case \"move\": {\n const from = parsePointer(operation.from);\n if (\n from.length < path.length &&\n from.every((token, index) => path[index] === token)\n ) {\n throw new Error(\"Cannot move a value into one of its own children\");\n }\n const value = getAtPointer(doc, from);\n if (value === undefined) {\n throw new Error(`Nothing to move at \"${operation.from}\"`);\n }\n const removed = writeAtPointer(doc, from, \"remove\");\n return writeAtPointer(removed, path, \"add\", value, options) as T;\n }\n case \"copy\": {\n const value = getAtPointer(doc, operation.from);\n if (value === undefined) {\n throw new Error(`Nothing to copy at \"${operation.from}\"`);\n }\n return writeAtPointer(doc, path, \"add\", clone(value), options) as T;\n }\n case \"test\":\n if (!deepEqual(getAtPointer(doc, path), operation.value)) {\n throw new Error(`Test failed at \"${operation.path}\"`);\n }\n return doc;\n }\n}\n\n/** Applies operations in order; throws on the first one that fails. */\nexport function applyPatch<T>(\n doc: T,\n operations: readonly PatchOperation[],\n options: WriteOptions = {},\n): T {\n return operations.reduce(\n (current, operation) => applyPatchOperation(current, operation, options),\n doc,\n );\n}\n"],"mappings":";;;AASA,MAAM,sBAAM,IAAI,IAAI;CAAC;CAAO;CAAW;CAAU;CAAQ;CAAQ;AAAM,CAAC;;AAGxE,SAAgB,oBACd,OACiE;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO;EAAE,IAAI;EAAO,OAAO;CAA0C;CAEvE,MAAM,KAAK;CACX,IAAI,OAAO,GAAG,UAAU,YAAY,CAAC,IAAI,IAAI,GAAG,KAAK,GACnD,OAAO;EACL,IAAI;EACJ,OAAO,uBAAuB,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI;CAClD;CAEF,IAAI,OAAO,GAAG,YAAY,UACxB,OAAO;EAAE,IAAI;EAAO,OAAO;CAA0B;CAEvD,KACG,GAAG,UAAU,UAAU,GAAG,UAAU,WACrC,OAAO,GAAG,YAAY,UAEtB,OAAO;EAAE,IAAI;EAAO,OAAO,IAAI,GAAG,MAAM;CAAyB;CAEnE,KACG,GAAG,UAAU,SAAS,GAAG,UAAU,aAAa,GAAG,UAAU,WAC9D,EAAE,WAAW,KAEb,OAAO;EAAE,IAAI;EAAO,OAAO,IAAI,GAAG,MAAM;CAAmB;CAE7D,OAAO;EAAE,IAAI;EAAU;CAAqB;AAC9C;AAEA,MAAM,SAAY,UAChB,UAAU,KAAA,IAAY,QAAS,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;;;;;;;AAQjE,SAAgB,oBACd,KACA,WACA,UAAwB,CAAC,GACtB;CACH,MAAM,OAAO,aAAa,UAAU,IAAI;CACxC,QAAQ,UAAU,IAAlB;EACE,KAAK,OACH,OAAO,eAAe,KAAK,MAAM,OAAO,UAAU,OAAO,OAAO;EAClE,KAAK,WACH,OAAO,eACL,KACA,MACA,WACA,UAAU,OACV,OACF;EACF,KAAK,UACH,OAAO,eAAe,KAAK,MAAM,QAAQ;EAC3C,KAAK,QAAQ;GACX,MAAM,OAAO,aAAa,UAAU,IAAI;GACxC,IACE,KAAK,SAAS,KAAK,UACnB,KAAK,OAAO,OAAO,UAAU,KAAK,WAAW,KAAK,GAElD,MAAM,IAAI,MAAM,kDAAkD;GAEpE,MAAM,QAAQ,aAAa,KAAK,IAAI;GACpC,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,uBAAuB,UAAU,KAAK,EAAE;GAE1D,MAAM,UAAU,eAAe,KAAK,MAAM,QAAQ;GAClD,OAAO,eAAe,SAAS,MAAM,OAAO,OAAO,OAAO;EAC5D;EACA,KAAK,QAAQ;GACX,MAAM,QAAQ,aAAa,KAAK,UAAU,IAAI;GAC9C,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,uBAAuB,UAAU,KAAK,EAAE;GAE1D,OAAO,eAAe,KAAK,MAAM,OAAO,MAAM,KAAK,GAAG,OAAO;EAC/D;EACA,KAAK;GACH,IAAI,CAAC,UAAU,aAAa,KAAK,IAAI,GAAG,UAAU,KAAK,GACrD,MAAM,IAAI,MAAM,mBAAmB,UAAU,KAAK,EAAE;GAEtD,OAAO;CACX;AACF;;AAGA,SAAgB,WACd,KACA,YACA,UAAwB,CAAC,GACtB;CACH,OAAO,WAAW,QACf,SAAS,cAAc,oBAAoB,SAAS,WAAW,OAAO,GACvE,GACF;AACF"}
@@ -0,0 +1,15 @@
1
+ export declare const PENDING_STYLE: {
2
+ readonly display: "inline-flex";
3
+ readonly gap: "4px";
4
+ readonly padding: "6px 2px";
5
+ };
6
+ export declare const PENDING_DOT_STYLE: {
7
+ readonly width: "5px";
8
+ readonly height: "5px";
9
+ readonly borderRadius: "50%";
10
+ readonly background: "currentColor";
11
+ readonly opacity: 0.2;
12
+ };
13
+ /** Pulses the pending indicator's dots in turn, and leaves them still under reduced motion; returns the cleanup. */
14
+ export declare function animatePendingDots(host: Element): () => void;
15
+ //# sourceMappingURL=pending.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending.d.ts","mappings":"AAEA,eAAO,MAAM,aAAa;;;;CAIhB;AAEV,eAAO,MAAM,iBAAiB;;;;;;CAMpB;AAEV;AACA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,IAAI","names":[],"ignoreList":[],"sources":["../../src/spec/pending.ts"],"sourcesContent":[null]}
@@ -0,0 +1,33 @@
1
+ // src/spec/pending.ts
2
+ const DOT_FRAMES = [
3
+ { opacity: .2 },
4
+ { opacity: .8 },
5
+ { opacity: .2 }
6
+ ];
7
+ const PENDING_STYLE = {
8
+ display: "inline-flex",
9
+ gap: "4px",
10
+ padding: "6px 2px"
11
+ };
12
+ const PENDING_DOT_STYLE = {
13
+ width: "5px",
14
+ height: "5px",
15
+ borderRadius: "50%",
16
+ background: "currentColor",
17
+ opacity: .2
18
+ };
19
+ /** Pulses the pending indicator's dots in turn, and leaves them still under reduced motion; returns the cleanup. */
20
+ function animatePendingDots(host) {
21
+ if (globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches) return () => {};
22
+ const animations = Array.from(host.children).flatMap((dot, index) => typeof dot.animate === "function" ? [dot.animate(DOT_FRAMES, {
23
+ duration: 1200,
24
+ delay: index * 150,
25
+ iterations: Infinity
26
+ })] : []);
27
+ return () => {
28
+ for (const animation of animations) animation.cancel();
29
+ };
30
+ }
31
+ export { PENDING_DOT_STYLE, PENDING_STYLE, animatePendingDots };
32
+
33
+ //# sourceMappingURL=pending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending.js","names":[],"sources":["../../src/spec/pending.ts"],"sourcesContent":["const DOT_FRAMES = [{ opacity: 0.2 }, { opacity: 0.8 }, { opacity: 0.2 }];\n\nexport const PENDING_STYLE = {\n display: \"inline-flex\",\n gap: \"4px\",\n padding: \"6px 2px\",\n} as const;\n\nexport const PENDING_DOT_STYLE = {\n width: \"5px\",\n height: \"5px\",\n borderRadius: \"50%\",\n background: \"currentColor\",\n opacity: 0.2,\n} as const;\n\n/** Pulses the pending indicator's dots in turn, and leaves them still under reduced motion; returns the cleanup. */\nexport function animatePendingDots(host: Element): () => void {\n if (globalThis.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches) {\n return () => {};\n }\n const animations = Array.from(host.children).flatMap((dot, index) =>\n typeof dot.animate === \"function\"\n ? [\n dot.animate(DOT_FRAMES, {\n duration: 1200,\n delay: index * 150,\n iterations: Infinity,\n }),\n ]\n : [],\n );\n return () => {\n for (const animation of animations) animation.cancel();\n };\n}\n"],"mappings":";AAAA,MAAM,aAAa;CAAC,EAAE,SAAS,GAAI;CAAG,EAAE,SAAS,GAAI;CAAG,EAAE,SAAS,GAAI;AAAC;AAExE,MAAa,gBAAgB;CAC3B,SAAS;CACT,KAAK;CACL,SAAS;AACX;AAEA,MAAa,oBAAoB;CAC/B,OAAO;CACP,QAAQ;CACR,cAAc;CACd,YAAY;CACZ,SAAS;AACX;;AAGA,SAAgB,mBAAmB,MAA2B;CAC5D,IAAI,WAAW,aAAa,kCAAkC,CAAC,EAAE,SAC/D,aAAa,CAAC;CAEhB,MAAM,aAAa,MAAM,KAAK,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK,UACzD,OAAO,IAAI,YAAY,aACnB,CACE,IAAI,QAAQ,YAAY;EACtB,UAAU;EACV,OAAO,QAAQ;EACf,YAAY;CACd,CAAC,CACH,IACA,CAAC,CACP;CACA,aAAa;EACX,KAAK,MAAM,aAAa,YAAY,UAAU,OAAO;CACvD;AACF"}
@@ -0,0 +1,17 @@
1
+ /** Splits a JSON Pointer (RFC 6901) into unescaped tokens. `""` is the whole document. */
2
+ export declare function parsePointer(pointer: string): string[];
3
+ export declare const escapePointerToken: (token: string | number) => string;
4
+ export declare const joinPointer: (tokens: readonly (string | number)[]) => string;
5
+ /** Reads the value at a pointer, or `undefined` when any step is missing. */
6
+ export declare function getAtPointer(doc: unknown, pointer: string | string[]): unknown;
7
+ export type WriteMode = "add" | "replace" | "remove";
8
+ export type WriteOptions = {
9
+ /** Create missing intermediate objects instead of failing. */
10
+ createMissing?: boolean;
11
+ };
12
+ /**
13
+ * Returns a copy of `doc` with the value at `tokens` added, replaced, or
14
+ * removed. Containers along the path are copied; everything else is shared.
15
+ */
16
+ export declare function writeAtPointer(doc: unknown, tokens: readonly string[], mode: WriteMode, value?: unknown, options?: WriteOptions, arrayParents?: readonly string[]): unknown;
17
+ //# sourceMappingURL=pointer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointer.d.ts","mappings":"AAAA;AACA,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;AAWvD,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,MAAM,KAAG,MACJ;AAExD,eAAO,MAAM,WAAW,GAAI,QAAQ,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAG,MACF;AAOjE;AACA,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GACzB,OAAO;AAmBV,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ;AAEpD,MAAM,MAAM,YAAY,GAAG;IACzB;IACA,aAAa,CAAC,EAAE,OAAO;CACxB;AAID;;;;AAIA,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,IAAI,EAAE,SAAS,EACf,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,GAAE,YAAiB,EAC1B,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,OAAO","names":[],"ignoreList":[],"sources":["../../src/spec/pointer.ts"],"sourcesContent":[null]}