diff --git a/README.md b/README.md index 1280466..f07c8b5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ pnpm playground # returns an example selector ## Projects -[`@bomb.sh/dom`](packages/dom/README.md) is a headless interactive DOM. +[`@bomb.sh/dom`](packages/dom/README.md) is a headless interactive DOM. Its framework integrations and demos live in `examples/preact`, `examples/react`, `examples/solid`, `examples/lit`, `examples/svelte`, and `examples/crank`. ## Support diff --git a/examples/crank/README.md b/examples/crank/README.md new file mode 100644 index 0000000..939f320 --- /dev/null +++ b/examples/crank/README.md @@ -0,0 +1,11 @@ +# Crank with @bomb.sh/dom + +This example owns its framework adapter, framework dependencies, and acceptance tests. The core DOM package has no framework runtime dependencies. The adapter uses the public `@bomb.sh/dom` exports and the same tree and event implementation as ordinary DOM callers. + +Run `pnpm --filter @bomb.sh/dom build`, then `pnpm --filter @examples/dom-crank test`, `pnpm --filter @examples/dom-crank typecheck`, or `pnpm --filter @examples/dom-crank demo`. + +The tested scope covers mounting, native event handlers, text identity, asynchronous updates in independent documents, and cleanup. Browser styling, hydration, and framework-specific browser APIs are not implied. See [framework boundaries](../../packages/dom/docs/frameworks.md). + +Crank’s own context-event declarations reference browser event interfaces. This fixture includes the DOM type library locally so those upstream declarations can be checked; its adapter still accepts only `@bomb.sh/dom` nodes and never casts them to browser Node. No DOM library is added to the core or workspace configuration. + +The generic Crank adapter exercises generator refreshes and generator cleanup. Raw HTML, hydration, and SVG/MathML namespace propagation are not implemented by this example. Native custom event props can use `on:ExactCase`; JSX-style built-in prop spellings are translated only in the adapter. diff --git a/examples/crank/package.json b/examples/crank/package.json new file mode 100644 index 0000000..825b583 --- /dev/null +++ b/examples/crank/package.json @@ -0,0 +1,27 @@ +{ + "name": "@examples/dom-crank", + "private": true, + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "demo": "node --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@b9g/crank": "0.7.12", + "@bomb.sh/dom": "workspace:*" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "vitest": "^4.1.2" + } +} diff --git a/examples/crank/src/demo.ts b/examples/crank/src/demo.ts new file mode 100644 index 0000000..d69820a --- /dev/null +++ b/examples/crank/src/demo.ts @@ -0,0 +1,10 @@ +import { Document } from '@bomb.sh/dom'; +import { createElement as h } from '@b9g/crank'; +import { createRoot } from './index.ts'; + +const document = new Document(); +const container = document.appendChild(document.createElement('box')); +const root = createRoot(container); +await root.render(h('text', null, 'Hello from Crank')); +console.info(container.textContent); +await root.unmount(); diff --git a/examples/crank/src/index.ts b/examples/crank/src/index.ts new file mode 100644 index 0000000..996341d --- /dev/null +++ b/examples/crank/src/index.ts @@ -0,0 +1,63 @@ +/* oxlint-disable bombshell-dev/no-generic-error -- Reject invalid host callback values with standard TypeError exceptions. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework mounting and host factories are synchronous by contract. */ +import { Renderer, type Children, type ElementValue } from '@b9g/crank'; +import { Element, Text, type Node } from '@bomb.sh/dom'; +import { updateHostProperties } from './properties.ts'; + +/** Crank's generic reconciler using the same native tree as all other adapters. */ +const renderer = new Renderer({ + create({ tag, root }) { + if (!root) throw new TypeError('A Crank container is required'); + if (typeof tag !== 'string') throw new TypeError('Unsupported Crank host tag'); + return root.ownerDocument.createElement(tag); + }, + text({ value, oldNode, root }) { + if (oldNode instanceof Text) { + oldNode.data = value; + return oldNode; + } + if (!root) throw new TypeError('A Crank container is required'); + return root.ownerDocument.createTextNode(value); + }, + patch({ node, props, oldProps }) { + if (!(node instanceof Element)) throw new TypeError('Expected an Element'); + updateHostProperties(node, oldProps ?? {}, props); + }, + arrange({ node, children }) { + let anchor = node.firstChild; + for (const child of children) { + if (child === anchor) anchor = anchor.nextSibling; + else node.insertBefore(child, anchor); + } + }, + remove({ node, parentNode, isNested }) { + if (!isNested && node.parentNode === parentNode) parentNode.removeChild(node); + }, + raw({ value }) { + if (typeof value === 'string') + throw new TypeError('Raw markup is not supported by this adapter'); + return value; + }, +}); + +export interface Root extends AsyncDisposable { + readonly element: Element; + render(children: Children): Promise>; + unmount(): Promise; +} + +/** Mount Crank generator components into an existing native element. */ +export function createRoot(element: Element): Root { + return { + element, + async render(children) { + return renderer.render(children, element); + }, + async unmount() { + await renderer.render(null, element); + }, + async [Symbol.asyncDispose]() { + await this.unmount(); + }, + }; +} diff --git a/examples/crank/src/properties.ts b/examples/crank/src/properties.ts new file mode 100644 index 0000000..35c30d6 --- /dev/null +++ b/examples/crank/src/properties.ts @@ -0,0 +1,59 @@ +/* oxlint-disable bombshell-dev/max-params -- These callbacks match framework host property signatures. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework property mutation must complete in the caller’s synchronous update. */ +/* oxlint-disable bombshell-dev/no-generic-error -- Unsupported prop values fail with TypeError at the adapter boundary. */ +import type { Element } from '@bomb.sh/dom'; +import type { Event } from '@bomb.sh/dom'; + +const handlers = new WeakMap void>>(); + +/** Translate framework prop spelling at the adapter boundary, never in the DOM. */ +export function setHostProperty(element: Element, name: string, value: unknown): void { + if (name === 'children' || name === 'ref' || name === 'key') return; + if (name.startsWith('on')) { + let eventName = name.startsWith('on:') ? name.slice(3) : name.slice(2); + const capture = !name.startsWith('on:') && eventName.endsWith('Capture'); + if (capture) eventName = eventName.slice(0, -7); + if (!name.startsWith('on:') && /^[A-Z]/.test(eventName)) eventName = eventName.toLowerCase(); + let registered = handlers.get(element); + const previous = registered?.get(name); + if (previous) element.removeEventListener(eventName, previous, capture); + registered?.delete(name); + if (typeof value === 'function') { + if (!registered) handlers.set(element, (registered = new Map())); + const listener = (event: Event): void => { + value.call(element, event); + }; + registered.set(name, listener); + element.addEventListener(eventName, listener, capture); + } + return; + } + if (name === 'style' || name === 'dangerouslySetInnerHTML') { + throw new TypeError(`${name} is not supported by this framework adapter`); + } + if (name === 'className') name = 'class'; + if (name === 'value' && 'value' in element) { + Reflect.set(element, 'value', value ?? ''); + return; + } + if ( + value === null || + value === undefined || + (value === false && !name.startsWith('data-') && !name.startsWith('aria-')) + ) + element.removeAttribute(name); + else element.setAttribute(name, value === true ? '' : String(value)); +} + +/** Apply removed and changed framework props to a native element. */ +export function updateHostProperties( + element: Element, + previous: Readonly>, + next: Readonly>, +): void { + for (const name of Object.keys(previous)) + if (!(name in next)) setHostProperty(element, name, undefined); + for (const [name, value] of Object.entries(next)) { + if (previous[name] !== value) setHostProperty(element, name, value); + } +} diff --git a/examples/crank/test/crank.test.ts b/examples/crank/test/crank.test.ts new file mode 100644 index 0000000..62abc33 --- /dev/null +++ b/examples/crank/test/crank.test.ts @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict'; +import { createElement as h, type Context, type Element as CrankElement } from '@b9g/crank'; +import { describe, expect, it } from 'vitest'; +import { Document, Element, Text } from '@bomb.sh/dom'; +import { Event } from '@bomb.sh/dom'; +import { createRoot } from '../src/index.ts'; + +describe('actual Crank', () => { + it('runs generator components, refreshes asynchronously, and cleans up without globals', async () => { + const documents = [new Document(), new Document()]; + await using first = createRoot(documents[0]!.createElement('box')); + await using second = createRoot(documents[1]!.createElement('box')); + const refreshes: Array<() => void> = []; + const cleanups: number[] = []; + function* Counter( + this: Context<{ index: number }>, + { index }: { index: number }, + ): Generator { + let count = 0; + refreshes[index] = () => { + count++; + void this.refresh(); + }; + this.cleanup(() => { + cleanups.push(index); + }); + for (const _ of this) yield h('text', null, count); + } + await Promise.all([ + first.render(h(Counter, { index: 0 })), + second.render(h(Counter, { index: 1 })), + ]); + const original = first.element.firstChild; + assert.ok(original instanceof Element); + const content = original.firstChild; + assert.ok(content instanceof Text); + await Promise.resolve(); + refreshes[0]?.(); + refreshes[1]?.(); + await expect.poll(() => first.element.textContent).toBe('1'); + await expect.poll(() => second.element.textContent).toBe('1'); + expect(first.element.firstChild).toBe(original); + expect(original.firstChild).toBe(content); + expect(content.ownerDocument).toBe(documents[0]); + expect(second.element.firstChild?.ownerDocument).toBe(documents[1]); + await first.unmount(); + await second.unmount(); + expect(cleanups).toEqual([0, 1]); + expect(first.element.childNodes).toHaveLength(0); + expect(globalThis).not.toHaveProperty('document'); + }); + + it('reorders keyed nodes and replaces native event handlers', async () => { + const document = new Document(); + await using root = createRoot(document.createElement('box')); + const calls: string[] = []; + const rows = (names: string[], label: string): CrankElement[] => + names.map((name) => + h( + 'text', + { + key: name, + onClick: () => calls.push(label), + }, + name, + ), + ); + await root.render(rows(['a', 'b', 'c'], 'before')); + const original = [...root.element.childNodes]; + await root.render(rows(['c', 'a'], 'after')); + expect(root.element.childNodes).toEqual([original[2], original[0]]); + root.element.firstChild?.dispatchEvent(new Event('click')); + expect(calls).toEqual(['after']); + }); +}); diff --git a/examples/crank/tsconfig.json b/examples/crank/tsconfig.json new file mode 100644 index 0000000..1b4ec8c --- /dev/null +++ b/examples/crank/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext", "DOM"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/examples/dom-realm/README.md b/examples/dom-realm/README.md new file mode 100644 index 0000000..00584e1 --- /dev/null +++ b/examples/dom-realm/README.md @@ -0,0 +1,13 @@ +# Persistent framework fixture realms + +This private example package supplies the explicit bootstrap boundary used by the Lit and Svelte examples. Each `FixtureRealm` receives an existing `@bomb.sh/dom` document and creates a persistent Node `vm` context. The context exposes that document's custom-element registry and HTMLElement constructor, the actual DOM node/event constructors, a window event target, timers, and microtasks. Parsing is explicitly installed for template creation. + +`bundleFixture()` uses esbuild's browser export condition and bundles the framework together with the example entry. Running that bundle separately in each context gives every document its own framework module state. Promises and callbacks retain their original context during later updates. Nothing installs or swaps the host process's `document`, `window`, or constructors. + +The context uses the same DOM nodes as the host; it creates no second tree. It is a bootstrap example for trusted local modules, not an untrusted-code sandbox, browser API implementation, or script execution facility exported by the DOM package. It does not supply layout, stylesheet rendering, navigation, networking, or browser default input actions. + +The adapter's TypeScript API accepts the package's actual `Document`, and code evaluation returns `unknown`. Framework browser modules are bundled JavaScript. This boundary does not claim that the package's public types structurally implement the complete browser `HTMLElement` expected by Lit or Svelte declarations. + +Dependencies: `@bomb.sh/dom` provides the one tree; esbuild selects and bundles the browser framework modules; Node's built-in `vm` retains their separate lexical environments. No worker, browser process, CommonJS output, or framework code is added to the core package. + +See [Node VM contexts](https://nodejs.org/api/vm.html#vmcreatecontextcontextobject-options). The DOM `window` facade here is deliberately limited to the capabilities exposed in `src/index.ts`. diff --git a/examples/dom-realm/package.json b/examples/dom-realm/package.json new file mode 100644 index 0000000..123ef2c --- /dev/null +++ b/examples/dom-realm/package.json @@ -0,0 +1,24 @@ +{ + "name": "@examples/dom-realm", + "private": true, + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "typecheck": "tsgo --noEmit -p tsconfig.json", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "esbuild": "^0.27.0" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1" + } +} diff --git a/examples/dom-realm/src/index.ts b/examples/dom-realm/src/index.ts new file mode 100644 index 0000000..c965901 --- /dev/null +++ b/examples/dom-realm/src/index.ts @@ -0,0 +1,101 @@ +import { createContext, Script, type Context } from 'node:vm'; +import { fileURLToPath } from 'node:url'; +import { build, type Plugin } from 'esbuild'; +import { installParsing } from '@bomb.sh/dom/parsing'; +import { + Attr, + CharacterData, + Comment, + CustomEvent, + Document, + DocumentFragment, + Element, + Event, + EventTarget, + MutationObserver, + Node, + ShadowRoot, + Text, +} from '@bomb.sh/dom'; + +class FixtureBuildError extends Error { + readonly entry: URL; + constructor(entry: URL) { + super(`Fixture compilation produced no JavaScript for ${entry.href}`); + this.name = 'FixtureBuildError'; + this.entry = entry; + } +} + +/** Browser-targeted fixture compilation, kept entirely outside the DOM package. */ +export async function bundleFixture(entry: URL, plugins: Plugin[] = []): Promise { + const result = await build({ + entryPoints: [fileURLToPath(entry)], + bundle: true, + write: false, + format: 'iife', + globalName: 'fixture', + platform: 'browser', + target: 'es2022', + conditions: ['browser'], + define: { 'process.env.NODE_ENV': '"production"' }, + plugins, + }); + const output = result.outputFiles[0]; + if (!output) throw new FixtureBuildError(entry); + return output.text; +} + +/** + * A persistent module/global environment for trusted local framework examples. + * This is an integration boundary, not a browser or an untrusted-code sandbox. + * Core nodes and their mutation implementation remain the same objects. + */ +export class FixtureRealm { + readonly document: Document; + readonly #context: Context; + + constructor(document: Document) { + this.document = document; + installParsing(document); + const windowEvents = new EventTarget(); + const environment: Record = { + document, + customElements: document.customElements, + HTMLElement: document.HTMLElement, + Attr, + CharacterData, + Comment, + CustomEvent, + Document, + DocumentFragment, + Element, + Event, + EventTarget, + MutationObserver, + Node, + ShadowRoot, + Text, + DOMException, + AbortController, + AbortSignal, + console, + queueMicrotask, + setTimeout, + clearTimeout, + performance, + navigator: { userAgent: '@bomb.sh/dom framework fixture' }, + addEventListener: windowEvents.addEventListener.bind(windowEvents), + removeEventListener: windowEvents.removeEventListener.bind(windowEvents), + dispatchEvent: windowEvents.dispatchEvent.bind(windowEvents), + }; + environment.window = environment; + environment.self = environment; + this.#context = createContext(environment); + } + + /** Evaluate trusted fixture code; its asynchronous closures retain this realm. */ + run(source: string): unknown { + return new Script(source, { filename: 'dom-framework-fixture.js' }).runInContext(this.#context); + } +} diff --git a/examples/dom-realm/tsconfig.json b/examples/dom-realm/tsconfig.json new file mode 100644 index 0000000..b18d814 --- /dev/null +++ b/examples/dom-realm/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/lit/README.md b/examples/lit/README.md new file mode 100644 index 0000000..8e38201 --- /dev/null +++ b/examples/lit/README.md @@ -0,0 +1,11 @@ +# Lit on the in-memory DOM + +After building `@bomb.sh/dom`, run `pnpm --filter @examples/dom-lit demo` or `pnpm --filter @examples/dom-lit test`. + +`src/component.js` is a real Lit 3 custom element using reactive properties, reflected attributes, a click listener, a shadow root, and a slot. There are no decorators. The tests cover asynchronous property/attribute updates, native event dispatch, lifecycle callbacks, reconnection identity, slot composition, and concurrent independent documents. + +Lit captures its HTMLElement constructor and template-creation document during module evaluation. The example loads Lit's browser implementation into the [persistent realm adapter](../dom-realm/README.md) once per document. Subsequent asynchronous updates keep that document without swapping globals. The package's ordinary Node/SSR entry is not used for acceptance. + +This demonstrates the tested interactive subset. Browser CSS rendering, hydration, adopted stylesheets, and arbitrary browser APIs are not supplied. Framework component source is JavaScript; the adapter and host tests use the real public `@bomb.sh/dom` TypeScript declarations without claiming a complete browser HTMLElement. + +Lit is a dependency of this example only. See [Lit's lifecycle documentation](https://lit.dev/docs/components/lifecycle/) and [browser/Node entry implementations](https://github.com/lit/lit/tree/main/packages). diff --git a/examples/lit/package.json b/examples/lit/package.json new file mode 100644 index 0000000..7e16bea --- /dev/null +++ b/examples/lit/package.json @@ -0,0 +1,25 @@ +{ + "name": "@examples/dom-lit", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "demo": "node --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "@examples/dom-realm": "workspace:*", + "lit": "3.3.3" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "vitest": "^4.1.2" + } +} diff --git a/examples/lit/src/component.js b/examples/lit/src/component.js new file mode 100644 index 0000000..d5567bf --- /dev/null +++ b/examples/lit/src/component.js @@ -0,0 +1,60 @@ +import { LitElement, html } from 'lit'; + +// This browser-targeted module is instantiated inside an explicit FixtureRealm. +export const lifecycle = []; + +export class Counter extends LitElement { + static properties = { count: { type: Number, reflect: true } }; + + constructor() { + super(); + this.count = 0; + } + + connectedCallback() { + super.connectedCallback(); + lifecycle.push('connected'); + } + + disconnectedCallback() { + super.disconnectedCallback(); + lifecycle.push('disconnected'); + } + + render() { + return html``; + } +} + +customElements.define('terminal-counter', Counter); + +export const counter = new Counter(); +counter.appendChild(document.createTextNode('light content')); + +export async function mount() { + document.documentElement.appendChild(counter); + await counter.updateComplete; +} + +export async function setCount(count) { + await Promise.resolve(); + counter.count = count; + await counter.updateComplete; +} + +export async function attributeCount(count) { + counter.setAttribute('count', String(count)); + await counter.updateComplete; +} + +export function remove() { + counter.remove(); +} diff --git a/examples/lit/src/demo.ts b/examples/lit/src/demo.ts new file mode 100644 index 0000000..2818d27 --- /dev/null +++ b/examples/lit/src/demo.ts @@ -0,0 +1,12 @@ +import { Document, Element } from '@bomb.sh/dom'; +import { bundleFixture, FixtureRealm } from '@examples/dom-realm'; + +const document = new Document(); +document.appendChild(document.createElement('application')); +const realm = new FixtureRealm(document); +realm.run(await bundleFixture(new URL('./component.js', import.meta.url))); +await realm.run('fixture.mount()'); +await realm.run('fixture.setCount(4)'); +const counter = document.documentElement?.firstChild; +if (counter instanceof Element) + process.stdout.write(`${counter.shadowRoot?.textContent?.trim()}\n`); diff --git a/examples/lit/test/lit.test.ts b/examples/lit/test/lit.test.ts new file mode 100644 index 0000000..5a7e2df --- /dev/null +++ b/examples/lit/test/lit.test.ts @@ -0,0 +1,98 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import { Document, Element, Event, HTMLSlotElement, type Node } from '@bomb.sh/dom'; +import { bundleFixture, FixtureRealm } from '@examples/dom-realm'; + +let bundle: string; +beforeAll(async () => { + bundle = await bundleFixture(new URL('../src/component.js', import.meta.url)); +}); + +function createFixture(): { document: Document; realm: FixtureRealm } { + const document = new Document(); + document.appendChild(document.createElement('application')); + const realm = new FixtureRealm(document); + realm.run(bundle); + return { document, realm }; +} + +function findElement(root: Node | null | undefined, name: string): Element | undefined { + for (const child of root?.childNodes ?? []) { + if (child instanceof Element && child.localName === name) return child; + const nested = findElement(child, name); + if (nested) return nested; + } +} + +function counter(document: Document): Element { + const element = document.documentElement?.firstChild; + if (!(element instanceof Element)) expect.fail('Counter was not mounted'); + return element; +} + +describe('Lit 3 browser runtime in a persistent document realm', () => { + it('upgrades an existing connected custom element without replacing it', async () => { + const document = new Document(); + const root = document.createElement('application'); + const existing = document.createElement('terminal-counter'); + existing.setAttribute('count', '7'); + root.append(existing); + document.append(root); + const realm = new FixtureRealm(document); + realm.run(bundle); + await realm.run('document.documentElement.firstChild.updateComplete'); + expect(root.firstChild).toBe(existing); + expect(realm.run('document.documentElement.firstChild instanceof fixture.Counter')).toBe(true); + expect(existing.shadowRoot?.textContent?.trim()).toBe('7'); + }); + + it('constructs a real LitElement and renders/reacts asynchronously in a shadow root', async () => { + const { document, realm } = createFixture(); + await realm.run('fixture.mount()'); + const element = counter(document); + expect(element.localName).toBe('terminal-counter'); + expect(element.shadowRoot?.textContent?.trim()).toBe('0'); + expect(realm.run('fixture.counter instanceof fixture.Counter')).toBe(true); + await realm.run('fixture.setCount(5)'); + expect(element.shadowRoot?.textContent?.trim()).toBe('5'); + expect(element.getAttribute('count')).toBe('5'); + await realm.run('fixture.attributeCount(8)'); + expect(element.shadowRoot?.textContent?.trim()).toBe('8'); + }); + + it('dispatches real listeners, composes slots, and preserves reconnect identity', async () => { + const { document, realm } = createFixture(); + await realm.run('fixture.mount()'); + const element = counter(document); + const button = findElement(element.shadowRoot, 'button'); + expect(button).not.toBeNull(); + button?.dispatchEvent(new Event('click', { bubbles: true, composed: true })); + await realm.run('fixture.counter.updateComplete'); + expect(element.shadowRoot?.textContent?.trim()).toBe('1'); + const slot = findElement(element.shadowRoot, 'slot'); + expect(slot).toBeInstanceOf(HTMLSlotElement); + if (slot instanceof HTMLSlotElement) expect(slot.assignedNodes()).toEqual([element.firstChild]); + realm.run('fixture.remove()'); + expect(element.isConnected).toBe(false); + expect(realm.run('fixture.lifecycle.join(",")')).toBe('connected,disconnected'); + await realm.run('fixture.mount()'); + expect(counter(document)).toBe(element); + expect(findElement(element.shadowRoot, 'button')).toBe(button); + expect(realm.run('fixture.lifecycle.join(",")')).toBe('connected,disconnected,connected'); + }); + + it('isolates simultaneous asynchronous updates without ambient globals', async () => { + const first = createFixture(); + const second = createFixture(); + await Promise.all([first.realm.run('fixture.mount()'), second.realm.run('fixture.mount()')]); + await Promise.all([ + first.realm.run('fixture.setCount(11)'), + second.realm.run('fixture.setCount(22)'), + ]); + expect(counter(first.document).shadowRoot?.textContent?.trim()).toBe('11'); + expect(counter(second.document).shadowRoot?.textContent?.trim()).toBe('22'); + expect(counter(first.document).ownerDocument).toBe(first.document); + expect(counter(second.document).ownerDocument).toBe(second.document); + expect(Reflect.has(globalThis, 'document')).toBe(false); + expect(Reflect.has(globalThis, 'window')).toBe(false); + }); +}); diff --git a/examples/lit/tsconfig.json b/examples/lit/tsconfig.json new file mode 100644 index 0000000..bf96cf7 --- /dev/null +++ b/examples/lit/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/examples/preact/LICENSE b/examples/preact/LICENSE new file mode 100644 index 0000000..8272839 --- /dev/null +++ b/examples/preact/LICENSE @@ -0,0 +1,21 @@ +MIT License Copyright (c) 2025-Present [Bombshell contributors](https://bomb.sh/team) + +Permission is hereby granted, free of +charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice +(including the next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/preact/README.md b/examples/preact/README.md new file mode 100644 index 0000000..a750b9a --- /dev/null +++ b/examples/preact/README.md @@ -0,0 +1,7 @@ +# Preact with @bomb.sh/dom + +This example owns its framework adapter, framework dependencies, and acceptance tests. The core DOM package has no framework runtime dependencies. The adapter uses the public `@bomb.sh/dom` exports and the same tree and event implementation as ordinary DOM callers. + +Run `pnpm --filter @bomb.sh/dom build`, then `pnpm --filter @examples/dom-preact test`, `pnpm --filter @examples/dom-preact typecheck`, or `pnpm --filter @examples/dom-preact demo`. + +The tested scope covers mounting, native event handlers, text identity, asynchronous updates in independent documents, and cleanup. Browser styling, hydration, and framework-specific browser APIs are not implied. See [framework boundaries](../../packages/dom/docs/frameworks.md). diff --git a/examples/preact/package.json b/examples/preact/package.json new file mode 100644 index 0000000..665dde2 --- /dev/null +++ b/examples/preact/package.json @@ -0,0 +1,29 @@ +{ + "name": "@examples/dom-preact", + "private": true, + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts", + "./jsx-runtime": "./src/jsx-runtime.ts", + "./jsx-dev-runtime": "./src/jsx-dev-runtime.ts" + }, + "scripts": { + "demo": "node --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "preact": "11.0.1" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "vitest": "^4.1.2" + } +} diff --git a/examples/preact/src/demo.ts b/examples/preact/src/demo.ts new file mode 100644 index 0000000..62c3f1e --- /dev/null +++ b/examples/preact/src/demo.ts @@ -0,0 +1,10 @@ +import { Document } from '@bomb.sh/dom'; +import { h } from 'preact'; +import { createRoot } from './index.ts'; + +const document = new Document(); +const container = document.appendChild(document.createElement('box')); +const root = createRoot(container); +root.render(h('text', { 'data-framework': 'preact' }, 'Hello from Preact')); +console.info(container.textContent); +root.unmount(); diff --git a/examples/preact/src/index.ts b/examples/preact/src/index.ts new file mode 100644 index 0000000..20269bb --- /dev/null +++ b/examples/preact/src/index.ts @@ -0,0 +1,50 @@ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework mounting and host factories are synchronous by contract. */ +// Adapted from @clack/ui-preact at 606675f16131c6ae7c4553a67ab477bdf661f772. +// Copyright (c) 2025-Present Bombshell contributors. MIT license; see LICENSE. +import { render, type ComponentChild, type ComponentChildren, type Key, type Ref } from 'preact'; +import type { Element } from '@bomb.sh/dom'; +import type { Event } from '@bomb.sh/dom'; + +export interface Root extends Disposable { + readonly element: Element; + render(node: ComponentChild): void; + unmount(): void; +} + +/** Mount into an existing element. Its ownerDocument determines every created node. */ +export function createRoot(element: Element): Root { + return { + element, + render(node) { + render(node, element); + }, + unmount() { + render(null, element); + }, + [Symbol.dispose]() { + this.unmount(); + }, + }; +} + +export type AttributeValue = string | number | boolean | null | undefined; +export type EventHandler = (event: Event) => void; + +/** Host props describe this DOM, without claiming browser rendering APIs. */ +export interface ElementProps { + children?: ComponentChildren; + key?: Key; + ref?: Ref; + id?: string; + class?: string; + title?: string; + tabIndex?: number; + [attribute: `data-${string}`]: AttributeValue; + [attribute: `aria-${string}`]: AttributeValue; + [event: `on${string}`]: EventHandler | undefined; + style?: never; +} + +export interface HostElements { + [tagName: string]: ElementProps; +} diff --git a/examples/preact/src/jsx-dev-runtime.ts b/examples/preact/src/jsx-dev-runtime.ts new file mode 100644 index 0000000..50c11cc --- /dev/null +++ b/examples/preact/src/jsx-dev-runtime.ts @@ -0,0 +1,2 @@ +export { Fragment, jsxDEV } from 'preact/jsx-dev-runtime'; +export type { JSX } from './jsx-runtime.ts'; diff --git a/examples/preact/src/jsx-runtime.ts b/examples/preact/src/jsx-runtime.ts new file mode 100644 index 0000000..26e2e05 --- /dev/null +++ b/examples/preact/src/jsx-runtime.ts @@ -0,0 +1,20 @@ +// Adapted from @clack/ui-preact at 606675f16131c6ae7c4553a67ab477bdf661f772. +// Copyright (c) 2025-Present Bombshell contributors. MIT license; see LICENSE. +import type { JSX as PreactJSX } from 'preact/jsx-runtime'; +import type { HostElements } from './index.ts'; + +export { Fragment, jsx, jsxs } from 'preact/jsx-runtime'; + +export declare namespace JSX { + type Element = PreactJSX.Element; + type ElementType = string | Exclude; + type ElementClass = PreactJSX.ElementClass; + type ElementAttributesProperty = PreactJSX.ElementAttributesProperty; + type ElementChildrenAttribute = PreactJSX.ElementChildrenAttribute; + type IntrinsicAttributes = PreactJSX.IntrinsicAttributes; + type LibraryManagedAttributes = PreactJSX.LibraryManagedAttributes< + Component, + Props + >; + interface IntrinsicElements extends HostElements {} +} diff --git a/examples/preact/test/jsx.test.tsx b/examples/preact/test/jsx.test.tsx new file mode 100644 index 0000000..bc3d152 --- /dev/null +++ b/examples/preact/test/jsx.test.tsx @@ -0,0 +1,25 @@ +import { Document, type Element, Event } from '@bomb.sh/dom'; +import { createRef } from 'preact'; +import { expect, it } from 'vitest'; +import { createRoot } from '../src/index.ts'; + +it('types JSX refs and listeners against the public host declarations', () => { + const document = new Document(); + const container = document.createElement('box'); + const ref = createRef(); + const events: Event[] = []; + using root = createRoot(container); + root.render( + { + events.push(event); + }} + > + hello + , + ); + ref.current?.dispatchEvent(new Event('click')); + expect(events).toHaveLength(1); + expect(ref.current?.textContent).toBe('hello'); +}); diff --git a/examples/preact/test/preact.test.ts b/examples/preact/test/preact.test.ts new file mode 100644 index 0000000..386a4cc --- /dev/null +++ b/examples/preact/test/preact.test.ts @@ -0,0 +1,174 @@ +// Adapted regression cases from @clack/ui-preact at +// 606675f16131c6ae7c4553a67ab477bdf661f772. +// Copyright (c) 2025-Present Bombshell contributors. MIT license; see LICENSE. +import assert from 'node:assert/strict'; +import { createPortal, createRef, h, type ContainerNode, type VNode, type Ref } from 'preact'; +import { useLayoutEffect, useState } from 'preact/hooks'; +import { act } from 'preact/test-utils'; +import { describe, expect, expectTypeOf, it, vi } from 'vitest'; +import { Document, Element, Text, type Node } from '@bomb.sh/dom'; +import { Event } from '@bomb.sh/dom'; +import { createRoot } from '../src/index.ts'; +import type { JSX } from '../src/jsx-runtime.ts'; + +function element(node: Node | null): Element { + assert.ok(node instanceof Element); + return node; +} + +function text(node: Node | null): Text { + assert.ok(node instanceof Text); + return node; +} + +function connectedRoot(document: Document): Element { + return document.appendChild(document.createElement('document')); +} + +describe('actual Preact 11', () => { + it('mounts into any existing container without altering siblings or installing globals', () => { + expect(globalThis).not.toHaveProperty('document'); + const document = new Document(); + const parent = connectedRoot(document); + const before = document.createTextNode('before'); + const container = document.createElement('box'); + const after = document.createTextNode('after'); + parent.appendChild(before); + parent.appendChild(container); + parent.appendChild(after); + using root = createRoot(container); + const ref = createRef(); + root.render(h('text', { ref }, 'hello')); + const output = element(container.firstChild); + expect(output.localName).toBe('text'); + expect(output.ownerDocument).toBe(document); + expect(ref.current).toBe(output); + expect(text(output.firstChild).data).toBe('hello'); + root.unmount(); + expect(ref.current).toBeNull(); + expect(parent.childNodes).toEqual([before, container, after]); + expect(container.childNodes).toHaveLength(0); + expect(globalThis).not.toHaveProperty('document'); + }); + + it('prepares detached trees without invalidation, then updates text identity and attributes', () => { + const notify = vi.fn(); + const document = new Document({ onInvalidate: notify }); + const container = document.createElement('box'); + using root = createRoot(container); + root.render(h('text', { 'data-state': 'before' }, 'hello')); + const output = element(container.firstChild); + const content = text(output.firstChild); + expect(notify).not.toHaveBeenCalled(); + document.appendChild(container); + expect(notify).toHaveBeenCalled(); + root.render(h('text', { 'data-state': 'after' }, 42)); + expect(container.firstChild).toBe(output); + expect(output.firstChild).toBe(content); + expect(content.data).toBe('42'); + expect(output.getAttribute('data-state')).toBe('after'); + }); + + it('updates independent documents asynchronously through their own ownerDocument', async () => { + const firstDocument = new Document(); + const secondDocument = new Document(); + using first = createRoot(connectedRoot(firstDocument)); + using second = createRoot(connectedRoot(secondDocument)); + const updates: Array<(value: number) => void> = []; + function Counter({ index }: { index: number }): VNode { + const [count, setCount] = useState(0); + updates[index] = setCount; + return h(count ? 'box' : 'text', null, count || 'initial'); + } + first.render(h(Counter, { index: 0 })); + second.render(h(Counter, { index: 1 })); + await act(() => { + updates[0]?.(1); + updates[1]?.(2); + }); + const firstChild = element(first.element.firstChild); + const secondChild = element(second.element.firstChild); + expect(firstChild.ownerDocument).toBe(firstDocument); + expect(text(firstChild.firstChild).ownerDocument).toBe(firstDocument); + expect(text(firstChild.firstChild).data).toBe('1'); + expect(secondChild.ownerDocument).toBe(secondDocument); + expect(text(secondChild.firstChild).ownerDocument).toBe(secondDocument); + expect(text(secondChild.firstChild).data).toBe('2'); + expect(globalThis).not.toHaveProperty('document'); + }); + + it('registers capture handlers and replaces/removes handlers without changing event names', () => { + const document = new Document(); + using root = createRoot(connectedRoot(document)); + const calls: string[] = []; + root.render( + h( + 'box', + { onKeyDownCapture: () => calls.push('capture') }, + h('text', { onKeyDown: (event: Event) => calls.push(event.type) }, 'target'), + ), + ); + const target = element(element(root.element.firstChild).firstChild); + target.dispatchEvent(new Event('keydown', { bubbles: true })); + expect(calls).toEqual(['capture', 'keydown']); + root.render( + h('box', null, h('text', { onKeyDown: () => calls.push('replacement') }, 'target')), + ); + target.dispatchEvent(new Event('keydown', { bubbles: true })); + expect(calls).toEqual(['capture', 'keydown', 'replacement']); + root.render(h('box', null, h('text', { onionChange: () => calls.push('custom') }, 'target'))); + target.dispatchEvent(new Event('keydown', { bubbles: true })); + target.dispatchEvent(new Event('ionchange')); + target.dispatchEvent(new Event('ionChange')); + expect(calls).toEqual(['capture', 'keydown', 'replacement', 'custom']); + }); + + it('preserves keyed nodes and cleans up layout effects on removal and disposal', () => { + const document = new Document(); + const container = connectedRoot(document); + const cleanups: string[] = []; + function Row({ name }: { name: string }): VNode { + useLayoutEffect( + () => () => { + cleanups.push(name); + }, + [], + ); + return h('text', null, name); + } + { + using root = createRoot(container); + root.render(['a', 'b', 'c'].map((name) => h(Row, { key: name, name }))); + const original = [...container.childNodes]; + root.render(['c', 'a'].map((name) => h(Row, { key: name, name }))); + expect(container.childNodes).toEqual([original[2], original[0]]); + expect(original[1]?.parentNode).toBeNull(); + expect(cleanups).toEqual(['b']); + } + expect(container.childNodes).toHaveLength(0); + expect(cleanups).toEqual(['b', 'c', 'a']); + }); + + it('creates portals in the destination document and removes them on unmount', () => { + const document = new Document(); + const destination = new Document(); + const remoteContainer = connectedRoot(destination); + using root = createRoot(connectedRoot(document)); + root.render(h('box', null, 'local', createPortal(h('text', null, 'remote'), remoteContainer))); + const remote = element(remoteContainer.firstChild); + expect(remote.ownerDocument).toBe(destination); + expect(text(remote.firstChild).ownerDocument).toBe(destination); + expect(remote.textContent).toBe('remote'); + root.unmount(); + expect(root.element.childNodes).toHaveLength(0); + expect(remoteContainer.childNodes).toHaveLength(0); + }); + + it('accepts public host types without application casts or browser declarations', () => { + expectTypeOf().toExtend(); + expectTypeOf().toEqualTypeOf | undefined>(); + expectTypeOf>() + .parameter(0) + .toEqualTypeOf(); + }); +}); diff --git a/examples/preact/tsconfig.json b/examples/preact/tsconfig.json new file mode 100644 index 0000000..90527a9 --- /dev/null +++ b/examples/preact/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext"], + "skipLibCheck": false, + "jsx": "react-jsx", + "jsxImportSource": "@examples/dom-preact" + }, + "include": ["src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] +} diff --git a/examples/react/README.md b/examples/react/README.md new file mode 100644 index 0000000..1e93104 --- /dev/null +++ b/examples/react/README.md @@ -0,0 +1,9 @@ +# React with @bomb.sh/dom + +This example owns its framework adapter, framework dependencies, and acceptance tests. The core DOM package has no framework runtime dependencies. The adapter uses the public `@bomb.sh/dom` exports and the same tree and event implementation as ordinary DOM callers. + +Run `pnpm --filter @bomb.sh/dom build`, then `pnpm --filter @examples/dom-react test`, `pnpm --filter @examples/dom-react typecheck`, or `pnpm --filter @examples/dom-react demo`. + +The tested scope covers mounting, native event handlers, text identity, asynchronous updates in independent documents, and cleanup. Browser styling, hydration, and framework-specific browser APIs are not implied. See [framework boundaries](../../packages/dom/docs/frameworks.md). + +This adapter uses React’s custom reconciler, not React DOM. It exposes native DOM events, without React’s synthetic event wrapper. Suspense/Activity hiding, hydration, CSS styles, resource hoisting, and browser form actions are unsupported. Render promises reject uncaught component errors; uncaught asynchronous state-update errors go to the document’s reportError hook. Namespace propagation for SVG/MathML is not implemented by this example. diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 0000000..e65e467 --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,30 @@ +{ + "name": "@examples/dom-react", + "private": true, + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "demo": "node --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "react": "19.3.1", + "react-reconciler": "0.34.0" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@types/react": "^19.3.0", + "@types/react-reconciler": "0.34.1", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "vitest": "^4.1.2" + } +} diff --git a/examples/react/src/demo.ts b/examples/react/src/demo.ts new file mode 100644 index 0000000..615f90d --- /dev/null +++ b/examples/react/src/demo.ts @@ -0,0 +1,10 @@ +import { Document } from '@bomb.sh/dom'; +import { createElement as h } from 'react'; +import { createRoot } from './index.ts'; + +const document = new Document(); +const container = document.appendChild(document.createElement('box')); +const root = createRoot(container); +await root.render(h('text', null, 'Hello from React')); +console.info(container.textContent); +root.unmount(); diff --git a/examples/react/src/index.ts b/examples/react/src/index.ts new file mode 100644 index 0000000..64160ce --- /dev/null +++ b/examples/react/src/index.ts @@ -0,0 +1,197 @@ +/* oxlint-disable bombshell-dev/max-params -- createPortal intentionally matches React’s public signature. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework mounting and host factories are synchronous by contract. */ +import { createContext, type ReactNode } from 'react'; +import Reconciler, { type HostConfig } from 'react-reconciler'; +import { + ConcurrentRoot, + DefaultEventPriority, + NoEventPriority, +} from 'react-reconciler/constants.js'; +import type { Element, Text, Node, Document } from '@bomb.sh/dom'; +import { updateHostProperties } from './properties.ts'; + +type Props = Readonly>; +type Config = HostConfig< + string, + Props, + Element, + Element, + Text, + never, + never, + never, + never, + Node, + Document, + never, + ReturnType, + -1, + null, + null, + null, + never, + never, + never +>; + +let priority = NoEventPriority; +const nothing = (): void => {}; +class UnsupportedHostOperationError extends Error { + override name = 'UnsupportedHostOperationError'; +} +const unsupported = (): never => { + throw new UnsupportedHostOperationError('This React host operation is not supported'); +}; + +const configuration: Config = { + rendererVersion: '0.0.0', + rendererPackageName: '@bomb.sh/dom', + extraDevToolsConfig: null, + bindToConsole: (_method, args: unknown[]) => () => console.info(...args), + supportsMutation: true, + supportsPersistence: false, + supportsHydration: false, + supportsMicrotasks: true, + isPrimaryRenderer: true, + getRootHostContext: (container) => container.ownerDocument, + getChildHostContext: (context) => context, + getPublicInstance: (instance) => instance, + createInstance(type, props, container) { + const element = container.ownerDocument.createElement(type); + updateHostProperties(element, {}, props); + return element; + }, + createTextInstance: (value, container) => container.ownerDocument.createTextNode(value), + appendInitialChild: (parent, child) => { + parent.appendChild(child); + }, + finalizeInitialChildren: () => false, + shouldSetTextContent: () => false, + prepareForCommit: () => null, + resetAfterCommit: nothing, + preparePortalMount: nothing, + appendChild: (parent, child) => { + parent.appendChild(child); + }, + appendChildToContainer: (parent, child) => { + parent.appendChild(child); + }, + insertBefore: (parent, child, anchor) => { + parent.insertBefore(child, anchor); + }, + insertInContainerBefore: (parent, child, anchor) => { + parent.insertBefore(child, anchor); + }, + removeChild: (parent, child) => { + parent.removeChild(child); + }, + removeChildFromContainer: (parent, child) => { + parent.removeChild(child); + }, + clearContainer: (container) => { + container.textContent = ''; + }, + resetTextContent: (element) => { + element.textContent = ''; + }, + commitTextUpdate: (text, _previous, next) => { + text.data = next; + }, + commitUpdate: (element, _type, previous, next) => updateHostProperties(element, previous, next), + scheduleTimeout: (callback, delay) => setTimeout(callback, delay), + cancelTimeout: clearTimeout, + noTimeout: -1, + scheduleMicrotask: queueMicrotask, + getInstanceFromNode: () => null, + beforeActiveInstanceBlur: nothing, + afterActiveInstanceBlur: nothing, + prepareScopeUpdate: unsupported, + getInstanceFromScope: () => null, + detachDeletedInstance: nothing, + NotPendingTransition: null, + HostTransitionContext: createContext(null), + setCurrentUpdatePriority: (value) => { + priority = value; + }, + getCurrentUpdatePriority: () => priority, + resolveUpdatePriority: () => priority || DefaultEventPriority, + resetFormInstance: unsupported, + requestPostPaintCallback: (callback) => queueMicrotask(() => callback(performance.now())), + shouldAttemptEagerTransition: () => false, + trackSchedulerEvent: nothing, + resolveEventType: () => null, + resolveEventTimeStamp: () => performance.now(), + maySuspendCommit: () => false, + maySuspendCommitOnUpdate: () => false, + maySuspendCommitInSyncRender: () => false, + preloadInstance: () => true, + startSuspendingCommit: () => null, + suspendInstance: unsupported, + suspendOnActiveViewTransition: unsupported, + waitForCommitToBeReady: () => null, + getSuspendedCommitReason: () => null, + hideInstance: unsupported, + hideTextInstance: unsupported, + unhideInstance: unsupported, + unhideTextInstance: unsupported, +}; + +const renderer = Reconciler(configuration); + +export interface Root extends Disposable { + readonly element: Element; + /** Resolve when React commits the render. */ + render(children: ReactNode): Promise; + unmount(): void; +} + +/** Official React reconciler targeting this package's native nodes and events. */ +export function createRoot(element: Element): Root { + const pending = new Set<(error: unknown) => void>(); + const onUncaughtError = (error: unknown): void => { + if (!pending.size) element.ownerDocument.reportError(error); + for (const reject of pending) reject(error); + pending.clear(); + }; + const root = renderer.createContainer( + element, + ConcurrentRoot, + null, + false, + null, + '', + onUncaughtError, + nothing, + (error: unknown) => element.ownerDocument.reportError(error), + nothing, + null, + ); + return { + element, + render(children) { + return new Promise((resolve, reject) => { + pending.add(reject); + renderer.updateContainer(children, root, null, () => { + // React can report an uncaught render error later in this commit. + queueMicrotask(() => { + pending.delete(reject); + resolve(); + }); + }); + }); + }, + unmount() { + renderer.updateContainerSync(null, root, null, null); + renderer.flushSyncWork(); + renderer.flushPassiveEffects(); + }, + [Symbol.dispose]() { + this.unmount(); + }, + }; +} + +/** Create a portal owned by the destination container’s document. */ +export function createPortal(children: ReactNode, container: Element, key?: string): ReactNode { + return renderer.createPortal(children, container, null, key); +} diff --git a/examples/react/src/properties.ts b/examples/react/src/properties.ts new file mode 100644 index 0000000..35c30d6 --- /dev/null +++ b/examples/react/src/properties.ts @@ -0,0 +1,59 @@ +/* oxlint-disable bombshell-dev/max-params -- These callbacks match framework host property signatures. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework property mutation must complete in the caller’s synchronous update. */ +/* oxlint-disable bombshell-dev/no-generic-error -- Unsupported prop values fail with TypeError at the adapter boundary. */ +import type { Element } from '@bomb.sh/dom'; +import type { Event } from '@bomb.sh/dom'; + +const handlers = new WeakMap void>>(); + +/** Translate framework prop spelling at the adapter boundary, never in the DOM. */ +export function setHostProperty(element: Element, name: string, value: unknown): void { + if (name === 'children' || name === 'ref' || name === 'key') return; + if (name.startsWith('on')) { + let eventName = name.startsWith('on:') ? name.slice(3) : name.slice(2); + const capture = !name.startsWith('on:') && eventName.endsWith('Capture'); + if (capture) eventName = eventName.slice(0, -7); + if (!name.startsWith('on:') && /^[A-Z]/.test(eventName)) eventName = eventName.toLowerCase(); + let registered = handlers.get(element); + const previous = registered?.get(name); + if (previous) element.removeEventListener(eventName, previous, capture); + registered?.delete(name); + if (typeof value === 'function') { + if (!registered) handlers.set(element, (registered = new Map())); + const listener = (event: Event): void => { + value.call(element, event); + }; + registered.set(name, listener); + element.addEventListener(eventName, listener, capture); + } + return; + } + if (name === 'style' || name === 'dangerouslySetInnerHTML') { + throw new TypeError(`${name} is not supported by this framework adapter`); + } + if (name === 'className') name = 'class'; + if (name === 'value' && 'value' in element) { + Reflect.set(element, 'value', value ?? ''); + return; + } + if ( + value === null || + value === undefined || + (value === false && !name.startsWith('data-') && !name.startsWith('aria-')) + ) + element.removeAttribute(name); + else element.setAttribute(name, value === true ? '' : String(value)); +} + +/** Apply removed and changed framework props to a native element. */ +export function updateHostProperties( + element: Element, + previous: Readonly>, + next: Readonly>, +): void { + for (const name of Object.keys(previous)) + if (!(name in next)) setHostProperty(element, name, undefined); + for (const [name, value] of Object.entries(next)) { + if (previous[name] !== value) setHostProperty(element, name, value); + } +} diff --git a/examples/react/test/react.test.ts b/examples/react/test/react.test.ts new file mode 100644 index 0000000..56a4681 --- /dev/null +++ b/examples/react/test/react.test.ts @@ -0,0 +1,97 @@ +import assert from 'node:assert/strict'; +import { createElement as h, createRef, useEffect, useState, type ReactElement } from 'react'; +import { describe, expect, it } from 'vitest'; +import { Document, Element, Text } from '@bomb.sh/dom'; +import { Event } from '@bomb.sh/dom'; +import { createPortal, createRoot } from '../src/index.ts'; + +describe('actual React reconciler', () => { + it('rejects a failed render and can subsequently mount a healthy tree', async () => { + const document = new Document(); + using root = createRoot(document.createElement('box')); + const failure = new RangeError('component failed'); + function Failing(): never { + throw failure; + } + await expect(root.render(h(Failing))).rejects.toBe(failure); + await root.render(h('text', null, 'recovered')); + expect(root.element.textContent).toBe('recovered'); + }); + + it('updates two documents asynchronously, preserves refs/text, and cleans up effects', async () => { + const firstDocument = new Document(); + const secondDocument = new Document(); + const firstContainer = firstDocument.createElement('box'); + const secondContainer = secondDocument.createElement('box'); + using first = createRoot(firstContainer); + using second = createRoot(secondContainer); + const setters: Array<(value: number) => void> = []; + const cleanups: number[] = []; + const firstRef = createRef(); + function Counter({ index }: { index: number }): ReactElement { + const [value, setValue] = useState(0); + setters[index] = setValue; + useEffect( + () => () => { + cleanups.push(index); + }, + [], + ); + return h('text', { ref: index === 0 ? firstRef : undefined }, value); + } + await Promise.all([ + first.render(h(Counter, { index: 0 })), + second.render(h(Counter, { index: 1 })), + ]); + const firstElement = firstContainer.firstChild; + assert.ok(firstElement instanceof Element); + const firstText = firstElement.firstChild; + assert.ok(firstText instanceof Text); + expect(firstRef.current).toBe(firstElement); + await Promise.resolve(); + setters[0]?.(1); + setters[1]?.(2); + await expect.poll(() => firstContainer.textContent).toBe('1'); + await expect.poll(() => secondContainer.textContent).toBe('2'); + expect(firstContainer.firstChild).toBe(firstElement); + expect(firstElement.firstChild).toBe(firstText); + expect(firstText.ownerDocument).toBe(firstDocument); + expect(secondContainer.firstChild?.ownerDocument).toBe(secondDocument); + first.unmount(); + second.unmount(); + expect(firstRef.current).toBeNull(); + expect(cleanups).toEqual([0, 1]); + expect(globalThis).not.toHaveProperty('document'); + }); + + it('reorders keyed native nodes, replaces event handlers, and owns cross-document portals', async () => { + const document = new Document(); + const remoteDocument = new Document(); + const container = document.createElement('box'); + const remote = remoteDocument.createElement('box'); + using root = createRoot(container); + const calls: string[] = []; + const rows = (names: string[], label: string): ReactElement[] => + names.map((name) => + h( + 'text', + { + key: name, + onClick: () => calls.push(label), + }, + name, + ), + ); + await root.render(rows(['a', 'b', 'c'], 'before')); + const original = [...container.childNodes]; + await root.render(rows(['c', 'a'], 'after')); + expect(container.childNodes).toEqual([original[2], original[0]]); + container.firstChild?.dispatchEvent(new Event('click')); + expect(calls).toEqual(['after']); + await root.render(createPortal(h('text', null, 'remote'), remote)); + expect(remote.textContent).toBe('remote'); + expect(remote.firstChild?.ownerDocument).toBe(remoteDocument); + root.unmount(); + expect(remote.childNodes).toHaveLength(0); + }); +}); diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json new file mode 100644 index 0000000..bf96cf7 --- /dev/null +++ b/examples/react/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/examples/solid/README.md b/examples/solid/README.md new file mode 100644 index 0000000..0df54f2 --- /dev/null +++ b/examples/solid/README.md @@ -0,0 +1,13 @@ +# Solid with @bomb.sh/dom + +This example owns its framework adapter, framework dependencies, and acceptance tests. The core DOM package has no framework runtime dependencies. The adapter uses the public `@bomb.sh/dom` exports and the same tree and event implementation as ordinary DOM callers. + +Run `pnpm --filter @bomb.sh/dom build`, then `pnpm --filter @examples/dom-solid test`, `pnpm --filter @examples/dom-solid typecheck`, or `pnpm --filter @examples/dom-solid demo`. + +Solid uses its reactive browser export condition even in Node. Run Node with `--conditions=browser` (as the demo script does). The fixture also aliases the reactive export consistently for signals and the universal renderer. This does not install browser globals. Browser-compiled Solid output must instead be compiled for the universal adapter. + +The tested scope covers mounting, native event handlers, text identity, asynchronous updates in independent documents, and cleanup. Browser styling, hydration, and framework-specific browser APIs are not implied. See [framework boundaries](../../packages/dom/docs/frameworks.md). + +Solid’s upstream signal declarations import browser JSX types. This fixture loads the DOM type library locally to validate them without skipLibCheck; its host callbacks and mounted nodes use the actual `@bomb.sh/dom` types. + +The tests call the real universal renderer with signals and node arrays. They do not compile browser JSX or exercise Solid’s web renderer. CSS style objects, raw markup, hydration, and SVG/MathML namespace propagation are unsupported. Disposal cleans the reactive owner and removes the rendered root. diff --git a/examples/solid/package.json b/examples/solid/package.json new file mode 100644 index 0000000..80ab656 --- /dev/null +++ b/examples/solid/package.json @@ -0,0 +1,27 @@ +{ + "name": "@examples/dom-solid", + "private": true, + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "demo": "node --conditions=browser --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "solid-js": "1.9.17" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "vitest": "^4.1.2" + } +} diff --git a/examples/solid/src/demo.ts b/examples/solid/src/demo.ts new file mode 100644 index 0000000..53f6f13 --- /dev/null +++ b/examples/solid/src/demo.ts @@ -0,0 +1,18 @@ +import { Document } from '@bomb.sh/dom'; +import { createSignal } from 'solid-js'; +import { createSolidRenderer } from './index.ts'; + +const document = new Document(); +const container = document.appendChild(document.createElement('box')); +const renderer = createSolidRenderer(document); +let update = (_value: string): void => {}; +const dispose = renderer.render(() => { + const [message, setMessage] = createSignal('Hello'); + update = setMessage; + const text = renderer.createElement('text'); + renderer.insert(text, message); + return text; +}, container); +update('Hello from Solid'); +console.info(container.textContent); +dispose(); diff --git a/examples/solid/src/index.ts b/examples/solid/src/index.ts new file mode 100644 index 0000000..05109c2 --- /dev/null +++ b/examples/solid/src/index.ts @@ -0,0 +1,71 @@ +/* oxlint-disable bombshell-dev/no-generic-error -- Reject invalid host callback values with standard TypeError exceptions. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework mounting and host factories are synchronous by contract. */ +import { createRenderer, type Renderer } from 'solid-js/universal'; +import { Element, Text, type Document, type Node } from '@bomb.sh/dom'; +import { setHostProperty } from './properties.ts'; + +export type SolidChild = + | Node + | string + | number + | boolean + | null + | undefined + | readonly SolidChild[]; +export interface SolidRenderer extends Pick< + Renderer, + | 'effect' + | 'memo' + | 'createComponent' + | 'createElement' + | 'createTextNode' + | 'insertNode' + | 'setProp' + | 'mergeProps' + | 'use' +> { + render(component: () => Node, container: Node): () => void; + insert(parent: Node, value: SolidChild | (() => SolidChild), marker?: Node | null): void; + spread( + node: Node, + props: Record | (() => Record), + skipChildren?: boolean, + ): void; +} + +/** Bind Solid's universal runtime to a document for its entire reactive lifetime. */ +export function createSolidRenderer(document: Document): SolidRenderer { + const renderer = createRenderer({ + createElement: (name) => document.createElement(name), + createTextNode: (value) => document.createTextNode(value), + replaceText(node, value) { + if (!(node instanceof Text)) throw new TypeError('Expected a Text node'); + node.data = value; + }, + isTextNode: (node) => node instanceof Text, + setProperty(node, name, value) { + if (!(node instanceof Element)) throw new TypeError('Expected an Element'); + setHostProperty(node, name, value); + }, + insertNode: (parent, child, anchor) => { + parent.insertBefore(child, anchor ?? null); + }, + removeNode: (parent, child) => { + parent.removeChild(child); + }, + getParentNode: (node) => node.parentNode ?? undefined, + getFirstChild: (node) => node.firstChild ?? undefined, + getNextSibling: (node) => node.nextSibling ?? undefined, + }); + return { + ...renderer, + render(component, container) { + let root: Node | undefined; + const dispose = renderer.render(() => (root = component()), container); + return () => { + dispose(); + root?.remove(); + }; + }, + }; +} diff --git a/examples/solid/src/properties.ts b/examples/solid/src/properties.ts new file mode 100644 index 0000000..35c30d6 --- /dev/null +++ b/examples/solid/src/properties.ts @@ -0,0 +1,59 @@ +/* oxlint-disable bombshell-dev/max-params -- These callbacks match framework host property signatures. */ +/* oxlint-disable bombshell-dev/exported-function-async -- Framework property mutation must complete in the caller’s synchronous update. */ +/* oxlint-disable bombshell-dev/no-generic-error -- Unsupported prop values fail with TypeError at the adapter boundary. */ +import type { Element } from '@bomb.sh/dom'; +import type { Event } from '@bomb.sh/dom'; + +const handlers = new WeakMap void>>(); + +/** Translate framework prop spelling at the adapter boundary, never in the DOM. */ +export function setHostProperty(element: Element, name: string, value: unknown): void { + if (name === 'children' || name === 'ref' || name === 'key') return; + if (name.startsWith('on')) { + let eventName = name.startsWith('on:') ? name.slice(3) : name.slice(2); + const capture = !name.startsWith('on:') && eventName.endsWith('Capture'); + if (capture) eventName = eventName.slice(0, -7); + if (!name.startsWith('on:') && /^[A-Z]/.test(eventName)) eventName = eventName.toLowerCase(); + let registered = handlers.get(element); + const previous = registered?.get(name); + if (previous) element.removeEventListener(eventName, previous, capture); + registered?.delete(name); + if (typeof value === 'function') { + if (!registered) handlers.set(element, (registered = new Map())); + const listener = (event: Event): void => { + value.call(element, event); + }; + registered.set(name, listener); + element.addEventListener(eventName, listener, capture); + } + return; + } + if (name === 'style' || name === 'dangerouslySetInnerHTML') { + throw new TypeError(`${name} is not supported by this framework adapter`); + } + if (name === 'className') name = 'class'; + if (name === 'value' && 'value' in element) { + Reflect.set(element, 'value', value ?? ''); + return; + } + if ( + value === null || + value === undefined || + (value === false && !name.startsWith('data-') && !name.startsWith('aria-')) + ) + element.removeAttribute(name); + else element.setAttribute(name, value === true ? '' : String(value)); +} + +/** Apply removed and changed framework props to a native element. */ +export function updateHostProperties( + element: Element, + previous: Readonly>, + next: Readonly>, +): void { + for (const name of Object.keys(previous)) + if (!(name in next)) setHostProperty(element, name, undefined); + for (const [name, value] of Object.entries(next)) { + if (previous[name] !== value) setHostProperty(element, name, value); + } +} diff --git a/examples/solid/test/solid.test.ts b/examples/solid/test/solid.test.ts new file mode 100644 index 0000000..2b23d02 --- /dev/null +++ b/examples/solid/test/solid.test.ts @@ -0,0 +1,66 @@ +import { createSignal, onCleanup } from 'solid-js'; +import { describe, expect, it } from 'vitest'; +import { Document, type Element } from '@bomb.sh/dom'; +import { Event } from '@bomb.sh/dom'; +import { createSolidRenderer } from '../src/index.ts'; + +describe('actual Solid universal renderer', () => { + it('keeps independent reactive roots and text nodes in their documents without globals', async () => { + const documents = [new Document(), new Document()]; + const containers = documents.map((document) => document.createElement('box')); + const setters: Array<(value: string) => void> = []; + const cleanups: number[] = []; + const disposers = documents.map((document, index) => { + const renderer = createSolidRenderer(document); + return renderer.render(() => { + const [value, setValue] = createSignal('initial'); + setters.push(setValue); + onCleanup(() => { + cleanups.push(index); + }); + const node = renderer.createElement('text'); + renderer.insert(node, value); + return node; + }, containers[index]!); + }); + const original = containers.map((container) => container.firstChild?.firstChild); + await Promise.resolve(); + setters[0]?.('first'); + setters[1]?.('second'); + expect(containers.map((container) => container.textContent)).toEqual(['first', 'second']); + for (const [index, container] of containers.entries()) { + expect(container.firstChild?.firstChild).toBe(original[index]); + expect(container.firstChild?.ownerDocument).toBe(documents[index]); + } + for (const dispose of disposers) dispose(); + expect(cleanups).toEqual([0, 1]); + expect(containers.every((container) => container.childNodes.length === 0)).toBe(true); + expect(globalThis).not.toHaveProperty('document'); + }); + + it('reorders node identities and updates native handlers through real reactive effects', () => { + const document = new Document(); + const container = document.createElement('box'); + const renderer = createSolidRenderer(document); + const calls: string[] = []; + const original = ['a', 'b', 'c'].map((name) => { + const element = document.createElement('text'); + element.textContent = name; + return element; + }); + let changeOrder = (_value: Element[]): void => {}; + const dispose = renderer.render(() => { + const [order, setOrder] = createSignal(original); + changeOrder = setOrder; + const parent = renderer.createElement('box'); + renderer.insert(parent, order); + renderer.setProp(original[0]!, 'onClick', () => calls.push('clicked')); + return parent; + }, container); + changeOrder([original[2]!, original[0]!]); + expect(container.firstChild?.childNodes).toEqual([original[2], original[0]]); + original[0]?.dispatchEvent(new Event('click')); + expect(calls).toEqual(['clicked']); + dispose(); + }); +}); diff --git a/examples/solid/tsconfig.json b/examples/solid/tsconfig.json new file mode 100644 index 0000000..1b4ec8c --- /dev/null +++ b/examples/solid/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext", "DOM"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/examples/solid/vitest.config.ts b/examples/solid/vitest.config.ts new file mode 100644 index 0000000..9e4eb3d --- /dev/null +++ b/examples/solid/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { fileURLToPath } from 'node:url'; + +// Solid's Node default is its SSR runtime. Select the reactive client runtime +// consistently for both application signals and solid-js/universal imports. +export default defineConfig({ + resolve: { + conditions: ['browser'], + alias: [ + { + find: /^solid-js$/, + replacement: fileURLToPath(import.meta.resolve('solid-js/dist/solid.js')), + }, + ], + }, + test: { + environment: 'node', + include: ['test/**/*.test.ts'], + server: { deps: { inline: ['solid-js/universal'] } }, + }, +}); diff --git a/examples/svelte/README.md b/examples/svelte/README.md new file mode 100644 index 0000000..4719f13 --- /dev/null +++ b/examples/svelte/README.md @@ -0,0 +1,13 @@ +# Svelte on the in-memory DOM + +After building `@bomb.sh/dom`, run `pnpm --filter @examples/dom-svelte demo` or `pnpm --filter @examples/dom-svelte test`. + +`src/Counter.svelte` is compiled by the real Svelte 5 client compiler. It uses a writable store, local reactive state, keyed each blocks, a delegated click listener, and mount/cleanup hooks. Tests exercise text and keyed-node identity, asynchronous updates in two documents, event delivery, and unmount cleanup. + +Svelte's client runtime caches document/window and node prototype getters during initialization. The [persistent realm adapter](../dom-realm/README.md) bundles and instantiates a separate browser runtime per document. The host process never receives browser globals, and asynchronous callbacks keep their original document. Template parsing is explicitly installed by the adapter. + +The `.svelte` component and its browser entry are compiler-validated JavaScript. Host tests and the realm adapter use the actual `@bomb.sh/dom` types. This demonstrates an explicit compilation/bootstrap boundary; it does not assert compatibility with full browser Element declarations or every Svelte feature. Hydration, CSS transitions/animation, layout, browser form defaults, and window navigation are outside this fixture's supported scope. + +Svelte supplies its compiler and client reactivity/runtime; esbuild bundles that runtime for the persistent context. Both are dependencies of example packages, not the core DOM. + +Sources: [Svelte mounting/unmounting API](https://svelte.dev/docs/svelte/imperative-component-api), [client DOM initialization](https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/internal/client/dom/operations.js), [client mount implementation](https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/internal/client/render.js). diff --git a/examples/svelte/package.json b/examples/svelte/package.json new file mode 100644 index 0000000..824ab36 --- /dev/null +++ b/examples/svelte/package.json @@ -0,0 +1,26 @@ +{ + "name": "@examples/dom-svelte", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "demo": "node --experimental-transform-types src/demo.ts", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run", + "format": "bsh format", + "format:check": "bsh format --check", + "lint": "bsh lint" + }, + "dependencies": { + "@bomb.sh/dom": "workspace:*", + "@examples/dom-realm": "workspace:*", + "svelte": "5.57.2" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.6.1", + "@types/node": "^22", + "@typescript/native-preview": "7.0.0-dev.20260623.1", + "esbuild": "^0.27.0", + "vitest": "^4.1.2" + } +} diff --git a/examples/svelte/src/Counter.svelte b/examples/svelte/src/Counter.svelte new file mode 100644 index 0000000..e2d325f --- /dev/null +++ b/examples/svelte/src/Counter.svelte @@ -0,0 +1,20 @@ + + + + {$model.label} + + {#each $model.items as item (item)} + {item} + {/each} + diff --git a/examples/svelte/src/compile.ts b/examples/svelte/src/compile.ts new file mode 100644 index 0000000..0a8bbbd --- /dev/null +++ b/examples/svelte/src/compile.ts @@ -0,0 +1,24 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { compile } from 'svelte/compiler'; +import { bundleFixture } from '@examples/dom-realm'; +import type { Plugin } from 'esbuild'; + +/** Compile real Svelte components for the client, then bundle their browser runtime. */ +export async function compileFixture(): Promise { + const plugin: Plugin = { + name: 'svelte-example', + setup(build) { + build.onLoad({ filter: /\.svelte$/ }, async (args) => { + const source = await readFile(args.path, 'utf8'); + const result = compile(source, { filename: args.path, generate: 'client', dev: false }); + return { + contents: result.js.code, + loader: 'js', + resolveDir: fileURLToPath(new URL('.', pathToFileURL(args.path))), + }; + }); + }, + }; + return bundleFixture(new URL('./entry.js', import.meta.url), [plugin]); +} diff --git a/examples/svelte/src/demo.ts b/examples/svelte/src/demo.ts new file mode 100644 index 0000000..697b757 --- /dev/null +++ b/examples/svelte/src/demo.ts @@ -0,0 +1,12 @@ +import { Document } from '@bomb.sh/dom'; +import { FixtureRealm } from '@examples/dom-realm'; +import { compileFixture } from './compile.ts'; + +const document = new Document(); +document.appendChild(document.createElement('application')); +const realm = new FixtureRealm(document); +realm.run(await compileFixture()); +await realm.run('fixture.mount()'); +await realm.run('fixture.update("updated")'); +process.stdout.write(`${document.documentElement?.textContent}\n`); +await realm.run('fixture.unmount()'); diff --git a/examples/svelte/src/entry.js b/examples/svelte/src/entry.js new file mode 100644 index 0000000..a354641 --- /dev/null +++ b/examples/svelte/src/entry.js @@ -0,0 +1,29 @@ +import { mount as mountComponent, unmount as unmountComponent, tick } from 'svelte'; +import { writable } from 'svelte/store'; +import Counter from './Counter.svelte'; + +export const state = writable({ label: 'initial', items: ['a', 'b'] }); +export const lifecycle = []; +let application; + +export async function mount() { + application = mountComponent(Counter, { + target: document.documentElement, + props: { model: state, lifecycle }, + }); + await tick(); +} + +export async function update(label, items = ['b', 'a']) { + await Promise.resolve(); + state.set({ label, items }); + await tick(); +} + +export async function settle() { + await tick(); +} + +export async function unmount() { + await unmountComponent(application); +} diff --git a/examples/svelte/test/svelte.test.ts b/examples/svelte/test/svelte.test.ts new file mode 100644 index 0000000..771fd6c --- /dev/null +++ b/examples/svelte/test/svelte.test.ts @@ -0,0 +1,84 @@ +import { beforeAll, describe, expect, it } from 'vitest'; +import { Document, Element, Event, type Node } from '@bomb.sh/dom'; +import { FixtureRealm } from '@examples/dom-realm'; +import { compileFixture } from '../src/compile.ts'; + +let bundle: string; +beforeAll(async () => { + bundle = await compileFixture(); +}); + +function createFixture(): { document: Document; realm: FixtureRealm } { + const document = new Document(); + document.appendChild(document.createElement('application')); + const realm = new FixtureRealm(document); + realm.run(bundle); + return { document, realm }; +} + +function findElement( + root: Node | null | undefined, + predicate: (element: Element) => boolean, +): Element | undefined { + for (const child of root?.childNodes ?? []) { + if (child instanceof Element && predicate(child)) return child; + const nested = findElement(child, predicate); + if (nested) return nested; + } +} + +describe('Svelte 5 compiled client component in a persistent document realm', () => { + it('mounts, updates keyed nodes and text, handles input, and cleans up', async () => { + const { document, realm } = createFixture(); + const sibling = document.createElement('sibling'); + sibling.textContent = 'untouched'; + document.documentElement?.appendChild(sibling); + await realm.run('fixture.mount()'); + const root = document.documentElement; + expect(root?.textContent).toContain('initial'); + const a = findElement(root, (element) => element.getAttribute('data-key') === 'a'); + const label = findElement(root, (element) => element.localName === 'text')?.firstChild; + expect(a).not.toBeNull(); + await realm.run('fixture.update("updated")'); + expect(root?.textContent).toContain('updated'); + expect(findElement(root, (element) => element.localName === 'box')?.getAttribute('title')).toBe( + 'updated', + ); + expect(sibling.textContent).toBe('untouched'); + expect(findElement(root, (element) => element.getAttribute('data-key') === 'a')).toBe(a); + expect(findElement(root, (element) => element.localName === 'text')?.firstChild).toBe(label); + const button = findElement(root, (element) => element.localName === 'button'); + button?.dispatchEvent(new Event('click', { bubbles: true, composed: true })); + await realm.run('fixture.settle()'); + expect(button?.textContent).toBe('1'); + expect(realm.run('fixture.lifecycle.join(",")')).toBe('mounted'); + await realm.run('fixture.unmount()'); + expect(root?.childNodes).toEqual([sibling]); + expect(root?.textContent).toBe('untouched'); + expect(realm.run('fixture.lifecycle.join(",")')).toBe('mounted,cleanup'); + }); + + it('keeps asynchronous updates in two documents independent without global swapping', async () => { + const first = createFixture(); + const second = createFixture(); + await Promise.all([first.realm.run('fixture.mount()'), second.realm.run('fixture.mount()')]); + await Promise.all([ + first.realm.run('fixture.update("first")'), + second.realm.run('fixture.update("second")'), + ]); + expect(first.document.documentElement?.textContent).toContain('first'); + expect(second.document.documentElement?.textContent).toContain('second'); + expect( + findElement(first.document, (element) => element.localName === 'button')?.ownerDocument, + ).toBe(first.document); + expect( + findElement(second.document, (element) => element.localName === 'button')?.ownerDocument, + ).toBe(second.document); + expect(Reflect.has(globalThis, 'document')).toBe(false); + expect(Reflect.has(globalThis, 'window')).toBe(false); + await Promise.all([ + first.realm.run('fixture.unmount()'), + second.realm.run('fixture.unmount()'), + ]); + }); +}); diff --git a/examples/svelte/tsconfig.json b/examples/svelte/tsconfig.json new file mode 100644 index 0000000..1b4ec8c --- /dev/null +++ b/examples/svelte/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "types": ["node"], + "lib": ["ESNext", "DOM"], + "skipLibCheck": false + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/package.json b/package.json index 6ad1388..2cbad73 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "format": "bsh format", "format:check": "bsh format --check", "lint": "bsh lint .", - "test": "pnpm --filter @bomb.sh/dom build && bsh test --exclude 'experiments/ghostwright/**' --exclude '**/node_modules/**' --exclude '**/dist/**'" + "test": "pnpm --filter @bomb.sh/dom build && bsh test --exclude 'experiments/ghostwright/**' --exclude '**/node_modules/**' --exclude '**/dist/**' --exclude 'examples/{preact,react,solid,lit,svelte,crank,dom-realm}/**' && pnpm --filter '@examples/dom-*' --if-present test" }, "devDependencies": { "@bomb.sh/args": "catalog:", diff --git a/packages/dom/README.md b/packages/dom/README.md index 75432c2..ed0a51c 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -105,6 +105,21 @@ Each environment has an explicit `document.HTMLElement` base and registry. Auton `parse5` is the only production dependency. The separate `@bomb.sh/dom/parsing` entry uses its HTML tokenizer/tree builder, then creates nodes in this implementation. The core entry does not load it. No LinkeDOM runtime, second DOM hierarchy, framework, browser process, or worker is required. +## Framework examples + +Integration code, framework dependencies, JSX settings, demos, and acceptance tests live in separate workspace packages: + +| Example | Boundary | +| -------------------------------------------- | ------------------------------------------------------------------------------- | +| [Preact 11](../../examples/preact/README.md) | Actual `render`/portals using container ownership; local typed JSX facade. | +| [React](../../examples/react/README.md) | Official custom reconciler and native host events. | +| [Solid](../../examples/solid/README.md) | Official generic universal renderer. | +| [Lit](../../examples/lit/README.md) | Browser Lit runtime in a persistent document-specific Node VM context. | +| [Svelte](../../examples/svelte/README.md) | Real client compiler/runtime in a persistent document-specific Node VM context. | +| [Crank](../../examples/crank/README.md) | Generic renderer with the package's real node types. | + +The [realm example](../../examples/dom-realm/README.md) explains Lit/Svelte bootstrap. It keeps asynchronous work bound to its original document without swapping process globals. VM execution and fixture bundling are example facilities, outside the DOM package. See [framework boundaries](docs/frameworks.md) for limitations. + ## Development From the playground root: @@ -114,8 +129,11 @@ pnpm install pnpm --filter @bomb.sh/dom build pnpm --filter @bomb.sh/dom typecheck pnpm --filter @bomb.sh/dom exec vitest run +pnpm --filter '@examples/dom-*' typecheck +pnpm --filter '@examples/dom-*' --if-present test pnpm --filter @bomb.sh/dom format:check pnpm --filter @bomb.sh/dom lint +pnpm --filter @examples/dom-preact demo ``` The package emits ESM JavaScript, declaration files, and maps through the playground's TypeScript compiler. Core checking uses strict TypeScript without the browser DOM library. JSX and upstream browser declaration requirements stay local to examples. No publishing is performed by these commands. diff --git a/packages/dom/docs/capabilities.md b/packages/dom/docs/capabilities.md index 7574a58..147cff0 100644 --- a/packages/dom/docs/capabilities.md +++ b/packages/dom/docs/capabilities.md @@ -14,22 +14,26 @@ Statuses describe this implementation and its checked-in tests. “Implemented a | Text controls | Implemented and tested | Input/textarea values and UTF-16 selection, directions/clamping, select/setSelectionRange/setRangeText, dirty values, value cloning, and observable selection. | | Document ranges/selection | Implemented and tested | Live boundaries, comparison, collapse/extend, direction, traversal/stringification, clone/extract/delete contents; insertion/removal/text/split updates. | | Autonomous custom elements | Implemented and tested | Per-document base/registry, construction, define/get/whenDefined/upgrade, preserved existing identity/listeners/user state, lifecycle and observed attributes, adoption, callback errors/reentrancy. | -| Templates and parsing | Implemented and tested | Opt-in parse5 HTML fragments, inert nested templates, namespaces/comments, cloning/importing, basic serialization, TreeWalker nextNode/parentNode and filtering. | +| Templates and parsing | Implemented and tested | Opt-in parse5 HTML fragments, inert nested templates, namespaces/comments, cloning/importing, basic serialization, TreeWalker nextNode/parentNode and filtering needed by Lit. | | Shadow trees and slots | Implemented and tested | Open/closed roots, named/default/fallback slot distribution, slotchange, shadow-including connectivity/adoption, event retargeting/closed composed paths, focus interactions. | +| Preact 11 example | Implemented and tested | Actual renderer: detached/nested roots, sibling preservation, async independent documents, refs/keys/text identity, native handlers, cross-document portals, cleanup and local public JSX types. | +| React / Solid / Crank examples | Implemented and tested | Real framework custom/universal/generic renderers with async updates, ownership, native events and cleanup; React also refs/keys/portals and error recovery. See individual example scope. | +| Lit / Svelte examples | Implemented and tested | Real browser runtimes in persistent example realms, concurrent documents, asynchronous rendering/events and cleanup; Lit upgrades/attributes/properties/shadow slots; Svelte compiler/keyed updates. | | Remaining Range/Selection APIs | Planned but unfinished | Range.insertNode/surroundContents/createContextualFragment, StaticRange, normalize-related updates, composed/shadow document selection and additional full-suite coverage. APIs absent from declarations until implemented. | | Further custom-element/shadow APIs | Planned but unfinished | Unicode custom-element names, customized built-ins, scoped registries, form-associated elements/ElementInternals, declarative shadow DOM, manual slot assignment, cloneable/serializable roots, complete specification edge-case coverage. | | Further DOM facilities | Planned but unfinished | CSS selector matching, complete traversal API, NodeList/NamedNodeMap legacy methods, DOMTokenList/style declaration model, DocumentType/processing instructions/XML documents and XML parsing, complete HTML serialization (including raw-text script/style serialization). | | Further interaction | Planned but unfinished | Full browser focus-navigation/radio-group behavior, editing/activation defaults, broader form values/validation and input sanitization. See focus documentation. | +| Additional framework features | Planned but unfinished | Hydration, richer namespace/property adapters, broader host components and renderer-specific APIs. Passing example fixtures does not establish full browser renderer compatibility. | | Rendering and terminal I/O | Intentionally unsupported | CSS layout, glyph/caret painting, ANSI, keyboard-byte decoding, hit testing, coordinates, frame coalescing/output backpressure. Consumer responsibilities. | | Browser subsystems | Intentionally unsupported | Canvas, media, navigation, networking, browser script execution, trusted user input, browser windows or rendering processes. | | Legacy distribution/global setup | Intentionally unsupported | CommonJS builds, worker bundles/infrastructure, implicit global document/window/constructor installation, decorators as a requirement. | ## Milestone record -1. The pinned DOM/event baseline established the initial tree and event regression coverage. +1. The pinned DOM/event baseline and actual Preact 11 acceptance were established first. 2. Independent host invalidation, MutationObserver, focus/Dispatch, and text controls followed. 3. Live Range/Selection tests cover both ordinary edits and detached-tree mutations. -4. Custom elements, inert templates, parsing, shadow roots, and slots have focused unit tests and custom-element browser differential cases. -5. Framework integrations and their acceptance tests are maintained separately from the core package. +4. Custom-element/template/shadow infrastructure enabled real Lit, while explicit realm bootstrap enabled Svelte. +5. React, Solid, and Crank use example-owned renderers over the same tree. All framework integrations and dependencies are outside `@bomb.sh/dom`. Further work should expand this matrix only after adding meaningful tests and accurate declarations. No unsupported browser API is advertised through structural casts or global interface replacement. diff --git a/packages/dom/docs/frameworks.md b/packages/dom/docs/frameworks.md new file mode 100644 index 0000000..cf52a22 --- /dev/null +++ b/packages/dom/docs/frameworks.md @@ -0,0 +1,48 @@ +# Framework boundaries + +The package never installs a process-wide `document`, `window`, or DOM constructors. Every root must have an explicit document identity that remains valid during asynchronous updates. An integration adapter is a supported boundary only for the behavior exercised by its tests; it does not imply compatibility with every browser renderer feature. + +| Framework | Inspected version | Integration boundary | +| --------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Preact | 11.0.1 | Actual `preact.render` and `createPortal`, with this package's Element satisfying public `ContainerNode` structurally. | +| React | 19.3.1 / reconciler 0.34.0 | Official custom reconciler host configuration; React DOM's browser container declaration is not asserted onto our elements. | +| Solid | 1.9.17 | Official `solid-js/universal` generic renderer, bound to an explicit document. | +| Lit | 3.3.3 | Persistent realm/bootstrap boundary needed for constructors and template creation. Depends on tested custom elements, templates, ranges/traversal, and shadow roots. | +| Svelte | 5.57.2 | Client runtime captures global window/document and node prototype getters; requires an isolated persistent runtime realm or a substantive compiler/runtime adapter. | +| Crank | 0.7.12 | Generic `Renderer` adapter gives accurate host types; browser DOMRenderer declarations alone are not compatible. | + +## Preact + +The pinned renderer gets the node-creation document from `parentDom.ownerDocument` each time it creates a node. Portals supply their own parent container. This works across concurrently updated roots without ambient global swapping. `examples/preact/src/index.ts` is a small root-lifecycle facade over the actual renderer; it is not a mock renderer. + +Preact's own JSX mapping converts `onKeyDown` into the native event name `keydown`. A prop such as `onionChange` retains `ionChange`. The underlying DOM preserves event-name case. Events remain extensible so Preact can add its per-renderer dispatch bookkeeping symbol. + +The example-local `@examples/dom-preact/jsx-runtime` facade describes our actual host refs and events. JSX configuration belongs in an application or fixture tsconfig. The core package and workspace do not enable JSX or browser DOM declarations globally. + +Sources: [Preact render and public types](https://github.com/preactjs/preact/tree/11.0.1/src), particularly `diff/index.js`, `diff/props.js`, `create-portal.js`, and `index.d.ts`. + +## React and Solid + +React's official reconciler supports a host configuration with our existing nodes. This supplies real React scheduling, reconciliation, refs, and effects without declaring that our elements implement every browser Node member. Events are native host listeners rather than React DOM's synthetic event system. CSS, hydration, browser form behavior, Suspense/Activity hiding, and React DOM-specific APIs are unsupported by this example. Namespace propagation for SVG/MathML is not implemented in the React/Solid/Crank adapters. + +Solid's official universal renderer takes explicit creation, insertion, property, text, and traversal operations. Its browser/web renderer is a different integration and expects browser globals. Universal compilation must target the adapter; ordinary browser-compiled Solid output is not interchangeable. Node's default Solid export condition is server-oriented, so acceptance fixtures must deliberately select the reactive client implementation consistently for both signals and universal renderer imports. + +Sources: [React custom renderers](https://github.com/facebook/react/tree/main/packages/react-reconciler), [Solid universal renderer](https://github.com/solidjs/solid/tree/main/packages/solid/universal). + +## Lit and Svelte + +Lit modules choose an HTMLElement constructor and a document during bootstrap. Its Node export can use an SSR shim, which does not prove browser-side interactive rendering support. An adapter must load the browser implementation in a stable environment whose constructors and document belong to the target DOM, then test asynchronous updates after bootstrap. Temporary global replacement around `render()` is unsafe. + +Svelte 5.57.2 `src/internal/client/dom/operations.js` caches `window`, `document`, and prototype accessors in `init_operations`. Later creation helpers still reference document, and its event delegation installs listeners on the container and document. Therefore a single shared browser runtime cannot be made document-independent by changing a global only while mounting. A persistent isolated realm must have its own module instance and globals, with no worker or browser runtime dependency. The checked-in examples/dom-realm boundary instantiates a persistent browser bundle per document; the Svelte acceptance tests exercise async updates in two independent documents. + +Sources: [Lit source](https://github.com/lit/lit/tree/main/packages), [Svelte DOM operations](https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/internal/client/dom/operations.js), [Svelte mounting](https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/internal/client/render.js). + +## Crank + +The inspected DOM renderer already derives its document from a root or root.ownerDocument and uses nodeType constants without installing Node/Element globals. However, its public DOMRenderer signature uses complete browser Node types. A generic renderer adapter keeps its declarations faithful to this package and uses the same mutation implementation as every other integration. + +Source: [Crank renderer implementation](https://github.com/bikeshaving/crank/tree/main/src). + +## Dependency policy + +Frameworks and their reconciler packages are dependencies of the separate `examples/preact`, `examples/react`, `examples/solid`, `examples/lit`, `examples/svelte`, and `examples/crank` workspace packages. The core does not depend on or export integrations. Preact provides reconciliation/hooks; React plus react-reconciler provide React scheduling and host reconciliation; Solid provides reactive scopes and universal reconciliation; Crank provides generator component reconciliation. None supplies a second DOM tree. Development dependencies provide TypeScript declaration checking, Vitest, and playground formatting/lint/build tools. diff --git a/packages/dom/docs/provenance.md b/packages/dom/docs/provenance.md index f5541ef..2f3c262 100644 --- a/packages/dom/docs/provenance.md +++ b/packages/dom/docs/provenance.md @@ -8,7 +8,7 @@ Read-only reference: [bombshell-dev/ui at `606675f16131c6ae7c4553a67ab477bdf661f Inspected files: `packages/ui/src/dom.ts`, `dom.test.ts`, `ui.ts`, `packages/ui-preact/src/root.ts`, `root.test.tsx`, `elements.ts`, `jsx-runtime.ts`, and `jsx-dev-runtime.ts`. -The core DOM/event regression tests preserve useful baseline behavior while covering the standards differences below. The upstream license is MIT, copyright (c) 2025-Present Bombshell contributors; its permission notice is retained in this package's LICENSE. +The Preact root and JSX facade, and the Preact regression cases in `examples/preact`, adapt that baseline. They retain source attribution. The upstream license is MIT, copyright (c) 2025-Present Bombshell contributors; its permission notice is retained in this package's LICENSE. The existing terminal renderer and its dependencies remain consumer concerns. `ui.ts` demonstrates why connected-document invalidation is separate from output scheduling: its consumer requests frames, coalesces them, and awaits writes independently of tree mutation. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd03e80..4fc4ce0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 0.3.1 '@bomb.sh/tools': specifier: ^0.6.1 - version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@clack/prompts': specifier: 'catalog:' version: 1.7.0 @@ -39,12 +39,53 @@ importers: specifier: ^22 version: 22.20.1 + examples/crank: + dependencies: + '@b9g/crank': + specifier: 0.7.12 + version: 0.7.12 + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + examples/demo: dependencies: '@bomb.sh/tty': specifier: 'catalog:' version: 0.8.0 + examples/dom-realm: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + esbuild: + specifier: ^0.27.0 + version: 0.27.7 + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + examples/input: dependencies: '@bomb.sh/tty': @@ -60,6 +101,134 @@ importers: specifier: 'catalog:' version: 2.0.8 + examples/lit: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + '@examples/dom-realm': + specifier: workspace:* + version: link:../dom-realm + lit: + specifier: 3.3.3 + version: 3.3.3 + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + + examples/preact: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + preact: + specifier: 11.0.1 + version: 11.0.1 + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + + examples/react: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + react: + specifier: 19.3.1 + version: 19.3.1 + react-reconciler: + specifier: 0.34.0 + version: 0.34.0(react@19.3.1) + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@types/react': + specifier: ^19.3.0 + version: 19.3.0 + '@types/react-reconciler': + specifier: 0.34.1 + version: 0.34.1(@types/react@19.3.0) + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + + examples/solid: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + solid-js: + specifier: 1.9.17 + version: 1.9.17 + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + + examples/svelte: + dependencies: + '@bomb.sh/dom': + specifier: workspace:* + version: link:../../packages/dom + '@examples/dom-realm': + specifier: workspace:* + version: link:../dom-realm + svelte: + specifier: 5.57.2 + version: 5.57.2 + devDependencies: + '@bomb.sh/tools': + specifier: ^0.6.1 + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@types/node': + specifier: ^22 + version: 22.20.1 + '@typescript/native-preview': + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 + esbuild: + specifier: ^0.27.0 + version: 0.27.7 + vitest: + specifier: ^4.1.2 + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + experiments/ghostwright: dependencies: effection: @@ -74,7 +243,7 @@ importers: devDependencies: '@bomb.sh/tools': specifier: ^0.6.1 - version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@types/node': specifier: ^22 version: 22.20.1 @@ -83,7 +252,7 @@ importers: version: 7.0.0-dev.20260623.1 vitest: specifier: ^4.1.2 - version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) vendor/ui: dependencies: @@ -96,13 +265,16 @@ importers: devDependencies: '@bomb.sh/tools': specifier: ^0.5.4 - version: 0.5.6(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 0.5.6(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) vitest: specifier: ^4.1.9 - version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) packages: + '@b9g/crank@0.7.12': + resolution: {integrity: sha512-7sEE65JA8Hb39SsqdmpiCvgZ3z0iCPWDfF6ys8cN5WVos4857o3wSC3T3LsfTrLjWuIUe4kznrDRa4btWFztsg==} + '@bomb.sh/args@0.3.1': resolution: {integrity: sha512-CwxKrfgcorUPP6KfYD59aRdBYWBTsfsxT+GmoLVnKo5Tmyoqbpo0UNcjngRMyU+6tiPbd18RuIYxhgAn44wU/Q==} @@ -158,6 +330,162 @@ packages: '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -174,9 +502,31 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lit-labs/ssr-dom-shim@1.6.0': + resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==} + + '@lit/reactive-element@2.1.2': + resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -828,6 +1178,11 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@sveltejs/acorn-typescript@1.0.13': + resolution: {integrity: sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==} + peerDependencies: + acorn: ^8.9.0 + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -843,6 +1198,17 @@ packages: '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/react-reconciler@0.34.1': + resolution: {integrity: sha512-XAzeH3rARrfApMETpkcFmfJQRS3rPpXsSR/XA8H3sV8WgA0bp6UE7sLNM92l1ZbCclUKmrH28Ik2fSejh9UxRg==} + peerDependencies: + '@types/react': '*' + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': resolution: {integrity: sha512-8AX9NwC+G6Sbh5hNLnx8YgxoRV/8BH8FQRtZ86OTtUQfESMRvwszOGTtfcC32g86O5jsEQPDHXKVSnsIzWh6lg==} engines: {node: '>=16.20.0'} @@ -1032,6 +1398,11 @@ packages: '@yuku-toolchain/types@0.5.43': resolution: {integrity: sha512-kSpvPntnXw5+lYjO71ffBEnQ5ycQ74KGIYknh0TS4xeyCuBkOqxyJumxZkMhLBBUCLjDAbx2+Icnr3Zh4ftjpQ==} + acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} + engines: {node: '>=0.4.0'} + hasBin: true + alien-signals@2.0.8: resolution: {integrity: sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==} @@ -1039,10 +1410,18 @@ packages: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -1051,9 +1430,16 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} @@ -1061,6 +1447,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devalue@5.9.4: + resolution: {integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==} + dts-resolver@3.0.0: resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} engines: {node: ^22.18.0 || >=24.0.0} @@ -1085,6 +1474,22 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@2.4.0: + resolution: {integrity: sha512-CQPcneEHw/czj32uwQC52nYnax8H4SXt25FCM+LIwGZ49cMIxpOj3bP8wmzcI+Z0HIy5KkClaPhRLfCySzwHPA==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1137,6 +1542,9 @@ packages: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1216,6 +1624,18 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lit-element@4.2.2: + resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} + + lit-html@3.3.3: + resolution: {integrity: sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==} + + lit@3.3.3: + resolution: {integrity: sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1277,6 +1697,14 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + preact@11.0.1: + resolution: {integrity: sha512-TNNwEq++jYNHy747LziCjPRVgEwnLSOIOAw/oYcjI3Tdnubx2EXtt76I0an8PATvUWtx/DX1kFIg1eFN+O/fkw==} + peerDependencies: + preact-render-to-string: '>=6.7.0' + peerDependenciesMeta: + preact-render-to-string: + optional: true + publint@0.3.21: resolution: {integrity: sha512-OqejcnMV6E9zel2oCrUOJEiiFkGiAAni0A6ibfQNh1k9Gu5z4F+Yso8lllam7AzmV6Do0vp7u3UpZNRBwuXaHQ==} engines: {node: '>=18'} @@ -1285,6 +1713,16 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + react-reconciler@0.34.0: + resolution: {integrity: sha512-eVibvB1fc9j5Qf1KRkiM3GzRPBdjg2P6G6QV2f3P6Eb3OGMxRBhdg2rrUwjbV5JrQaJj8nOa8uajmmJA5tJWjQ==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.3.0 + + react@19.3.1: + resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -1321,11 +1759,24 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true + seroval-plugins@1.6.12: + resolution: {integrity: sha512-+reiwp/lYxiyctSrdvX7OQ1YLrcbWYgBiZrhSFL+V43b0/DGRLNVSDyZINbQe+tWplPOI2fw2dPrdv6hcc09kQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.6.12: + resolution: {integrity: sha512-CqbIVfk/7GL3ITwZI55lcPbpQbjHZ2DD5cUj4iH7E9YsCfHNJV1mx7yNp86Z84w5IpmkZZS/2s5vPOWmVWSdQg==} + engines: {node: '>=10'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1336,6 +1787,9 @@ packages: resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} engines: {node: '>= 18'} + solid-js@1.9.17: + resolution: {integrity: sha512-4GAmLOWPfNV8H9Jfn2LSFTpNEG6jiOKPiwd5lnLGQtehHaX6l3TOLzJqQ7mqwB/3k1Yy3zyumFVopmc/tm34ow==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1350,6 +1804,10 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} + svelte@5.57.2: + resolution: {integrity: sha512-1YiPCAXT0Ct6g1vOMmSLa0Nsc3QRMvtDF32ihgglGVrugIuIK38RyDiFNXVDhtQ81TJjSO57NdMw6DyYLqSGDQ==} + engines: {node: '>=18'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1545,14 +2003,19 @@ packages: yuku-parser@0.6.3: resolution: {integrity: sha512-iI6uABvvup9mvv8Mcpz7Tp//gehQlvcSnX4A4/0bf9i6X3RVQDuVUZel8jdpljwlF7WrbKsvD19y55Mc6+sKZw==} + zimmerframe@1.1.5: + resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==} + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: + '@b9g/crank@0.7.12': {} + '@bomb.sh/args@0.3.1': {} - '@bomb.sh/tools@0.5.6(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))': + '@bomb.sh/tools@0.5.6(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@bomb.sh/args': 0.3.1 '@humanfs/node': 0.16.8 @@ -1565,8 +2028,8 @@ snapshots: tinyexec: 1.2.4 tsdown: 0.22.7(@typescript/native-preview@7.0.0-dev.20260623.1)(oxc-resolver@11.21.3)(publint@0.3.21)(unrun@0.2.39) ultramatter: 0.0.4 - vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) - vitest-ansi-serializer: 0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))) + vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest-ansi-serializer: 0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))) transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -1595,7 +2058,7 @@ snapshots: - vite-plus - vue-tsc - '@bomb.sh/tools@0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))': + '@bomb.sh/tools@0.6.1(@types/node@22.20.1)(oxc-resolver@11.21.3)(unrun@0.2.39)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@bomb.sh/args': 0.3.1 '@humanfs/node': 0.16.8 @@ -1608,8 +2071,8 @@ snapshots: tinyexec: 1.2.4 tsdown: 0.22.7(@typescript/native-preview@7.0.0-dev.20260623.1)(oxc-resolver@11.21.3)(publint@0.3.21)(unrun@0.2.39) ultramatter: 0.0.4 - vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) - vitest-ansi-serializer: 0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))) + vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest-ansi-serializer: 0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))) transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -1702,6 +2165,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -1716,8 +2257,33 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@lit-labs/ssr-dom-shim@1.6.0': {} + + '@lit/reactive-element@2.1.2': + dependencies: + '@lit-labs/ssr-dom-shim': 1.6.0 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -2098,6 +2664,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@sveltejs/acorn-typescript@1.0.13(acorn@8.19.0)': + dependencies: + acorn: 8.19.0 + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -2116,6 +2686,16 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/react-reconciler@0.34.1(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': {} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': optional: true @@ -2156,13 +2736,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.9(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.9': dependencies: @@ -2256,22 +2836,34 @@ snapshots: '@yuku-toolchain/types@0.5.43': {} + acorn@8.19.0: {} + alien-signals@2.0.8: {} ansis@4.3.1: {} + aria-query@5.3.1: {} + assertion-error@2.0.1: {} + axobject-query@4.1.0: {} + cac@7.0.0: {} chai@6.2.2: {} + clsx@2.1.1: {} + convert-source-map@2.0.0: {} + csstype@3.2.3: {} + defu@6.1.7: {} detect-libc@2.1.2: {} + devalue@5.9.4: {} + dts-resolver@3.0.0(oxc-resolver@11.21.3): optionalDependencies: oxc-resolver: 11.21.3 @@ -2284,6 +2876,41 @@ snapshots: es-module-lexer@2.1.0: {} + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + esm-env@1.2.2: {} + + esrap@2.4.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -2327,6 +2954,10 @@ snapshots: import-without-cache@0.4.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.9 + jiti@2.7.0: {} knip@6.18.0: @@ -2394,6 +3025,24 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lit-element@4.2.2: + dependencies: + '@lit-labs/ssr-dom-shim': 1.6.0 + '@lit/reactive-element': 2.1.2 + lit-html: 3.3.3 + + lit-html@3.3.3: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.3: + dependencies: + '@lit/reactive-element': 2.1.2 + lit-element: 4.2.2 + lit-html: 3.3.3 + + locate-character@3.0.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2515,6 +3164,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@11.0.1: {} + publint@0.3.21: dependencies: '@publint/pack': 0.1.5 @@ -2524,6 +3175,13 @@ snapshots: quansync@1.0.0: {} + react-reconciler@0.34.0(react@19.3.1): + dependencies: + react: 19.3.1 + scheduler: 0.28.0 + + react@19.3.1: {} + resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.27.9(@typescript/native-preview@7.0.0-dev.20260623.1)(oxc-resolver@11.21.3)(rolldown@1.1.5): @@ -2587,14 +3245,28 @@ snapshots: dependencies: mri: 1.2.0 + scheduler@0.28.0: {} + semver@7.8.5: {} + seroval-plugins@1.6.12(seroval@1.6.12): + dependencies: + seroval: 1.6.12 + + seroval@1.6.12: {} + siginfo@2.0.0: {} sisteransi@1.0.5: {} smol-toml@1.7.0: {} + solid-js@1.9.17: + dependencies: + csstype: 3.2.3 + seroval: 1.6.12 + seroval-plugins: 1.6.12(seroval@1.6.12) + source-map-js@1.2.1: {} stackback@0.0.2: {} @@ -2603,6 +3275,26 @@ snapshots: strip-json-comments@5.0.3: {} + svelte@5.57.2: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.6.0 + '@sveltejs/acorn-typescript': 1.0.13(acorn@8.19.0) + '@types/estree': 1.0.9 + acorn: 8.19.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.9.4 + esm-env: 1.2.2 + esrap: 2.4.0 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.5 + transitivePeerDependencies: + - '@typescript-eslint/types' + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -2663,7 +3355,7 @@ snapshots: rolldown: 1.0.0-rc.17 optional: true - vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0): + vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.5 @@ -2672,18 +3364,19 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.20.1 + esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.0 - vitest-ansi-serializer@0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0))): + vitest-ansi-serializer@0.2.1(vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))): dependencies: - vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) - vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.9(@types/node@22.20.1)(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -2700,7 +3393,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.0(@types/node@22.20.1)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.20.1 @@ -2752,4 +3445,6 @@ snapshots: '@yuku-parser/binding-win32-arm64': 0.6.3 '@yuku-parser/binding-win32-x64': 0.6.3 + zimmerframe@1.1.5: {} + zod@4.4.3: {}