@gyral/core 0.3.0 → 0.3.1

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 (381) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/NOTICE +19 -0
  3. package/README.md +30 -5
  4. package/dist/call.d.ts +32 -0
  5. package/dist/call.js +63 -0
  6. package/dist/call.js.map +1 -0
  7. package/dist/children.d.ts +25 -3
  8. package/dist/children.js +18 -1
  9. package/dist/children.js.map +1 -1
  10. package/dist/cli/gyral-types.d.ts +2 -0
  11. package/dist/cli/gyral-types.js +23 -0
  12. package/dist/cli/gyral-types.js.map +1 -0
  13. package/dist/command.d.ts +54 -12
  14. package/dist/command.js +25 -11
  15. package/dist/command.js.map +1 -1
  16. package/dist/compiler/ast.d.ts +2 -0
  17. package/dist/compiler/ast.js +5 -1
  18. package/dist/compiler/ast.js.map +1 -1
  19. package/dist/compiler/compile.d.ts +5 -2
  20. package/dist/compiler/compile.js +36 -17
  21. package/dist/compiler/compile.js.map +1 -1
  22. package/dist/compiler/component-entries.d.ts +7 -0
  23. package/dist/compiler/component-entries.js +42 -0
  24. package/dist/compiler/component-entries.js.map +1 -0
  25. package/dist/compiler/components.d.ts +4 -0
  26. package/dist/compiler/components.js +182 -0
  27. package/dist/compiler/components.js.map +1 -0
  28. package/dist/compiler/facts.d.ts +31 -0
  29. package/dist/compiler/facts.js +285 -0
  30. package/dist/compiler/facts.js.map +1 -0
  31. package/dist/compiler/features.d.ts +34 -0
  32. package/dist/compiler/features.js +158 -0
  33. package/dist/compiler/features.js.map +1 -0
  34. package/dist/compiler/hooks.d.ts +4 -0
  35. package/dist/compiler/hooks.js +144 -89
  36. package/dist/compiler/hooks.js.map +1 -1
  37. package/dist/compiler/loader-source.d.ts +14 -0
  38. package/dist/compiler/loader-source.js +86 -0
  39. package/dist/compiler/loader-source.js.map +1 -0
  40. package/dist/compiler/locate.d.ts +14 -0
  41. package/dist/compiler/locate.js +78 -0
  42. package/dist/compiler/locate.js.map +1 -0
  43. package/dist/compiler/parse5-check.d.ts +1 -1
  44. package/dist/compiler/parse5-check.js +8 -3
  45. package/dist/compiler/parse5-check.js.map +1 -1
  46. package/dist/compiler/prop-schemas.d.ts +4 -0
  47. package/dist/compiler/prop-schemas.js +108 -0
  48. package/dist/compiler/prop-schemas.js.map +1 -0
  49. package/dist/compiler/scan.d.ts +13 -3
  50. package/dist/compiler/scan.js +21 -13
  51. package/dist/compiler/scan.js.map +1 -1
  52. package/dist/compiler/scope.d.ts +8 -0
  53. package/dist/compiler/scope.js +116 -0
  54. package/dist/compiler/scope.js.map +1 -0
  55. package/dist/compiler/tag-map.d.ts +41 -0
  56. package/dist/compiler/tag-map.js +126 -0
  57. package/dist/compiler/tag-map.js.map +1 -0
  58. package/dist/components-ready.d.ts +7 -0
  59. package/dist/components-ready.js +19 -0
  60. package/dist/components-ready.js.map +1 -0
  61. package/dist/define.d.ts +13 -4
  62. package/dist/define.js +8 -12
  63. package/dist/define.js.map +1 -1
  64. package/dist/devtools-events.d.ts +8 -0
  65. package/dist/devtools-events.js.map +1 -1
  66. package/dist/devtools-off.d.ts +1 -0
  67. package/dist/devtools-off.js +1 -0
  68. package/dist/devtools-off.js.map +1 -1
  69. package/dist/devtools.d.ts +3 -0
  70. package/dist/devtools.js +14 -0
  71. package/dist/devtools.js.map +1 -1
  72. package/dist/drivers-scope.js.map +1 -1
  73. package/dist/element-internals.d.ts +1 -0
  74. package/dist/element-internals.js +11 -0
  75. package/dist/element-internals.js.map +1 -0
  76. package/dist/element-types.d.ts +6 -2
  77. package/dist/element-types.js.map +1 -1
  78. package/dist/element.js +80 -27
  79. package/dist/element.js.map +1 -1
  80. package/dist/error-view.d.ts +10 -0
  81. package/dist/error-view.js +30 -0
  82. package/dist/error-view.js.map +1 -0
  83. package/dist/errors.d.ts +21 -0
  84. package/dist/errors.js +47 -0
  85. package/dist/errors.js.map +1 -0
  86. package/dist/errors.json +289 -0
  87. package/dist/eslint/imports.d.ts +2 -0
  88. package/dist/eslint/imports.js +2 -2
  89. package/dist/eslint/imports.js.map +1 -1
  90. package/dist/eslint/index.d.ts +2 -1
  91. package/dist/eslint/index.js +11 -3
  92. package/dist/eslint/index.js.map +1 -1
  93. package/dist/eslint/template-rule.d.ts +1 -0
  94. package/dist/eslint/template-rule.js +15 -5
  95. package/dist/eslint/template-rule.js.map +1 -1
  96. package/dist/eslint/unused-intent-rule.d.ts +2 -0
  97. package/dist/eslint/unused-intent-rule.js +186 -0
  98. package/dist/eslint/unused-intent-rule.js.map +1 -0
  99. package/dist/features.d.ts +2 -2
  100. package/dist/features.js.map +1 -1
  101. package/dist/focus.d.ts +6 -0
  102. package/dist/focus.js +32 -2
  103. package/dist/focus.js.map +1 -1
  104. package/dist/form-associated.d.ts +46 -0
  105. package/dist/form-associated.js +77 -0
  106. package/dist/form-associated.js.map +1 -0
  107. package/dist/forms.d.ts +42 -30
  108. package/dist/forms.js +85 -41
  109. package/dist/forms.js.map +1 -1
  110. package/dist/frame-lane.d.ts +11 -0
  111. package/dist/frame-lane.js +28 -0
  112. package/dist/frame-lane.js.map +1 -0
  113. package/dist/head.d.ts +61 -0
  114. package/dist/head.js +85 -0
  115. package/dist/head.js.map +1 -0
  116. package/dist/host-model.d.ts +34 -5
  117. package/dist/host-model.js +146 -23
  118. package/dist/host-model.js.map +1 -1
  119. package/dist/hydration-client.js +2 -2
  120. package/dist/hydration-client.js.map +1 -1
  121. package/dist/hydration-loader.d.ts +13 -0
  122. package/dist/hydration-loader.js +58 -0
  123. package/dist/hydration-loader.js.map +1 -0
  124. package/dist/hydration-off.d.ts +8 -0
  125. package/dist/hydration-off.js +25 -0
  126. package/dist/hydration-off.js.map +1 -0
  127. package/dist/hydration.d.ts +0 -12
  128. package/dist/hydration.js +3 -52
  129. package/dist/hydration.js.map +1 -1
  130. package/dist/index.d.ts +20 -24
  131. package/dist/index.js +10 -19
  132. package/dist/index.js.map +1 -1
  133. package/dist/indexeddb-types.d.ts +97 -0
  134. package/dist/indexeddb-types.js +2 -0
  135. package/dist/indexeddb-types.js.map +1 -0
  136. package/dist/indexeddb.d.ts +5 -0
  137. package/dist/indexeddb.js +118 -0
  138. package/dist/indexeddb.js.map +1 -0
  139. package/dist/intent.d.ts +41 -19
  140. package/dist/intent.js +144 -106
  141. package/dist/intent.js.map +1 -1
  142. package/dist/internal/indexeddb-open.d.ts +8 -0
  143. package/dist/internal/indexeddb-open.js +96 -0
  144. package/dist/internal/indexeddb-open.js.map +1 -0
  145. package/dist/internal/indexeddb-transaction.d.ts +4 -0
  146. package/dist/internal/indexeddb-transaction.js +134 -0
  147. package/dist/internal/indexeddb-transaction.js.map +1 -0
  148. package/dist/internal/interpreter.d.ts +12 -4
  149. package/dist/internal/interpreter.js +36 -48
  150. package/dist/internal/interpreter.js.map +1 -1
  151. package/dist/internal/token.d.ts +1 -0
  152. package/dist/internal/token.js +7 -0
  153. package/dist/internal/token.js.map +1 -0
  154. package/dist/internal.d.ts +20 -0
  155. package/dist/internal.js +22 -0
  156. package/dist/internal.js.map +1 -0
  157. package/dist/invoker-fallback-used.d.ts +4 -0
  158. package/dist/invoker-fallback-used.js +10 -0
  159. package/dist/invoker-fallback-used.js.map +1 -0
  160. package/dist/invoker-fallback.d.ts +4 -0
  161. package/dist/invoker-fallback.js +15 -0
  162. package/dist/invoker-fallback.js.map +1 -0
  163. package/dist/light-dom.d.ts +1 -1
  164. package/dist/light-dom.js.map +1 -1
  165. package/dist/prop.d.ts +30 -7
  166. package/dist/prop.js +19 -0
  167. package/dist/prop.js.map +1 -1
  168. package/dist/props.d.ts +8 -3
  169. package/dist/props.js +22 -15
  170. package/dist/props.js.map +1 -1
  171. package/dist/retry.d.ts +15 -0
  172. package/dist/retry.js +48 -0
  173. package/dist/retry.js.map +1 -0
  174. package/dist/scheduler.d.ts +7 -3
  175. package/dist/scheduler.js +38 -56
  176. package/dist/scheduler.js.map +1 -1
  177. package/dist/script-json.d.ts +2 -0
  178. package/dist/script-json.js +13 -0
  179. package/dist/script-json.js.map +1 -0
  180. package/dist/server-component.d.ts +1 -1
  181. package/dist/server-component.js +45 -14
  182. package/dist/server-component.js.map +1 -1
  183. package/dist/server.d.ts +8 -2
  184. package/dist/server.js +11 -2
  185. package/dist/server.js.map +1 -1
  186. package/dist/settled.d.ts +4 -3
  187. package/dist/settled.js +48 -26
  188. package/dist/settled.js.map +1 -1
  189. package/dist/spec-features-used.d.ts +21 -0
  190. package/dist/spec-features-used.js +50 -0
  191. package/dist/spec-features-used.js.map +1 -0
  192. package/dist/spec-features.d.ts +28 -0
  193. package/dist/spec-features.js +42 -0
  194. package/dist/spec-features.js.map +1 -0
  195. package/dist/states.d.ts +3 -5
  196. package/dist/states.js +22 -8
  197. package/dist/states.js.map +1 -1
  198. package/dist/storage.d.ts +69 -0
  199. package/dist/storage.js +158 -0
  200. package/dist/storage.js.map +1 -0
  201. package/dist/store-binding.js +2 -2
  202. package/dist/store-binding.js.map +1 -1
  203. package/dist/store-scope.d.ts +2 -4
  204. package/dist/store-scope.js +6 -16
  205. package/dist/store-scope.js.map +1 -1
  206. package/dist/store.js +36 -8
  207. package/dist/store.js.map +1 -1
  208. package/dist/subscription.d.ts +35 -0
  209. package/dist/subscription.js +74 -0
  210. package/dist/subscription.js.map +1 -0
  211. package/dist/subscriptions.d.ts +26 -0
  212. package/dist/subscriptions.js +98 -0
  213. package/dist/subscriptions.js.map +1 -0
  214. package/dist/transitions.d.ts +4 -5
  215. package/dist/transitions.js +10 -4
  216. package/dist/transitions.js.map +1 -1
  217. package/dist/types.d.ts +124 -27
  218. package/dist/types.js.map +1 -1
  219. package/dist/use-dialogs.d.ts +2 -0
  220. package/dist/use-dialogs.js +9 -0
  221. package/dist/use-dialogs.js.map +1 -0
  222. package/dist/use-disposal.d.ts +2 -0
  223. package/dist/use-disposal.js +9 -0
  224. package/dist/use-disposal.js.map +1 -0
  225. package/dist/use-error-view.d.ts +2 -0
  226. package/dist/use-error-view.js +9 -0
  227. package/dist/use-error-view.js.map +1 -0
  228. package/dist/use-errored.d.ts +2 -0
  229. package/dist/use-errored.js +9 -0
  230. package/dist/use-errored.js.map +1 -0
  231. package/dist/use-form-associated.d.ts +2 -0
  232. package/dist/use-form-associated.js +7 -0
  233. package/dist/use-form-associated.js.map +1 -0
  234. package/dist/use-frame-lane.d.ts +2 -0
  235. package/dist/use-frame-lane.js +10 -0
  236. package/dist/use-frame-lane.js.map +1 -0
  237. package/dist/use-invokers.d.ts +2 -0
  238. package/dist/use-invokers.js +10 -0
  239. package/dist/use-invokers.js.map +1 -0
  240. package/dist/use-states.d.ts +2 -0
  241. package/dist/use-states.js +10 -0
  242. package/dist/use-states.js.map +1 -0
  243. package/dist/use-subscriptions.d.ts +2 -0
  244. package/dist/use-subscriptions.js +10 -0
  245. package/dist/use-subscriptions.js.map +1 -0
  246. package/dist/use-transitions.d.ts +2 -0
  247. package/dist/use-transitions.js +10 -0
  248. package/dist/use-transitions.js.map +1 -0
  249. package/dist/view/attributes.d.ts +5 -0
  250. package/dist/view/attributes.js +5 -0
  251. package/dist/view/attributes.js.map +1 -1
  252. package/dist/view/compiled.d.ts +1 -1
  253. package/dist/view/compiled.js +1 -1
  254. package/dist/view/compiled.js.map +1 -1
  255. package/dist/view/index.d.ts +9 -4
  256. package/dist/view/index.js +9 -3
  257. package/dist/view/index.js.map +1 -1
  258. package/dist/view/loc.d.ts +9 -0
  259. package/dist/view/loc.js +50 -0
  260. package/dist/view/loc.js.map +1 -0
  261. package/dist/view/message.d.ts +17 -0
  262. package/dist/view/message.js +40 -0
  263. package/dist/view/message.js.map +1 -0
  264. package/dist/view/messages.d.ts +43 -0
  265. package/dist/view/messages.js +84 -0
  266. package/dist/view/messages.js.map +1 -0
  267. package/dist/view/normalize/check.d.ts +5 -2
  268. package/dist/view/normalize/check.js +6 -3
  269. package/dist/view/normalize/check.js.map +1 -1
  270. package/dist/view/normalize/emit.d.ts +2 -0
  271. package/dist/view/normalize/emit.js +40 -11
  272. package/dist/view/normalize/emit.js.map +1 -1
  273. package/dist/view/normalize/errors.js +1 -1
  274. package/dist/view/normalize/errors.js.map +1 -1
  275. package/dist/view/normalize/id.d.ts +5 -2
  276. package/dist/view/normalize/id.js +9 -2
  277. package/dist/view/normalize/id.js.map +1 -1
  278. package/dist/view/normalize/normalize.d.ts +7 -4
  279. package/dist/view/normalize/normalize.js +15 -9
  280. package/dist/view/normalize/normalize.js.map +1 -1
  281. package/dist/view/normalize/rules.d.ts +9 -0
  282. package/dist/view/normalize/rules.js +35 -5
  283. package/dist/view/normalize/rules.js.map +1 -1
  284. package/dist/view/normalize/svg.d.ts +14 -0
  285. package/dist/view/normalize/svg.js +40 -0
  286. package/dist/view/normalize/svg.js.map +1 -0
  287. package/dist/view/normalize/tokens.d.ts +5 -1
  288. package/dist/view/normalize/tokens.js.map +1 -1
  289. package/dist/view/normalize/tree.d.ts +8 -2
  290. package/dist/view/normalize/tree.js +58 -13
  291. package/dist/view/normalize/tree.js.map +1 -1
  292. package/dist/view/normalize/types.d.ts +24 -4
  293. package/dist/view/normalize/types.js +4 -1
  294. package/dist/view/normalize/types.js.map +1 -1
  295. package/dist/view/normalize/whitespace.js +4 -3
  296. package/dist/view/normalize/whitespace.js.map +1 -1
  297. package/dist/view/prepare-stub.js +2 -2
  298. package/dist/view/prepare-stub.js.map +1 -1
  299. package/dist/view/prepare.d.ts +5 -2
  300. package/dist/view/prepare.js +9 -3
  301. package/dist/view/prepare.js.map +1 -1
  302. package/dist/view/registry.d.ts +15 -0
  303. package/dist/view/registry.js +10 -2
  304. package/dist/view/registry.js.map +1 -1
  305. package/dist/view/render/adopt-attr.js +7 -1
  306. package/dist/view/render/adopt-attr.js.map +1 -1
  307. package/dist/view/render/adopt.js +21 -4
  308. package/dist/view/render/adopt.js.map +1 -1
  309. package/dist/view/render/attr-parts.d.ts +12 -1
  310. package/dist/view/render/attr-parts.js +38 -5
  311. package/dist/view/render/attr-parts.js.map +1 -1
  312. package/dist/view/render/child-part.js +5 -2
  313. package/dist/view/render/child-part.js.map +1 -1
  314. package/dist/view/render/dispose.d.ts +3 -0
  315. package/dist/view/render/dispose.js +76 -0
  316. package/dist/view/render/dispose.js.map +1 -0
  317. package/dist/view/render/hook-part.d.ts +3 -1
  318. package/dist/view/render/hook-part.js +21 -5
  319. package/dist/view/render/hook-part.js.map +1 -1
  320. package/dist/view/render/hook-run.d.ts +2 -0
  321. package/dist/view/render/hook-run.js +72 -0
  322. package/dist/view/render/hook-run.js.map +1 -0
  323. package/dist/view/render/hooks.d.ts +73 -5
  324. package/dist/view/render/hooks.js +79 -17
  325. package/dist/view/render/hooks.js.map +1 -1
  326. package/dist/view/render/hydrate.js +2 -2
  327. package/dist/view/render/hydrate.js.map +1 -1
  328. package/dist/view/render/keyed.d.ts +6 -0
  329. package/dist/view/render/keyed.js +15 -0
  330. package/dist/view/render/keyed.js.map +1 -0
  331. package/dist/view/render/mismatch.js +5 -3
  332. package/dist/view/render/mismatch.js.map +1 -1
  333. package/dist/view/render/modal.d.ts +3 -0
  334. package/dist/view/render/modal.js +39 -0
  335. package/dist/view/render/modal.js.map +1 -0
  336. package/dist/view/render/raw.d.ts +4 -1
  337. package/dist/view/render/raw.js +6 -2
  338. package/dist/view/render/raw.js.map +1 -1
  339. package/dist/view/render/render.d.ts +4 -1
  340. package/dist/view/render/render.js +17 -12
  341. package/dist/view/render/render.js.map +1 -1
  342. package/dist/view/render/toggles.d.ts +6 -0
  343. package/dist/view/render/toggles.js +6 -0
  344. package/dist/view/render/toggles.js.map +1 -0
  345. package/dist/view/render/warn.d.ts +14 -0
  346. package/dist/view/render/warn.js +45 -0
  347. package/dist/view/render/warn.js.map +1 -1
  348. package/dist/view/server/component.d.ts +6 -3
  349. package/dist/view/server/component.js +29 -7
  350. package/dist/view/server/component.js.map +1 -1
  351. package/dist/view/server/index.d.ts +20 -0
  352. package/dist/view/server/index.js +12 -4
  353. package/dist/view/server/index.js.map +1 -1
  354. package/dist/view/server/tags.d.ts +13 -2
  355. package/dist/view/server/tags.js +13 -2
  356. package/dist/view/server/tags.js.map +1 -1
  357. package/dist/view/server/values.d.ts +2 -0
  358. package/dist/view/server/values.js +8 -0
  359. package/dist/view/server/values.js.map +1 -1
  360. package/dist/view/server/writer.d.ts +9 -4
  361. package/dist/view/server/writer.js +33 -18
  362. package/dist/view/server/writer.js.map +1 -1
  363. package/dist/view/template-element.d.ts +7 -0
  364. package/dist/view/template-element.js +25 -5
  365. package/dist/view/template-element.js.map +1 -1
  366. package/dist/view/template.d.ts +10 -2
  367. package/dist/view/template.js +62 -8
  368. package/dist/view/template.js.map +1 -1
  369. package/dist/view/trusted-html.d.ts +6 -0
  370. package/dist/view/trusted-html.js +34 -0
  371. package/dist/view/trusted-html.js.map +1 -0
  372. package/dist/vite.d.ts +38 -31
  373. package/dist/vite.js +198 -31
  374. package/dist/vite.js.map +1 -1
  375. package/package.json +34 -3
  376. package/dist/hooks/labelled-by.d.ts +0 -13
  377. package/dist/hooks/labelled-by.js +0 -51
  378. package/dist/hooks/labelled-by.js.map +0 -1
  379. package/dist/random.d.ts +0 -13
  380. package/dist/random.js +0 -19
  381. package/dist/random.js.map +0 -1
package/dist/intent.js CHANGED
@@ -1,6 +1,8 @@
1
+ import { loadInvokerShim } from '#invoker-fallback';
1
2
  import { commandOf, invokersSupported } from './invokers.js';
2
- import { hold } from './scheduler.js';
3
- /** Event a child component dispatches on its host to send an output up (ADR 0010). */
3
+ import { isLightComponent } from './light-dom.js';
4
+ import { message } from './view/index.js';
5
+ /** Event a child component dispatches on its host to send an output up (ADR 0010); public. */
4
6
  export const OUTPUT_EVENT = 'gyral-output';
5
7
  /** Default triggers (`defaultTrigger`): every component root listens for these. */
6
8
  export const DEFAULT_EVENTS = [
@@ -12,9 +14,11 @@ export const DEFAULT_EVENTS = [
12
14
  ];
13
15
  /**
14
16
  * The events `data-intent-on` usually names. A component root listens (capture phase, so
15
- * non-bubbling events such as `toggle` are seen too) for the defaults, for `spec.events`, and
16
- * for the `data-intent-on` values in the templates it renders; a template that binds
17
- * `data-intent-on` dynamically adds all of these (view/05-element.md "Intent events").
17
+ * non-bubbling events such as `toggle` are seen too) for the defaults and for the events its
18
+ * templates name in `data-intent-<event>` attributes and static `data-intent-on` values; a
19
+ * template that binds `data-intent-on` dynamically adds all of these. Any other event with a
20
+ * bound intent takes a per-event attribute, `data-intent-pointerdown=${i.Press}`, whose name the
21
+ * template carries (view/05-element.md "Intent events").
18
22
  */
19
23
  export const INTENT_EVENTS = [
20
24
  ...DEFAULT_EVENTS,
@@ -25,49 +29,56 @@ export const INTENT_EVENTS = [
25
29
  'toggle',
26
30
  'command',
27
31
  ];
28
- /** A static `data-intent-on` value, quoted or not (event names have no spaces or quotes). */
29
- const INTENT_ON = /\sdata-intent-on=["']?([^"'\s>]+)/gi;
32
+ /**
33
+ * A static `data-intent-<event>` attribute: a per-event intent (the event is in the name), or
34
+ * `data-intent-on` (its value is the event, or a quoted list of them separated by spaces).
35
+ */
36
+ const INTENT_ATTR = /\sdata-intent-([\w-]+)=(["']?)(.*?)\2(?=[\s/>])/gi;
37
+ // Names separated by any whitespace; none empty, so no listener for "".
38
+ const eventList = (value) => value.match(/\S+/g) ?? [];
30
39
  const eventsByTemplate = new WeakMap();
31
- /** The intent events a template's (or `raw()` markup's) `data-intent-on` attributes name. */
40
+ /**
41
+ * The intent events a template's (or `raw()` markup's) `data-intent-<event>` and
42
+ * `data-intent-on` attributes name. A bound attribute's name is in its part; a bound
43
+ * `data-intent-on` adds every intent event.
44
+ */
32
45
  export function eventsOf(markup) {
33
46
  let events = eventsByTemplate.get(markup);
34
47
  if (events === undefined) {
35
- events = markup.parts?.some((p) => p[2] === 'data-intent-on') // a bound attribute's name
36
- ? INTENT_EVENTS
37
- : Array.from(markup.html.matchAll(INTENT_ON), (m) => m[1] ?? '');
48
+ // Bound attributes are in the parts, by name: listed as ` name=* ` and matched like markup
49
+ // (other parts' names are never intent attributes; `*` marks a bound data-intent-on).
50
+ const bound = markup.parts?.map((p) => ` ${p[2]}=* `).join('') ?? '';
51
+ events = Array.from((markup.html + bound).matchAll(INTENT_ATTR)).flatMap(([, event, , value]) => event !== 'on'
52
+ ? event
53
+ : value === '*'
54
+ ? INTENT_EVENTS
55
+ : eventList(value));
38
56
  eventsByTemplate.set(markup, events);
39
57
  }
40
58
  return events;
41
59
  }
42
60
  const isToggle = (el) => el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');
43
- // ToggleEvent (popover, <details>) is not Baseline widely available; read it structurally.
44
- function toggleState(event) {
45
- if (event.type !== 'toggle' || !('newState' in event))
46
- return undefined;
47
- return event.newState === 'open' ? 'open' : 'closed';
48
- }
49
61
  const CLICK_INPUT_TYPES = /^(button|submit|reset|image)$/;
50
- /** The event that fires an intent unless `data-intent-on` overrides it. */
62
+ /**
63
+ * The event that fires an intent unless `data-intent-on` overrides it. Custom elements
64
+ * (autonomous: the name has a dash) talk to their parent via outputs.
65
+ */
51
66
  export function defaultTrigger(el) {
52
- switch (el.localName) {
53
- case 'form':
54
- return 'submit';
55
- case 'select':
56
- return 'change';
57
- case 'textarea':
58
- return 'input';
59
- case 'input': {
60
- const type = el.type;
61
- if (CLICK_INPUT_TYPES.test(type))
62
- return 'click';
63
- return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
64
- }
65
- }
66
- // Custom elements (autonomous: the name has a dash) talk to their parent via outputs.
67
- return el.localName.includes('-') ? OUTPUT_EVENT : 'click';
67
+ const name = el.localName;
68
+ return name === 'form'
69
+ ? 'submit'
70
+ : name === 'select' || isToggle(el)
71
+ ? 'change'
72
+ : name === 'textarea' ||
73
+ (name === 'input' && !CLICK_INPUT_TYPES.test(el.type))
74
+ ? 'input'
75
+ : name.includes('-')
76
+ ? OUTPUT_EVENT
77
+ : 'click';
68
78
  }
69
- function triggerOf(el) {
70
- return el.getAttribute('data-intent-on') ?? defaultTrigger(el);
79
+ /** The events that fire `el`'s intent: its `data-intent-on` list, or its default trigger. */
80
+ function triggersOf(el) {
81
+ return eventList(el.getAttribute('data-intent-on') ?? defaultTrigger(el));
71
82
  }
72
83
  /** Every class `define()` creates, so intent lookup can tell where a component begins. */
73
84
  const hostClasses = new WeakSet();
@@ -91,9 +102,11 @@ export function hostDepth(el) {
91
102
  /**
92
103
  * Does `node` belong to the component whose intent root is `root`? `root` is the component's
93
104
  * shadow root, or the host itself for light-DOM components (ADR 0014). The node must be in the
94
- * same tree (not inside a nested shadow root) with no other Gyral host between them: a nested
95
- * component, light-DOM or shadow, owns its own content. The nested host element itself (with
96
- * the parent's `data-intent` on it) still belongs to the parent.
105
+ * same tree (not inside a nested shadow root) with no light-DOM Gyral host between them: a
106
+ * light-DOM component owns its children (it rendered them). Children of a shadow-DOM host are
107
+ * the outer template's content, slotted into it, so they stay the outer component's (a form or
108
+ * a child component slotted into a card). The nested host element itself (with the parent's
109
+ * `data-intent` on it) still belongs to the parent.
97
110
  */
98
111
  function ownedBy(node, root) {
99
112
  const tree = root instanceof ShadowRoot ? root : root.getRootNode();
@@ -102,28 +115,11 @@ function ownedBy(node, root) {
102
115
  for (let p = node.parentNode; p !== null; p = p.parentNode) {
103
116
  if (p === root)
104
117
  return true;
105
- if (isGyralHost(p))
118
+ if (isGyralHost(p) && isLightComponent(p.constructor))
106
119
  return false;
107
120
  }
108
121
  return false;
109
122
  }
110
- /**
111
- * Finds the nearest `data-intent` element for this event that belongs to `root`. Elements
112
- * inside nested components are ignored: that is component isolation.
113
- */
114
- export function findIntentElement(event, root) {
115
- for (const node of event.composedPath()) {
116
- if (node === root)
117
- return undefined;
118
- if (node instanceof Element &&
119
- node.hasAttribute('data-intent') &&
120
- triggerOf(node) === event.type &&
121
- ownedBy(node, root)) {
122
- return node;
123
- }
124
- }
125
- return undefined;
126
- }
127
123
  function valueOf(el) {
128
124
  if (el instanceof HTMLInputElement ||
129
125
  el instanceof HTMLSelectElement ||
@@ -133,29 +129,50 @@ function valueOf(el) {
133
129
  }
134
130
  return undefined;
135
131
  }
136
- /** Reads an event into an IntentInput. Form submissions are prevented and turned into FormData. */
137
- export function readIntent(event, root) {
138
- const target = findIntentElement(event, root);
139
- const name = target?.getAttribute('data-intent');
140
- if (target === undefined || name == null)
141
- return undefined;
142
- let formData;
143
- if (target instanceof HTMLFormElement && event instanceof SubmitEvent) {
144
- event.preventDefault();
145
- formData = new FormData(target, event.submitter);
132
+ /**
133
+ * Reads an event into an IntentInput for the nearest element on its path that names an intent
134
+ * for it and belongs to `root`: its `data-intent-<type>`, else its `data-intent` when the
135
+ * event is one of its triggers. With `after`, the search starts past that element (the intent
136
+ * whose parser declined). Elements inside nested components are ignored: that is component
137
+ * isolation. Form submissions are prevented and turned into FormData.
138
+ */
139
+ export function readIntent(event, root, after) {
140
+ const path = event.composedPath();
141
+ // indexOf(undefined) is -1: without `after`, the whole path.
142
+ for (const target of path.slice(path.indexOf(after) + 1)) {
143
+ if (target === root)
144
+ return undefined;
145
+ if (!(target instanceof Element))
146
+ continue;
147
+ // An output is the dispatching child's own: only the element it was dispatched on (the
148
+ // child host, where the direct parent's `data-intent` sits) takes it, never an ancestor.
149
+ if (event.type === OUTPUT_EVENT && target !== path[0])
150
+ continue;
151
+ const name = target.getAttribute('data-intent-' + event.type) ??
152
+ (triggersOf(target).includes(event.type) ? target.getAttribute('data-intent') : null);
153
+ if (name === null || !ownedBy(target, root))
154
+ continue;
155
+ let formData;
156
+ if (target instanceof HTMLFormElement && event instanceof SubmitEvent) {
157
+ event.preventDefault();
158
+ formData = new FormData(target, event.submitter);
159
+ }
160
+ return {
161
+ name,
162
+ event,
163
+ target,
164
+ value: valueOf(target),
165
+ checked: isToggle(target) ? target.checked : undefined,
166
+ formData,
167
+ detail: event instanceof CustomEvent ? event.detail : undefined,
168
+ // Read structurally too: only keyboard events carry `key` to elements.
169
+ key: event.key,
170
+ // ToggleEvent (popover, <details>) is not Baseline widely available: read it structurally.
171
+ newState: event.newState,
172
+ command: commandOf(event),
173
+ };
146
174
  }
147
- return {
148
- name,
149
- event,
150
- target,
151
- value: valueOf(target),
152
- checked: isToggle(target) ? target.checked : undefined,
153
- formData,
154
- detail: event instanceof CustomEvent ? event.detail : undefined,
155
- key: event instanceof KeyboardEvent ? event.key : undefined,
156
- newState: toggleState(event),
157
- command: commandOf(event),
158
- };
175
+ return undefined;
159
176
  }
160
177
  // Any property read returns its own name, so `intents.Increment === 'Increment'`.
161
178
  // Types restrict reads to real message tags; a tag without a parser warns at event time.
@@ -163,12 +180,14 @@ export const intentNames = new Proxy({}, { get: (_target, key) => (typeof key ==
163
180
  /**
164
181
  * A component's intent names as a module-level constant, so pure list rows can name intents
165
182
  * without passing them through `pick` (view/03-lists.md "Rows must be pure"). The same object
166
- * the view gets as `i`:
183
+ * the view gets as `i`, typed by the component's class (ADR 0023). A row that names intents
184
+ * this way needs an explicit return type, or the row, the view and the class infer each other
185
+ * in a circle:
167
186
  *
168
- * const i = intents<Msg>();
169
- * const Row = (t: Todo) => html`<input value=${t.id} data-intent=${i.Toggle} />`;
187
+ * const i = intentsOf<typeof TodoList>();
188
+ * const Row = (t: Todo): TemplateResult => html`<input value=${t.id} data-intent=${i.Toggle} />`;
170
189
  */
171
- export function intents() {
190
+ export function intentsOf() {
172
191
  return intentNames;
173
192
  }
174
193
  /**
@@ -182,31 +201,50 @@ export function listenForIntents(root, listening, types, handler) {
182
201
  continue;
183
202
  listening.add(type);
184
203
  root.addEventListener(type, handler, { capture: true });
185
- if (type === 'command' && !invokersSupported()) {
186
- hold(import('./invokers-shim.js').then((shim) => {
187
- shim.shimInvokers(root);
188
- }, reportError));
189
- }
204
+ if (type === 'command' && !invokersSupported())
205
+ loadInvokerShim(root);
190
206
  }
191
207
  }
192
- /** Parses one event with its matching parser and delivers the message (sync or async). */
193
- export function handleIntent(event, root, parsers, tag, deliver) {
194
- const input = readIntent(event, root);
195
- if (input === undefined)
196
- return;
197
- const parser = parsers[input.name];
198
- if (parser === undefined) {
199
- console.warn(`<${tag}> has no intent parser for data-intent="${input.name}".`);
200
- return;
201
- }
202
- const result = parser(input);
203
- if (result instanceof Promise) {
204
- result.then(deliver, (error) => {
205
- console.error(`<${tag}> intent parser for "${input.name}" failed`, error);
206
- });
207
- }
208
- else {
209
- deliver(result);
208
+ /**
209
+ * Parses one event with its matching parser and delivers the message (sync or async). A
210
+ * parser that returns `undefined` synchronously declines: the next intent outward for the same
211
+ * event gets it, up to the component root. A promise can't decline. The model's `ctx()` is
212
+ * built only when a parser runs, so its props are those at event time.
213
+ */
214
+ export function handleIntent(event, root, parsers, tag, model, deliver) {
215
+ for (let input; (input = readIntent(event, root, input?.target));) {
216
+ const { name } = input;
217
+ const parser = parsers[name];
218
+ if (parser === undefined) {
219
+ console.warn(message(11, tag, name));
220
+ return;
221
+ }
222
+ // A parser that throws or rejects is reported (ADR 0024) and sends nothing.
223
+ const failed = (cause) => {
224
+ model.fail(cause, 'parse', message(12, tag, name), name);
225
+ };
226
+ let result;
227
+ try {
228
+ // `true`: the intent sends its own tag (types allow it only for field-less variants).
229
+ result =
230
+ parser === true
231
+ ? { _tag: name }
232
+ : parser(input, model.ctx());
233
+ }
234
+ catch (cause) {
235
+ failed(cause);
236
+ return;
237
+ }
238
+ // The direct parent took a child's output: it goes no further (no outer component or page
239
+ // listener sees it, ADR 0010 addendum). A declining parser lets it go on.
240
+ if (event.type === OUTPUT_EVENT && result !== undefined)
241
+ event.stopPropagation();
242
+ if (result instanceof Promise)
243
+ return void result.then(deliver, failed);
244
+ if (result !== undefined) {
245
+ deliver(result);
246
+ return;
247
+ }
210
248
  }
211
249
  }
212
250
  //# sourceMappingURL=intent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"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","sourcesContent":["import { commandOf, invokersSupported } from './invokers.js';\nimport { hold } from './scheduler.js';\nimport type { IntentInput, IntentNames, IntentParser, Tagged } from './types.js';\nimport type { Markup } from './view/index.js';\n\n/** Event a child component dispatches on its host to send an output up (ADR 0010). */\nexport const OUTPUT_EVENT = 'gyral-output';\n\n/** Default triggers (`defaultTrigger`): every component root listens for these. */\nexport const DEFAULT_EVENTS: readonly string[] = [\n 'click',\n 'submit',\n 'input',\n 'change',\n OUTPUT_EVENT,\n];\n\n/**\n * The events `data-intent-on` usually names. A component root listens (capture phase, so\n * non-bubbling events such as `toggle` are seen too) for the defaults, for `spec.events`, and\n * for the `data-intent-on` values in the templates it renders; a template that binds\n * `data-intent-on` dynamically adds all of these (view/05-element.md \"Intent events\").\n */\nexport const INTENT_EVENTS: readonly string[] = [\n ...DEFAULT_EVENTS,\n 'keydown',\n 'keyup',\n 'focusin',\n 'focusout',\n 'toggle',\n 'command',\n];\n\n/** A static `data-intent-on` value, quoted or not (event names have no spaces or quotes). */\nconst INTENT_ON = /\\sdata-intent-on=[\"']?([^\"'\\s>]+)/gi;\nconst eventsByTemplate = new WeakMap<Markup, readonly string[]>();\n\n/** The intent events a template's (or `raw()` markup's) `data-intent-on` attributes name. */\nexport function eventsOf(markup: Markup): readonly string[] {\n let events = eventsByTemplate.get(markup);\n if (events === undefined) {\n events = markup.parts?.some((p) => p[2] === 'data-intent-on') // a bound attribute's name\n ? INTENT_EVENTS\n : Array.from(markup.html.matchAll(INTENT_ON), (m) => m[1] ?? '');\n eventsByTemplate.set(markup, events);\n }\n return events;\n}\n\nconst isToggle = (el: Element): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\n// ToggleEvent (popover, <details>) is not Baseline widely available; read it structurally.\nfunction toggleState(event: Event): 'open' | 'closed' | undefined {\n if (event.type !== 'toggle' || !('newState' in event)) return undefined;\n return event.newState === 'open' ? 'open' : 'closed';\n}\n\nconst CLICK_INPUT_TYPES = /^(button|submit|reset|image)$/;\n\n/** The event that fires an intent unless `data-intent-on` overrides it. */\nexport function defaultTrigger(el: Element): string {\n switch (el.localName) {\n case 'form':\n return 'submit';\n case 'select':\n return 'change';\n case 'textarea':\n return 'input';\n case 'input': {\n const type = (el as HTMLInputElement).type;\n if (CLICK_INPUT_TYPES.test(type)) return 'click';\n return type === 'checkbox' || type === 'radio' ? 'change' : 'input';\n }\n }\n // Custom elements (autonomous: the name has a dash) talk to their parent via outputs.\n return el.localName.includes('-') ? OUTPUT_EVENT : 'click';\n}\n\nfunction triggerOf(el: Element): string {\n return el.getAttribute('data-intent-on') ?? defaultTrigger(el);\n}\n\n/** Every class `define()` creates, so intent lookup can tell where a component begins. */\nconst hostClasses = new WeakSet<object>();\n\nexport function markGyralHost(ctor: object): void {\n hostClasses.add(ctor);\n}\n\nconst isGyralHost = (node: Node): boolean => hostClasses.has(node.constructor);\n\n/**\n * The number of Gyral host ancestors of `el` in the composed tree (across shadow roots): the\n * scheduler renders smaller depths first (view/04-scheduler.md \"Marking\").\n */\nexport function hostDepth(el: Element): number {\n let depth = 0;\n for (let node: Node | null = el.parentNode; node !== null;) {\n if (isGyralHost(node)) depth += 1;\n node = node instanceof ShadowRoot ? node.host : node.parentNode;\n }\n return depth;\n}\n\n/**\n * Does `node` belong to the component whose intent root is `root`? `root` is the component's\n * shadow root, or the host itself for light-DOM components (ADR 0014). The node must be in the\n * same tree (not inside a nested shadow root) with no other Gyral host between them: a nested\n * component, light-DOM or shadow, owns its own content. The nested host element itself (with\n * the parent's `data-intent` on it) still belongs to the parent.\n */\nfunction ownedBy(node: Element, root: Node): boolean {\n const tree = root instanceof ShadowRoot ? root : root.getRootNode();\n if (node.getRootNode() !== tree) return false;\n for (let p = node.parentNode; p !== null; p = p.parentNode) {\n if (p === root) return true;\n if (isGyralHost(p)) return false;\n }\n return false;\n}\n\n/**\n * Finds the nearest `data-intent` element for this event that belongs to `root`. Elements\n * inside nested components are ignored: that is component isolation.\n */\nexport function findIntentElement(event: Event, root: Node): Element | undefined {\n for (const node of event.composedPath()) {\n if (node === root) return undefined;\n if (\n node instanceof Element &&\n node.hasAttribute('data-intent') &&\n triggerOf(node) === event.type &&\n ownedBy(node, root)\n ) {\n return node;\n }\n }\n return undefined;\n}\n\nfunction valueOf(el: Element): string | undefined {\n if (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement ||\n el instanceof HTMLButtonElement\n ) {\n return el.value;\n }\n return undefined;\n}\n\n/** Reads an event into an IntentInput. Form submissions are prevented and turned into FormData. */\nexport function readIntent(event: Event, root: Node): IntentInput | undefined {\n const target = findIntentElement(event, root);\n const name = target?.getAttribute('data-intent');\n if (target === undefined || name == null) return undefined;\n let formData: FormData | undefined;\n if (target instanceof HTMLFormElement && event instanceof SubmitEvent) {\n event.preventDefault();\n formData = new FormData(target, event.submitter);\n }\n return {\n name,\n event,\n target,\n value: valueOf(target),\n checked: isToggle(target) ? target.checked : undefined,\n formData,\n detail: event instanceof CustomEvent ? (event.detail as unknown) : undefined,\n key: event instanceof KeyboardEvent ? event.key : undefined,\n newState: toggleState(event),\n command: commandOf(event),\n };\n}\n\n// Any property read returns its own name, so `intents.Increment === 'Increment'`.\n// Types restrict reads to real message tags; a tag without a parser warns at event time.\nexport const intentNames: unknown = new Proxy(\n {},\n { get: (_target, key) => (typeof key === 'string' ? key : undefined) },\n);\n\n/**\n * A component's intent names as a module-level constant, so pure list rows can name intents\n * without passing them through `pick` (view/03-lists.md \"Rows must be pure\"). The same object\n * the view gets as `i`:\n *\n * const i = intents<Msg>();\n * const Row = (t: Todo) => html`<input value=${t.id} data-intent=${i.Toggle} />`;\n */\nexport function intents<M extends Tagged>(): IntentNames<M> {\n return intentNames as IntentNames<M>;\n}\n\n/**\n * Adds capture listeners for `types` to a component root, skipping those in `listening`. A\n * root that listens for `command` in a browser without invoker commands loads the fallback\n * (invokers-shim.ts, ADR 0003 tier 3); settled() waits for it.\n */\nexport function listenForIntents(\n root: Node,\n listening: Set<string>,\n types: readonly string[],\n handler: (event: Event) => void,\n): void {\n for (const type of types) {\n if (listening.has(type)) continue;\n listening.add(type);\n root.addEventListener(type, handler, { capture: true });\n if (type === 'command' && !invokersSupported()) {\n hold(\n import('./invokers-shim.js').then((shim) => {\n shim.shimInvokers(root);\n }, reportError),\n );\n }\n }\n}\n\n/** Parses one event with its matching parser and delivers the message (sync or async). */\nexport function handleIntent<M>(\n event: Event,\n root: Node,\n parsers: Readonly<Record<string, IntentParser<M> | undefined>>,\n tag: string,\n deliver: (msg: Tagged | undefined) => void,\n): void {\n const input = readIntent(event, root);\n if (input === undefined) return;\n const parser = parsers[input.name];\n if (parser === undefined) {\n console.warn(`<${tag}> has no intent parser for data-intent=\"${input.name}\".`);\n return;\n }\n const result = parser(input) as Tagged | undefined | Promise<Tagged | undefined>;\n if (result instanceof Promise) {\n result.then(deliver, (error: unknown) => {\n console.error(`<${tag}> intent parser for \"${input.name}\" failed`, error);\n });\n } else {\n deliver(result);\n }\n}\n"]}
1
+ {"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadInvokerShim } from '#invoker-fallback';\nimport { commandOf, invokersSupported } from './invokers.js';\nimport { isLightComponent } from './light-dom.js';\nimport type { IntentInput, IntentNames, IntentParser, ParserCtx, Tagged } from './types.js';\nimport { message, type Markup } from './view/index.js';\n\n/** Event a child component dispatches on its host to send an output up (ADR 0010); public. */\nexport const OUTPUT_EVENT = 'gyral-output';\n\n/** Default triggers (`defaultTrigger`): every component root listens for these. */\nexport const DEFAULT_EVENTS: readonly string[] = [\n 'click',\n 'submit',\n 'input',\n 'change',\n OUTPUT_EVENT,\n];\n\n/**\n * The events `data-intent-on` usually names. A component root listens (capture phase, so\n * non-bubbling events such as `toggle` are seen too) for the defaults and for the events its\n * templates name in `data-intent-<event>` attributes and static `data-intent-on` values; a\n * template that binds `data-intent-on` dynamically adds all of these. Any other event with a\n * bound intent takes a per-event attribute, `data-intent-pointerdown=${i.Press}`, whose name the\n * template carries (view/05-element.md \"Intent events\").\n */\nexport const INTENT_EVENTS: readonly string[] = [\n ...DEFAULT_EVENTS,\n 'keydown',\n 'keyup',\n 'focusin',\n 'focusout',\n 'toggle',\n 'command',\n];\n\n/**\n * A static `data-intent-<event>` attribute: a per-event intent (the event is in the name), or\n * `data-intent-on` (its value is the event, or a quoted list of them separated by spaces).\n */\nconst INTENT_ATTR = /\\sdata-intent-([\\w-]+)=([\"']?)(.*?)\\2(?=[\\s/>])/gi;\n// Names separated by any whitespace; none empty, so no listener for \"\".\nconst eventList = (value: string): string[] => value.match(/\\S+/g) ?? [];\nconst eventsByTemplate = new WeakMap<Markup, readonly string[]>();\n\n/**\n * The intent events a template's (or `raw()` markup's) `data-intent-<event>` and\n * `data-intent-on` attributes name. A bound attribute's name is in its part; a bound\n * `data-intent-on` adds every intent event.\n */\nexport function eventsOf(markup: Markup): readonly string[] {\n let events = eventsByTemplate.get(markup);\n if (events === undefined) {\n // Bound attributes are in the parts, by name: listed as ` name=* ` and matched like markup\n // (other parts' names are never intent attributes; `*` marks a bound data-intent-on).\n const bound = markup.parts?.map((p) => ` ${p[2] as string}=* `).join('') ?? '';\n events = Array.from((markup.html + bound).matchAll(INTENT_ATTR)).flatMap(\n ([, event, , value]) =>\n event !== 'on'\n ? (event as string)\n : value === '*'\n ? INTENT_EVENTS\n : eventList(value as string),\n );\n eventsByTemplate.set(markup, events);\n }\n return events;\n}\n\nconst isToggle = (el: Element): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\nconst CLICK_INPUT_TYPES = /^(button|submit|reset|image)$/;\n\n/**\n * The event that fires an intent unless `data-intent-on` overrides it. Custom elements\n * (autonomous: the name has a dash) talk to their parent via outputs.\n */\nexport function defaultTrigger(el: Element): string {\n const name = el.localName;\n return name === 'form'\n ? 'submit'\n : name === 'select' || isToggle(el)\n ? 'change'\n : name === 'textarea' ||\n (name === 'input' && !CLICK_INPUT_TYPES.test((el as HTMLInputElement).type))\n ? 'input'\n : name.includes('-')\n ? OUTPUT_EVENT\n : 'click';\n}\n\n/** The events that fire `el`'s intent: its `data-intent-on` list, or its default trigger. */\nfunction triggersOf(el: Element): string[] {\n return eventList(el.getAttribute('data-intent-on') ?? defaultTrigger(el));\n}\n\n/** Every class `define()` creates, so intent lookup can tell where a component begins. */\nconst hostClasses = new WeakSet<object>();\n\nexport function markGyralHost(ctor: object): void {\n hostClasses.add(ctor);\n}\n\nconst isGyralHost = (node: Node): boolean => hostClasses.has(node.constructor);\n\n/**\n * The number of Gyral host ancestors of `el` in the composed tree (across shadow roots): the\n * scheduler renders smaller depths first (view/04-scheduler.md \"Marking\").\n */\nexport function hostDepth(el: Element): number {\n let depth = 0;\n for (let node: Node | null = el.parentNode; node !== null;) {\n if (isGyralHost(node)) depth += 1;\n node = node instanceof ShadowRoot ? node.host : node.parentNode;\n }\n return depth;\n}\n\n/**\n * Does `node` belong to the component whose intent root is `root`? `root` is the component's\n * shadow root, or the host itself for light-DOM components (ADR 0014). The node must be in the\n * same tree (not inside a nested shadow root) with no light-DOM Gyral host between them: a\n * light-DOM component owns its children (it rendered them). Children of a shadow-DOM host are\n * the outer template's content, slotted into it, so they stay the outer component's (a form or\n * a child component slotted into a card). The nested host element itself (with the parent's\n * `data-intent` on it) still belongs to the parent.\n */\nfunction ownedBy(node: Element, root: Node): boolean {\n const tree = root instanceof ShadowRoot ? root : root.getRootNode();\n if (node.getRootNode() !== tree) return false;\n for (let p = node.parentNode; p !== null; p = p.parentNode) {\n if (p === root) return true;\n if (isGyralHost(p) && isLightComponent(p.constructor)) return false;\n }\n return false;\n}\n\nfunction valueOf(el: Element): string | undefined {\n if (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement ||\n el instanceof HTMLButtonElement\n ) {\n return el.value;\n }\n return undefined;\n}\n\n/**\n * Reads an event into an IntentInput for the nearest element on its path that names an intent\n * for it and belongs to `root`: its `data-intent-<type>`, else its `data-intent` when the\n * event is one of its triggers. With `after`, the search starts past that element (the intent\n * whose parser declined). Elements inside nested components are ignored: that is component\n * isolation. Form submissions are prevented and turned into FormData.\n */\nexport function readIntent(event: Event, root: Node, after?: Element): IntentInput | undefined {\n const path = event.composedPath();\n // indexOf(undefined) is -1: without `after`, the whole path.\n for (const target of path.slice(path.indexOf(after as Element) + 1)) {\n if (target === root) return undefined;\n if (!(target instanceof Element)) continue;\n // An output is the dispatching child's own: only the element it was dispatched on (the\n // child host, where the direct parent's `data-intent` sits) takes it, never an ancestor.\n if (event.type === OUTPUT_EVENT && target !== path[0]) continue;\n const name =\n target.getAttribute('data-intent-' + event.type) ??\n (triggersOf(target).includes(event.type) ? target.getAttribute('data-intent') : null);\n if (name === null || !ownedBy(target, root)) continue;\n let formData: FormData | undefined;\n if (target instanceof HTMLFormElement && event instanceof SubmitEvent) {\n event.preventDefault();\n formData = new FormData(target, event.submitter);\n }\n return {\n name,\n event,\n target,\n value: valueOf(target),\n checked: isToggle(target) ? target.checked : undefined,\n formData,\n detail: event instanceof CustomEvent ? (event.detail as unknown) : undefined,\n // Read structurally too: only keyboard events carry `key` to elements.\n key: (event as { readonly key?: string }).key,\n // ToggleEvent (popover, <details>) is not Baseline widely available: read it structurally.\n newState: (event as { readonly newState?: 'open' | 'closed' }).newState,\n command: commandOf(event),\n };\n }\n return undefined;\n}\n\n// Any property read returns its own name, so `intents.Increment === 'Increment'`.\n// Types restrict reads to real message tags; a tag without a parser warns at event time.\nexport const intentNames: unknown = new Proxy(\n {},\n { get: (_target, key) => (typeof key === 'string' ? key : undefined) },\n);\n\n/**\n * A component's intent names as a module-level constant, so pure list rows can name intents\n * without passing them through `pick` (view/03-lists.md \"Rows must be pure\"). The same object\n * the view gets as `i`, typed by the component's class (ADR 0023). A row that names intents\n * this way needs an explicit return type, or the row, the view and the class infer each other\n * in a circle:\n *\n * const i = intentsOf<typeof TodoList>();\n * const Row = (t: Todo): TemplateResult => html`<input value=${t.id} data-intent=${i.Toggle} />`;\n */\nexport function intentsOf<C extends { readonly intentNames?: string }>(): IntentNames<\n NonNullable<C['intentNames']>\n> {\n return intentNames as IntentNames<NonNullable<C['intentNames']>>;\n}\n\n/**\n * Adds capture listeners for `types` to a component root, skipping those in `listening`. A\n * root that listens for `command` in a browser without invoker commands loads the fallback\n * (invokers-shim.ts, ADR 0003 tier 3); settled() waits for it.\n */\nexport function listenForIntents(\n root: Node,\n listening: Set<string>,\n types: readonly string[],\n handler: (event: Event) => void,\n): void {\n for (const type of types) {\n if (listening.has(type)) continue;\n listening.add(type);\n root.addEventListener(type, handler, { capture: true });\n if (type === 'command' && !invokersSupported()) loadInvokerShim(root);\n }\n}\n\n/**\n * Parses one event with its matching parser and delivers the message (sync or async). A\n * parser that returns `undefined` synchronously declines: the next intent outward for the same\n * event gets it, up to the component root. A promise can't decline. The model's `ctx()` is\n * built only when a parser runs, so its props are those at event time.\n */\nexport function handleIntent<M>(\n event: Event,\n root: Node,\n parsers: Readonly<Record<string, IntentParser<M> | true | undefined>>,\n tag: string,\n model: {\n ctx(): ParserCtx<unknown>;\n fail(cause: unknown, phase: 'parse', text: string, msg?: string): unknown;\n },\n deliver: (msg: Tagged | undefined) => void,\n): void {\n for (let input: IntentInput | undefined; (input = readIntent(event, root, input?.target));) {\n const { name } = input;\n const parser = parsers[name];\n if (parser === undefined) {\n console.warn(message(11, tag, name));\n return;\n }\n // A parser that throws or rejects is reported (ADR 0024) and sends nothing.\n const failed = (cause: unknown): void => {\n model.fail(cause, 'parse', message(12, tag, name), name);\n };\n let result: Tagged | undefined | Promise<Tagged | undefined>;\n try {\n // `true`: the intent sends its own tag (types allow it only for field-less variants).\n result =\n parser === true\n ? { _tag: name }\n : (parser(input, model.ctx()) as Tagged | undefined | Promise<Tagged | undefined>);\n } catch (cause) {\n failed(cause);\n return;\n }\n // The direct parent took a child's output: it goes no further (no outer component or page\n // listener sees it, ADR 0010 addendum). A declining parser lets it go on.\n if (event.type === OUTPUT_EVENT && result !== undefined) event.stopPropagation();\n if (result instanceof Promise) return void result.then(deliver, failed);\n if (result !== undefined) {\n deliver(result);\n return;\n }\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { IndexedDBError, IndexedDBOptions, IndexedDBStores } from '../indexeddb-types.js';
2
+ export declare class DatabaseFailure extends Error {
3
+ readonly error: IndexedDBError;
4
+ constructor(error: IndexedDBError);
5
+ }
6
+ export declare function databaseError(database: string, phase: 'encode' | 'open' | 'upgrade' | 'transaction', cause: unknown): DatabaseFailure;
7
+ /** Native open requests cannot be cancelled: retain handlers to abort/close late results. */
8
+ export declare function openDatabase<S extends IndexedDBStores>(options: IndexedDBOptions<S>, signal: AbortSignal): Promise<IDBDatabase>;
@@ -0,0 +1,96 @@
1
+ export class DatabaseFailure extends Error {
2
+ constructor(error) {
3
+ super(error._tag);
4
+ this.error = error;
5
+ }
6
+ }
7
+ export function databaseError(database, phase, cause) {
8
+ return new DatabaseFailure({
9
+ _tag: 'IndexedDBAccessError',
10
+ database,
11
+ phase,
12
+ name: cause instanceof Error ? cause.name : 'Error',
13
+ message: cause instanceof Error ? cause.message : String(cause),
14
+ });
15
+ }
16
+ /** Native open requests cannot be cancelled: retain handlers to abort/close late results. */
17
+ export function openDatabase(options, signal) {
18
+ return new Promise((resolve, reject) => {
19
+ signal.throwIfAborted();
20
+ const factory = options.factory?.() ?? (typeof indexedDB === 'undefined' ? undefined : indexedDB);
21
+ if (factory === undefined)
22
+ throw new Error('IndexedDB is unavailable.');
23
+ signal.throwIfAborted();
24
+ const request = factory.open(options.database, options.version);
25
+ const state = { ended: false };
26
+ const end = (cause) => {
27
+ if (state.ended)
28
+ return;
29
+ state.ended = true;
30
+ signal.removeEventListener('abort', abort);
31
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve AbortSignal reasons
32
+ reject(cause);
33
+ };
34
+ const abort = () => {
35
+ end(signal.reason);
36
+ abortUpgrade();
37
+ };
38
+ const abortUpgrade = () => {
39
+ try {
40
+ request.transaction?.abort();
41
+ }
42
+ catch {
43
+ /* Native abort may already be pending. */
44
+ }
45
+ };
46
+ const cleanup = () => {
47
+ request.onblocked = null;
48
+ request.onupgradeneeded = null;
49
+ request.onerror = null;
50
+ request.onsuccess = null;
51
+ signal.removeEventListener('abort', abort);
52
+ };
53
+ signal.addEventListener('abort', abort, { once: true });
54
+ request.onblocked = () => {
55
+ end(new DatabaseFailure({
56
+ _tag: 'IndexedDBBlockedError',
57
+ database: options.database,
58
+ version: options.version,
59
+ }));
60
+ };
61
+ request.onupgradeneeded = (event) => {
62
+ const transaction = request.transaction;
63
+ if (transaction === null)
64
+ return;
65
+ if (state.ended) {
66
+ abortUpgrade();
67
+ return;
68
+ }
69
+ try {
70
+ options.upgrade?.(request.result, event.oldVersion, event.newVersion ?? options.version, transaction);
71
+ }
72
+ catch (cause) {
73
+ end(databaseError(options.database, 'upgrade', cause));
74
+ abortUpgrade();
75
+ }
76
+ };
77
+ request.onerror = () => {
78
+ end(databaseError(options.database, 'open', request.error));
79
+ cleanup();
80
+ };
81
+ request.onsuccess = () => {
82
+ const db = request.result;
83
+ if (state.ended)
84
+ db.close();
85
+ else {
86
+ state.ended = true;
87
+ db.onversionchange = () => {
88
+ db.close();
89
+ };
90
+ resolve(db);
91
+ }
92
+ cleanup();
93
+ };
94
+ });
95
+ }
96
+ //# sourceMappingURL=indexeddb-open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb-open.js","sourceRoot":"","sources":["../../src/internal/indexeddb-open.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IndexedDBError, IndexedDBOptions, IndexedDBStores } from '../indexeddb-types.js';\n\nexport class DatabaseFailure extends Error {\n constructor(readonly error: IndexedDBError) {\n super(error._tag);\n }\n}\nexport function databaseError(\n database: string,\n phase: 'encode' | 'open' | 'upgrade' | 'transaction',\n cause: unknown,\n): DatabaseFailure {\n return new DatabaseFailure({\n _tag: 'IndexedDBAccessError',\n database,\n phase,\n name: cause instanceof Error ? cause.name : 'Error',\n message: cause instanceof Error ? cause.message : String(cause),\n });\n}\n\n/** Native open requests cannot be cancelled: retain handlers to abort/close late results. */\nexport function openDatabase<S extends IndexedDBStores>(\n options: IndexedDBOptions<S>,\n signal: AbortSignal,\n): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n signal.throwIfAborted();\n const factory =\n options.factory?.() ?? (typeof indexedDB === 'undefined' ? undefined : indexedDB);\n if (factory === undefined) throw new Error('IndexedDB is unavailable.');\n signal.throwIfAborted();\n const request = factory.open(options.database, options.version);\n const state = { ended: false };\n const end = (cause: unknown): void => {\n if (state.ended) return;\n state.ended = true;\n signal.removeEventListener('abort', abort);\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve AbortSignal reasons\n reject(cause);\n };\n const abort = (): void => {\n end(signal.reason);\n abortUpgrade();\n };\n const abortUpgrade = (): void => {\n try {\n request.transaction?.abort();\n } catch {\n /* Native abort may already be pending. */\n }\n };\n const cleanup = (): void => {\n request.onblocked = null;\n request.onupgradeneeded = null;\n request.onerror = null;\n request.onsuccess = null;\n signal.removeEventListener('abort', abort);\n };\n signal.addEventListener('abort', abort, { once: true });\n request.onblocked = () => {\n end(\n new DatabaseFailure({\n _tag: 'IndexedDBBlockedError',\n database: options.database,\n version: options.version,\n }),\n );\n };\n request.onupgradeneeded = (event) => {\n const transaction = request.transaction;\n if (transaction === null) return;\n if (state.ended) {\n abortUpgrade();\n return;\n }\n try {\n options.upgrade?.(\n request.result,\n event.oldVersion,\n event.newVersion ?? options.version,\n transaction,\n );\n } catch (cause) {\n end(databaseError(options.database, 'upgrade', cause));\n abortUpgrade();\n }\n };\n request.onerror = () => {\n end(databaseError(options.database, 'open', request.error));\n cleanup();\n };\n request.onsuccess = () => {\n const db = request.result;\n if (state.ended) db.close();\n else {\n state.ended = true;\n db.onversionchange = () => {\n db.close();\n };\n resolve(db);\n }\n cleanup();\n };\n });\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { IndexedDBOperation, IndexedDBResult, IndexedDBStores } from '../indexeddb-types.js';
2
+ export type RawResult = IndexedDBResult<IndexedDBStores>;
3
+ /** Queue requests synchronously and resolve only from transaction complete. */
4
+ export declare function transact(db: IDBDatabase, operations: readonly IndexedDBOperation<IndexedDBStores>[], signal: AbortSignal): Promise<readonly RawResult[]>;