This version adds a link to the GitHub repo.
The old WillowElement component has been removed. Instead, we have a new shadow DOM based component: WillowElement (v2)!
is a custom element constructor and provides shadow root functionality. To use
it, you must create a subclass and provide a render
method that returns a DOM
node. To get the value of existing attributes, use the .attribute()
method.
export abstract class WillowElement extends HTMLElement {
static observedAttributes?: readonly string[];
connectedCallback?(): void;
disconnectedCallback?(): void;
adoptedCallback?(): void;
styles?: string | Accessor<string>;
shadowRoot: ShadowRoot;
constructor();
abstract render(): JSX.Element;
attribute(name: string): Accessor<string | undefined>;
attribute<T extends number>(name: string, defaultValue: T): Accessor<T>;
attribute<T extends string>(name: string, defaultValue: T): Accessor<T>;
attribute<T extends boolean>(name: string, defaultValue: T): Accessor<T>;
}
is a list of attributes that should be watched by the DOM. See .attribute()
for more information.
constructs a new component. This will create a shadow root, append a <style>
element with the content of the .styles
property, call the .render()
method,
and append its return value (if any) to the shadow root.
is called when the element's parent node is changed.
creates an Accessor
that reflects the value of a given attribute. If the
attribute's name is in observedAttributes
, the accessor will update when the
corresponding attribute is changed. It accepts a default value and will force
the accessor's type to match the default value's. If no default value is passed,
the accessor will return a string or undefined
.
is called when the element is appended to the DOM.
is called when the element is removed from the DOM.
contains the shadow root with this element's content.
has a string or Accessor<string>
to the scoped styles for this custom element.
0.3.6 brings our biggest update yet. I mean, we could add nothing and it'd be our biggest update ever: it's our first release. Hooray!
This release is 0.3.6 instead of 0.1.0 because I needed to fix a few things immediately after releasing 0.1.0, but you can download those from NPM.
renders a component based on a Promise's state: pending, fulfilled, or rejected.
If it is passed an accessor to a Promise, the component will update when the
accessor's value changes. Because of the possible ambiguity of a default state,
this component uses named pending
, then
, and catch
props instead of
accepting children.
declare function Await<T>(props: {
value: ValueOrAccessor<T>;
pending?: JSX.Element;
then?: (value: Awaited<T>) => JSX.Element;
catch?: (error: unknown) => JSX.Element;
}): JSX.Element;
creates an element and replaces it whenever the accessor passed to it changes.
declare function Dynamic(props: {
children: Accessor<JSX.Element>;
}): JSX.Element;
takes an iterable of values (e.g. an array, Set, or Map), maps them through a
function, and outputs the results into a WillowFragment
. In order to respond
to changes, it may be passed a store. When changes occur, it attempts to reuse
old nodes and move them around instead of creating new nodes. It uses the value
of an object to cache new DOM nodes instead of a manual key function. This
requires that all of the iterable's items have a different value. If they
don't, unexpected errors will occur.
declare function For<T>(props: {
children: (value: T, index: Accessor<number>) => JSX.Element;
each: Iterable<T>;
}): JSX.Element;
accepts an iterable (e.g. an array, Set, or Map) of DOM nodes or an accessor to them and renders them into the DOM. It updates the nodes whenever the underlying accessor or iterable changes. To receive updates on a plain iterable, it must be wrapped in a store.
declare function List(props: {
children: ValueOrAccessor<Iterable<JSX.Element>>;
}): JSX.Element;
conditionally renders a node and an optional fallback if the condition is false. It changes the output node whenever the accessor passed to it changes.
declare function Maybe(props: {
when: ValueOrAccessor<boolean>;
fallback?: JSX.Element;
children: JSX.Element;
}): JSX.Element;
cleans up listeners associated with a DOM node.
declare function cleanupNode(node: Node): void;
creates a new computed signal. It is like a memo, but the callback is passed its
previous value and takes an initial value. If not passed an initial value, the
updater may be passed undefined
.
declare function createComputed<T>(
update: Updater<T | undefined, T>
): Accessor<T>;
declare function createComputed<T>(value: T, update: Updater<T>): Accessor<T>;
creates a new effect. The callback provided is called once to set up tracking on
any signals accessed. Whenever linked signals change, the effect is rerun. A
name
may be passed for debugging purposes.
declare function createEffect(
effect: Effect,
options?: { name?: string }
): EffectScope;
creates a memo. A memo caches the value returned by its accessor and updates it whenever signals accessed in the callback are changed. It output an accessor to the underlying value and notifies linked effects when its value changes.
declare function createMemo<T>(
update: Accessor<T>,
options?: { name?: string }
): Accessor<T>;
creates a new manual store. It outputs a tuple of two items: a proxy to a store
and an updater function. The proxy tracks when its properties are accessed in
effects and links those effects. Then, calling the updater will rerun all of
these linked effects. A name
may be passed for debugging purposes.
function createManualStore<T extends object>(
object: T,
options?: { name?: string }
): ManualStore<T>;
creates a new signal with an initial value. It returns a tuple of two elements:
an accessor and a setter. The accessor may be called from within an effect to
get the current value of the signal. The setter may be called with a value to
set the signal's value and notify linked effects about the change. The setter
can also be called with an updater function that takes the old signal value and
returns the new one. A name
may be passed for debugging purposes.
declare function createSignal<T = any>(): Signal<T | undefined>;
declare function createSignal<T>(
value: T,
options?: { name?: string }
): Signal<T>;
creates a reactive store. When a property is accessed, it returns the object's
property and links the current effect. When that property is set, it reruns any
linked effects. In essence, it works like a signal, but for objects. When passed
an array, Set, or Map, calling any methods which mutate the object (e.g. push,
set, clear) will also trigger linked effects. A name
may be passed for
debugging purposes.
declare function createStore<T extends object>(
object: T,
options?: { name?: string }
): T;
gets the current effect scope and returns it. It may return undefined
if no
scope exists at the moment.
declare function getScope(): EffectScope | undefined;
is used to create new DOM nodes. It takes one of two signatures. The first
accepts the name of an HTML or SVG tag, a list of properties, and an array of
children. It has the exact same signature as React.createElement
, so it
should be familiar to those coming from that framework. The second accepts a
component constructor, a list of properties, and an array to pass to the
component's children
prop. The actual typings are more complex and cover
stricter types. In most cases, using JSX with the automatic JSX runtime or the
h
function works.
declare function h(component: () => JSX.Element): JSX.Element;
declare function h<P extends JSX.Props>(
component: JSX.FcOrCc<P>,
props: P
): JSX.Element;
declare function h<P extends JSX.Props>(
component: JSX.FcOrCc<P>,
props: Omit<P, "children">,
...children: JSX.ChildrenAsArray<P>
): JSX.Element;
declare function h<
K extends keyof JSX.IntrinsicElements & keyof HTMLElementTagNameMap
>(
tag: K,
props?: JSX.IntrinsicElements[K] | null,
...children: JSX.Child[]
): HTMLElementTagNameMap[K];
declare function h<
K extends keyof JSX.IntrinsicElements & keyof SVGElementTagNameMap
>(
tag: K,
props?: JSX.IntrinsicElements[K] | null,
...children: JSX.Child[]
): SVGElementTagNameMap[K];
declare function h<K extends keyof JSX.IntrinsicElements>(
tag: K,
props?: JSX.IntrinsicElements[K] | null,
...children: JSX.Child[]
): JSX.Element;
creates fragments. It can be used in TypeScript's jsxFragmentFactory
config
option to provide proper typing and support for <> ... </>
syntax.
Alternatively, you may pass it a children
prop to construct it without the
overhead of h
.
declare namespace h {
function f(props: {
children: JSX.Child;
}): WillowFragment;
}
checks whether a value is an accessor for a signal or memo. Under the hood, it just checks if the value is a function.
declare function isAccessor(value: unknown): value is Accessor<any>;
checks whether a value is an setter for a signal. Under the hood, it just checks if the value is a function.
declare function isSetter(value: unknown): value is Setter<any>;
checks whether a value is a signal. Under the hood, it just checks if the value is an array of two elements: an accessor and a setter.
declare function isSignal(value: unknown): value is Signal<any>;
takes an iterable of values (e.g. an array, Set, or Map), maps them through a
function, and outputs the results into a store of an array. In order to respond
to changes, it may be passed a store. When changes occur, it attempts to reuse
old outputs and move them around instead of creating new outputs. It uses the
value of an object to cache results instead of a manual key function. This
requires that all of the iterable's items have a different value. If they
don't, unexpected errors will occur. A name
may be passed for debugging
purposes.
declare function map<T, U>(
list: Iterable<T>,
fn: (value: T, index: Accessor<number>) => U,
options?: { name?: string }
): U[];
accepts an object whose properties may be accessors and signals. It then returns a new object with getters and setters for each property. Accessors and standard properties will be read only and not have setters. Signals will be converted to a getter and setter pair.
declare function toStore<T extends object>(object: T): UnwrapNestedAccessors<T>;
takes a value or accessor. If passed an accessor, it will call it and return the underlying value. If passed a value, it returns the value. It can be useful for unwrapping values that may be values or accessors.
declare function unref<T>(accessor: ValueOrAccessor<T>): T;
computes a function without tracking any signals accessed within. It may be
directly passed the accessor for a signal or memo to get the value without
tracking the signal or memo in the current effect. It returns the value that the
callback returns. A name
may be passed for debugging purposes.
declare function untrack<T>(
accessor: Accessor<T>,
options?: {
name?: string;
}
): T;
is used to create effects and track signals accessed within them.
declare class EffectScope {
readonly name?: string | undefined;
constructor(effect: Effect, name?: string | undefined);
track(set: Set<EffectScope>): void;
cleanup(): void;
run(): void;
}
creates a new effect scope. It must be passed an effect to run and an optional
name
used for debugging purposes.
declare class EffectScope {
constructor(effect: Effect, name?: string | undefined);
}
stops tracking any signals linked with this effect and empties the internal tracking list. If the effect is rerun after this, the signals will be tracked again.
declare class EffectScope {
cleanup(): void;
}
identifies the name of this effect scope. It is used for debugging purposes.
declare class EffectScope {
readonly name?: string | undefined;
}
sets the current effect scope to this
, runs the effect passed to the
constructor, and resets the current effect scope to whatever it was previously,
be that a parent scope or undefined
. Any signals accessed in the effect will
be added to an internal tracking list.
declare class EffectScope {
run(): void;
}
adds the given set of effects to an internal tracking list. When .cleanup
is
called, the effect scope will remove itself from this set. This is usually
called by the createSignal
and createStore
functions, but user-defined
signals may also use this method.
declare class EffectScope {
readonly name?: string | undefined;
constructor(effect: Effect, name?: string | undefined);
track(set: Set<EffectScope>): void;
cleanup(): void;
run(): void;
}
can be used for more advanced components that emit events. To use it, you must
create a subclass of it and provide a render
function that accepts a list of
properties and returns a DOM node.
declare abstract class WillowElement<T extends JSX.Props = JSX.Props> {
static of<T extends JSX.Props>(
render: (self: WillowElement<T>, props: T) => JSX.Element
): typeof WillowElement<T>;
node: ChildNode;
[propsSymbol]: T;
constructor(props: T);
cleanup(): void;
emit<K extends keyof T & `on:${string}`>(
type: K extends `on:${infer T}` ? T : never,
...data: Parameters<T[K]>
): void;
abstract render(props: T): JSX.Element;
}
constructs a new component with a set of props. This will call .render
once
and set the node
property to its result.
declare abstract class WillowElement<T extends JSX.Props = JSX.Props> {
constructor(props: T);
}
creates a new subclass of WillowElement
. It accepts a render function that is
passed two arguments: the instance of WillowElement
and the component's props.
You may call .emit
on the element instance to emit events and type them
properly.
class WillowElement<T extends JSX.Props = JSX.Props> {
static of<T extends JSX.Props>(
render: (self: WillowElement<T>, props: T) => JSX.Element
): typeof WillowElement<T>;
}
cleans up any listeners and effects associated with the component.
class WillowElement<T extends JSX.Props = JSX.Props> {
cleanup(): void;
}
emits an event to the listener passed to the component's props. It can optionally send one or more values as data to the object.
class WillowElement<T extends JSX.Props = JSX.Props> {
emit<K extends keyof T & `on:${string}`>(
type: K extends `on:${infer T}` ? T : never,
...data: Parameters<T[K]>
): void;
}
is the outputted DOM node of the component. It is returned by the render
function and must not be accessed until then.
class WillowElement<T extends JSX.Props = JSX.Props> {
node: ChildNode;
}
is run once when the component is initialized. It is passed the props of the component and must return a DOM node.
class WillowElement<T extends JSX.Props = JSX.Props> {
abstract render(props: T): JSX.Element;
}
is used to type the props of the element but does not have any actual value.
Accessing this field will result in undefined
as it is purely a compile-time
construct. Additionally, the symbol used is not exported and cannot be
recreated.
class WillowElement<T extends JSX.Props = JSX.Props> {
[propsSymbol]: T;
}
is used as a simple interface for rendering multiple elements into the DOM while being able to pass them around cleanly. Most of its method simply alter DOM behavior and aren't new, but there is one new method.
declare class WillowFragment extends Comment {
constructor(name?: string);
after(...nodes: (string | Node)[]): void;
appendChild<T extends Node>(node: T): T;
before(...nodes: (string | Node)[]): void;
get children(): HTMLCollection;
get childNodes(): NodeListOf<ChildNode>;
contains(other: Node | null): boolean;
get firstChild(): ChildNode & NonDocumentTypeChildNode;
hasChildNodes(): boolean;
insertBefore<T extends Node>(node: T, child: Node | null): T;
get lastChild(): (ChildNode & NonDocumentTypeChildNode) | null;
get nextElementSibling(): Element | null;
get nextSibling(): ChildNode | null;
remove(): void;
removeChild<T extends Node>(child: T): T;
replaceChild<T extends Node>(node: Node, child: T): T;
setTo(...nodes: (Node | null | undefined)[]): void;
replaceWith(...nodes: (string | Node)[]): void;
}
constructs a new fragment with an optional name. The name is used in the
underlying Comment's data. It can be helpful for debugging purposes. If not
passed, it defaults to Fragment
.
declare class WillowFragment extends Comment {
constructor(name?: string);
}
replaces the children of this fragment with the nodes passed to it. If any nodes
are null
or undefined
, they will be skipped. We chose the name setTo
instead of replaceChildrenWith
because the frequent use of it in our codebase
would've increased bundle size too much.
declare class WillowFragment extends Comment {
setTo(...nodes: (Node | null | undefined)[]): void;
}