@vielzeug/ore 1.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) 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.map +1 -1
  33. package/dist/directives/each.cjs +1 -1
  34. package/dist/directives/each.cjs.map +1 -1
  35. package/dist/directives/each.d.ts +1 -1
  36. package/dist/directives/each.d.ts.map +1 -1
  37. package/dist/directives/each.js +1 -1
  38. package/dist/directives/each.js.map +1 -1
  39. package/dist/directives/live.cjs +1 -1
  40. package/dist/directives/live.cjs.map +1 -1
  41. package/dist/directives/live.d.ts +10 -12
  42. package/dist/directives/live.d.ts.map +1 -1
  43. package/dist/directives/live.js +1 -1
  44. package/dist/directives/live.js.map +1 -1
  45. package/dist/directives/styleMap.cjs.map +1 -1
  46. package/dist/directives/styleMap.d.ts +1 -1
  47. package/dist/directives/styleMap.js.map +1 -1
  48. package/dist/directives/unsafe-html.cjs +2 -0
  49. package/dist/directives/unsafe-html.cjs.map +1 -0
  50. package/dist/directives/unsafe-html.d.ts +14 -0
  51. package/dist/directives/unsafe-html.d.ts.map +1 -0
  52. package/dist/directives/unsafe-html.js +2 -0
  53. package/dist/directives/unsafe-html.js.map +1 -0
  54. package/dist/directives/when.cjs +1 -1
  55. package/dist/directives/when.cjs.map +1 -1
  56. package/dist/directives/when.d.ts +1 -1
  57. package/dist/directives/when.d.ts.map +1 -1
  58. package/dist/directives/when.js +1 -1
  59. package/dist/directives/when.js.map +1 -1
  60. package/dist/errors.cjs +2 -2
  61. package/dist/errors.cjs.map +1 -1
  62. package/dist/errors.d.ts +6 -4
  63. package/dist/errors.d.ts.map +1 -1
  64. package/dist/errors.js +2 -2
  65. package/dist/errors.js.map +1 -1
  66. package/dist/forms/field.cjs +1 -1
  67. package/dist/forms/field.cjs.map +1 -1
  68. package/dist/forms/field.d.ts +5 -1
  69. package/dist/forms/field.d.ts.map +1 -1
  70. package/dist/forms/field.js +1 -1
  71. package/dist/forms/field.js.map +1 -1
  72. package/dist/host-bind.cjs.map +1 -1
  73. package/dist/index.cjs +1 -1
  74. package/dist/index.d.ts +9 -4
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1 -1
  77. package/dist/observers/intersection-observe.cjs +1 -1
  78. package/dist/observers/intersection-observe.cjs.map +1 -1
  79. package/dist/observers/intersection-observe.d.ts.map +1 -1
  80. package/dist/observers/intersection-observe.js +1 -1
  81. package/dist/observers/intersection-observe.js.map +1 -1
  82. package/dist/observers/media-observe.cjs +1 -1
  83. package/dist/observers/media-observe.cjs.map +1 -1
  84. package/dist/observers/media-observe.d.ts.map +1 -1
  85. package/dist/observers/media-observe.js +1 -1
  86. package/dist/observers/media-observe.js.map +1 -1
  87. package/dist/observers/mutation-observe.cjs +1 -1
  88. package/dist/observers/mutation-observe.cjs.map +1 -1
  89. package/dist/observers/mutation-observe.d.ts.map +1 -1
  90. package/dist/observers/mutation-observe.js +1 -1
  91. package/dist/observers/mutation-observe.js.map +1 -1
  92. package/dist/observers/resize-observe.cjs +1 -1
  93. package/dist/observers/resize-observe.cjs.map +1 -1
  94. package/dist/observers/resize-observe.d.ts.map +1 -1
  95. package/dist/observers/resize-observe.js +1 -1
  96. package/dist/observers/resize-observe.js.map +1 -1
  97. package/dist/ore.cjs +2 -2
  98. package/dist/ore.cjs.map +1 -1
  99. package/dist/ore.iife.js +2 -2
  100. package/dist/ore.iife.js.map +1 -1
  101. package/dist/ore.js +2 -2
  102. package/dist/ore.js.map +1 -1
  103. package/dist/props.cjs +1 -1
  104. package/dist/props.cjs.map +1 -1
  105. package/dist/props.d.ts +1 -6
  106. package/dist/props.d.ts.map +1 -1
  107. package/dist/props.js +1 -1
  108. package/dist/props.js.map +1 -1
  109. package/dist/runtime.cjs +1 -1
  110. package/dist/runtime.cjs.map +1 -1
  111. package/dist/runtime.d.ts +7 -10
  112. package/dist/runtime.d.ts.map +1 -1
  113. package/dist/runtime.js +1 -1
  114. package/dist/runtime.js.map +1 -1
  115. package/dist/slots.cjs +1 -1
  116. package/dist/slots.cjs.map +1 -1
  117. package/dist/slots.d.ts.map +1 -1
  118. package/dist/slots.js +1 -1
  119. package/dist/slots.js.map +1 -1
  120. package/dist/template/binding-types.cjs +1 -0
  121. package/dist/template/binding-types.d.ts +59 -0
  122. package/dist/template/binding-types.d.ts.map +1 -0
  123. package/dist/template/binding-types.js +1 -0
  124. package/dist/template/bindings.cjs +1 -1
  125. package/dist/template/bindings.cjs.map +1 -1
  126. package/dist/template/bindings.d.ts +18 -3
  127. package/dist/template/bindings.d.ts.map +1 -1
  128. package/dist/template/bindings.js +1 -1
  129. package/dist/template/bindings.js.map +1 -1
  130. package/dist/template/compiler.cjs +1 -1
  131. package/dist/template/compiler.cjs.map +1 -1
  132. package/dist/template/compiler.d.ts +0 -4
  133. package/dist/template/compiler.d.ts.map +1 -1
  134. package/dist/template/compiler.js +1 -1
  135. package/dist/template/compiler.js.map +1 -1
  136. package/dist/template/instantiator.cjs +1 -1
  137. package/dist/template/instantiator.cjs.map +1 -1
  138. package/dist/template/instantiator.d.ts +6 -1
  139. package/dist/template/instantiator.d.ts.map +1 -1
  140. package/dist/template/instantiator.js +1 -1
  141. package/dist/template/instantiator.js.map +1 -1
  142. package/dist/template/result.cjs +2 -0
  143. package/dist/template/result.cjs.map +1 -0
  144. package/dist/template/result.d.ts +42 -0
  145. package/dist/template/result.d.ts.map +1 -0
  146. package/dist/template/result.js +2 -0
  147. package/dist/template/result.js.map +1 -0
  148. package/dist/testing/flush.cjs +1 -1
  149. package/dist/testing/flush.cjs.map +1 -1
  150. package/dist/testing/flush.d.ts +4 -4
  151. package/dist/testing/flush.js +1 -1
  152. package/dist/testing/flush.js.map +1 -1
  153. package/dist/testing/form-internals-polyfill.cjs +1 -1
  154. package/dist/testing/form-internals-polyfill.cjs.map +1 -1
  155. package/dist/testing/form-internals-polyfill.d.ts +9 -7
  156. package/dist/testing/form-internals-polyfill.d.ts.map +1 -1
  157. package/dist/testing/form-internals-polyfill.js +1 -1
  158. package/dist/testing/form-internals-polyfill.js.map +1 -1
  159. package/dist/testing/index.d.ts +14 -9
  160. package/dist/testing/index.d.ts.map +1 -1
  161. package/dist/testing/mount.cjs +1 -1
  162. package/dist/testing/mount.cjs.map +1 -1
  163. package/dist/testing/mount.d.ts +6 -9
  164. package/dist/testing/mount.d.ts.map +1 -1
  165. package/dist/testing/mount.js +1 -1
  166. package/dist/testing/mount.js.map +1 -1
  167. package/dist/testing/render-hook.cjs +1 -1
  168. package/dist/testing/render-hook.cjs.map +1 -1
  169. package/dist/testing/render-hook.js +1 -1
  170. package/dist/testing/render-hook.js.map +1 -1
  171. package/dist/testing/reset.cjs +2 -0
  172. package/dist/testing/reset.cjs.map +1 -0
  173. package/dist/testing/reset.d.ts +12 -0
  174. package/dist/testing/reset.d.ts.map +1 -0
  175. package/dist/testing/reset.js +2 -0
  176. package/dist/testing/reset.js.map +1 -0
  177. package/dist/testing.cjs +1 -1
  178. package/dist/testing.cjs.map +1 -1
  179. package/dist/testing.js +1 -1
  180. package/dist/testing.js.map +1 -1
  181. package/dist/utils/brand.cjs +2 -0
  182. package/dist/utils/brand.cjs.map +1 -0
  183. package/dist/utils/brand.d.ts +16 -0
  184. package/dist/utils/brand.d.ts.map +1 -0
  185. package/dist/utils/brand.js +2 -0
  186. package/dist/utils/brand.js.map +1 -0
  187. package/dist/utils/css.cjs +1 -1
  188. package/dist/utils/css.cjs.map +1 -1
  189. package/dist/utils/css.d.ts.map +1 -1
  190. package/dist/utils/css.js +1 -1
  191. package/dist/utils/css.js.map +1 -1
  192. package/dist/utils/dom.cjs +1 -1
  193. package/dist/utils/dom.cjs.map +1 -1
  194. package/dist/utils/dom.d.ts +3 -5
  195. package/dist/utils/dom.d.ts.map +1 -1
  196. package/dist/utils/dom.js +1 -1
  197. package/dist/utils/dom.js.map +1 -1
  198. package/dist/utils/emit.cjs.map +1 -1
  199. package/dist/utils/emit.d.ts +10 -3
  200. package/dist/utils/emit.d.ts.map +1 -1
  201. package/dist/utils/emit.js.map +1 -1
  202. package/package.json +22 -27
  203. package/dist/aria.cjs +0 -2
  204. package/dist/aria.cjs.map +0 -1
  205. package/dist/aria.d.ts +0 -21
  206. package/dist/aria.d.ts.map +0 -1
  207. package/dist/aria.js +0 -2
  208. package/dist/aria.js.map +0 -1
  209. package/dist/directives/index.d.ts +0 -37
  210. package/dist/directives/index.d.ts.map +0 -1
  211. package/dist/directives/model.cjs +0 -2
  212. package/dist/directives/model.cjs.map +0 -1
  213. package/dist/directives/model.d.ts +0 -28
  214. package/dist/directives/model.d.ts.map +0 -1
  215. package/dist/directives/model.js +0 -2
  216. package/dist/directives/model.js.map +0 -1
  217. package/dist/directives/raw.cjs +0 -2
  218. package/dist/directives/raw.cjs.map +0 -1
  219. package/dist/directives/raw.d.ts +0 -16
  220. package/dist/directives/raw.d.ts.map +0 -1
  221. package/dist/directives/raw.js +0 -2
  222. package/dist/directives/raw.js.map +0 -1
  223. package/dist/directives.cjs +0 -1
  224. package/dist/directives.js +0 -1
  225. package/dist/forms/context.cjs +0 -2
  226. package/dist/forms/context.cjs.map +0 -1
  227. package/dist/forms/context.d.ts +0 -56
  228. package/dist/forms/context.d.ts.map +0 -1
  229. package/dist/forms/context.js +0 -2
  230. package/dist/forms/context.js.map +0 -1
  231. package/dist/forms/index.d.ts +0 -11
  232. package/dist/forms/index.d.ts.map +0 -1
  233. package/dist/forms.cjs +0 -1
  234. package/dist/forms.js +0 -1
  235. package/dist/observers/index.d.ts +0 -5
  236. package/dist/observers/index.d.ts.map +0 -1
  237. package/dist/observers.cjs +0 -1
  238. package/dist/observers.js +0 -1
  239. package/dist/template.cjs +0 -1
  240. package/dist/template.d.ts +0 -4
  241. package/dist/template.d.ts.map +0 -1
  242. package/dist/template.js +0 -1
  243. package/dist/testing/interactions.cjs +0 -2
  244. package/dist/testing/interactions.cjs.map +0 -1
  245. package/dist/testing/interactions.d.ts +0 -27
  246. package/dist/testing/interactions.d.ts.map +0 -1
  247. package/dist/testing/interactions.js +0 -2
  248. package/dist/testing/interactions.js.map +0 -1
  249. package/dist/types/bindings.cjs +0 -2
  250. package/dist/types/bindings.cjs.map +0 -1
  251. package/dist/types/bindings.d.ts +0 -125
  252. package/dist/types/bindings.d.ts.map +0 -1
  253. package/dist/types/bindings.js +0 -2
  254. package/dist/types/bindings.js.map +0 -1
  255. package/dist/types.cjs +0 -2
  256. package/dist/types.cjs.map +0 -1
  257. package/dist/types.d.ts +0 -19
  258. package/dist/types.d.ts.map +0 -1
  259. package/dist/types.js +0 -2
  260. package/dist/types.js.map +0 -1
  261. package/dist/utils/event-modifiers.cjs +0 -2
  262. package/dist/utils/event-modifiers.cjs.map +0 -1
  263. package/dist/utils/event-modifiers.d.ts +0 -11
  264. package/dist/utils/event-modifiers.d.ts.map +0 -1
  265. package/dist/utils/event-modifiers.js +0 -2
  266. 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 // Throws immediately, every build: a malformed dynamic tag name is a programming bug in\n // the template, not a runtime race, and this happens synchronously during instantiation\n // (not inside a reactive effect) — there's no risk of an uncaught throw here corrupting an\n // unrelated batch of effects the way rethrowing from each()'s reconciliation would (see\n // each.ts's duplicate-key handling, which instead reports via reportRuntimeError() since it\n // runs inside a reactive effect).\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
- 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 (async setup, 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, or an async setup() that never resolves.`);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;
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\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 (an awaited async `setup()`, scheduled `onMounted` microtasks — including\n * ones registered by other `onMounted` callbacks) to settle, then yields one animation\n * frame and one final 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 (async setup, 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, or an async setup() that never resolves.`,\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 (async setup, 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,kFAAkF,EAE3F,IAAI,EAAQ,EAEZ,KAAO,EAAA,eAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EAAA,gBACR,wDAAwD,EAAqB,wIAE/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
+ {"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"}
@@ -16,9 +16,9 @@ export interface FlushOptions {
16
16
  }
17
17
  /**
18
18
  * Flush pending reactive updates: waits for every tracked in-flight component
19
- * operation (an awaited async `setup()`, scheduled `onMounted` microtasks — including
20
- * ones registered by other `onMounted` callbacks) to settle, then yields one animation
21
- * frame and one final microtask pass for rAF-scheduled work.
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
22
  *
23
23
  * This is deterministic, not a fixed number of guessed turns — `@vielzeug/ripple`'s
24
24
  * reactive graph itself settles synchronously on every signal write, so the only real
@@ -50,7 +50,7 @@ export declare function flush(options?: FlushOptions): Promise<void>;
50
50
  *
51
51
  * // in a test
52
52
  * await debugFlush();
53
- * // [flush] draining pending component work (async setup, scheduled mount callbacks)
53
+ * // [flush] draining pending component work (scheduled mount callbacks)
54
54
  * // [flush] pending work remains, turn 1
55
55
  * // ...
56
56
  * ```
@@ -1,2 +1,2 @@
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 (async setup, 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, or an async setup() that never resolves.`);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};
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\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 (an awaited async `setup()`, scheduled `onMounted` microtasks — including\n * ones registered by other `onMounted` callbacks) to settle, then yields one animation\n * frame and one final 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 (async setup, 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, or an async setup() that never resolves.`,\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 (async setup, 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,kFAAkF,EAE3F,IAAI,EAAQ,EAEZ,KAAO,EAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EACR,wDAAwD,EAAqB,wIAE/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
+ {"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":"6BAmBA,IAAa,MAAmD,CAK9D,GAAI,OAAO,iBAAqB,IAAa,UAAa,CAAC,EAE3D,IAAM,EAAiB,OAAO,IAAI,qDAAqD,EACjF,EAAW,WAEjB,GAAI,EAAS,GAAiB,UAAa,CAAC,EAE5C,EAAS,GAAkB,GAG3B,IAAM,EAA+B,CAAC,EAEhC,EAAQ,iBAAiB,UACzB,EAAgB,IAAI,QACpB,EAAkB,IAAI,QACtB,EAAkB,IAAI,QAEtB,GAAc,EAAc,IAAyC,CACrE,KAAQ,IAEZ,OAAO,eAAe,EAAO,EAAM,CAAU,EAC7C,EAAU,SAAW,OAAO,EAAM,EAAK,EACzC,EAEM,EAAW,GAAyC,CACxD,IAAM,EAAQ,EAAc,IAAI,CAAS,EAEzC,MAAO,CAAC,GAAS,CAAC,OAAO,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,OAAO,CAC3D,EAEA,EAAW,eAAgB,CACzB,aAAc,GACd,MAAO,SAAkC,EAAwC,CAC/E,IAAM,EAAO,EAAgB,IAAI,IAAI,EAEjC,GAAM,EAAgB,IAAI,EAAM,CAAK,CAC3C,EACA,SAAU,EACZ,CAAC,EAED,EAAW,cAAe,CACxB,aAAc,GAKd,MAAO,SAAkC,EAA4B,CAAC,EAAG,EAAU,GAAI,CACrF,GAAI,OAAO,OAAO,CAAK,CAAC,CAAC,KAAK,OAAO,GAAK,CAAC,EACzC,MAAU,UACR,mJAEF,EAGF,EAAc,IAAI,KAAM,CAAE,QAAO,SAAQ,CAAC,CAC5C,EACA,SAAU,EACZ,CAAC,EAED,EAAW,gBAAiB,CAC1B,aAAc,GACd,MAAO,UAAkC,CACvC,OAAO,EAAQ,IAAI,CACrB,EACA,SAAU,EACZ,CAAC,EAED,EAAW,iBAAkB,CAC3B,aAAc,GACd,MAAO,UAAkC,CACvC,OAAO,EAAQ,IAAI,CACrB,EACA,SAAU,EACZ,CAAC,EAED,EAAW,oBAAqB,CAC9B,aAAc,GACd,KAA4B,CAC1B,OAAO,EAAQ,IAAI,EAAI,GAAM,EAAc,IAAI,IAAI,CAAC,EAAE,SAAW,EACnE,CACF,CAAC,EAID,EAAW,WAAY,CACrB,aAAc,GACd,KAA2C,CACzC,IAAM,EAAQ,EAAc,IAAI,IAAI,EAEpC,MAAO,CACL,SAAU,GACV,YAAa,GACb,gBAAiB,GACjB,cAAe,GACf,eAAgB,GAChB,aAAc,GACd,QAAS,GACT,SAAU,GACV,aAAc,GACd,MAAO,EAAQ,IAAI,EACnB,aAAc,GACd,GAAI,GAAO,OAAS,CAAC,CACvB,CACF,CACF,CAAC,EAED,EAAW,SAAU,CACnB,aAAc,GACd,IAAK,UAA8D,CAGjE,MAFA,MAAK,UAAY,IAAI,IAEd,KAAK,OACd,CACF,CAAC,EAOD,IAAM,EAA0B,YAAY,UAAU,gBAEtD,YAAY,UAAU,gBAAkB,SAA6B,GAAG,EAA4B,CAClG,IAAM,EAAY,EAAwB,MAAM,KAAM,CAAI,EAW1D,OATA,EAAgB,IAAI,EAAW,IAAI,EAEnC,OAAO,iBAAiB,KAAM,CAC5B,cAAe,CAAE,aAAc,GAAM,UAAa,EAAU,cAAc,CAAE,EAC5E,eAAgB,CAAE,aAAc,GAAM,UAAa,EAAU,eAAe,CAAE,EAC9E,kBAAmB,CAAE,aAAc,GAAM,QAAW,EAAU,iBAAkB,EAChF,SAAU,CAAE,aAAc,GAAM,QAAW,EAAU,QAAS,CAChE,CAAC,EAEM,CACT,EACA,EAAU,SAAW,CACnB,YAAY,UAAU,gBAAkB,CAC1C,CAAC,EAED,IAAM,GAAmB,EAAoB,EAAc,IAA0C,CACnG,GAAI,aAAiB,SAAU,CAC7B,IAAK,GAAM,CAAC,EAAW,KAAe,EAAM,QAAQ,EAAG,EAAS,OAAO,EAAW,CAAU,EAE5F,MACF,CAEA,EAAS,OAAO,EAAM,CAAK,CAC7B,EAIM,EAAiB,WAAW,SAElC,WAAW,SAAW,cAAiD,CAAe,CACpF,YAAY,EAAwB,EAAgC,CAClE,MAAM,EAAqC,CAA2C,EAEhF,aAAgB,iBAEtB,EAAA,aAAa,EAAO,GAAY,CAC9B,IAAM,EAAO,EAAQ,aAAa,MAAM,EAClC,EAAQ,EAAgB,IAAI,CAAO,EAErC,CAAC,GAAQ,GAAS,MAAQ,EAAQ,aAAa,UAAU,GAE7D,EAAgB,KAAM,EAAM,CAAK,CACnC,CAAC,CACH,CACF,EACA,EAAU,SAAW,CACnB,WAAW,SAAW,CACxB,CAAC,EAOD,IAAM,EAAgB,gBAAgB,UAAU,MAahD,MAXA,iBAAgB,UAAU,MAAQ,SAAiC,GAAG,EAAU,CAC9E,EAAc,MAAM,KAAM,CAAI,EAE9B,EAAA,aAAa,KAAO,GAAY,CAC9B,EAAwD,oBAAoB,CAC9E,CAAC,CACH,EACA,EAAU,SAAW,CACnB,gBAAgB,UAAU,MAAQ,CACpC,CAAC,MAEY,CACX,IAAK,IAAM,KAAW,EAAW,EAAQ,EAEzC,EAAU,OAAS,EACnB,EAAS,GAAkB,EAC7B,CACF"}
@@ -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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"}
@@ -7,25 +7,30 @@
7
7
  * For tree-shaking, import directly from the focused sub-modules.
8
8
  */
9
9
  export { OreTimeoutError } from '../errors';
10
- export { fire, createPointerEvent, within, waitFor, waitForEvent, type QueryScope, type WaitOptions, } from '@vielzeug/assay';
11
10
  export { debugFlush, flush, type FlushOptions } from './flush';
12
11
  export { walkFlatTree } from './dom';
13
12
  export { installFormInternalsPolyfill } from './form-internals-polyfill';
14
- export { user } from './interactions';
15
13
  export { cleanup, mock, mount, mountComponent, type Fixture, type MountOptions, type MountSetup } from './mount';
16
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
+ };
17
25
  /**
18
- * Register auto-cleanup after each test, and polyfill the jsdom/happy-dom gaps in
19
- * `ElementInternals`/`FormData`/`<form>.reset()` that `useField()` needs (see
20
- * `installFormInternalsPolyfill` — safe to call even in a suite with no form-associated
21
- * components; every patch it installs is a guarded no-op when its target is missing).
22
- * Call once in your test setup file.
26
+ * Register auto-cleanup after each test. Call once in your test setup file.
27
+ * Pass `{ formInternals: true }` when testing form-associated (`useField`) components.
23
28
  *
24
29
  * @example
25
30
  * // vitest.setup.ts
26
31
  * import { afterEach } from 'vitest';
27
32
  * import { install } from '@vielzeug/ore/testing';
28
- * install(afterEach);
33
+ * install(afterEach, { formInternals: true });
29
34
  */
30
- export declare function install(afterEachHook: (fn: () => void) => void): void;
35
+ export declare function install(afterEachHook: (fn: () => void) => void, options?: InstallOptions): void;
31
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAI5C,OAAO,EACL,IAAI,EACJ,kBAAkB,EAClB,MAAM,EACN,OAAO,EACP,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AAEzB,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,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,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;AAE7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,IAAI,CAGrE"}
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("../directives/live.cjs");require("../types/bindings.cjs");const t=require("../directives/raw.cjs"),n=require("../utils/css.cjs"),r=require("../define.cjs"),i=require("../utils/id.cjs"),a=require("./flush.cjs");let o=require("@vielzeug/assay");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(e,t={}){let{attrs:n={},componentOptions:i,container:u=document.body,html:f,props:p={}}=t,m,h;typeof e==`string`?m=e:(m=`trial-${++c}-${Math.random().toString(36).slice(2,7)}`,h={...i??{},setup:e}),h&&r.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(n))l(g,e,t);try{await d(async()=>{u.appendChild(g),s.push(g),await a.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(e){await e(),await a.flush()},async attr(e,t){l(g,e,t),await a.flush()},async attrs(e){for(let[t,n]of Object.entries(e))l(g,t,n);await a.flush()},dispose:v,get disposed(){return _},element:g,flush:a.flush,query(e){return(0,o.query)(g.shadowRoot??g,e)},queryAll(e){return(0,o.queryAll)(g.shadowRoot??g,e)},queryAllByTestId(e){return(0,o.queryAllByTestId)(g.shadowRoot??g,e)},queryAllByText(e,t=`*`){return(0,o.queryAllByText)(g.shadowRoot??g,e,t)},queryByTestId(e){return(0,o.queryByTestId)(g.shadowRoot??g,e)},queryByText(e,t=`*`){return(0,o.queryByText)(g.shadowRoot??g,e,t)},get shadow(){return g.shadowRoot},[Symbol.dispose](){v()}}}async function p(e,t,n){return r.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,t._resetRawSanitizer(),e._resetLiveSignals(),n._clearStylesheetCache(),i._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
@@ -1 +1 @@
1
- {"version":3,"file":"mount.cjs","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport {\n query,\n queryAll,\n queryAllByTestId,\n queryAllByText,\n queryByTestId,\n queryByText,\n type QueryScope,\n} from '@vielzeug/assay';\n\nimport { type ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport { _resetLiveSignals } from '../directives/live';\nimport { _resetRawSanitizer } from '../directives/raw';\nimport { type HTMLResult } from '../types/bindings';\nimport { _clearStylesheetCache } from '../utils/css';\nimport { _resetIdCounter } from '../utils/id';\nimport { flush, type FlushOptions } from './flush';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n// Fixture's query/queryAll/queryBy*/queryAllBy* methods are exactly QueryScope's surface —\n// extending it (rather than redeclaring all six signatures a second time) means a future\n// addition to one is automatically part of the other, with the type system enforcing it.\n/**\n * A mounted component ready for assertions. Every inherited `QueryScope` method (`query`,\n * `queryAll`, `queryByText`, `queryAllByText`, `queryByTestId`, `queryAllByTestId`) is scoped\n * to `element.shadowRoot` — falling back to `element` itself for light-DOM components\n * (`shadow: false` / no shadow root).\n */\nexport interface Fixture<T extends HTMLElement = HTMLElement> extends QueryScope {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<any>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null | Promise<HTMLResult | null>;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\nexport let _componentTagCounter = 0;\n\n/**\n * Resets global test counters used for deterministic IDs/markers.\n * @internal\n */\nexport const _resetCounters = (): void => {\n _resetIdCounter();\n _componentTagCounter = 0;\n};\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n if (value === false) element.removeAttribute(name);\n else element.setAttribute(name, value === true ? '' : String(value));\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<any> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}-${Math.random().toString(36).slice(2, 7)}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup as ComponentDefinition<any>['setup'],\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\n }\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n return {\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n query<E extends Element = Element>(selector: string): E | null {\n return query<E>((element.shadowRoot ?? element) as Element, selector);\n },\n\n queryAll<E extends Element = Element>(selector: string): E[] {\n return queryAll<E>((element.shadowRoot ?? element) as Element, selector);\n },\n\n queryAllByTestId<E extends Element = Element>(testId: string): E[] {\n return queryAllByTestId<E>((element.shadowRoot ?? element) as Element, testId);\n },\n\n queryAllByText<E extends Element = Element>(text: string, selector = '*'): E[] {\n return queryAllByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n queryByTestId<E extends Element = Element>(testId: string): E | null {\n return queryByTestId<E>((element.shadowRoot ?? element) as Element, testId);\n },\n\n queryByText<E extends Element = Element>(text: string, selector = '*'): E | null {\n return queryByText<E>((element.shadowRoot ?? element) as Element, text, selector);\n },\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<any>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n _resetRawSanitizer();\n _resetLiveSignals();\n _clearStylesheetCache();\n _resetIdCounter();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}
1
+ {"version":3,"file":"mount.cjs","names":[],"sources":["../../src/testing/mount.ts"],"sourcesContent":["/**\n * Component mounting utilities for test environments.\n */\n\nimport type { Readable } from '@vielzeug/ripple';\n\nimport { type QueryScope, within } from '@vielzeug/assay';\n\nimport { type ComponentDefinition } from '../component-types';\nimport { define } from '../define';\nimport { type HTMLResult } from '../template/result';\nimport { setAttr } from '../utils/dom';\nimport { flush, type FlushOptions } from './flush';\nimport { resetOreForTests } from './reset';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n// Fixture inherits Assay's complete QueryScope. Keeping that relationship structural means new\n// scoped query capabilities are immediately available to fixtures without a duplicate contract.\n/**\n * A mounted component ready for assertions. All inherited `QueryScope` methods are scoped to\n * `element.shadowRoot`, falling back to `element` for light-DOM components (`shadow: false`).\n */\nexport interface Fixture<T extends HTMLElement = HTMLElement> extends QueryScope {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose](): void;\n /** Run a callback then flush — the standard way to trigger and assert a reactive update */\n act(fn: () => unknown): Promise<void>;\n /** Set an attribute (boolean `false` removes it) then flush */\n attr(name: string, value: string | number | boolean): Promise<void>;\n /** Set multiple attributes then flush */\n attrs(record: Record<string, string | number | boolean>): Promise<void>;\n /** Remove the component from the DOM. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** The component element */\n element: T;\n /** Wait for all reactive updates and animation frames */\n flush(options?: FlushOptions): Promise<void>;\n /** The component's shadow root (null for light-DOM components) */\n readonly shadow: ShadowRoot | null;\n}\n\nexport interface MountOptions {\n /** Properties assigned directly onto the element */\n props?: Record<string, unknown>;\n /** HTML attributes to set on the element */\n attrs?: Record<string, string | number | boolean>;\n /** Inner HTML for slot content */\n html?: string;\n /** Parent container (default: document.body) */\n container?: HTMLElement;\n /** Extra component options when passing an inline setup function */\n componentOptions?: Omit<ComponentDefinition<Record<string, unknown>>, 'setup'>;\n}\n\ntype MountProps = { readonly [x: string]: Readable<unknown> };\n\n// Bivariant callback type keeps inline test callbacks ergonomic across varying prop specializations.\nexport type MountSetup = {\n bivarianceHack: (props: MountProps) => HTMLResult | null;\n}['bivarianceHack'];\n\n// ─── Test environment state ───────────────────────────────────────────────────\n\nexport const _mountedElements: HTMLElement[] = [];\n\n// Monotonic across the whole test run — never reset: custom element registrations\n// are permanent, so a re-used tag name would throw on re-define. Deterministic\n// within a run (trial-1, trial-2, ...) without a random suffix.\nlet _componentTagCounter = 0;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction applyAttr(element: Element, name: string, value: string | number | boolean): void {\n // Same write path as the runtime's attribute bindings, so tests never set up\n // states the runtime itself can't produce (e.g. boolean true → \"true\").\n setAttr(element, name, value);\n}\n\nconst toError = (value: unknown): Error => {\n return value instanceof Error ? value : new Error(String(value));\n};\n\nconst withWindowErrorCapture = async <T>(action: () => Promise<T>): Promise<T> => {\n if (typeof window === 'undefined') return action();\n\n let captured: Error | null = null;\n const onError = (event: ErrorEvent) => {\n captured = toError(event.error ?? event.message);\n event.preventDefault();\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n captured = toError(event.reason);\n event.preventDefault();\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n\n try {\n const result = await action();\n\n if (captured) throw captured;\n\n return result;\n } finally {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n }\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Mount a component into the DOM and return a test fixture.\n *\n * Accepts a registered tag name, an inline setup function, or a component\n * options object. Setup functions are auto-registered with generated tag names.\n *\n * @example — inline setup function\n * const { query } = await mount(() => {\n * const count = signal(0);\n * return html`<button @click=${() => count.value++}>${count}</button>`;\n * });\n *\n * @example — registered tag name\n * const { query } = await mount('my-counter');\n */\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: MountSetup,\n options?: MountOptions,\n): Promise<Fixture<T>>;\nexport async function mount<T extends HTMLElement = HTMLElement>(\n tagOrSetup: string | MountSetup,\n options: MountOptions = {},\n): Promise<Fixture<T>> {\n const { attrs = {}, componentOptions, container = document.body, html, props = {} } = options;\n\n let tagName: string;\n let inlineDefinition: ComponentDefinition<Record<string, unknown>> | undefined;\n\n if (typeof tagOrSetup === 'string') {\n tagName = tagOrSetup;\n } else {\n tagName = `trial-${++_componentTagCounter}`;\n inlineDefinition = {\n ...(componentOptions ?? {}),\n setup: tagOrSetup,\n };\n }\n\n if (inlineDefinition) {\n define(tagName, inlineDefinition);\n }\n\n const element = document.createElement(tagName) as T;\n\n if (html) element.innerHTML = html;\n\n if (Object.keys(props).length) Object.assign(element, props);\n\n for (const [name, value] of Object.entries(attrs)) applyAttr(element, name, value);\n\n try {\n await withWindowErrorCapture(async () => {\n container.appendChild(element);\n _mountedElements.push(element);\n await flush();\n });\n } catch (err) {\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n\n throw err;\n }\n\n let isDisposed = false;\n\n function dispose() {\n if (isDisposed) return;\n\n isDisposed = true;\n element.remove();\n\n const i = _mountedElements.indexOf(element);\n\n if (i !== -1) _mountedElements.splice(i, 1);\n }\n\n const scope = within((element.shadowRoot ?? element) as Element);\n\n return {\n ...scope,\n\n async act(fn) {\n await fn();\n await flush();\n },\n\n async attr(name, value) {\n applyAttr(element, name, value);\n await flush();\n },\n\n async attrs(record) {\n for (const [name, value] of Object.entries(record)) applyAttr(element, name, value);\n await flush();\n },\n\n dispose,\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n element,\n\n flush,\n\n get shadow(): ShadowRoot | null {\n return element.shadowRoot;\n },\n\n [Symbol.dispose]() {\n dispose();\n },\n };\n}\n\n/**\n * Register and mount a component definition in a single call.\n *\n * Combines `define(tag, definition)` + `mount(tag, options)` — the standard\n * pattern for testing full custom-element lifecycle (props, reconnect, etc.).\n *\n * @example\n * const { query } = await mountComponent('my-counter', {\n * props: { count: prop.number(0) },\n * setup: (props) => html`<div>${props.count}</div>`,\n * });\n */\nexport async function mountComponent<Props extends Record<string, unknown>, T extends HTMLElement = HTMLElement>(\n tag: string,\n definition: ComponentDefinition<Props>,\n options?: MountOptions,\n): Promise<Fixture<T>> {\n define(tag, definition);\n\n return mount<T>(tag, options);\n}\n\n/**\n * Register a stub custom element (no-op if already defined).\n *\n * @example\n * mock('child-button', '<slot></slot>');\n */\nexport function mock(tagName: string, template = ''): void {\n if (!customElements.get(tagName)) {\n customElements.define(\n tagName,\n class extends HTMLElement {\n connectedCallback() {\n this.innerHTML = template;\n }\n },\n );\n }\n}\n\n/**\n * Remove all elements mounted via `mount()`.\n * Call in `afterEach` to keep tests isolated.\n *\n * @example\n * afterEach(() => cleanup());\n */\nexport function cleanup(): void {\n for (const el of _mountedElements) el.remove();\n _mountedElements.length = 0;\n resetOreForTests();\n}\n\n/** @internal re-export for within() */\nexport type { QueryScope };\n"],"mappings":"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"}