From afb5c8efe877e1a06f3e535684f637883542553f Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Thu, 6 Aug 2026 23:43:09 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20preact:=20add=20adapter=20and=20hel?= =?UTF-8?q?lo-world=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/hello-world-preact/package.json | 19 +++ examples/hello-world-preact/src/app.tsx | 45 ++++++ examples/hello-world-preact/src/index.tsx | 9 ++ examples/hello-world-preact/tsconfig.json | 12 ++ packages/ui-preact/package.json | 77 ++++++++++ packages/ui-preact/src/components.ts | 10 ++ packages/ui-preact/src/elements.test.ts | 47 ++++++ packages/ui-preact/src/elements.ts | 23 +++ packages/ui-preact/src/facade.ts | 161 +++++++++++++++++++++ packages/ui-preact/src/index.ts | 5 + packages/ui-preact/src/jsx-dev-runtime.ts | 23 +++ packages/ui-preact/src/jsx-runtime.ts | 23 +++ packages/ui-preact/src/root.test.ts | 165 ++++++++++++++++++++++ packages/ui-preact/src/root.ts | 31 ++++ packages/ui-preact/tsconfig.json | 6 + pnpm-lock.yaml | 51 +++++++ 16 files changed, 707 insertions(+) create mode 100644 examples/hello-world-preact/package.json create mode 100644 examples/hello-world-preact/src/app.tsx create mode 100644 examples/hello-world-preact/src/index.tsx create mode 100644 examples/hello-world-preact/tsconfig.json create mode 100644 packages/ui-preact/package.json create mode 100644 packages/ui-preact/src/components.ts create mode 100644 packages/ui-preact/src/elements.test.ts create mode 100644 packages/ui-preact/src/elements.ts create mode 100644 packages/ui-preact/src/facade.ts create mode 100644 packages/ui-preact/src/index.ts create mode 100644 packages/ui-preact/src/jsx-dev-runtime.ts create mode 100644 packages/ui-preact/src/jsx-runtime.ts create mode 100644 packages/ui-preact/src/root.test.ts create mode 100644 packages/ui-preact/src/root.ts create mode 100644 packages/ui-preact/tsconfig.json diff --git a/examples/hello-world-preact/package.json b/examples/hello-world-preact/package.json new file mode 100644 index 0000000..1e7504c --- /dev/null +++ b/examples/hello-world-preact/package.json @@ -0,0 +1,19 @@ +{ + "name": "@examples/hello-world-preact", + "private": true, + "type": "module", + "scripts": { + "start": "tsx src/index.tsx", + "dev": "tsx watch src/index.tsx" + }, + "dependencies": { + "@bomb.sh/tty": "^0.7.0", + "@clack/ui": "workspace:*", + "@clack/ui-preact": "workspace:*", + "preact": "11.0.0-beta.2" + }, + "devDependencies": { + "@types/node": "^22.20.0", + "tsx": "^4.19.0" + } +} diff --git a/examples/hello-world-preact/src/app.tsx b/examples/hello-world-preact/src/app.tsx new file mode 100644 index 0000000..9e01941 --- /dev/null +++ b/examples/hello-world-preact/src/app.tsx @@ -0,0 +1,45 @@ +// oxlint-disable bombshell-dev/exported-function-async -- Preact components render synchronously. +import { fixed, grow, percent } from '@bomb.sh/tty'; +import { rgba } from '@clack/ui-preact'; +import type { VNode } from 'preact'; +import { useState } from 'preact/hooks'; + +/** Render the interactive hello-world example. */ +export function HelloWorld(): VNode { + const [say, setSay] = useState('Hello'); + const [to, setTo] = useState('World'); + + return ( + + + {say}, {to}! + + + + + say: + + + to: + + + + setSay(event.value)} /> + setTo(event.value)} /> + + + + ); +} + +const blue = rgba(0, 0, 238); +const cyan = rgba(0, 205, 205); +const gray = rgba(127, 127, 127); diff --git a/examples/hello-world-preact/src/index.tsx b/examples/hello-world-preact/src/index.tsx new file mode 100644 index 0000000..ad7a94b --- /dev/null +++ b/examples/hello-world-preact/src/index.tsx @@ -0,0 +1,9 @@ +import { stdin, stdout } from 'node:process'; +import { createUI } from '@clack/ui'; +import { createRoot } from '@clack/ui-preact'; +import { HelloWorld } from './app.tsx'; + +await using ui = await createUI({ input: stdin, output: stdout }); +const root = createRoot(ui.host.element); +root.render(); +await ui.main(); diff --git a/examples/hello-world-preact/tsconfig.json b/examples/hello-world-preact/tsconfig.json new file mode 100644 index 0000000..989f487 --- /dev/null +++ b/examples/hello-world-preact/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "composite": false, + "declaration": false, + "declarationMap": false, + "jsx": "react-jsx", + "jsxImportSource": "@clack/ui-preact", + "noEmit": true, + "types": ["node"] + } +} diff --git a/packages/ui-preact/package.json b/packages/ui-preact/package.json new file mode 100644 index 0000000..085ca8d --- /dev/null +++ b/packages/ui-preact/package.json @@ -0,0 +1,77 @@ +{ + "name": "@clack/ui-preact", + "version": "0.0.0", + "keywords": [ + "bombshell", + "cli", + "preact" + ], + "homepage": "https://bomb.sh/docs/ui", + "license": "MIT", + "author": { + "name": "Bombshell", + "email": "oss@bomb.sh", + "url": "https://bomb.sh" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bombshell-dev/ui.git" + }, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + }, + "./jsx-runtime": { + "types": "./dist/jsx-runtime.d.mts", + "import": "./dist/jsx-runtime.mjs" + }, + "./jsx-dev-runtime": { + "types": "./dist/jsx-dev-runtime.d.mts", + "import": "./dist/jsx-dev-runtime.mjs" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "bsh dev", + "build": "bsh build --dts", + "format": "bsh format", + "lint": "bsh lint", + "test": "bsh test" + }, + "dependencies": { + "@clack/ui": "workspace:*" + }, + "devDependencies": { + "@bomb.sh/tools": "^0.5.4", + "@types/node": "^22.20.0", + "preact": "11.0.0-beta.2", + "vitest": "^4.1.9" + }, + "peerDependencies": { + "preact": ">=11.0.0-beta.2 <12" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "^10.7.0", + "onFail": "error" + }, + "runtime": { + "name": "node", + "version": "^22.14.0", + "onFail": "error" + } + }, + "knip": { + "entry": [ + "src/index.ts", + "src/jsx-runtime.ts", + "src/jsx-dev-runtime.ts" + ] + } +} diff --git a/packages/ui-preact/src/components.ts b/packages/ui-preact/src/components.ts new file mode 100644 index 0000000..6ac8a3e --- /dev/null +++ b/packages/ui-preact/src/components.ts @@ -0,0 +1,10 @@ +import { type FunctionComponent, createElement } from 'preact'; +import type { BoxProps, TextProps } from './elements.ts'; + +export type { BoxProps, TextProps } from './elements.ts'; + +/** A layout container backed by the Host's `box` element. */ +export const Box: FunctionComponent = (props) => createElement('box', props); + +/** A styled text scope backed by the Host's `text` element. */ +export const Text: FunctionComponent = (props) => createElement('text', props); diff --git a/packages/ui-preact/src/elements.test.ts b/packages/ui-preact/src/elements.test.ts new file mode 100644 index 0000000..e8d1d2b --- /dev/null +++ b/packages/ui-preact/src/elements.test.ts @@ -0,0 +1,47 @@ +import type { HostElements as CoreHostElements, InputEvent } from '@clack/ui'; +import type { ComponentChildren } from 'preact'; +import { describe, expectTypeOf, test } from 'vitest'; +import type { JSX } from './jsx-runtime.ts'; + +interface PreactProbeEvent { + readonly value: number; +} + +interface PreactProbeProps { + count: number; + onprobe?: (event: PreactProbeEvent) => void; +} + +declare module '@clack/ui/elements' { + interface HostElements { + '@test/preact-probe': PreactProbeProps; + } +} + +describe('Preact host elements', () => { + test('maps box and text properties from the core registry', () => { + expectTypeOf().toEqualTypeOf< + CoreHostElements['box']['layout'] + >(); + expectTypeOf().toEqualTypeOf< + CoreHostElements['text']['color'] + >(); + expectTypeOf().toEqualTypeOf(); + }); + + test('maps lowercase host event properties to Preact casing', () => { + expectTypeOf>() + .parameter(0) + .toEqualTypeOf(); + expectTypeOf< + 'oninput' extends keyof JSX.IntrinsicElements['input'] ? true : false + >().toEqualTypeOf(); + }); + + test('derives custom elements from core registry augmentation', () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf>() + .parameter(0) + .toEqualTypeOf(); + }); +}); diff --git a/packages/ui-preact/src/elements.ts b/packages/ui-preact/src/elements.ts new file mode 100644 index 0000000..46ab385 --- /dev/null +++ b/packages/ui-preact/src/elements.ts @@ -0,0 +1,23 @@ +import type { HostElements as CoreHostElements } from '@clack/ui'; +import type { ComponentChildren, Key, Ref } from 'preact'; +import type { ElementHandle } from './facade.ts'; + +type PreactProperties = { + [Name in keyof Properties as Name extends string + ? Name extends `on${infer Type}` + ? `on${Capitalize}` + : Name + : Name]: Properties[Name]; +} & { + children?: ComponentChildren; + key?: Key | null; + ref?: Ref; +}; + +export type HostElements = { + [Name in keyof CoreHostElements]: PreactProperties; +}; + +export type BoxProps = HostElements['box']; +export type InputProps = HostElements['input']; +export type TextProps = HostElements['text']; diff --git a/packages/ui-preact/src/facade.ts b/packages/ui-preact/src/facade.ts new file mode 100644 index 0000000..a6da7a7 --- /dev/null +++ b/packages/ui-preact/src/facade.ts @@ -0,0 +1,161 @@ +// oxlint-disable bombshell-dev/exported-function-async -- This is an internal synchronous adapter factory. +import type { Host } from '@clack/ui'; +import type { HostElement, HostElementChild, HostLiteral } from '@clack/ui/elements'; +import type { HostEventListener, HostEventType } from '@clack/ui/events'; + +type PreactNode = PreactElementNode | PreactTextNode; + +interface HostAttribute { + readonly name: string; + readonly value: unknown; +} + +export interface ElementHandle { + /** The framework-neutral element represented by this Preact host instance. */ + readonly element: HostElement; +} + +/** Create the DOM-shaped container Preact uses to mutate a Host. */ +export function createContainer(host: Host, element: HostElement): ElementHandle { + const document = new HostDocument(host); + return document.wrap(element) as PreactElementNode; +} + +const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; +const hostChild = Symbol('host child'); + +class HostDocument { + readonly #instances = new WeakMap(); + readonly host: Host; + + constructor(host: Host) { + this.host = host; + } + + createElementNS(_namespace: string | null, name: string): PreactElementNode { + return this.wrap(this.host.createElement(name)) as PreactElementNode; + } + + createTextNode(content: string): PreactTextNode { + return this.wrap(this.host.createLiteral(String(content))) as PreactTextNode; + } + + wrap(child: HostElementChild): PreactNode { + const existing = this.#instances.get(child); + if (existing) return existing; + + const instance = + child.type === 'element' + ? new PreactElementNode(this, child) + : new PreactTextNode(this, child); + this.#instances.set(child, instance); + return instance; + } +} + +abstract class PreactNodeBase { + abstract readonly nodeType: number; + readonly ownerDocument: HostDocument; + readonly [hostChild]: Child; + + constructor(ownerDocument: HostDocument, child: Child) { + this.ownerDocument = ownerDocument; + this[hostChild] = child; + } + + get parentNode(): PreactElementNode | null { + const parent = this[hostChild].parent; + return parent ? (this.ownerDocument.wrap(parent) as PreactElementNode) : null; + } + + get nextSibling(): PreactNode | null { + const parent = this[hostChild].parent; + if (!parent) return null; + + const index = parent.children.indexOf(this[hostChild]); + const sibling = parent.children[index + 1]; + return sibling ? this.ownerDocument.wrap(sibling) : null; + } + + remove(): void { + const parent = this[hostChild].parent; + if (parent) this.ownerDocument.host.removeChild(parent, this[hostChild]); + } +} + +class PreactElementNode extends PreactNodeBase implements ElementHandle { + readonly #listeners = new WeakMap<(event: unknown) => void, HostEventListener>(); + readonly nodeType = 1; + readonly namespaceURI = XHTML_NAMESPACE; + _children?: unknown; + + get element(): HostElement { + return this[hostChild]; + } + + get localName(): string { + return this.element.name; + } + + get attributes(): readonly HostAttribute[] { + return Object.entries(this.element.properties).map(([name, value]) => ({ name, value })); + } + + get childNodes(): PreactNode[] { + return this.element.children.map((child) => this.ownerDocument.wrap(child)); + } + + get firstChild(): PreactNode | null { + const child = this.element.children[0]; + return child ? this.ownerDocument.wrap(child) : null; + } + + setAttribute(name: string, value: unknown): void { + this.ownerDocument.host.setProperty(this.element, name, value); + } + + removeAttribute(name: string): void { + this.ownerDocument.host.setProperty(this.element, name, undefined); + } + + insertBefore(child: PreactNode, anchor: PreactNode | null): PreactNode { + this.ownerDocument.host.insertBefore(this.element, child[hostChild], anchor?.[hostChild]); + return child; + } + + appendChild(child: PreactNode): PreactNode { + return this.insertBefore(child, null); + } + + removeChild(child: PreactNode): PreactNode { + this.ownerDocument.host.removeChild(this.element, child[hostChild]); + return child; + } + + addEventListener(type: string, listener: (event: unknown) => void): void { + let wrapped = this.#listeners.get(listener); + if (!wrapped) { + wrapped = (event) => listener.call(this, event); + this.#listeners.set(listener, wrapped); + } + this.ownerDocument.host.addEventListener(this.element, type as HostEventType, wrapped); + } + + removeEventListener(type: string, listener: (event: unknown) => void): void { + const wrapped = this.#listeners.get(listener); + if (!wrapped) return; + this.ownerDocument.host.removeEventListener(this.element, type as HostEventType, wrapped); + } +} + +class PreactTextNode extends PreactNodeBase { + readonly nodeType = 3; + + get data(): string { + return this[hostChild].content; + } + + set data(content: string) { + this.ownerDocument.host.setText(this[hostChild], String(content)); + } +} diff --git a/packages/ui-preact/src/index.ts b/packages/ui-preact/src/index.ts new file mode 100644 index 0000000..ed85099 --- /dev/null +++ b/packages/ui-preact/src/index.ts @@ -0,0 +1,5 @@ +export { rgba } from '@clack/ui'; +export { Box, Text } from './components.ts'; +export type { BoxProps, HostElements, InputProps, TextProps } from './elements.ts'; +export type { ElementHandle } from './facade.ts'; +export { createRoot, type Root } from './root.ts'; diff --git a/packages/ui-preact/src/jsx-dev-runtime.ts b/packages/ui-preact/src/jsx-dev-runtime.ts new file mode 100644 index 0000000..edb1528 --- /dev/null +++ b/packages/ui-preact/src/jsx-dev-runtime.ts @@ -0,0 +1,23 @@ +import type { Component, ComponentType, FunctionComponent, VNode } from 'preact'; +import type { JSX as PreactJSX } from 'preact/jsx-dev-runtime'; +import type { HostElements } from './index.ts'; + +export { Fragment, jsxDEV } from 'preact/jsx-dev-runtime'; + +export declare namespace JSX { + type LibraryManagedAttributes = PreactJSX.LibraryManagedAttributes< + ManagedComponent, + Props + >; + interface IntrinsicAttributes extends PreactJSX.IntrinsicAttributes {} + type ElementType = keyof HostElements | ComponentType; + interface Element extends VNode {} + type ElementClass = Component | FunctionComponent; + interface ElementAttributesProperty { + props: unknown; + } + interface ElementChildrenAttribute { + children: unknown; + } + interface IntrinsicElements extends HostElements {} +} diff --git a/packages/ui-preact/src/jsx-runtime.ts b/packages/ui-preact/src/jsx-runtime.ts new file mode 100644 index 0000000..0d13c08 --- /dev/null +++ b/packages/ui-preact/src/jsx-runtime.ts @@ -0,0 +1,23 @@ +import type { Component, ComponentType, FunctionComponent, VNode } from 'preact'; +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 LibraryManagedAttributes = PreactJSX.LibraryManagedAttributes< + ManagedComponent, + Props + >; + interface IntrinsicAttributes extends PreactJSX.IntrinsicAttributes {} + type ElementType = keyof HostElements | ComponentType; + interface Element extends VNode {} + type ElementClass = Component | FunctionComponent; + interface ElementAttributesProperty { + props: unknown; + } + interface ElementChildrenAttribute { + children: unknown; + } + type IntrinsicElements = HostElements; +} diff --git a/packages/ui-preact/src/root.test.ts b/packages/ui-preact/src/root.test.ts new file mode 100644 index 0000000..7370f5d --- /dev/null +++ b/packages/ui-preact/src/root.test.ts @@ -0,0 +1,165 @@ +import { HostApi, createHost, type HostElement, type HostLiteral } from '@clack/ui'; +import type { InputEvent } from '@clack/ui'; +import type { HostEventListener } from '@clack/ui/events'; +import { createElement as h, type VNode } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; +import { assert, describe, expect, test, vi } from 'vitest'; +import { Box, Text, createRoot } from './index.ts'; + +function element(child: HostElement['children'][number]): HostElement { + assert(child.type === 'element'); + return child; +} + +function literal(child: HostElement['children'][number]): HostLiteral { + assert(child.type === 'literal'); + return child; +} + +function item(key: string): VNode<{ key: string; name: string }> { + return h('item', { key, name: key }); +} + +function Counter(): VNode { + const [count, setCount] = useState(0); + useEffect(() => setCount(1), []); + return h(Text, {}, String(count)); +} + +describe('createRoot', () => { + test('builds and attaches a Preact subtree through the Host', () => { + const host = createHost(); + const root = createRoot(host.element); + + root.render(h(Box, { layout: { direction: 'ttb' } }, h(Text, { color: 15 }, 'hello'))); + + const box = element(host.element.children[0]!); + const text = element(box.children[0]!); + expect(box.name).toBe('box'); + expect(box.properties).toEqual({ layout: { direction: 'ttb' } }); + expect(box.node).not.toBeNull(); + expect(text.name).toBe('text'); + expect(text.properties).toEqual({ color: 15 }); + expect(text.node).not.toBeNull(); + expect(literal(text.children[0]!)).toMatchObject({ content: 'hello', parent: text }); + }); + + test('mounts within the supplied attached element', () => { + const host = createHost(); + const container = host.createElement('container'); + host.insertBefore(host.element, container); + const root = createRoot(container); + + root.render(h('probe', null)); + + expect(root.element).toBe(container); + expect(host.element.children).toEqual([container]); + expect(element(container.children[0]!)).toMatchObject({ name: 'probe' }); + + root.unmount(); + + expect(container.children).toEqual([]); + expect(container.node).not.toBeNull(); + }); + + test('updates and removes properties and updates text literals', () => { + const host = createHost(); + const root = createRoot(host.element); + + root.render(h('probe', { removable: 'yes', structured: { value: 1 } }, 'first')); + const probe = element(host.element.children[0]!); + const text = literal(probe.children[0]!); + + root.render(h('probe', { added: 0, structured: { value: 2 } }, 'second')); + + expect(element(host.element.children[0]!)).toBe(probe); + expect(probe.properties).toEqual({ added: 0, structured: { value: 2 } }); + expect(literal(probe.children[0]!)).toBe(text); + expect(text.content).toBe('second'); + }); + + test('moves keyed children within the same parent without recreating them', () => { + const host = createHost(); + const root = createRoot(host.element); + + root.render(h('list', {}, item('first'), item('second'), item('third'))); + const list = element(host.element.children[0]!); + const [first, second, third] = list.children; + + root.render(h('list', {}, item('third'), item('first'), item('second'))); + + expect(list.children).toEqual([third, first, second]); + }); + + test('routes element creation through Host middleware', () => { + const host = createHost(); + const createElement = vi.fn(); + HostApi.decorate(host.root, () => ({ + createElement(name) { + createElement(name); + return super.createElement(name); + }, + })); + const root = createRoot(host.element); + + root.render(h('custom-element', null)); + + expect(createElement).toHaveBeenCalledWith('custom-element'); + }); + + test('delivers Host events through Preact event properties', () => { + const host = createHost(); + let listener: HostEventListener<'input'> | undefined; + HostApi.decorate(host.root, () => ({ + addEventListener(target, type, current) { + if (type === 'input') listener = current as HostEventListener<'input'>; + return super.addEventListener(target, type, current); + }, + removeEventListener(target, type, current) { + if (current === listener) listener = undefined; + return super.removeEventListener(target, type, current); + }, + })); + const root = createRoot(host.element); + const first = vi.fn(); + const second = vi.fn(); + + root.render(h('input', { onInput: first })); + const input = element(host.element.children[0]!); + const event: InputEvent = { type: 'input', target: input, value: 'hello' }; + listener!(event); + + expect(first).toHaveBeenCalledWith(event); + expect(input.properties).toEqual({}); + + root.render(h('input', { onInput: second })); + listener!(event); + + expect(first).toHaveBeenCalledTimes(1); + expect(second).toHaveBeenCalledWith(event); + }); + + test('applies updates scheduled by Preact hooks', async () => { + const host = createHost(); + const root = createRoot(host.element); + + root.render(h(Counter, {})); + + const text = element(host.element.children[0]!); + await vi.waitFor(() => expect(literal(text.children[0]!).content).toBe('1')); + }); + + test('unmounts and detaches the Preact subtree', () => { + const host = createHost(); + const root = createRoot(host.element); + root.render(h(Box, {}, h(Text, {}, 'bye'))); + const box = element(host.element.children[0]!); + const text = element(box.children[0]!); + + root.unmount(); + + expect(host.element.children).toEqual([]); + expect(box.node).toBeNull(); + expect(text.node).toBeNull(); + }); +}); diff --git a/packages/ui-preact/src/root.ts b/packages/ui-preact/src/root.ts new file mode 100644 index 0000000..549c8b7 --- /dev/null +++ b/packages/ui-preact/src/root.ts @@ -0,0 +1,31 @@ +// oxlint-disable bombshell-dev/exported-function-async -- Preact root creation is synchronous. +import { HostApi, type Host } from '@clack/ui'; +import type { HostElement } from '@clack/ui/elements'; +import { type ComponentChild, render } from 'preact'; +import { createContainer } from './facade.ts'; + +export interface Root { + readonly element: HostElement; + readonly host: Host; + /** Reconcile `node` into the Host synchronously. */ + render(node: ComponentChild): void; + /** Remove the Preact tree from the Host synchronously. */ + unmount(): void; +} + +/** Create a Preact root which reconciles into an attached Host element. */ +export function createRoot(element: HostElement): Root { + const host = HostApi.at(element.node!).getHost(); + const container = createContainer(host, element) as unknown as Element; + + return { + element, + host, + render(node) { + render(node, container); + }, + unmount() { + render(null, container); + }, + }; +} diff --git a/packages/ui-preact/tsconfig.json b/packages/ui-preact/tsconfig.json new file mode 100644 index 0000000..a055ad0 --- /dev/null +++ b/packages/ui-preact/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@bomb.sh/tools/tsconfig.json", + "compilerOptions": { + "types": ["node"] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e704e7..2995d24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,28 @@ importers: specifier: ^4.19.0 version: 4.23.15 + examples/hello-world-preact: + dependencies: + '@bomb.sh/tty': + specifier: ^0.7.0 + version: 0.7.0 + '@clack/ui': + specifier: workspace:* + version: link:../../packages/ui + '@clack/ui-preact': + specifier: workspace:* + version: link:../../packages/ui-preact + preact: + specifier: 11.0.0-beta.2 + version: 11.0.0-beta.2 + devDependencies: + '@types/node': + specifier: ^22.20.0 + version: 22.20.4 + tsx: + specifier: ^4.19.0 + version: 4.23.15 + examples/hello-world-solid: dependencies: '@bomb.sh/tty': @@ -96,6 +118,25 @@ importers: specifier: 'catalog:' version: 0.7.0 + packages/ui-preact: + dependencies: + '@clack/ui': + specifier: workspace:* + version: link:../ui + devDependencies: + '@bomb.sh/tools': + specifier: ^0.5.4 + version: 0.5.6(@types/node@22.20.4)(oxc-resolver@11.24.2)(tsx@4.23.15)(vite@8.3.0(@types/node@22.20.4)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@types/node': + specifier: ^22.20.0 + version: 22.20.4 + preact: + specifier: 11.0.0-beta.2 + version: 11.0.0-beta.2 + vitest: + specifier: ^4.1.9 + version: 4.1.11(@types/node@22.20.4)(vite@8.3.0(@types/node@22.20.4)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + packages/ui-solid: dependencies: '@clack/ui': @@ -1645,6 +1686,14 @@ packages: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} + preact@11.0.0-beta.2: + resolution: {integrity: sha512-7nIgtr4wtls48+fuazknYNuZ3tOuJoo9ZrT+dzXrMBWkcQH5xmOJiibJpmbU8sFmo2IKLFe3eMp84n0Lq0dCHg==} + peerDependencies: + preact-render-to-string: '>=5' + peerDependenciesMeta: + preact-render-to-string: + optional: true + publint@0.3.24: resolution: {integrity: sha512-9zS56KrKBoqi5Qt8h92uMP8TTM9AYZSgnmCo4u2priMqkOZvQnTsziZ2p5LJ2ywbYkAjoCDp2jda9u4cgFefIw==} engines: {node: '>=18'} @@ -3274,6 +3323,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@11.0.0-beta.2: {} + publint@0.3.24: dependencies: '@publint/pack': 0.1.7