@vielzeug/ore 1.2.3 → 2.0.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 (318) hide show
  1. package/README.md +11 -8
  2. package/dist/_component-class.cjs +2 -0
  3. package/dist/_component-class.cjs.map +1 -0
  4. package/dist/_component-class.d.ts +2 -0
  5. package/dist/_component-class.d.ts.map +1 -0
  6. package/dist/_component-class.js +2 -0
  7. package/dist/_component-class.js.map +1 -0
  8. package/dist/_dev.cjs +1 -1
  9. package/dist/_dev.cjs.map +1 -1
  10. package/dist/_dev.js +1 -1
  11. package/dist/_dev.js.map +1 -1
  12. package/dist/base-element.cjs +1 -1
  13. package/dist/base-element.cjs.map +1 -1
  14. package/dist/base-element.d.ts +9 -15
  15. package/dist/base-element.d.ts.map +1 -1
  16. package/dist/base-element.js +1 -1
  17. package/dist/base-element.js.map +1 -1
  18. package/dist/component-types.d.ts +3 -14
  19. package/dist/component-types.d.ts.map +1 -1
  20. package/dist/context.cjs +1 -1
  21. package/dist/context.cjs.map +1 -1
  22. package/dist/context.d.ts +13 -0
  23. package/dist/context.d.ts.map +1 -1
  24. package/dist/context.js +1 -1
  25. package/dist/context.js.map +1 -1
  26. package/dist/define.cjs +1 -1
  27. package/dist/define.cjs.map +1 -1
  28. package/dist/define.d.ts +6 -5
  29. package/dist/define.d.ts.map +1 -1
  30. package/dist/define.js +1 -1
  31. package/dist/define.js.map +1 -1
  32. package/dist/directives/classMap.cjs +1 -1
  33. package/dist/directives/classMap.cjs.map +1 -1
  34. package/dist/directives/classMap.d.ts.map +1 -1
  35. package/dist/directives/classMap.js +1 -1
  36. package/dist/directives/classMap.js.map +1 -1
  37. package/dist/directives/each.cjs +1 -1
  38. package/dist/directives/each.cjs.map +1 -1
  39. package/dist/directives/each.d.ts +9 -2
  40. package/dist/directives/each.d.ts.map +1 -1
  41. package/dist/directives/each.js +1 -1
  42. package/dist/directives/each.js.map +1 -1
  43. package/dist/directives/live.cjs +1 -1
  44. package/dist/directives/live.cjs.map +1 -1
  45. package/dist/directives/live.d.ts +10 -12
  46. package/dist/directives/live.d.ts.map +1 -1
  47. package/dist/directives/live.js +1 -1
  48. package/dist/directives/live.js.map +1 -1
  49. package/dist/directives/styleMap.cjs +1 -1
  50. package/dist/directives/styleMap.cjs.map +1 -1
  51. package/dist/directives/styleMap.d.ts +1 -1
  52. package/dist/directives/styleMap.d.ts.map +1 -1
  53. package/dist/directives/styleMap.js +1 -1
  54. package/dist/directives/styleMap.js.map +1 -1
  55. package/dist/directives/unsafe-html.cjs +2 -0
  56. package/dist/directives/unsafe-html.cjs.map +1 -0
  57. package/dist/directives/unsafe-html.d.ts +14 -0
  58. package/dist/directives/unsafe-html.d.ts.map +1 -0
  59. package/dist/directives/unsafe-html.js +2 -0
  60. package/dist/directives/unsafe-html.js.map +1 -0
  61. package/dist/directives/when.cjs +1 -1
  62. package/dist/directives/when.cjs.map +1 -1
  63. package/dist/directives/when.d.ts +1 -1
  64. package/dist/directives/when.d.ts.map +1 -1
  65. package/dist/directives/when.js +1 -1
  66. package/dist/directives/when.js.map +1 -1
  67. package/dist/errors.cjs +2 -2
  68. package/dist/errors.cjs.map +1 -1
  69. package/dist/errors.d.ts +21 -6
  70. package/dist/errors.d.ts.map +1 -1
  71. package/dist/errors.js +2 -2
  72. package/dist/errors.js.map +1 -1
  73. package/dist/forms/field.cjs +1 -1
  74. package/dist/forms/field.cjs.map +1 -1
  75. package/dist/forms/field.d.ts +5 -1
  76. package/dist/forms/field.d.ts.map +1 -1
  77. package/dist/forms/field.js +1 -1
  78. package/dist/forms/field.js.map +1 -1
  79. package/dist/host-bind.cjs +1 -1
  80. package/dist/host-bind.cjs.map +1 -1
  81. package/dist/host-bind.d.ts +6 -0
  82. package/dist/host-bind.d.ts.map +1 -1
  83. package/dist/host-bind.js +1 -1
  84. package/dist/host-bind.js.map +1 -1
  85. package/dist/index.cjs +1 -1
  86. package/dist/index.d.ts +15 -5
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/observers/intersection-observe.cjs +1 -1
  90. package/dist/observers/intersection-observe.cjs.map +1 -1
  91. package/dist/observers/intersection-observe.d.ts.map +1 -1
  92. package/dist/observers/intersection-observe.js +1 -1
  93. package/dist/observers/intersection-observe.js.map +1 -1
  94. package/dist/observers/media-observe.cjs +1 -1
  95. package/dist/observers/media-observe.cjs.map +1 -1
  96. package/dist/observers/media-observe.d.ts.map +1 -1
  97. package/dist/observers/media-observe.js +1 -1
  98. package/dist/observers/media-observe.js.map +1 -1
  99. package/dist/observers/mutation-observe.cjs +1 -1
  100. package/dist/observers/mutation-observe.cjs.map +1 -1
  101. package/dist/observers/mutation-observe.d.ts.map +1 -1
  102. package/dist/observers/mutation-observe.js +1 -1
  103. package/dist/observers/mutation-observe.js.map +1 -1
  104. package/dist/observers/resize-observe.cjs +1 -1
  105. package/dist/observers/resize-observe.cjs.map +1 -1
  106. package/dist/observers/resize-observe.d.ts.map +1 -1
  107. package/dist/observers/resize-observe.js +1 -1
  108. package/dist/observers/resize-observe.js.map +1 -1
  109. package/dist/ore.cjs +2 -2
  110. package/dist/ore.cjs.map +1 -1
  111. package/dist/ore.iife.js +2 -2
  112. package/dist/ore.iife.js.map +1 -1
  113. package/dist/ore.js +2 -2
  114. package/dist/ore.js.map +1 -1
  115. package/dist/props.cjs +1 -1
  116. package/dist/props.cjs.map +1 -1
  117. package/dist/props.d.ts +1 -6
  118. package/dist/props.d.ts.map +1 -1
  119. package/dist/props.js +1 -1
  120. package/dist/props.js.map +1 -1
  121. package/dist/runtime.cjs +1 -1
  122. package/dist/runtime.cjs.map +1 -1
  123. package/dist/runtime.d.ts +7 -10
  124. package/dist/runtime.d.ts.map +1 -1
  125. package/dist/runtime.js +1 -1
  126. package/dist/runtime.js.map +1 -1
  127. package/dist/slots.cjs +1 -1
  128. package/dist/slots.cjs.map +1 -1
  129. package/dist/slots.d.ts.map +1 -1
  130. package/dist/slots.js +1 -1
  131. package/dist/slots.js.map +1 -1
  132. package/dist/template/binding-types.cjs +1 -0
  133. package/dist/template/binding-types.d.ts +59 -0
  134. package/dist/template/binding-types.d.ts.map +1 -0
  135. package/dist/template/binding-types.js +1 -0
  136. package/dist/template/bindings.cjs +1 -1
  137. package/dist/template/bindings.cjs.map +1 -1
  138. package/dist/template/bindings.d.ts +18 -3
  139. package/dist/template/bindings.d.ts.map +1 -1
  140. package/dist/template/bindings.js +1 -1
  141. package/dist/template/bindings.js.map +1 -1
  142. package/dist/template/compiler.cjs +1 -1
  143. package/dist/template/compiler.cjs.map +1 -1
  144. package/dist/template/compiler.d.ts +0 -4
  145. package/dist/template/compiler.d.ts.map +1 -1
  146. package/dist/template/compiler.js +1 -1
  147. package/dist/template/compiler.js.map +1 -1
  148. package/dist/template/instantiator.cjs +1 -1
  149. package/dist/template/instantiator.cjs.map +1 -1
  150. package/dist/template/instantiator.d.ts +6 -1
  151. package/dist/template/instantiator.d.ts.map +1 -1
  152. package/dist/template/instantiator.js +1 -1
  153. package/dist/template/instantiator.js.map +1 -1
  154. package/dist/template/result.cjs +2 -0
  155. package/dist/template/result.cjs.map +1 -0
  156. package/dist/template/result.d.ts +42 -0
  157. package/dist/template/result.d.ts.map +1 -0
  158. package/dist/template/result.js +2 -0
  159. package/dist/template/result.js.map +1 -0
  160. package/dist/testing/flush.cjs +1 -1
  161. package/dist/testing/flush.cjs.map +1 -1
  162. package/dist/testing/flush.d.ts +30 -26
  163. package/dist/testing/flush.d.ts.map +1 -1
  164. package/dist/testing/flush.js +1 -1
  165. package/dist/testing/flush.js.map +1 -1
  166. package/dist/testing/form-internals-polyfill.cjs +1 -1
  167. package/dist/testing/form-internals-polyfill.cjs.map +1 -1
  168. package/dist/testing/form-internals-polyfill.d.ts +9 -7
  169. package/dist/testing/form-internals-polyfill.d.ts.map +1 -1
  170. package/dist/testing/form-internals-polyfill.js +1 -1
  171. package/dist/testing/form-internals-polyfill.js.map +1 -1
  172. package/dist/testing/index.d.ts +35 -1
  173. package/dist/testing/index.d.ts.map +1 -1
  174. package/dist/testing/mount.cjs +1 -1
  175. package/dist/testing/mount.cjs.map +1 -1
  176. package/dist/testing/mount.d.ts +10 -19
  177. package/dist/testing/mount.d.ts.map +1 -1
  178. package/dist/testing/mount.js +1 -1
  179. package/dist/testing/mount.js.map +1 -1
  180. package/dist/testing/render-hook.cjs +1 -1
  181. package/dist/testing/render-hook.cjs.map +1 -1
  182. package/dist/testing/render-hook.js +1 -1
  183. package/dist/testing/render-hook.js.map +1 -1
  184. package/dist/testing/reset.cjs +2 -0
  185. package/dist/testing/reset.cjs.map +1 -0
  186. package/dist/testing/reset.d.ts +12 -0
  187. package/dist/testing/reset.d.ts.map +1 -0
  188. package/dist/testing/reset.js +2 -0
  189. package/dist/testing/reset.js.map +1 -0
  190. package/dist/testing.cjs +2 -1
  191. package/dist/testing.cjs.map +1 -0
  192. package/dist/testing.js +2 -1
  193. package/dist/testing.js.map +1 -0
  194. package/dist/utils/aria.cjs.map +1 -1
  195. package/dist/utils/aria.d.ts +1 -1
  196. package/dist/utils/aria.js.map +1 -1
  197. package/dist/utils/brand.cjs +2 -0
  198. package/dist/utils/brand.cjs.map +1 -0
  199. package/dist/utils/brand.d.ts +16 -0
  200. package/dist/utils/brand.d.ts.map +1 -0
  201. package/dist/utils/brand.js +2 -0
  202. package/dist/utils/brand.js.map +1 -0
  203. package/dist/utils/css.cjs +1 -1
  204. package/dist/utils/css.cjs.map +1 -1
  205. package/dist/utils/css.d.ts.map +1 -1
  206. package/dist/utils/css.js +1 -1
  207. package/dist/utils/css.js.map +1 -1
  208. package/dist/utils/dom.cjs +1 -1
  209. package/dist/utils/dom.cjs.map +1 -1
  210. package/dist/utils/dom.d.ts +34 -0
  211. package/dist/utils/dom.d.ts.map +1 -1
  212. package/dist/utils/dom.js +1 -1
  213. package/dist/utils/dom.js.map +1 -1
  214. package/dist/utils/emit.cjs.map +1 -1
  215. package/dist/utils/emit.d.ts +10 -3
  216. package/dist/utils/emit.d.ts.map +1 -1
  217. package/dist/utils/emit.js.map +1 -1
  218. package/dist/utils/id.cjs +1 -1
  219. package/dist/utils/id.cjs.map +1 -1
  220. package/dist/utils/id.d.ts +11 -2
  221. package/dist/utils/id.d.ts.map +1 -1
  222. package/dist/utils/id.js +1 -1
  223. package/dist/utils/id.js.map +1 -1
  224. package/package.json +15 -30
  225. package/dist/aria.cjs +0 -2
  226. package/dist/aria.cjs.map +0 -1
  227. package/dist/aria.d.ts +0 -20
  228. package/dist/aria.d.ts.map +0 -1
  229. package/dist/aria.js +0 -2
  230. package/dist/aria.js.map +0 -1
  231. package/dist/devtools.cjs +0 -2
  232. package/dist/devtools.cjs.map +0 -1
  233. package/dist/devtools.d.ts +0 -32
  234. package/dist/devtools.d.ts.map +0 -1
  235. package/dist/devtools.js +0 -2
  236. package/dist/devtools.js.map +0 -1
  237. package/dist/directives/index.d.ts +0 -8
  238. package/dist/directives/index.d.ts.map +0 -1
  239. package/dist/directives/model.cjs +0 -2
  240. package/dist/directives/model.cjs.map +0 -1
  241. package/dist/directives/model.d.ts +0 -28
  242. package/dist/directives/model.d.ts.map +0 -1
  243. package/dist/directives/model.js +0 -2
  244. package/dist/directives/model.js.map +0 -1
  245. package/dist/directives/raw.cjs +0 -2
  246. package/dist/directives/raw.cjs.map +0 -1
  247. package/dist/directives/raw.d.ts +0 -16
  248. package/dist/directives/raw.d.ts.map +0 -1
  249. package/dist/directives/raw.js +0 -2
  250. package/dist/directives/raw.js.map +0 -1
  251. package/dist/directives.cjs +0 -1
  252. package/dist/directives.js +0 -1
  253. package/dist/forms/context.cjs +0 -2
  254. package/dist/forms/context.cjs.map +0 -1
  255. package/dist/forms/context.d.ts +0 -56
  256. package/dist/forms/context.d.ts.map +0 -1
  257. package/dist/forms/context.js +0 -2
  258. package/dist/forms/context.js.map +0 -1
  259. package/dist/forms/index.d.ts +0 -11
  260. package/dist/forms/index.d.ts.map +0 -1
  261. package/dist/forms.cjs +0 -1
  262. package/dist/forms.js +0 -1
  263. package/dist/observers/index.d.ts +0 -5
  264. package/dist/observers/index.d.ts.map +0 -1
  265. package/dist/observers.cjs +0 -1
  266. package/dist/observers.js +0 -1
  267. package/dist/template.cjs +0 -1
  268. package/dist/template.d.ts +0 -4
  269. package/dist/template.d.ts.map +0 -1
  270. package/dist/template.js +0 -1
  271. package/dist/testing/events.cjs +0 -2
  272. package/dist/testing/events.cjs.map +0 -1
  273. package/dist/testing/events.d.ts +0 -37
  274. package/dist/testing/events.d.ts.map +0 -1
  275. package/dist/testing/events.js +0 -2
  276. package/dist/testing/events.js.map +0 -1
  277. package/dist/testing/interactions.cjs +0 -2
  278. package/dist/testing/interactions.cjs.map +0 -1
  279. package/dist/testing/interactions.d.ts +0 -27
  280. package/dist/testing/interactions.d.ts.map +0 -1
  281. package/dist/testing/interactions.js +0 -2
  282. package/dist/testing/interactions.js.map +0 -1
  283. package/dist/testing/query.cjs +0 -2
  284. package/dist/testing/query.cjs.map +0 -1
  285. package/dist/testing/query.d.ts +0 -24
  286. package/dist/testing/query.d.ts.map +0 -1
  287. package/dist/testing/query.js +0 -2
  288. package/dist/testing/query.js.map +0 -1
  289. package/dist/testing/testing.cjs +0 -2
  290. package/dist/testing/testing.cjs.map +0 -1
  291. package/dist/testing/testing.d.ts +0 -32
  292. package/dist/testing/testing.d.ts.map +0 -1
  293. package/dist/testing/testing.js +0 -2
  294. package/dist/testing/testing.js.map +0 -1
  295. package/dist/testing/wait.cjs +0 -2
  296. package/dist/testing/wait.cjs.map +0 -1
  297. package/dist/testing/wait.d.ts +0 -35
  298. package/dist/testing/wait.d.ts.map +0 -1
  299. package/dist/testing/wait.js +0 -2
  300. package/dist/testing/wait.js.map +0 -1
  301. package/dist/types/bindings.cjs +0 -2
  302. package/dist/types/bindings.cjs.map +0 -1
  303. package/dist/types/bindings.d.ts +0 -125
  304. package/dist/types/bindings.d.ts.map +0 -1
  305. package/dist/types/bindings.js +0 -2
  306. package/dist/types/bindings.js.map +0 -1
  307. package/dist/types.cjs +0 -2
  308. package/dist/types.cjs.map +0 -1
  309. package/dist/types.d.ts +0 -19
  310. package/dist/types.d.ts.map +0 -1
  311. package/dist/types.js +0 -2
  312. package/dist/types.js.map +0 -1
  313. package/dist/utils/event-modifiers.cjs +0 -2
  314. package/dist/utils/event-modifiers.cjs.map +0 -1
  315. package/dist/utils/event-modifiers.d.ts +0 -11
  316. package/dist/utils/event-modifiers.d.ts.map +0 -1
  317. package/dist/utils/event-modifiers.js +0 -2
  318. package/dist/utils/event-modifiers.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport {\n type Binding,\n createHtmlResult,\n type HtmlBindingValue,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n isSpreadObject,\n type Ref,\n type RefCallback,\n} from '../types/bindings';\nimport { applyModifiers } from '../utils/event-modifiers';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult whose fragment is ready to insert.\n */\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.CLOSE_TAG) {\n return { slot, value };\n }\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = compiled.commentPaths.get(slot.commentId!);\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = compiled.elementPaths.get(slot.elementId!);\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2a: Process tagname slots first — replace placeholders with real elements\n const tagReplacements = new Map<HTMLElement, HTMLElement>();\n\n for (const { el, slot, value } of boundSlots) {\n if (slot.kind !== SlotKind.TAG_NAME) continue;\n\n const tagName = String(value);\n\n // Same \"fail immediately, every build\" treatment as each()'s duplicate-key\n // guard: a malformed dynamic tag name is a programming bug in the template,\n // not a runtime race — degrading to a silently-broken partial render would\n // hide it instead of surfacing it at the call site.\n if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) {\n throw new OreApiError(ORE_ERRORS.invalidDynamicTagName(tagName));\n }\n\n const realEl = document.createElement(tagName);\n\n for (const attr of Array.from(el!.attributes)) realEl.setAttribute(attr.name, attr.value);\n\n while (el!.firstChild) realEl.appendChild(el!.firstChild);\n\n el!.replaceWith(realEl);\n tagReplacements.set(el!, realEl);\n }\n\n // Phase 2b: Build bindings (may modify DOM for static content)\n for (const { comment, el: rawEl, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.TAG_NAME || slot.kind === SlotKind.CLOSE_TAG) continue;\n\n // Resolve element through tagname replacements\n const el = rawEl ? (tagReplacements.get(rawEl) ?? rawEl) : rawEl;\n\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment!;\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (value.fragment.firstChild) parent.insertBefore(value.fragment.firstChild, anchor);\n\n anchor.remove();\n chainedApplies.push(value.apply.bind(value));\n continue;\n }\n\n if (typeof value === 'function') {\n const sig = computed(() => {\n const res = (value as () => unknown)();\n\n return Array.isArray(res) ? (res as HtmlBindingValue[]) : [res as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (isReactive(value)) {\n // Always use the html binding for signals — it handles both text values and\n // HTMLResult values, preventing silent \"[object Object]\" corruption when a\n // signal's runtime type changes from null/string to HTMLResult.\n const sig = computed(() => {\n const raw = (value as Readable<HtmlBindingValue | HtmlBindingValue[]>).value;\n\n return Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n });\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n for (const item of value) {\n if (isHtmlResult(item)) {\n while (item.fragment.firstChild) parent.insertBefore(item.fragment.firstChild, anchor);\n\n chainedApplies.push(item.apply.bind(item));\n } else {\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n const target = el!;\n\n if (slot.kind === SlotKind.EVENT) {\n if (typeof value === 'function') {\n const { handler, options } = applyModifiers(value as (e: Event) => void, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler, name: slot.name!, options, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n const { handler: wrapped, options } = applyModifiers(handler, slot.modifiers ?? []);\n\n bindings.push({ el: target, handler: wrapped, name: slot.name!, options, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el: target, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.SPREAD) {\n if (isSpreadObject(value)) {\n bindings.push({ el: target, spread: value, type: 'spread' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n bindings.push(createAttrBindingFromValue(target, slot.mode ?? 'attr', slot.name!, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
1
+ {"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport { type Binding, type HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values — it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({ el, handler: value as (e: Event) => void, name, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../utils/brand.cjs");let t=require("@vielzeug/ripple");function n(){return(0,t.signal)(null)}var r=e.makeBrand(`ore:directive`),i=e=>r.stamp({mount:e}),a=r.is,o=e.makeBrand(`ore:html-result`),s=o.is;function c(e,t){return o.stamp({apply:t,fragment:e,mount:(n,r,i)=>{let a=Array.from(e.childNodes);return n.insertBefore(e,r),t(i),a}})}exports.createDirectiveResult=i,exports.createHtmlResult=c,exports.isDirectiveResult=a,exports.isHtmlResult=s,exports.ref=n;
2
+ //# sourceMappingURL=result.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.cjs","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts — Branded result objects produced by the template authoring APIs.\n *\n * Runtime code (factories, brand guards) lives here next to the template engine that\n * consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes\n * through `utils/brand.ts` (`Symbol.for`) — see that module for why.\n */\n\nimport { signal, type Signal } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nexport type Ref<T extends Element> = Signal<T | null>;\n\nexport function ref<T extends Element>(): Ref<T> {\n return signal<T | null>(null);\n}\n\nexport type RefCallback<T extends Element> = (el: T | null) => void;\n\n// ─── Directive result ─────────────────────────────────────────────────────────\n\nexport type DirectiveResult = {\n mount: (anchor: Comment, registerCleanup: (fn: () => void) => void) => void;\n};\n\nconst directiveBrand = makeBrand<DirectiveResult>('ore:directive');\n\n/**\n * Creates a registered DirectiveResult. All directive factories must use this\n * function — only objects created here pass `isDirectiveResult()`.\n */\nexport const createDirectiveResult = (mount: DirectiveResult['mount']): DirectiveResult =>\n directiveBrand.stamp({ mount });\n\nexport const isDirectiveResult = directiveBrand.is;\n\n// ─── HTML result ──────────────────────────────────────────────────────────────\n\n/**\n * The output of an `html` tagged template call.\n *\n * Each `html` call produces an independent fragment — there is no shared mutable\n * state between instances, so the same template can be safely rendered multiple\n * times (e.g. inside `each()`).\n *\n * `mount()` is the entire public surface: insertion and reactive wiring in one\n * step, so the two can never get out of sync (the old public `fragment` + `apply`\n * pair made it possible to insert without wiring, or wire without inserting).\n */\nexport interface HTMLResult {\n /**\n * Insert the template's nodes before `anchor` (or append to `parent` when\n * `anchor` is null) and wire up all reactive effects. Returns the inserted\n * nodes so callers can remove them later.\n */\n mount(parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[];\n}\n\n/**\n * @internal The full result the template engine itself works with. `fragment` and\n * `apply` are the engine's own two-phase insertion protocol (static-embed merging in\n * the instantiator, `insertHtmlValues` in the binding layer) — not part of the\n * public API. Consumers only ever see `HTMLResult`.\n */\nexport interface CompiledHTMLResult extends HTMLResult {\n /** The DOM fragment ready to insert into the document. Consumed on insertion. */\n readonly fragment: DocumentFragment;\n /** Wire up reactive effects to the fragment's nodes. Call after insertion. */\n apply(registerCleanup: (fn: () => void) => void): void;\n}\n\nconst htmlResultBrand = makeBrand<CompiledHTMLResult>('ore:html-result');\n\nexport const isHtmlResult = htmlResultBrand.is;\n\nexport function createHtmlResult(\n fragment: DocumentFragment,\n applyFn: (registerCleanup: (fn: () => void) => void) => void,\n): CompiledHTMLResult {\n const mount = (parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[] => {\n const nodes = Array.from(fragment.childNodes);\n\n parent.insertBefore(fragment, anchor);\n applyFn(registerCleanup);\n\n return nodes;\n };\n\n return htmlResultBrand.stamp({ apply: applyFn, fragment, mount });\n}\n"],"mappings":"wEAgBA,SAAgB,GAAiC,CAC/C,OAAA,EAAO,EAAA,OAAA,CAAiB,IAAI,CAC9B,CAUA,IAAM,EAAiB,EAAA,UAA2B,eAAe,EAMpD,EAAyB,GACpC,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnB,EAAoB,EAAe,GAqC1C,EAAkB,EAAA,UAA8B,iBAAiB,EAE1D,EAAe,EAAgB,GAE5C,SAAgB,EACd,EACA,EACoB,CAUpB,OAAO,EAAgB,MAAM,CAAE,MAAO,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAM,EAAQ,MAAM,KAAK,EAAS,UAAU,EAK5C,OAHA,EAAO,aAAa,EAAU,CAAM,EACpC,EAAQ,CAAe,EAEhB,CACT,CAE+D,CAAC,CAClE"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * template/result.ts — Branded result objects produced by the template authoring APIs.
3
+ *
4
+ * Runtime code (factories, brand guards) lives here next to the template engine that
5
+ * consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes
6
+ * through `utils/brand.ts` (`Symbol.for`) — see that module for why.
7
+ */
8
+ import { type Signal } from '@vielzeug/ripple';
9
+ export type Ref<T extends Element> = Signal<T | null>;
10
+ export declare function ref<T extends Element>(): Ref<T>;
11
+ export type RefCallback<T extends Element> = (el: T | null) => void;
12
+ export type DirectiveResult = {
13
+ mount: (anchor: Comment, registerCleanup: (fn: () => void) => void) => void;
14
+ };
15
+ /**
16
+ * Creates a registered DirectiveResult. All directive factories must use this
17
+ * function — only objects created here pass `isDirectiveResult()`.
18
+ */
19
+ export declare const createDirectiveResult: (mount: DirectiveResult["mount"]) => DirectiveResult;
20
+ export declare const isDirectiveResult: (value: unknown) => value is DirectiveResult;
21
+ /**
22
+ * The output of an `html` tagged template call.
23
+ *
24
+ * Each `html` call produces an independent fragment — there is no shared mutable
25
+ * state between instances, so the same template can be safely rendered multiple
26
+ * times (e.g. inside `each()`).
27
+ *
28
+ * `mount()` is the entire public surface: insertion and reactive wiring in one
29
+ * step, so the two can never get out of sync (the old public `fragment` + `apply`
30
+ * pair made it possible to insert without wiring, or wire without inserting).
31
+ */
32
+ export interface HTMLResult {
33
+ /**
34
+ * Insert the template's nodes before `anchor` (or append to `parent` when
35
+ * `anchor` is null) and wire up all reactive effects. Returns the inserted
36
+ * nodes so callers can remove them later.
37
+ */
38
+ mount(parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[];
39
+ }
40
+ export declare const isHtmlResult: (value: unknown) => value is CompiledHTMLResult;
41
+ export declare function createHtmlResult(fragment: DocumentFragment, applyFn: (registerCleanup: (fn: () => void) => void) => void): CompiledHTMLResult;
42
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/template/result.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAMvD,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,OAAO,IAAI,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAEtD,wBAAgB,GAAG,CAAC,CAAC,SAAS,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAE/C;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;AAIpE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC;CAC7E,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,eAAe,CAAC,OAAO,CAAC,KAAG,eACvC,CAAC;AAElC,eAAO,MAAM,iBAAiB,8CAAoB,CAAC;AAInD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;CACnG;AAiBD,eAAO,MAAM,YAAY,iDAAqB,CAAC;AAE/C,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,GAC3D,kBAAkB,CAWpB"}
@@ -0,0 +1,2 @@
1
+ import{makeBrand as e}from"../utils/brand.js";import{signal as t}from"@vielzeug/ripple";function n(){return t(null)}var r=e(`ore:directive`),i=e=>r.stamp({mount:e}),a=r.is,o=e(`ore:html-result`),s=o.is;function c(e,t){return o.stamp({apply:t,fragment:e,mount:(n,r,i)=>{let a=Array.from(e.childNodes);return n.insertBefore(e,r),t(i),a}})}export{i as createDirectiveResult,c as createHtmlResult,a as isDirectiveResult,s as isHtmlResult,n as ref};
2
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts — Branded result objects produced by the template authoring APIs.\n *\n * Runtime code (factories, brand guards) lives here next to the template engine that\n * consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes\n * through `utils/brand.ts` (`Symbol.for`) — see that module for why.\n */\n\nimport { signal, type Signal } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nexport type Ref<T extends Element> = Signal<T | null>;\n\nexport function ref<T extends Element>(): Ref<T> {\n return signal<T | null>(null);\n}\n\nexport type RefCallback<T extends Element> = (el: T | null) => void;\n\n// ─── Directive result ─────────────────────────────────────────────────────────\n\nexport type DirectiveResult = {\n mount: (anchor: Comment, registerCleanup: (fn: () => void) => void) => void;\n};\n\nconst directiveBrand = makeBrand<DirectiveResult>('ore:directive');\n\n/**\n * Creates a registered DirectiveResult. All directive factories must use this\n * function — only objects created here pass `isDirectiveResult()`.\n */\nexport const createDirectiveResult = (mount: DirectiveResult['mount']): DirectiveResult =>\n directiveBrand.stamp({ mount });\n\nexport const isDirectiveResult = directiveBrand.is;\n\n// ─── HTML result ──────────────────────────────────────────────────────────────\n\n/**\n * The output of an `html` tagged template call.\n *\n * Each `html` call produces an independent fragment — there is no shared mutable\n * state between instances, so the same template can be safely rendered multiple\n * times (e.g. inside `each()`).\n *\n * `mount()` is the entire public surface: insertion and reactive wiring in one\n * step, so the two can never get out of sync (the old public `fragment` + `apply`\n * pair made it possible to insert without wiring, or wire without inserting).\n */\nexport interface HTMLResult {\n /**\n * Insert the template's nodes before `anchor` (or append to `parent` when\n * `anchor` is null) and wire up all reactive effects. Returns the inserted\n * nodes so callers can remove them later.\n */\n mount(parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[];\n}\n\n/**\n * @internal The full result the template engine itself works with. `fragment` and\n * `apply` are the engine's own two-phase insertion protocol (static-embed merging in\n * the instantiator, `insertHtmlValues` in the binding layer) — not part of the\n * public API. Consumers only ever see `HTMLResult`.\n */\nexport interface CompiledHTMLResult extends HTMLResult {\n /** The DOM fragment ready to insert into the document. Consumed on insertion. */\n readonly fragment: DocumentFragment;\n /** Wire up reactive effects to the fragment's nodes. Call after insertion. */\n apply(registerCleanup: (fn: () => void) => void): void;\n}\n\nconst htmlResultBrand = makeBrand<CompiledHTMLResult>('ore:html-result');\n\nexport const isHtmlResult = htmlResultBrand.is;\n\nexport function createHtmlResult(\n fragment: DocumentFragment,\n applyFn: (registerCleanup: (fn: () => void) => void) => void,\n): CompiledHTMLResult {\n const mount = (parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[] => {\n const nodes = Array.from(fragment.childNodes);\n\n parent.insertBefore(fragment, anchor);\n applyFn(registerCleanup);\n\n return nodes;\n };\n\n return htmlResultBrand.stamp({ apply: applyFn, fragment, mount });\n}\n"],"mappings":"wFAgBA,SAAgB,GAAiC,CAC/C,OAAO,EAAiB,IAAI,CAC9B,CAUA,IAAM,EAAiB,EAA2B,eAAe,EAMpD,EAAyB,GACpC,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnB,EAAoB,EAAe,GAqC1C,EAAkB,EAA8B,iBAAiB,EAE1D,EAAe,EAAgB,GAE5C,SAAgB,EACd,EACA,EACoB,CAUpB,OAAO,EAAgB,MAAM,CAAE,MAAO,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAM,EAAQ,MAAM,KAAK,EAAS,UAAU,EAK5C,OAHA,EAAO,aAAa,EAAU,CAAM,EACpC,EAAQ,CAAe,EAEhB,CACT,CAE+D,CAAC,CAClE"}
@@ -1,2 +1,2 @@
1
- var e={maxTurns:12};async function t(e={}){let{logger:t,maxTurns:n=5}=e,r=async(e,n)=>{for(let r=0;r<e;r++)t?.(`[flush] ${n} turn ${r+1}/${e}`),await Promise.resolve(),await new Promise(e=>queueMicrotask(e))};t?.(`[flush] starting with maxTurns=${n}`),await r(n,`microtask`),t?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),t?.(`[flush] final microtask pass`),await r(2,`final`),t?.(`[flush] complete`)}exports.FLUSH_DEEP=e,exports.flush=t;
1
+ const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=1e3;async function r(r={}){let{logger:i}=r;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t.hasPendingWork();){if(++a>n)throw new e.OreTimeoutError(`flush(): pending component work did not settle after ${n} microtask turns check for a component whose onMounted callback keeps re-registering itself.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}async function i(){return r({logger:console.debug})}exports.debugFlush=i,exports.flush=r;
2
2
  //# sourceMappingURL=flush.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Maximum number of microtask drain turns.\n * Default: 5, sufficient for normal reactive propagation depth.\n * For deep async chains use `FLUSH_DEEP` (12 turns).\n */\n maxTurns?: number;\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` from `@vielzeug/ore/devtools` over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n/**\n * Pre-built flush options for deep async chains (async setup, nested effects).\n * Use when the default `flush()` isn't sufficient to drain all pending work.\n *\n * @example\n * ```ts\n * await flush(FLUSH_DEEP);\n * ```\n */\nexport const FLUSH_DEEP: FlushOptions = { maxTurns: 12 };\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates.\n * Drains several microtask turns, then yields one animation frame and\n * one final microtask pass for rAF-scheduled work.\n *\n * The default drain count (5) covers typical reactive propagation depth in\n * ore components: effect queueMicrotask (onMounted) nested reactive\n * updates. For deeper async chains, use `FLUSH_DEEP`.\n *\n * @example\n * ```ts\n * // Standard flush for most tests\n * await flush();\n *\n * // Shorter flush for simple updates\n * await flush({ maxTurns: 4 });\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger, maxTurns = 5 } = options;\n\n const drainMicrotasks = async (turns: number, label: string): Promise<void> => {\n for (let i = 0; i < turns; i++) {\n logger?.(`[flush] ${label} turn ${i + 1}/${turns}`);\n\n await Promise.resolve();\n await new Promise<void>((resolve) => queueMicrotask(resolve));\n }\n };\n\n logger?.(`[flush] starting with maxTurns=${maxTurns}`);\n\n await drainMicrotasks(maxTurns, 'microtask');\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await drainMicrotasks(2, 'final');\n\n logger?.('[flush] complete');\n}\n"],"mappings":"AAmCA,IAAa,EAA2B,CAAE,SAAU,EAAG,EAyBvD,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,SAAQ,WAAW,GAAM,EAE3B,EAAkB,MAAO,EAAe,IAAiC,CAC7E,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,IACzB,IAAS,WAAW,EAAM,QAAQ,EAAI,EAAE,GAAG,GAAO,EAElD,MAAM,QAAQ,QAAQ,EACtB,MAAM,IAAI,QAAe,GAAY,eAAe,CAAO,CAAC,CAEhE,EAEA,IAAS,kCAAkC,GAAU,EAErD,MAAM,EAAgB,EAAU,WAAW,EAE3C,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,EAAgB,EAAG,OAAO,EAEhC,IAAS,kBAAkB,CAC7B"}
1
+ {"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreTimeoutError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever) not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (scheduled `onMounted` microtasks including ones registered by other\n * `onMounted` callbacks) to settle, then yields one animation frame and one final\n * microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush — waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns ` +\n `check for a component whose onMounted callback keeps re-registering itself.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n\n/**\n * `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work\n * turn so you can diagnose unexpected update order or timing issues.\n *\n * @example\n * ```ts\n * import { debugFlush } from '@vielzeug/ore/testing';\n *\n * // in a test\n * await debugFlush();\n * // [flush] draining pending component work (scheduled mount callbacks)\n * // [flush] pending work remains, turn 1\n * // ...\n * ```\n */\nexport async function debugFlush(): Promise<void> {\n return flush({ logger: console.debug });\n}\n"],"mappings":"6DAUA,IAAM,EAAuB,IA6C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,qEAAqE,EAE9E,IAAI,EAAQ,EAEZ,KAAO,EAAA,eAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EAAA,gBACR,wDAAwD,EAAqB,+FAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B,CAiBA,eAAsB,GAA4B,CAChD,OAAO,EAAM,CAAE,OAAQ,QAAQ,KAAM,CAAC,CACxC"}
@@ -5,51 +5,55 @@
5
5
  * Options for flushing pending reactive updates.
6
6
  */
7
7
  export interface FlushOptions {
8
- /**
9
- * Maximum number of microtask drain turns.
10
- * Default: 5, sufficient for normal reactive propagation depth.
11
- * For deep async chains use `FLUSH_DEEP` (12 turns).
12
- */
13
- maxTurns?: number;
14
8
  /**
15
9
  * Logger function called with each diagnostic message during the flush.
16
10
  * Pass `console.debug` or any `(msg: string) => void` to enable output.
17
11
  * Omit (or pass `undefined`) to disable all logging.
18
12
  *
19
- * Prefer `debugFlush()` from `@vielzeug/ore/devtools` over wiring this manually.
13
+ * Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.
20
14
  */
21
15
  logger?: (msg: string) => void;
22
16
  }
23
17
  /**
24
- * Pre-built flush options for deep async chains (async setup, nested effects).
25
- * Use when the default `flush()` isn't sufficient to drain all pending work.
18
+ * Flush pending reactive updates: waits for every tracked in-flight component
19
+ * operation (scheduled `onMounted` microtasks including ones registered by other
20
+ * `onMounted` callbacks) to settle, then yields one animation frame and one final
21
+ * microtask pass for rAF-scheduled work.
22
+ *
23
+ * This is deterministic, not a fixed number of guessed turns — `@vielzeug/ripple`'s
24
+ * reactive graph itself settles synchronously on every signal write, so the only real
25
+ * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s
26
+ * `hasPendingWork()`). There is no "deep" variant to reach for anymore: `flush()` waits
27
+ * for exactly as much work as is actually pending, however deep that turns out to be.
26
28
  *
27
29
  * @example
28
30
  * ```ts
29
- * await flush(FLUSH_DEEP);
31
+ * // Standard flush — waits for whatever is actually pending, shallow or deep
32
+ * await flush();
33
+ *
34
+ * // Debug timing issues
35
+ * await flush({ logger: console.debug });
30
36
  * ```
37
+ *
38
+ * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}
39
+ * microtask turns — almost always a sign of a genuine bug (e.g. an `onMounted` callback that
40
+ * keeps re-registering itself), not a case for raising a limit.
31
41
  */
32
- export declare const FLUSH_DEEP: FlushOptions;
42
+ export declare function flush(options?: FlushOptions): Promise<void>;
33
43
  /**
34
- * Flush pending reactive updates.
35
- * Drains several microtask turns, then yields one animation frame and
36
- * one final microtask pass for rAF-scheduled work.
37
- *
38
- * The default drain count (5) covers typical reactive propagation depth in
39
- * ore components: effect → queueMicrotask (onMounted) → nested reactive
40
- * updates. For deeper async chains, use `FLUSH_DEEP`.
44
+ * `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work
45
+ * turn so you can diagnose unexpected update order or timing issues.
41
46
  *
42
47
  * @example
43
48
  * ```ts
44
- * // Standard flush for most tests
45
- * await flush();
46
- *
47
- * // Shorter flush for simple updates
48
- * await flush({ maxTurns: 4 });
49
+ * import { debugFlush } from '@vielzeug/ore/testing';
49
50
  *
50
- * // Debug timing issues
51
- * await flush({ logger: console.debug });
51
+ * // in a test
52
+ * await debugFlush();
53
+ * // [flush] draining pending component work (scheduled mount callbacks)
54
+ * // [flush] pending work remains, turn 1
55
+ * // ...
52
56
  * ```
53
57
  */
54
- export declare function flush(options?: FlushOptions): Promise<void>;
58
+ export declare function debugFlush(): Promise<void>;
55
59
  //# sourceMappingURL=flush.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/testing/flush.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,EAAE,YAA+B,CAAC;AAIzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BrE"}
1
+ {"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/testing/flush.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhD"}
@@ -1,2 +1,2 @@
1
- var e={maxTurns:12};async function t(e={}){let{logger:t,maxTurns:n=5}=e,r=async(e,n)=>{for(let r=0;r<e;r++)t?.(`[flush] ${n} turn ${r+1}/${e}`),await Promise.resolve(),await new Promise(e=>queueMicrotask(e))};t?.(`[flush] starting with maxTurns=${n}`),await r(n,`microtask`),t?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),t?.(`[flush] final microtask pass`),await r(2,`final`),t?.(`[flush] complete`)}export{e as FLUSH_DEEP,t as flush};
1
+ import{OreTimeoutError as e}from"../errors.js";import{hasPendingWork as t}from"../runtime.js";var n=1e3;async function r(r={}){let{logger:i}=r;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t();){if(++a>n)throw new e(`flush(): pending component work did not settle after ${n} microtask turns check for a component whose onMounted callback keeps re-registering itself.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}async function i(){return r({logger:console.debug})}export{i as debugFlush,r as flush};
2
2
  //# sourceMappingURL=flush.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Maximum number of microtask drain turns.\n * Default: 5, sufficient for normal reactive propagation depth.\n * For deep async chains use `FLUSH_DEEP` (12 turns).\n */\n maxTurns?: number;\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` from `@vielzeug/ore/devtools` over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n/**\n * Pre-built flush options for deep async chains (async setup, nested effects).\n * Use when the default `flush()` isn't sufficient to drain all pending work.\n *\n * @example\n * ```ts\n * await flush(FLUSH_DEEP);\n * ```\n */\nexport const FLUSH_DEEP: FlushOptions = { maxTurns: 12 };\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates.\n * Drains several microtask turns, then yields one animation frame and\n * one final microtask pass for rAF-scheduled work.\n *\n * The default drain count (5) covers typical reactive propagation depth in\n * ore components: effect queueMicrotask (onMounted) nested reactive\n * updates. For deeper async chains, use `FLUSH_DEEP`.\n *\n * @example\n * ```ts\n * // Standard flush for most tests\n * await flush();\n *\n * // Shorter flush for simple updates\n * await flush({ maxTurns: 4 });\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger, maxTurns = 5 } = options;\n\n const drainMicrotasks = async (turns: number, label: string): Promise<void> => {\n for (let i = 0; i < turns; i++) {\n logger?.(`[flush] ${label} turn ${i + 1}/${turns}`);\n\n await Promise.resolve();\n await new Promise<void>((resolve) => queueMicrotask(resolve));\n }\n };\n\n logger?.(`[flush] starting with maxTurns=${maxTurns}`);\n\n await drainMicrotasks(maxTurns, 'microtask');\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await drainMicrotasks(2, 'final');\n\n logger?.('[flush] complete');\n}\n"],"mappings":"AAmCA,IAAa,EAA2B,CAAE,SAAU,EAAG,EAyBvD,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,SAAQ,WAAW,GAAM,EAE3B,EAAkB,MAAO,EAAe,IAAiC,CAC7E,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,IACzB,IAAS,WAAW,EAAM,QAAQ,EAAI,EAAE,GAAG,GAAO,EAElD,MAAM,QAAQ,QAAQ,EACtB,MAAM,IAAI,QAAe,GAAY,eAAe,CAAO,CAAC,CAEhE,EAEA,IAAS,kCAAkC,GAAU,EAErD,MAAM,EAAgB,EAAU,WAAW,EAE3C,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,EAAgB,EAAG,OAAO,EAEhC,IAAS,kBAAkB,CAC7B"}
1
+ {"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreTimeoutError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever) not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n *\n * Prefer `debugFlush()` (also from `@vielzeug/ore/testing`) over wiring this manually.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (scheduled `onMounted` microtasks including ones registered by other\n * `onMounted` callbacks) to settle, then yields one animation frame and one final\n * microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush — waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns ` +\n `check for a component whose onMounted callback keeps re-registering itself.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n\n/**\n * `flush()` with debug logging pre-wired to `console.debug`. Logs each phase and pending-work\n * turn so you can diagnose unexpected update order or timing issues.\n *\n * @example\n * ```ts\n * import { debugFlush } from '@vielzeug/ore/testing';\n *\n * // in a test\n * await debugFlush();\n * // [flush] draining pending component work (scheduled mount callbacks)\n * // [flush] pending work remains, turn 1\n * // ...\n * ```\n */\nexport async function debugFlush(): Promise<void> {\n return flush({ logger: console.debug });\n}\n"],"mappings":"8FAUA,IAAM,EAAuB,IA6C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,qEAAqE,EAE9E,IAAI,EAAQ,EAEZ,KAAO,EAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EACR,wDAAwD,EAAqB,+FAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B,CAiBA,eAAsB,GAA4B,CAChD,OAAO,EAAM,CAAE,OAAQ,QAAQ,KAAM,CAAC,CACxC"}
@@ -1,2 +1,2 @@
1
- const e=require("./dom.cjs");var t=()=>{if(typeof ElementInternals>`u`)return;let t=Symbol.for(`vielzeug.ore.testing.formInternalsPolyfillInstalled`),n=globalThis;if(n[t])return;n[t]=!0;let r=ElementInternals.prototype,i=new WeakMap,a=new WeakMap,o=new WeakMap,s=e=>{let t=i.get(e);return!t||!Object.values(t.flags).some(Boolean)};`setFormValue`in r||Object.defineProperty(r,"setFormValue",{configurable:!0,value:function(e){let t=o.get(this);t&&a.set(t,e)},writable:!0}),`setValidity`in r||Object.defineProperty(r,"setValidity",{configurable:!0,value:function(e={},t=``){if(Object.values(e).some(Boolean)&&!t)throw TypeError(`Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if one or more flags in the first argument are true.`);i.set(this,{flags:e,message:t})},writable:!0}),`checkValidity`in r||Object.defineProperty(r,"checkValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`reportValidity`in r||Object.defineProperty(r,"reportValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`validationMessage`in r||Object.defineProperty(r,"validationMessage",{configurable:!0,get(){return s(this)?``:i.get(this)?.message??``}}),`states`in r||Object.defineProperty(r,"states",{configurable:!0,get:function(){return this._states??=new Set,this._states}});let c=HTMLElement.prototype.attachInternals;HTMLElement.prototype.attachInternals=function(...e){let t=c.apply(this,e);return o.set(t,this),Object.defineProperties(this,{checkValidity:{configurable:!0,value:()=>t.checkValidity()},reportValidity:{configurable:!0,value:()=>t.reportValidity()},validationMessage:{configurable:!0,get:()=>t.validationMessage},validity:{configurable:!0,get:()=>t.validity}}),t};let l=(e,t,n)=>{if(n instanceof FormData){for(let[t,r]of n.entries())e.append(t,r);return}e.append(t,n)},u=globalThis.FormData;globalThis.FormData=class extends u{constructor(t,n){super(t,n),t instanceof HTMLFormElement&&e.walkFlatTree(t,e=>{let t=e.getAttribute(`name`),n=a.get(e);!t||n==null||e.hasAttribute(`disabled`)||l(this,t,n)})}};let d=HTMLFormElement.prototype.reset;HTMLFormElement.prototype.reset=function(...t){d.apply(this,t),e.walkFlatTree(this,e=>{e.formResetCallback?.()})}};exports.installFormInternalsPolyfill=t;
1
+ const e=require("./dom.cjs");var t=()=>{if(typeof ElementInternals>`u`)return()=>{};let t=Symbol.for(`vielzeug.ore.testing.formInternalsPolyfillInstalled`),n=globalThis;if(n[t])return()=>{};n[t]=!0;let r=[],i=ElementInternals.prototype,a=new WeakMap,o=new WeakMap,s=new WeakMap,c=(e,t)=>{e in i||(Object.defineProperty(i,e,t),r.push(()=>delete i[e]))},l=e=>{let t=a.get(e);return!t||!Object.values(t.flags).some(Boolean)};c(`setFormValue`,{configurable:!0,value:function(e){let t=s.get(this);t&&o.set(t,e)},writable:!0}),c(`setValidity`,{configurable:!0,value:function(e={},t=``){if(Object.values(e).some(Boolean)&&!t)throw TypeError(`Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if one or more flags in the first argument are true.`);a.set(this,{flags:e,message:t})},writable:!0}),c(`checkValidity`,{configurable:!0,value:function(){return l(this)},writable:!0}),c(`reportValidity`,{configurable:!0,value:function(){return l(this)},writable:!0}),c(`validationMessage`,{configurable:!0,get(){return l(this)?``:a.get(this)?.message??``}}),c(`validity`,{configurable:!0,get(){let e=a.get(this);return{badInput:!1,customError:!1,patternMismatch:!1,rangeOverflow:!1,rangeUnderflow:!1,stepMismatch:!1,tooLong:!1,tooShort:!1,typeMismatch:!1,valid:l(this),valueMissing:!1,...e?.flags??{}}}}),c(`states`,{configurable:!0,get:function(){return this._states??=new Set,this._states}});let u=HTMLElement.prototype.attachInternals;HTMLElement.prototype.attachInternals=function(...e){let t=u.apply(this,e);return s.set(t,this),Object.defineProperties(this,{checkValidity:{configurable:!0,value:()=>t.checkValidity()},reportValidity:{configurable:!0,value:()=>t.reportValidity()},validationMessage:{configurable:!0,get:()=>t.validationMessage},validity:{configurable:!0,get:()=>t.validity}}),t},r.push(()=>{HTMLElement.prototype.attachInternals=u});let d=(e,t,n)=>{if(n instanceof FormData){for(let[t,r]of n.entries())e.append(t,r);return}e.append(t,n)},f=globalThis.FormData;globalThis.FormData=class extends f{constructor(t,n){super(t,n),t instanceof HTMLFormElement&&e.walkFlatTree(t,e=>{let t=e.getAttribute(`name`),n=o.get(e);!t||n==null||e.hasAttribute(`disabled`)||d(this,t,n)})}},r.push(()=>{globalThis.FormData=f});let p=HTMLFormElement.prototype.reset;return HTMLFormElement.prototype.reset=function(...t){p.apply(this,t),e.walkFlatTree(this,e=>{e.formResetCallback?.()})},r.push(()=>{HTMLFormElement.prototype.reset=p}),()=>{for(let e of r)e();r.length=0,n[t]=!1}};exports.installFormInternalsPolyfill=t;
2
2
  //# sourceMappingURL=form-internals-polyfill.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-internals-polyfill.cjs","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore/forms`),\n * so any package testing a form-associated component needs all of them, not just one — this is\n * called once, automatically, by `install()`.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `@vielzeug/refine`'s `<ore-form>` adds one more wrinkle on top — its fields\n * are slotted into a shadow `<form>` — which `walkFlatTree` already handles here, so refine\n * doesn't need a second, package-local copy of this polyfill at all.\n */\nexport const installFormInternalsPolyfill = (): void => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return;\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return;\n\n flagHost[INSTALLED_FLAG] = true;\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n if (!('setFormValue' in proto)) {\n Object.defineProperty(proto, 'setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n }\n\n if (!('setValidity' in proto)) {\n Object.defineProperty(proto, 'setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n }\n\n if (!('checkValidity' in proto)) {\n Object.defineProperty(proto, 'checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('reportValidity' in proto)) {\n Object.defineProperty(proto, 'reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('validationMessage' in proto)) {\n Object.defineProperty(proto, 'validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n }\n\n if (!('states' in proto)) {\n Object.defineProperty(proto, 'states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n }\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"form-internals-polyfill.cjs","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),\n * so any package testing a form-associated component needs all of them, not just one.\n *\n * Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) —\n * the patches are global monkey-patches, so suites without form-associated components\n * shouldn't pay for them. Returns an `uninstall()` that restores every patched global.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `walkFlatTree` handles form-associated custom elements across shadow\n * boundaries, so consumer packages do not need package-local copies of this polyfill.\n */\nexport const installFormInternalsPolyfill = (): (() => void) => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return () => {};\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return () => {};\n\n flagHost[INSTALLED_FLAG] = true;\n\n // Every patch pushes its inverse here, so uninstall() restores the environment exactly.\n const restorers: Array<() => void> = [];\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const patchProto = (name: string, descriptor: PropertyDescriptor): void => {\n if (name in proto) return;\n\n Object.defineProperty(proto, name, descriptor);\n restorers.push(() => delete proto[name]);\n };\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n patchProto('setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n\n patchProto('setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n\n patchProto('checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n\n // A ValidityState-shaped view of the flags passed to setValidity — every known\n // flag defaults to false, `valid` reflects whether any flag is set.\n patchProto('validity', {\n configurable: true,\n get(this: ElementInternals): ValidityState {\n const state = validityState.get(this);\n\n return {\n badInput: false,\n customError: false,\n patternMismatch: false,\n rangeOverflow: false,\n rangeUnderflow: false,\n stepMismatch: false,\n tooLong: false,\n tooShort: false,\n typeMismatch: false,\n valid: isValid(this),\n valueMissing: false,\n ...(state?.flags ?? {}),\n } as ValidityState;\n },\n });\n\n patchProto('states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n restorers.push(() => {\n HTMLElement.prototype.attachInternals = originalAttachInternals;\n });\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n restorers.push(() => {\n globalThis.FormData = NativeFormData;\n });\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n restorers.push(() => {\n HTMLFormElement.prototype.reset = originalReset;\n });\n\n return () => {\n for (const restore of restorers) restore();\n\n restorers.length = 0;\n flagHost[INSTALLED_FLAG] = false;\n };\n};\n"],"mappings":"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"}
@@ -3,15 +3,17 @@
3
3
  * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`
4
4
  * mixin real browsers put on every `formAssociated: true` element, not even `FormData`
5
5
  * collecting a form-associated element's set value, or `<form>.reset()` invoking
6
- * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore/forms`),
7
- * so any package testing a form-associated component needs all of them, not just one — this is
8
- * called once, automatically, by `install()`.
6
+ * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),
7
+ * so any package testing a form-associated component needs all of them, not just one.
8
+ *
9
+ * Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) —
10
+ * the patches are global monkey-patches, so suites without form-associated components
11
+ * shouldn't pay for them. Returns an `uninstall()` that restores every patched global.
9
12
  *
10
13
  * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the
11
14
  * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs
12
- * with the feature. `@vielzeug/refine`'s `<ore-form>` adds one more wrinkle on top — its fields
13
- * are slotted into a shadow `<form>` which `walkFlatTree` already handles here, so refine
14
- * doesn't need a second, package-local copy of this polyfill at all.
15
+ * with the feature. `walkFlatTree` handles form-associated custom elements across shadow
16
+ * boundaries, so consumer packages do not need package-local copies of this polyfill.
15
17
  */
16
- export declare const installFormInternalsPolyfill: () => void;
18
+ export declare const installFormInternalsPolyfill: () => (() => void);
17
19
  //# sourceMappingURL=form-internals-polyfill.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-internals-polyfill.d.ts","sourceRoot":"","sources":["../../src/testing/form-internals-polyfill.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,QAAO,IAqK/C,CAAC"}
1
+ {"version":3,"file":"form-internals-polyfill.d.ts","sourceRoot":"","sources":["../../src/testing/form-internals-polyfill.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,4BAA4B,QAAO,CAAC,MAAM,IAAI,CA2M1D,CAAC"}
@@ -1,2 +1,2 @@
1
- import{walkFlatTree as e}from"./dom.js";var t=()=>{if(typeof ElementInternals>`u`)return;let t=Symbol.for(`vielzeug.ore.testing.formInternalsPolyfillInstalled`),n=globalThis;if(n[t])return;n[t]=!0;let r=ElementInternals.prototype,i=new WeakMap,a=new WeakMap,o=new WeakMap,s=e=>{let t=i.get(e);return!t||!Object.values(t.flags).some(Boolean)};`setFormValue`in r||Object.defineProperty(r,"setFormValue",{configurable:!0,value:function(e){let t=o.get(this);t&&a.set(t,e)},writable:!0}),`setValidity`in r||Object.defineProperty(r,"setValidity",{configurable:!0,value:function(e={},t=``){if(Object.values(e).some(Boolean)&&!t)throw TypeError(`Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if one or more flags in the first argument are true.`);i.set(this,{flags:e,message:t})},writable:!0}),`checkValidity`in r||Object.defineProperty(r,"checkValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`reportValidity`in r||Object.defineProperty(r,"reportValidity",{configurable:!0,value:function(){return s(this)},writable:!0}),`validationMessage`in r||Object.defineProperty(r,"validationMessage",{configurable:!0,get(){return s(this)?``:i.get(this)?.message??``}}),`states`in r||Object.defineProperty(r,"states",{configurable:!0,get:function(){return this._states??=new Set,this._states}});let c=HTMLElement.prototype.attachInternals;HTMLElement.prototype.attachInternals=function(...e){let t=c.apply(this,e);return o.set(t,this),Object.defineProperties(this,{checkValidity:{configurable:!0,value:()=>t.checkValidity()},reportValidity:{configurable:!0,value:()=>t.reportValidity()},validationMessage:{configurable:!0,get:()=>t.validationMessage},validity:{configurable:!0,get:()=>t.validity}}),t};let l=(e,t,n)=>{if(n instanceof FormData){for(let[t,r]of n.entries())e.append(t,r);return}e.append(t,n)},u=globalThis.FormData;globalThis.FormData=class extends u{constructor(t,n){super(t,n),t instanceof HTMLFormElement&&e(t,e=>{let t=e.getAttribute(`name`),n=a.get(e);!t||n==null||e.hasAttribute(`disabled`)||l(this,t,n)})}};let d=HTMLFormElement.prototype.reset;HTMLFormElement.prototype.reset=function(...t){d.apply(this,t),e(this,e=>{e.formResetCallback?.()})}};export{t as installFormInternalsPolyfill};
1
+ import{walkFlatTree as e}from"./dom.js";var t=()=>{if(typeof ElementInternals>`u`)return()=>{};let t=Symbol.for(`vielzeug.ore.testing.formInternalsPolyfillInstalled`),n=globalThis;if(n[t])return()=>{};n[t]=!0;let r=[],i=ElementInternals.prototype,a=new WeakMap,o=new WeakMap,s=new WeakMap,c=(e,t)=>{e in i||(Object.defineProperty(i,e,t),r.push(()=>delete i[e]))},l=e=>{let t=a.get(e);return!t||!Object.values(t.flags).some(Boolean)};c(`setFormValue`,{configurable:!0,value:function(e){let t=s.get(this);t&&o.set(t,e)},writable:!0}),c(`setValidity`,{configurable:!0,value:function(e={},t=``){if(Object.values(e).some(Boolean)&&!t)throw TypeError(`Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if one or more flags in the first argument are true.`);a.set(this,{flags:e,message:t})},writable:!0}),c(`checkValidity`,{configurable:!0,value:function(){return l(this)},writable:!0}),c(`reportValidity`,{configurable:!0,value:function(){return l(this)},writable:!0}),c(`validationMessage`,{configurable:!0,get(){return l(this)?``:a.get(this)?.message??``}}),c(`validity`,{configurable:!0,get(){let e=a.get(this);return{badInput:!1,customError:!1,patternMismatch:!1,rangeOverflow:!1,rangeUnderflow:!1,stepMismatch:!1,tooLong:!1,tooShort:!1,typeMismatch:!1,valid:l(this),valueMissing:!1,...e?.flags??{}}}}),c(`states`,{configurable:!0,get:function(){return this._states??=new Set,this._states}});let u=HTMLElement.prototype.attachInternals;HTMLElement.prototype.attachInternals=function(...e){let t=u.apply(this,e);return s.set(t,this),Object.defineProperties(this,{checkValidity:{configurable:!0,value:()=>t.checkValidity()},reportValidity:{configurable:!0,value:()=>t.reportValidity()},validationMessage:{configurable:!0,get:()=>t.validationMessage},validity:{configurable:!0,get:()=>t.validity}}),t},r.push(()=>{HTMLElement.prototype.attachInternals=u});let d=(e,t,n)=>{if(n instanceof FormData){for(let[t,r]of n.entries())e.append(t,r);return}e.append(t,n)},f=globalThis.FormData;globalThis.FormData=class extends f{constructor(t,n){super(t,n),t instanceof HTMLFormElement&&e(t,e=>{let t=e.getAttribute(`name`),n=o.get(e);!t||n==null||e.hasAttribute(`disabled`)||d(this,t,n)})}},r.push(()=>{globalThis.FormData=f});let p=HTMLFormElement.prototype.reset;return HTMLFormElement.prototype.reset=function(...t){p.apply(this,t),e(this,e=>{e.formResetCallback?.()})},r.push(()=>{HTMLFormElement.prototype.reset=p}),()=>{for(let e of r)e();r.length=0,n[t]=!1}};export{t as installFormInternalsPolyfill};
2
2
  //# sourceMappingURL=form-internals-polyfill.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-internals-polyfill.js","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore/forms`),\n * so any package testing a form-associated component needs all of them, not just one — this is\n * called once, automatically, by `install()`.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `@vielzeug/refine`'s `<ore-form>` adds one more wrinkle on top — its fields\n * are slotted into a shadow `<form>` — which `walkFlatTree` already handles here, so refine\n * doesn't need a second, package-local copy of this polyfill at all.\n */\nexport const installFormInternalsPolyfill = (): void => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return;\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return;\n\n flagHost[INSTALLED_FLAG] = true;\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n if (!('setFormValue' in proto)) {\n Object.defineProperty(proto, 'setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n }\n\n if (!('setValidity' in proto)) {\n Object.defineProperty(proto, 'setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n }\n\n if (!('checkValidity' in proto)) {\n Object.defineProperty(proto, 'checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('reportValidity' in proto)) {\n Object.defineProperty(proto, 'reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n }\n\n if (!('validationMessage' in proto)) {\n Object.defineProperty(proto, 'validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n }\n\n if (!('states' in proto)) {\n Object.defineProperty(proto, 'states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n }\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"form-internals-polyfill.js","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),\n * so any package testing a form-associated component needs all of them, not just one.\n *\n * Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) —\n * the patches are global monkey-patches, so suites without form-associated components\n * shouldn't pay for them. Returns an `uninstall()` that restores every patched global.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `walkFlatTree` handles form-associated custom elements across shadow\n * boundaries, so consumer packages do not need package-local copies of this polyfill.\n */\nexport const installFormInternalsPolyfill = (): (() => void) => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return () => {};\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return () => {};\n\n flagHost[INSTALLED_FLAG] = true;\n\n // Every patch pushes its inverse here, so uninstall() restores the environment exactly.\n const restorers: Array<() => void> = [];\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const patchProto = (name: string, descriptor: PropertyDescriptor): void => {\n if (name in proto) return;\n\n Object.defineProperty(proto, name, descriptor);\n restorers.push(() => delete proto[name]);\n };\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n patchProto('setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n\n patchProto('setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n\n patchProto('checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n\n // A ValidityState-shaped view of the flags passed to setValidity — every known\n // flag defaults to false, `valid` reflects whether any flag is set.\n patchProto('validity', {\n configurable: true,\n get(this: ElementInternals): ValidityState {\n const state = validityState.get(this);\n\n return {\n badInput: false,\n customError: false,\n patternMismatch: false,\n rangeOverflow: false,\n rangeUnderflow: false,\n stepMismatch: false,\n tooLong: false,\n tooShort: false,\n typeMismatch: false,\n valid: isValid(this),\n valueMissing: false,\n ...(state?.flags ?? {}),\n } as ValidityState;\n },\n });\n\n patchProto('states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n restorers.push(() => {\n HTMLElement.prototype.attachInternals = originalAttachInternals;\n });\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n restorers.push(() => {\n globalThis.FormData = NativeFormData;\n });\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n restorers.push(() => {\n HTMLFormElement.prototype.reset = originalReset;\n });\n\n return () => {\n for (const restore of restorers) restore();\n\n restorers.length = 0;\n flagHost[INSTALLED_FLAG] = false;\n };\n};\n"],"mappings":"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"}
@@ -1,2 +1,36 @@
1
- export * from './testing';
1
+ /**
2
+ * Testing utilities for Ore components
3
+ *
4
+ * ⚠️ Requires DOM environment (browser / jsdom / happy-dom)
5
+ *
6
+ * This barrel re-exports the full testing API for convenience.
7
+ * For tree-shaking, import directly from the focused sub-modules.
8
+ */
9
+ export { OreTimeoutError } from '../errors';
10
+ export { debugFlush, flush, type FlushOptions } from './flush';
11
+ export { walkFlatTree } from './dom';
12
+ export { installFormInternalsPolyfill } from './form-internals-polyfill';
13
+ export { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';
14
+ export { renderHook, type HookFixture } from './render-hook';
15
+ export { resetOreForTests } from './reset';
16
+ export type InstallOptions = {
17
+ /**
18
+ * Polyfill the jsdom/happy-dom gaps in `ElementInternals`/`FormData`/`<form>.reset()`
19
+ * that `useField()` needs (see `installFormInternalsPolyfill`). Global monkey-patches,
20
+ * so opt in only when the suite tests form-associated components.
21
+ * @default false
22
+ */
23
+ formInternals?: boolean;
24
+ };
25
+ /**
26
+ * Register auto-cleanup after each test. Call once in your test setup file.
27
+ * Pass `{ formInternals: true }` when testing form-associated (`useField`) components.
28
+ *
29
+ * @example
30
+ * // vitest.setup.ts
31
+ * import { afterEach } from 'vitest';
32
+ * import { install } from '@vielzeug/ore/testing';
33
+ * install(afterEach, { formInternals: true });
34
+ */
35
+ export declare function install(afterEachHook: (fn: () => void) => void, options?: InstallOptions): void;
2
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AACjH,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,GAAE,cAAmB,GAAG,IAAI,CAInG"}
@@ -1,2 +1,2 @@
1
- const e=require("./flush.cjs");require("../types/bindings.cjs");const t=require("../directives/live.cjs"),n=require("../directives/raw.cjs"),r=require("../utils/css.cjs"),i=require("../define.cjs"),a=require("../utils/id.cjs"),o=require("./query.cjs");var s=[],c=0;function l(e,t,n){n===!1?e.removeAttribute(t):e.setAttribute(t,n===!0?``:String(n))}var u=e=>e instanceof Error?e:Error(String(e)),d=async e=>{if(typeof window>`u`)return e();let t=null,n=e=>{t=u(e.error??e.message),e.preventDefault()},r=e=>{t=u(e.reason),e.preventDefault()};window.addEventListener(`error`,n),window.addEventListener(`unhandledrejection`,r);try{let n=await e();if(t)throw t;return n}finally{window.removeEventListener(`error`,n),window.removeEventListener(`unhandledrejection`,r)}};async function f(t,n={}){let{attrs:r={},componentOptions:a,container:u=document.body,html:f,props:p={}}=n,m,h;typeof t==`string`?m=t:(m=`trial-${++c}-${Math.random().toString(36).slice(2,7)}`,h={...a??{},setup:t}),h&&i.define(m,h);let g=document.createElement(m);f&&(g.innerHTML=f),Object.keys(p).length&&Object.assign(g,p);for(let[e,t]of Object.entries(r))l(g,e,t);try{await d(async()=>{u.appendChild(g),s.push(g),await e.flush()})}catch(e){g.remove();let t=s.indexOf(g);throw t!==-1&&s.splice(t,1),e}let _=!1;function v(){if(_)return;_=!0,g.remove();let e=s.indexOf(g);e!==-1&&s.splice(e,1)}return{async act(t){await t(),await e.flush()},async attr(t,n){l(g,t,n),await e.flush()},async attrs(t){for(let[e,n]of Object.entries(t))l(g,e,n);await e.flush()},dispose:v,get disposed(){return _},element:g,flush:e.flush,query(e){return(g.shadowRoot??g).querySelector(e)},queryAll(e){return Array.from((g.shadowRoot??g).querySelectorAll(e))},queryAllByTestId(e){return Array.from((g.shadowRoot??g).querySelectorAll(`[data-testid="${e}"]`))},queryAllByText(e,t=`*`){return o.queryAllByText(g.shadowRoot??g,e,t)},queryByTestId(e){return(g.shadowRoot??g).querySelector(`[data-testid="${e}"]`)},queryByText(e,t=`*`){return o.queryByText(g.shadowRoot??g,e,t)},get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}async function p(e,t,n){return i.define(e,t),f(e,n)}function m(e,t=``){customElements.get(e)||customElements.define(e,class extends HTMLElement{connectedCallback(){this.innerHTML=t}})}function h(){for(let e of s)e.remove();s.length=0,n._resetRawSanitizer(),t._resetLiveSignals(),r._clearStylesheetCache(),a._resetIdCounter()}Object.defineProperty(exports,"_componentTagCounter",{enumerable:!0,get:function(){return c}}),exports._mountedElements=s,exports.cleanup=h,exports.mock=m,exports.mount=f,exports.mountComponent=p;
1
+ const e=require("../utils/dom.cjs");require("../template/result.cjs");const t=require("../define.cjs"),n=require("./flush.cjs"),r=require("./reset.cjs");let i=require("@vielzeug/assay");var a=[],o=0;function s(t,n,r){e.setAttr(t,n,r)}var c=e=>e instanceof Error?e:Error(String(e)),l=async e=>{if(typeof window>`u`)return e();let t=null,n=e=>{t=c(e.error??e.message),e.preventDefault()},r=e=>{t=c(e.reason),e.preventDefault()};window.addEventListener(`error`,n),window.addEventListener(`unhandledrejection`,r);try{let n=await e();if(t)throw t;return n}finally{window.removeEventListener(`error`,n),window.removeEventListener(`unhandledrejection`,r)}};async function u(e,r={}){let{attrs:c={},componentOptions:u,container:d=document.body,html:f,props:p={}}=r,m,h;typeof e==`string`?m=e:(m=`trial-${++o}`,h={...u??{},setup:e}),h&&t.define(m,h);let g=document.createElement(m);f&&(g.innerHTML=f),Object.keys(p).length&&Object.assign(g,p);for(let[e,t]of Object.entries(c))s(g,e,t);try{await l(async()=>{d.appendChild(g),a.push(g),await n.flush()})}catch(e){g.remove();let t=a.indexOf(g);throw t!==-1&&a.splice(t,1),e}let _=!1;function v(){if(_)return;_=!0,g.remove();let e=a.indexOf(g);e!==-1&&a.splice(e,1)}return{...(0,i.within)(g.shadowRoot??g),async act(e){await e(),await n.flush()},async attr(e,t){s(g,e,t),await n.flush()},async attrs(e){for(let[t,n]of Object.entries(e))s(g,t,n);await n.flush()},dispose:v,get disposed(){return _},element:g,flush:n.flush,get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}async function d(e,n,r){return t.define(e,n),u(e,r)}function f(e,t=``){customElements.get(e)||customElements.define(e,class extends HTMLElement{connectedCallback(){this.innerHTML=t}})}function p(){for(let e of a)e.remove();a.length=0,r.resetOreForTests()}exports._mountedElements=a,exports.cleanup=p,exports.mock=f,exports.mount=u,exports.mountComponent=d;
2
2
  //# sourceMappingURL=mount.cjs.map