New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@bikeshaving/crank

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bikeshaving/crank - npm Package Compare versions

Comparing version 0.3.0 to 0.3.1-beta.0

cjs/crank.d.ts

12

cjs/dom.d.ts

@@ -1,6 +0,2 @@

import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./crank";
export declare class DOMRenderer extends Renderer<Node, string | undefined> {

@@ -15,1 +11,7 @@ render(children: Children, root: Node, ctx?: Context): Promise<ElementValue<Node>> | ElementValue<Node>;

export declare const renderer: DOMRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}

@@ -5,6 +5,6 @@ 'use strict';

var index = require('./index.js');
var crank = require('./crank.js');
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
class DOMRenderer extends index.Renderer {
class DOMRenderer extends crank.Renderer {
render(children, root, ctx) {

@@ -32,3 +32,3 @@ if (!(root instanceof Node)) {

switch (el.tag) {
case index.Portal:
case crank.Portal:
case "foreignObject":

@@ -132,3 +132,3 @@ return undefined;

arrange(el, parent, children) {
if (el.tag === index.Portal && !(parent instanceof Node)) {
if (el.tag === crank.Portal && !(parent instanceof Node)) {
throw new TypeError("Portal root is not a node");

@@ -135,0 +135,0 @@ }

@@ -1,10 +0,6 @@

import { Element, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Element, ElementValue, Renderer } from "./crank";
interface Node {
value: string;
}
export declare class StringRenderer extends Renderer<Node | string, undefined, unknown, string> {
export declare class HTMLRenderer extends Renderer<Node | string, undefined, unknown, string> {
create(): Node;

@@ -15,3 +11,13 @@ escape(text: string): string;

}
export declare const renderer: StringRenderer;
export declare const renderer: HTMLRenderer;
/**
* @deprecated
*/
export declare const StringRenderer: typeof HTMLRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}
export {};

@@ -5,3 +5,3 @@ 'use strict';

var index = require('./index.js');
var crank = require('./crank.js');

@@ -84,3 +84,3 @@ const voidTags = new Set([

}
class StringRenderer extends index.Renderer {
class HTMLRenderer extends crank.Renderer {
create() {

@@ -107,3 +107,3 @@ return { value: "" };

arrange(el, node, children) {
if (el.tag === index.Portal) {
if (el.tag === crank.Portal) {
return;

@@ -128,6 +128,11 @@ }

}
const renderer = new StringRenderer();
const renderer = new HTMLRenderer();
/**
* @deprecated
*/
const StringRenderer = HTMLRenderer;
exports.HTMLRenderer = HTMLRenderer;
exports.StringRenderer = StringRenderer;
exports.renderer = renderer;
//# sourceMappingURL=html.js.map

@@ -1,498 +0,1 @@

/**
* Represents all valid values which can be used for the tag of an element.
*
* @remarks
* Elements whose tags are strings or symbols are called “host” or “intrinsic” elements, and their behavior is determined by the renderer, while elements whose tags are components are called “component” elements, and their behavior is determined by the execution of the component.
*/
export declare type Tag = string | symbol | Component;
/**
* Maps the tag of an element to its expected props.
*
* @typeparam TTag - The element’s tag.
*/
export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown;
/***
* SPECIAL TAGS
* Crank provides a couple tags which have special meaning for the renderer.
***/
/**
* A special element tag for grouping multiple children within a parent.
*
* @remarks
* All iterables which appear in the element tree are implicitly wrapped in fragments. The Fragment tag is just the empty string, and you can use the empty string in createElement calls/transpiler options to avoid having to reference the Fragment export directly.
*/
export declare const Fragment = "";
export declare type Fragment = typeof Fragment;
/**
* A special element tag for creating a new element subtree with a different root, passed via the root prop.
*
* @remarks
* This element tag is useful for creating element trees with multiple roots. Renderer.prototype.render will implicitly wrap the children which have been passed in in an implicit Portal element.
*/
export declare const Portal: any;
export declare type Portal = typeof Portal;
/**
* A special element tag which copies whatever child appeared previously in the element’s position.
*
* @remarks
* Copy elements are useful when you want to prevent a subtree from updating as a performance optimization.
*/
export declare const Copy: any;
export declare type Copy = typeof Copy;
/**
* A special element tag for injecting raw nodes into an element tree via its value prop.
*
* @remarks
* If the value prop is a string, Renderer.prototype.parse will be called on the string and the element’s rendered value will be the result.
*/
export declare const Raw: any;
export declare type Raw = typeof Raw;
/**
* Describes all valid singular values of an element tree.
*
* @remarks
* Arbitrary objects can also be safely rendered but they will be converted to a string using the toString method. We exclude them from this type to catch potential type errors.
*/
export declare type Child = Element | string | number | boolean | null | undefined;
interface ChildIterable extends Iterable<Child | ChildIterable> {
}
/**
* Describes all valid values of an element tree, including arbitrarily nested iterables of such values.
*/
export declare type Children = Child | ChildIterable;
/**
* Represents all functions which can be used as a component.
*
* @typeparam TProps - The expected props for the component.
*
* @remarks
* The return type of iterator objects returned from components has to be void because typescript will infer most generators as having a void return type.
*/
export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>;
/**
* All nodes in the element tree are narrowed from the union in Child to NarrowedChild. This greatly simplifies element diffing.
*/
declare type NarrowedChild = Element | string | undefined;
declare type Key = unknown;
declare const ElementSymbol: unique symbol;
/**
* Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by special classes called renderers to produce and manage stateful nodes.
*
* @typeparam TTag - the type of the tag of the element.
*
* @example
* // specific element types
* let div: Element<"div">;
* let portal: Element<Portal>;
* let myEl: Element<MyComponent>;
*
* // general element types
* let host: Element<string | symbol>;
* let component: Element<Component>;
*
* @remarks
* Typically, you use the createElement function to create elements and not this class directly.
*/
export declare class Element<TTag extends Tag = Tag> {
/**
* @internal
* A unique symbol to identify elements as elements across versions and realms, and to protect against basic injection attacks.
* https://overreacted.io/why-do-react-elements-have-typeof-property/
*/
$$typeof: typeof ElementSymbol;
/**
* @internal
* flags - A bitmask. See ELEMENT FLAGS.
*/
_f: number;
/**
* The tag of the element. Can be a function, string or symbol depending on the kind of element.
*/
tag: TTag;
/**
* An object containing the “properties” of an element. These correspond to the attributes of the element when using JSX syntax.
*
* @remarks
* The props of an object are passed to most renderer host methods, and as the first argument to components.
*/
props: TagProps<TTag>;
/**
* A value which uniquely identifies an element from its siblings so that it can be added/updated/moved/removed by the identity of the key rather than its position within the parent.
*
* @remarks
* Passed to the element as the prop "crank-key".
*/
key: Key;
/**
* A callback which is called with the element’s value when the value is committed.
*
* @remarks
* Passed to the element as the prop "crank-ref".
*/
ref: Function | undefined;
/**
* @internal
* children - The rendered children of the element.
*/
_ch: Array<NarrowedChild> | NarrowedChild;
/**
* @internal
* node - The node associated with the element.
*
* @remarks
* Set by Renderer.prototype.create when the component is mounted. This property will only be set for host elements.
*/
_n: any;
/**
* @internal
* context - The Context object associated with this element.
*
* @remarks
* Created and assigned by the Renderer for component elements when it mounts the element tree.
*/
_ctx: Context<TagProps<TTag>> | undefined;
/**
* @internal
* fallback - The value of the element while it has never committed.
*
* @remarks
* If an element takes place of a previously rendered value but renders asynchronously, this property is set to the previously rendered value until the element commits. This allows asynchronously updating element trees to show something while pending.
*/
_fb: any;
/**
* @internal
* inflightPromise - The current async run of the element.
*
* @remarks
* This value is used to make sure element copies do not fulfill immediately, to set the fallback of the next element when the previous element commits, and as the yield value of async generator components with async children. It is unset when the element is committed.
*/
_inf: Promise<any> | undefined;
/**
* @internal
* onNewValues - the resolve function of a promise which represents the next child result. See the chase function for more info.
*/
_onv: Function | undefined;
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: Function | undefined);
}
export declare function isElement(value: any): value is Element;
/**
* Creates an element with the specified tag, props and children.
*
* @remarks
* This function is usually used as a transpilation target for JSX transpilers, but it can also be called directly. It additionally extracts the crank-key and crank-ref props so they aren’t accessible to the renderer methods or components, and assigns the children prop according to the remaining arguments passed to the function.
*/
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>;
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, children?: unknown): Element<TTag>;
/**
* Clones a given element.
*
* @remarks
* Mainly used internally to make sure we don’t accidentally reuse elements in an element tree, because elements are directly mutated by the renderer.
*/
export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>;
/*** ELEMENT VALUE UTILITIES ***/
/**
* A helper type which repesents all the possible rendered values of an element.
*
* @typeparam TNode - The node type for the element assigned by the renderer.
*
* @remarks
* When asking the question, what is the value of a specific element, the answer varies depending on the type of the element. For host or Raw elements, the answer is simply the nodes (DOM nodes in the case of the DOM renderer) created for the element. For fragments, the values are usually an array of nodes. For portals, the value is undefined, because a Portal element’s root and children are opaque to parents. For components, the value can be any of the above, because the value of a component is determined by its children. Rendered values can also be strings or arrays of nodes and strings, in the case of a component or fragment with strings for children. All of these possible values are reflected in this utility type.
*/
export declare type ElementValue<TNode> = Array<TNode | string> | TNode | string | undefined;
/**
* An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment.
*
* @typeparam TNode - The type of the node for a specific rendering environment.
* @typeparam TScope - Data which is passed down the tree.
* @typeparam TRoot - The type of the root for a specific rendering environment.
* @typeparam TResult - The type of the exposed values.
*/
export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> {
/**
* @internal
* A weakmap which stores element trees by root.
*/
_cache: WeakMap<object, Element<Portal>>;
constructor();
/**
* Renders an element tree into a specific root.
*
* @param children - An element tree. You can render null with a previously used root to delete the previously rendered element tree from the cache.
* @param root - The node to be rendered into. The renderer will cache element trees per root.
* @param ctx - An optional context that will be the ancestor context of all elements in the tree. Useful for connecting renderers which call each other so that events/provisions properly propagate. The context for a given root must be the same or an error will be thrown.
*
* @returns The read result of rendering the children, or a possible promise of the read result if the element tree renders asynchronously.
*/
render(children: Children, root?: TRoot | undefined, ctx?: Context | undefined): Promise<TResult> | TResult;
/**
* Called when an element’s value is exposed via render, schedule, refresh, refs, or generator yield expressions.
*
* @param value - The value of the element being read. Can be a node, a string, undefined, or an array of nodes and strings, depending on the element.
* @returns Varies according to the specific renderer subclass. By default, it exposes the element’s value.
*
* @remarks
* This is useful for renderers which don’t want to expose their internal nodes. For instance, the HTML renderer will convert all internal nodes to strings.
*
*/
read(value: ElementValue<TNode>): TResult;
/**
* Called in a preorder traversal for each host element.
*
* @remarks
* Useful for passing data down the element tree. For instance, the DOM renderer uses this method to keep track of whether we’re in an SVG subtree.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns The scope to be passed to create and scope for child host elements.
*
* @remarks
* This method sets the scope for child host elements, not the current host element.
*/
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope;
/**
* Called for each string in an element tree.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The escaped string.
*
* @remarks
* Rather than returning text nodes for whatever environment we’re rendering to, we defer that step for Renderer.prototype.arrange. We do this so that adjacent strings can be concatenated and the actual element tree can be rendered in a normalized form.
*/
escape(text: string, _scope: TScope): string;
/**
* Called for each Raw element whose value prop is a string.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The parsed node or string.
*/
parse(text: string, _scope: TScope): TNode | string;
/**
* Called for each host element when it is committed for the first time.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns A “node” which determines the value of the host element.
*/
create(_el: Element<string | symbol>, _scope: TScope): TNode;
/**
* Called for each host element when it is committed.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param scope - The current scope.
*
* @returns The return value is ignored.
*
* @remarks
* Used to mutate the node associated with an element when new props are passed.
*/
patch(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called for each host element after its children have committed with the actual values of the children.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param children - An array of nodes and strings from child elements.
*
* @returns The return value is ignored.
*
* @remarks
* This method is also called by child components contexts as the last step of a refresh.
*/
arrange(_el: Element<string | symbol>, _parent: TNode | TRoot, _children: Array<TNode | string>): unknown;
/**
* Called for each host element when it is unmounted.
*
* @param tag - The tag of the host element.
* @param props - The props of the host element.
* @param node - The node associated with the host element.
*
* @returns The return value is ignored.
*/
dispose(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called at the end of the rendering process for each root of the tree.
*
* @param root - The root prop passed to portals or the render method.
*
* @returns The return value is ignored.
*/
complete(_root: TRoot): unknown;
}
/**
* A map of event type strings to Event subclasses. Can be extended via TypeScript module augmentation to have strongly typed event listeners.
*/
export interface EventMap {
[type: string]: Event;
}
declare type MappedEventListener<T extends string> = (ev: EventMap[T]) => unknown;
declare type MappedEventListenerOrEventListenerObject<T extends string> = MappedEventListener<T> | {
handleEvent: MappedEventListener<T>;
};
interface EventListenerRecord {
type: string;
listener: MappedEventListenerOrEventListenerObject<any>;
callback: MappedEventListener<any>;
options: AddEventListenerOptions;
}
/**
* An interface which can be extended to provide strongly typed provisions (see Context.prototype.get and Context.prototype.set)
*/
export interface ProvisionMap {
}
/**
* A class which is instantiated and passed to every component as its this value. Contexts form a tree just like elements and all components in the element tree are connected via contexts. Components can use this tree to communicate data upwards via events and downwards via provisions.
* @typeparam TProps - The expected shape of the props passed to the component. Used to strongly type the Context iterator methods.
* @typeparam TResult - The readable element value type. It is used in places such as the return value of refresh and the argument passed to schedule/cleanup callbacks.
*/
export declare class Context<TProps = any, TResult = any> implements EventTarget {
/**
* @internal
* flags - A bitmask. See CONTEXT FLAGS above.
*/
_f: number;
/**
* @internal
* renderer - The renderer which created this context.
*/
_re: Renderer<unknown, unknown, unknown, TResult>;
/**
* @internal
* root - The root node set by an ancestor’s Portal prop.
*/
_rt: unknown;
/**
* @internal
* host - The nearest ancestor host element.
* @remarks
* When refresh is called, the host element will be arranged as the last step of the commit, to make sure the parent’s children properly reflects the components’s children.
*/
_ho: Element<string | symbol>;
/**
* @internal
* parent - The parent context.
*/
_pa: Context<unknown, TResult> | undefined;
/**
* @internal
* scope - The value of the scope at the point of element’s creation.
*/
_sc: unknown;
/**
* @internal
* el - The associated component element.
*/
_el: Element<Component>;
/**
* @internal
* iterator - The iterator returned by the component function.
*/
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined;
/**
* @internal
* onProps - A callback used in conjunction with the Available flag to implement the props async iterator. See the Symbol.asyncIterator method and the resume function.
*/
_op: ((props: any) => unknown) | undefined;
/**
* @internal
* inflightBlock
*/
_ib: Promise<unknown> | undefined;
/**
* @internal
* inflightValue
*/
_iv: Promise<ElementValue<any>> | undefined;
/**
* @internal
* enqueuedBlock
*/
_eb: Promise<unknown> | undefined;
/**
* @internal
* enqueuedValue
*/
_ev: Promise<ElementValue<any>> | undefined;
/**
* @internal
* listeners - An array of event listeners added to the context via Context.prototype.addEventListener
*/
_ls: Array<EventListenerRecord> | undefined;
/**
* @internal
* provisions - A map of values which can be set via Context.prototype.set and read from child contexts via Context.prototype.get
*/
_ps: Map<unknown, unknown> | undefined;
/**
* @internal
* schedules - a set of callbacks registered via Context.prototype.schedule, which fire when the component has committed.
*/
_ss: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
* cleanups - a set of callbacks registered via Context.prototype.cleanup, which fire when the component has unmounted.
*/
_cs: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
*/
constructor(renderer: Renderer<unknown, unknown, unknown, TResult>, root: unknown, host: Element<string | symbol>, parent: Context<unknown, TResult> | undefined, scope: unknown, el: Element<Component>);
consume<TKey extends keyof ProvisionMap>(key: TKey): ProvisionMap[TKey];
consume(key: unknown): any;
provide<TKey extends keyof ProvisionMap>(key: TKey, value: ProvisionMap[TKey]): void;
provide(key: unknown, value: any): void;
/**
* The current props of the associated element.
*
* @remarks
* Typically, you should read props either via the first parameter of the component or via the context iterator methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get props(): TProps;
/**
* The current value of the associated element.
*
* @remarks
* Typically, you should read values via refs, generator yield expressions, or the refresh, schedule or cleanup methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get value(): TResult;
[Symbol.iterator](): Generator<TProps>;
[Symbol.asyncIterator](): AsyncGenerator<TProps>;
/**
* Re-executes the component.
*
* @returns The rendered value of the component or a promise of the rendered value if the component or its children execute asynchronously.
*
* @remarks
* The refresh method works a little differently for async generator components, in that it will resume the Context async iterator rather than resuming execution. This is because async generator components are perpetually resumed independent of updates/refresh.
*/
refresh(): Promise<TResult> | TResult;
/**
* Registers a callback which fires when the component commits. Will only fire once per callback and update.
*/
schedule(callback: (value: TResult) => unknown): void;
/**
* Registers a callback which fires when the component unmounts. Will only fire once per callback.
*/
cleanup(callback: (value: TResult) => unknown): void;
addEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: boolean | AddEventListenerOptions): void;
removeEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: EventListenerOptions | boolean): void;
dispatchEvent(ev: Event): boolean;
}
declare global {
module JSX {
interface IntrinsicElements {
[tag: string]: any;
}
interface ElementChildrenAttribute {
children: {};
}
}
}
export {};
export * from "./crank";

@@ -5,1503 +5,16 @@ 'use strict';

/*** UTILITIES ***/
function wrap(value) {
return value === undefined ? [] : Array.isArray(value) ? value : [value];
}
function unwrap(arr) {
return arr.length > 1 ? arr : arr[0];
}
function isIterable(value) {
return value != null && typeof value[Symbol.iterator] === "function";
}
function isNonStringIterable(value) {
return typeof value !== "string" && isIterable(value);
}
function isIteratorLike(value) {
return value != null && typeof value.next === "function";
}
function isPromiseLike(value) {
return value != null && typeof value.then === "function";
}
/***
* SPECIAL TAGS
* Crank provides a couple tags which have special meaning for the renderer.
***/
/**
* A special element tag for grouping multiple children within a parent.
*
* @remarks
* All iterables which appear in the element tree are implicitly wrapped in fragments. The Fragment tag is just the empty string, and you can use the empty string in createElement calls/transpiler options to avoid having to reference the Fragment export directly.
*/
const Fragment = "";
// NOTE: We assert the following symbol tags to be any because typescript support for symbol tags in JSX does not exist yet.
// https://github.com/microsoft/TypeScript/issues/38367
/**
* A special element tag for creating a new element subtree with a different root, passed via the root prop.
*
* @remarks
* This element tag is useful for creating element trees with multiple roots. Renderer.prototype.render will implicitly wrap the children which have been passed in in an implicit Portal element.
*/
const Portal = Symbol.for("crank.Portal");
/**
* A special element tag which copies whatever child appeared previously in the element’s position.
*
* @remarks
* Copy elements are useful when you want to prevent a subtree from updating as a performance optimization.
*/
const Copy = Symbol.for("crank.Copy");
/**
* A special element tag for injecting raw nodes into an element tree via its value prop.
*
* @remarks
* If the value prop is a string, Renderer.prototype.parse will be called on the string and the element’s rendered value will be the result.
*/
const Raw = Symbol.for("crank.Raw");
function narrow(child) {
if (child == null || typeof child === "boolean") {
return undefined;
}
else if (typeof child === "string" || isElement(child)) {
return child;
}
else {
return child.toString();
}
}
const ElementSymbol = Symbol.for("crank.Element");
/*** ELEMENT FLAGS ***/
/**
* A flag which is set when the component has been mounted. Used mainly to detect whether an element is being reused so that it can be cloned.
*/
const Mounted = 1 << 0;
/**
* A flag which is set when the component has committed at least once.
*/
const Committed = 1 << 1;
// NOTE: To save on filesize, we mangle the internal properties of Crank classes by hand. These internal properties are prefixed with an underscore. Refer to their definitions to see their unabbreviated names.
// NOTE: to maximize compatibility between Crank versions, starting with 0.2.0, the $$typeof property and the non-internal properties will not be changed, and any change to these properties will be considered a breaking change. This is to ensure maximum compatibility between components which use different Crank versions.
/**
* Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by special classes called renderers to produce and manage stateful nodes.
*
* @typeparam TTag - the type of the tag of the element.
*
* @example
* // specific element types
* let div: Element<"div">;
* let portal: Element<Portal>;
* let myEl: Element<MyComponent>;
*
* // general element types
* let host: Element<string | symbol>;
* let component: Element<Component>;
*
* @remarks
* Typically, you use the createElement function to create elements and not this class directly.
*/
class Element {
constructor(tag, props, key, ref) {
this.$$typeof = ElementSymbol;
this._f = 0;
this.tag = tag;
this.props = props;
this.key = key;
this.ref = ref;
this._ch = undefined;
this._n = undefined;
this._ctx = undefined;
}
}
function isElement(value) {
return value != null && value.$$typeof === ElementSymbol;
}
function createElement(tag, props, children) {
let key;
let ref;
const props1 = {};
if (props != null) {
for (const name in props) {
if (name === "crank-key") {
// NOTE: We have to make sure we don’t assign null to the key because we don’t check for null keys in the diffing functions.
if (props[name] != null) {
key = props[name];
}
}
else if (name === "crank-ref") {
if (typeof props["crank-ref"] === "function") {
ref = props[name];
}
}
else {
props1[name] = props[name];
}
}
}
let length = arguments.length;
if (length > 3) {
const children1 = [];
while (length-- > 2) {
children1[length - 2] = arguments[length];
}
props1.children = children1;
}
else if (length > 2) {
props1.children = children;
}
return new Element(tag, props1, key, ref);
}
/**
* Clones a given element.
*
* @remarks
* Mainly used internally to make sure we don’t accidentally reuse elements in an element tree, because elements are directly mutated by the renderer.
*/
function cloneElement(el) {
if (!isElement(el)) {
throw new TypeError("Cannot clone non-element");
}
return new Element(el.tag, { ...el.props }, el.key, el.ref);
}
/**
* Takes an array of element values and normalizes the output as an array of nodes and strings.
*
* @remarks
* Normalize will flatten only one level of nested arrays, because it is designed to be called once at each level of the tree. It will also concatenate adjacent strings and remove all undefineds.
*/
function normalize(values) {
const result = [];
let buffer;
for (let i = 0; i < values.length; i++) {
const value = values[i];
if (!value) ;
else if (typeof value === "string") {
buffer = (buffer || "") + value;
}
else if (!Array.isArray(value)) {
if (buffer) {
result.push(buffer);
buffer = undefined;
}
result.push(value);
}
else {
for (let j = 0; j < value.length; j++) {
const value1 = value[j];
if (!value1) ;
else if (typeof value1 === "string") {
buffer = (buffer || "") + value1;
}
else {
if (buffer) {
result.push(buffer);
buffer = undefined;
}
result.push(value1);
}
}
}
}
if (buffer) {
result.push(buffer);
}
return result;
}
/**
* Walks an element’s children to find its child values.
* @returns A normalized array of nodes and strings.
*/
function getChildValues(el) {
const values = [];
const children = wrap(el._ch);
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "string") {
values.push(child);
}
else if (typeof child !== "undefined") {
values.push(getValue(child));
}
}
return normalize(values);
}
/**
* Finds the value of the element according to its type.
* @returns The value of the element.
*/
function getValue(el) {
if (typeof el._fb !== "undefined") {
return el._fb;
}
else if (typeof el.tag === Portal) {
return undefined;
}
else if (typeof el.tag !== "function" && el.tag !== Fragment) {
return el._n;
}
return unwrap(getChildValues(el));
}
/**
* An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment.
*
* @typeparam TNode - The type of the node for a specific rendering environment.
* @typeparam TScope - Data which is passed down the tree.
* @typeparam TRoot - The type of the root for a specific rendering environment.
* @typeparam TResult - The type of the exposed values.
*/
class Renderer {
constructor() {
this._cache = new WeakMap();
}
/**
* Renders an element tree into a specific root.
*
* @param children - An element tree. You can render null with a previously used root to delete the previously rendered element tree from the cache.
* @param root - The node to be rendered into. The renderer will cache element trees per root.
* @param ctx - An optional context that will be the ancestor context of all elements in the tree. Useful for connecting renderers which call each other so that events/provisions properly propagate. The context for a given root must be the same or an error will be thrown.
*
* @returns The read result of rendering the children, or a possible promise of the read result if the element tree renders asynchronously.
*/
render(children, root, ctx) {
let portal;
if (typeof root === "object" && root !== null) {
portal = this._cache.get(root);
}
if (portal === undefined) {
portal = createElement(Portal, { children, root });
portal._ctx = ctx;
if (typeof root === "object" && root !== null && children != null) {
this._cache.set(root, portal);
}
}
else {
if (portal._ctx !== ctx) {
throw new Error("render must be called with the same context per root");
}
portal.props = { children, root };
if (typeof root === "object" && root !== null && children == null) {
this._cache.delete(root);
}
}
const value = update(this, root, portal, ctx, undefined, portal);
// NOTE: we return the read child values of the portal because portals themselves have no readable value.
if (isPromiseLike(value)) {
return value.then(() => {
const result = this.read(unwrap(getChildValues(portal)));
if (root == null) {
unmount(this, portal, undefined, portal);
}
return result;
});
}
const result = this.read(unwrap(getChildValues(portal)));
if (root == null) {
unmount(this, portal, undefined, portal);
}
return result;
}
/**
* Called when an element’s value is exposed via render, schedule, refresh, refs, or generator yield expressions.
*
* @param value - The value of the element being read. Can be a node, a string, undefined, or an array of nodes and strings, depending on the element.
* @returns Varies according to the specific renderer subclass. By default, it exposes the element’s value.
*
* @remarks
* This is useful for renderers which don’t want to expose their internal nodes. For instance, the HTML renderer will convert all internal nodes to strings.
*
*/
read(value) {
return value;
}
/**
* Called in a preorder traversal for each host element.
*
* @remarks
* Useful for passing data down the element tree. For instance, the DOM renderer uses this method to keep track of whether we’re in an SVG subtree.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns The scope to be passed to create and scope for child host elements.
*
* @remarks
* This method sets the scope for child host elements, not the current host element.
*/
scope(el, scope) {
return scope;
}
/**
* Called for each string in an element tree.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The escaped string.
*
* @remarks
* Rather than returning text nodes for whatever environment we’re rendering to, we defer that step for Renderer.prototype.arrange. We do this so that adjacent strings can be concatenated and the actual element tree can be rendered in a normalized form.
*/
escape(text, _scope) {
return text;
}
/**
* Called for each Raw element whose value prop is a string.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The parsed node or string.
*/
parse(text, _scope) {
return text;
}
/**
* Called for each host element when it is committed for the first time.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns A “node” which determines the value of the host element.
*/
create(_el, _scope) {
throw new Error("Not implemented");
}
/**
* Called for each host element when it is committed.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param scope - The current scope.
*
* @returns The return value is ignored.
*
* @remarks
* Used to mutate the node associated with an element when new props are passed.
*/
patch(_el, _node) {
return;
}
// TODO: pass hints into arrange about where the dirty children start and end
/**
* Called for each host element after its children have committed with the actual values of the children.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param children - An array of nodes and strings from child elements.
*
* @returns The return value is ignored.
*
* @remarks
* This method is also called by child components contexts as the last step of a refresh.
*/
arrange(_el, _parent, _children) {
return;
}
// TODO: remove(): a method which is called to remove a child from a parent to optimize arrange
/**
* Called for each host element when it is unmounted.
*
* @param tag - The tag of the host element.
* @param props - The props of the host element.
* @param node - The node associated with the host element.
*
* @returns The return value is ignored.
*/
dispose(_el, _node) {
return;
}
/**
* Called at the end of the rendering process for each root of the tree.
*
* @param root - The root prop passed to portals or the render method.
*
* @returns The return value is ignored.
*/
complete(_root) {
return;
}
}
/*** PRIVATE RENDERER FUNCTIONS ***/
// NOTE: to aid in the mangling of this module, we use functions rather than methods for internal logic.
function mount(renderer, root, host, ctx, scope, el) {
el._f |= Mounted;
if (typeof el.tag === "function") {
el._ctx = new Context(renderer, root, host, ctx, scope, el);
return updateCtx(el._ctx);
}
else if (el.tag === Raw) {
return commit(renderer, scope, el, []);
}
else if (el.tag !== Fragment) {
if (el.tag === Portal) {
root = el.props.root;
}
host = el;
scope = renderer.scope(host, scope);
}
// NOTE: The primary benefit of having a separate codepath for mounting is that it’s slightly faster because we don’t have to align and diff children against old children. But for singular child values, updateChild is sufficient.
if (isNonStringIterable(el.props.children)) {
return mountChildren(renderer, root, host, ctx, scope, el, el.props.children);
}
return updateChild(renderer, root, host, ctx, scope, el, el.props.children);
}
function mountChildren(renderer, root, host, ctx, scope, parent, children) {
const values = [];
const newChildren = Array.from(children);
let async = false;
for (let i = 0; i < newChildren.length; i++) {
let value;
let child = newChildren[i];
if (isNonStringIterable(child)) {
child = createElement(Fragment, null, child);
}
else {
child = narrow(child);
}
[child, value] = diff(renderer, root, host, ctx, scope, undefined, // oldChild
child);
newChildren[i] = child;
values.push(value);
if (!async && isPromiseLike(value)) {
async = true;
}
}
parent._ch = unwrap(newChildren);
let values1;
if (async) {
values1 = Promise.all(values);
}
else {
values1 = values;
}
return chase(renderer, host, ctx, scope, parent, values1);
}
function update(renderer, root, host, ctx, scope, el) {
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
return updateCtx(el._ctx);
}
return undefined;
}
else if (el.tag === Raw) {
return commit(renderer, scope, el, []);
}
else if (el.tag !== Fragment) {
host = el;
scope = renderer.scope(host, scope);
if (el.tag === Portal) {
root = el.props.root;
}
}
if (isNonStringIterable(el.props.children)) {
return updateChildren(renderer, root, host, ctx, scope, el, el.props.children);
}
else if (Array.isArray(el._ch)) {
return updateChildren(renderer, root, host, ctx, scope, el, [
el.props.children,
]);
}
return updateChild(renderer, root, host, ctx, scope, el, el.props.children);
}
function updateChild(renderer, root, host, ctx, scope, parent, child) {
let oldChild = parent._ch;
let newChild = narrow(child);
if (typeof oldChild === "object" &&
typeof newChild === "object" &&
oldChild.key !== newChild.key) {
oldChild = undefined;
}
let value;
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild);
if (typeof oldChild === "object" && oldChild !== newChild) {
unmount(renderer, host, ctx, oldChild);
}
parent._ch = newChild;
// TODO: allow single values to be passed to chase
const values = isPromiseLike(value) ? value.then(wrap) : wrap(value);
return chase(renderer, host, ctx, scope, parent, values);
}
function mapChildrenByKey(children) {
const childrenByKey = new Map();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "object" && typeof child.key !== "undefined") {
childrenByKey.set(child.key, child);
}
}
return childrenByKey;
}
function updateChildren(renderer, root, host, ctx, scope, parent, children) {
if (typeof parent._ch === "undefined") {
return mountChildren(renderer, root, host, ctx, scope, parent, children);
}
const values = [];
const oldChildren = wrap(parent._ch);
const newChildren = Array.from(children);
const graveyard = [];
let i = 0;
let async = false;
let seen;
let childrenByKey;
// TODO: switch to mountChildren if there are no more children
for (let j = 0; j < newChildren.length; j++) {
let oldChild = oldChildren[i];
let newChild = newChildren[j];
if (isNonStringIterable(newChild)) {
newChild = createElement(Fragment, null, newChild);
}
else {
newChild = narrow(newChild);
}
// ALIGNMENT
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined;
let newKey = typeof newChild === "object" ? newChild.key : undefined;
if (seen !== undefined && seen.has(newKey)) {
// TODO: warn about a duplicate key
newKey = undefined;
}
if (oldKey !== newKey) {
if (!childrenByKey) {
childrenByKey = mapChildrenByKey(oldChildren.slice(i));
}
if (newKey === undefined) {
while (oldChild !== undefined && oldKey !== undefined) {
i++;
oldChild = oldChildren[i];
oldKey = typeof oldChild === "object" ? oldChild.key : undefined;
}
i++;
}
else {
oldChild = childrenByKey.get(newKey);
if (oldChild !== undefined) {
childrenByKey.delete(newKey);
}
if (!seen) {
seen = new Set();
}
seen.add(newKey);
}
}
else {
if (childrenByKey !== undefined && newKey !== undefined) {
childrenByKey.delete(newKey);
}
i++;
}
// UPDATING
let value;
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild);
values.push(value);
newChildren[j] = newChild;
if (!async && isPromiseLike(value)) {
async = true;
}
if (typeof oldChild === "object" && oldChild !== newChild) {
graveyard.push(oldChild);
}
}
parent._ch = unwrap(newChildren);
// cleanup
for (; i < oldChildren.length; i++) {
const oldChild = oldChildren[i];
if (typeof oldChild === "object" && typeof oldChild.key === "undefined") {
graveyard.push(oldChild);
}
}
// TODO: async unmounting
if (childrenByKey !== undefined && childrenByKey.size > 0) {
graveyard.push(...childrenByKey.values());
}
let values1;
if (async) {
values1 = Promise.all(values).finally(() => graveyard.forEach((child) => unmount(renderer, host, ctx, child)));
}
else {
values1 = values;
graveyard.forEach((child) => unmount(renderer, host, ctx, child));
}
return chase(renderer, host, ctx, scope, parent, values1);
}
function diff(renderer, root, host, ctx, scope, oldChild, newChild) {
let value;
if (typeof oldChild === "object" &&
typeof newChild === "object" &&
oldChild.tag === newChild.tag) {
// TODO: implement Raw element parse caching
if (oldChild.tag === Portal) {
if (oldChild.props.root !== newChild.props.root) {
renderer.arrange(oldChild, oldChild.props.root, []);
}
}
if (oldChild !== newChild) {
oldChild.props = newChild.props;
oldChild.ref = newChild.ref;
newChild = oldChild;
}
value = update(renderer, root, host, ctx, scope, newChild);
}
else if (typeof newChild === "object") {
if (newChild.tag === Copy) {
if (typeof oldChild === "object") {
value = oldChild._inf || getValue(oldChild);
}
else {
value = oldChild;
}
if (typeof newChild.ref === "function") {
if (isPromiseLike(value)) {
value.then(newChild.ref).catch(() => { });
}
else {
newChild.ref(value);
}
}
newChild = oldChild;
}
else {
if (newChild._f & Mounted) {
newChild = cloneElement(newChild);
}
if (typeof oldChild === "object") {
newChild._fb = oldChild._n;
if (typeof oldChild._inf === "object") {
oldChild._inf
.then((value) => {
if (!(newChild._f & Committed)) {
newChild._fb = value;
}
})
.catch(() => { });
}
}
value = mount(renderer, root, host, ctx, scope, newChild);
}
}
else if (typeof newChild === "string") {
newChild = renderer.escape(newChild, scope);
value = newChild;
}
return [newChild, value];
}
/**
* A function to race current child values with future child values.
*
* @remarks
* When an element’s children update asynchronously, we race the resulting promise with the next update of the element’s children. By induction, this ensures that when any update to an element settles, all past updates to that same element will have settled as well. This prevents deadlocks and unnecessary awaiting when an element’s children have been cleared, for instance.
*/
function chase(renderer, host, ctx, scope, el, values) {
if (isPromiseLike(values)) {
let onNewValues;
const newValues = new Promise((resolve) => (onNewValues = resolve));
const valuesP = Promise.race([values, newValues]);
if (typeof el._onv === "function") {
el._onv(valuesP);
}
el._onv = onNewValues;
const value = valuesP.then((values) => commit(renderer, scope, el, normalize(values)));
el._inf = value;
return value;
}
if (typeof el._onv === "function") {
el._onv(values);
el._onv = undefined;
}
return commit(renderer, scope, el, normalize(values));
}
function commit(renderer, scope, el, values) {
let value = unwrap(values);
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
commitCtx(el._ctx, value);
}
}
else if (el.tag === Portal) {
renderer.arrange(el, el.props.root, values);
renderer.complete(el.props.root);
value = undefined;
}
else if (el.tag === Raw) {
if (typeof el.props.value === "string") {
el._n = renderer.parse(el.props.value, scope);
}
else {
el._n = el.props.value;
}
value = el._n;
}
else if (el.tag !== Fragment) {
if (!(el._f & Committed)) {
el._n = renderer.create(el, scope);
}
renderer.patch(el, el._n);
renderer.arrange(el, el._n, values);
value = el._n;
}
el._f |= Committed;
if (typeof el.ref === "function") {
el.ref(renderer.read(value));
}
if (typeof el._inf !== "undefined") {
el._inf = undefined;
}
if (typeof el._fb !== "undefined") {
el._fb = undefined;
}
return value;
}
function unmount(renderer, host, ctx, el) {
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
unmountCtx(el._ctx);
}
ctx = el._ctx;
}
else if (el.tag === Portal) {
host = el;
renderer.arrange(host, host.props.root, []);
renderer.complete(el.props.root);
}
else if (el.tag !== Fragment) {
if (isEventTarget(el._n)) {
const listeners = getListeners(ctx, host);
if (listeners !== undefined && listeners.length > 0) {
for (let i = 0; i < listeners.length; i++) {
const record = listeners[i];
el._n.removeEventListener(record.type, record.callback, record.options);
}
}
}
host = el;
renderer.dispose(host, host._n);
}
const children = wrap(el._ch);
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "object") {
unmount(renderer, host, ctx, child);
}
}
}
/*** EVENT UTILITIES ***/
// EVENT PHASE CONSTANTS (https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase)
const NONE = 0;
const CAPTURING_PHASE = 1;
const AT_TARGET = 2;
const BUBBLING_PHASE = 3;
function normalizeOptions(options) {
if (typeof options === "boolean") {
return { capture: options };
}
else if (options == null) {
return {};
}
else {
return options;
}
}
function isEventTarget(value) {
return (value != null &&
typeof value.addEventListener === "function" &&
typeof value.removeEventListener === "function" &&
typeof value.dispatchEvent === "function");
}
function setEventProperty(ev, key, value) {
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
}
/**
* A function to reconstruct an array of every listener given a context and a host element.
*
* @remarks
* This function exploits the fact that contexts retain their nearest ancestor host element. We can determine all the contexts which are directly listening to an element by traversing up the context tree and checking that the host element passed in matches the context’s host property.
*/
function getListeners(ctx, host) {
let listeners;
while (ctx !== undefined && ctx._ho === host) {
if (typeof ctx._ls !== "undefined") {
listeners = (listeners || []).concat(ctx._ls);
}
ctx = ctx._pa;
}
return listeners;
}
function clearEventListeners(ctx) {
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) {
for (const value of getChildValues(ctx._el)) {
if (isEventTarget(value)) {
for (const record of ctx._ls) {
value.removeEventListener(record.type, record.callback, record.options);
}
}
}
ctx._ls = undefined;
}
}
// CONTEXT FLAGS
/**
* A flag which is set when the component is being updated by the parent and cleared when the component has committed. Used to determine whether the nearest host ancestor needs to be rearranged.
*/
const Updating = 1 << 0;
/**
* A flag which is set when the component is called or stepped through. It is used to ensure that a component which synchronously triggers a second update in the course of rendering does not cause an infinite loop or a generator error.
*/
const Stepping = 1 << 1;
/**
* A flag used to make sure multiple values are not pulled from context prop iterators without a yield.
*/
const Iterating = 1 << 2;
/**
* A flag used by async generator components in conjunction with the onProps functions (_op) to mark whether new props can be pulled via the context iterator methods.
*/
const Available = 1 << 3;
/**
* A flag which is set when generator components return. Set whenever an iterator returns an iteration with the done property set to true. Finished components will stick to their last rendered value and ignore further updates.
*/
const Finished = 1 << 4;
/**
* A flag which is set when the component is unmounted. Unmounted components are no longer in the element tree, and cannot run or refresh.
*/
const Unmounted = 1 << 5;
/**
* A flag which indicates that the component is a sync generator component.
*/
const SyncGen = 1 << 6;
/**
* A flag which indicates that the component is an async generator component.
*/
const AsyncGen = 1 << 7;
/**
* A class which is instantiated and passed to every component as its this value. Contexts form a tree just like elements and all components in the element tree are connected via contexts. Components can use this tree to communicate data upwards via events and downwards via provisions.
* @typeparam TProps - The expected shape of the props passed to the component. Used to strongly type the Context iterator methods.
* @typeparam TResult - The readable element value type. It is used in places such as the return value of refresh and the argument passed to schedule/cleanup callbacks.
*/
class Context {
/**
* @internal
*/
constructor(renderer, root, host, parent, scope, el) {
this._f = 0;
this._re = renderer;
this._rt = root;
this._ho = host;
this._pa = parent;
this._sc = scope;
this._el = el;
}
consume(key) {
for (let parent = this._pa; parent !== undefined; parent = parent._pa) {
if (typeof parent._ps === "object" && parent._ps.has(key)) {
return parent._ps.get(key);
}
}
}
provide(key, value) {
if (typeof this._ps === "undefined") {
this._ps = new Map();
}
this._ps.set(key, value);
}
/**
* The current props of the associated element.
*
* @remarks
* Typically, you should read props either via the first parameter of the component or via the context iterator methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get props() {
return this._el.props;
}
/**
* The current value of the associated element.
*
* @remarks
* Typically, you should read values via refs, generator yield expressions, or the refresh, schedule or cleanup methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get value() {
return this._re.read(getValue(this._el));
}
*[Symbol.iterator]() {
const el = this._el;
while (!(this._f & Unmounted)) {
if (this._f & Iterating) {
throw new Error("You must yield for each iteration of this.");
}
else if (this._f & AsyncGen) {
throw new Error("Use for await...of in async generator components.");
}
this._f |= Iterating;
yield el.props;
}
}
async *[Symbol.asyncIterator]() {
const el = this._el;
do {
if (this._f & Iterating) {
throw new Error("You must yield for each iteration of this.");
}
else if (this._f & SyncGen) {
throw new Error("Use for...of in sync generator components.");
}
this._f |= Iterating;
if (this._f & Available) {
this._f &= ~Available;
yield el.props;
}
else {
const props = await new Promise((resolve) => (this._op = resolve));
if (!(this._f & Unmounted)) {
yield props;
}
}
} while (!(this._f & Unmounted));
}
/**
* Re-executes the component.
*
* @returns The rendered value of the component or a promise of the rendered value if the component or its children execute asynchronously.
*
* @remarks
* The refresh method works a little differently for async generator components, in that it will resume the Context async iterator rather than resuming execution. This is because async generator components are perpetually resumed independent of updates/refresh.
*/
refresh() {
if (this._f & (Stepping | Unmounted)) {
// TODO: log an error
return this._re.read(undefined);
}
this._f &= ~Updating;
resume(this);
return this._re.read(run(this));
}
/**
* Registers a callback which fires when the component commits. Will only fire once per callback and update.
*/
schedule(callback) {
if (typeof this._ss === "undefined") {
this._ss = new Set();
}
this._ss.add(callback);
}
/**
* Registers a callback which fires when the component unmounts. Will only fire once per callback.
*/
cleanup(callback) {
if (typeof this._cs === "undefined") {
this._cs = new Set();
}
this._cs.add(callback);
}
addEventListener(type, listener, options) {
if (listener == null) {
return;
}
else if (typeof this._ls === "undefined") {
this._ls = [];
}
options = normalizeOptions(options);
let callback;
if (typeof listener === "object") {
callback = () => listener.handleEvent.apply(listener, arguments);
}
else {
callback = listener;
}
const record = { type, callback, listener, options };
if (options.once) {
const self = this;
record.callback = function () {
if (typeof self._ls !== "undefined") {
self._ls = self._ls.filter((record1) => record !== record1);
if (self._ls.length === 0) {
self._ls = undefined;
}
}
return callback.apply(this, arguments);
};
}
if (this._ls.some((record1) => record.type === record1.type &&
record.listener === record1.listener &&
!record.options.capture === !record1.options.capture)) {
return;
}
this._ls.push(record);
for (const value of getChildValues(this._el)) {
if (isEventTarget(value)) {
value.addEventListener(record.type, record.callback, record.options);
}
}
}
removeEventListener(type, listener, options) {
if (listener == null || typeof this._ls === "undefined") {
return;
}
const options1 = normalizeOptions(options);
const i = this._ls.findIndex((record) => record.type === type &&
record.listener === listener &&
!record.options.capture === !options1.capture);
if (i === -1) {
return;
}
const record = this._ls[i];
this._ls.splice(i, 1);
for (const value of getChildValues(this._el)) {
if (isEventTarget(value)) {
value.removeEventListener(record.type, record.callback, record.options);
}
}
if (this._ls.length === 0) {
this._ls = undefined;
}
}
dispatchEvent(ev) {
const path = [];
for (let parent = this._pa; parent !== undefined; parent = parent._pa) {
path.push(parent);
}
let stopped = false;
const stopImmediatePropagation = ev.stopImmediatePropagation;
setEventProperty(ev, "stopImmediatePropagation", () => {
stopped = true;
return stopImmediatePropagation.call(ev);
});
setEventProperty(ev, "target", this);
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
try {
for (let i = path.length - 1; i >= 0; i--) {
const et = path[i];
if (typeof et._ls !== "undefined") {
setEventProperty(ev, "currentTarget", et);
for (const record of et._ls) {
if (record.type === ev.type && record.options.capture) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
if (typeof this._ls !== "undefined") {
setEventProperty(ev, "eventPhase", AT_TARGET);
setEventProperty(ev, "currentTarget", this);
for (const record of this._ls) {
if (record.type === ev.type) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
if (ev.bubbles) {
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
for (const et of path) {
if (typeof et._ls !== "undefined") {
setEventProperty(ev, "currentTarget", et);
for (const record of et._ls) {
if (record.type === ev.type && !record.options.capture) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
}
return !ev.defaultPrevented;
}
finally {
setEventProperty(ev, "eventPhase", NONE);
setEventProperty(ev, "currentTarget", null);
}
}
}
/*** PRIVATE CONTEXT FUNCTIONS ***/
/**
* Called to make props available to the Context async iterator for async generator components.
*/
function resume(ctx) {
if (typeof ctx._op === "function") {
ctx._op(ctx._el.props);
ctx._op = undefined;
}
else {
ctx._f |= Available;
}
}
// NOTE: The functions run, step and advance work together to implement the async queueing behavior of components. The run function calls the step function, which returns two results in a tuple. The first result, called “block,” is a possible promise which settles when the component can accept new updates, and represents the duration during which the component is blocked from accepting new updates. The second result, called “value,” is the actual result of the update. The run function caches block/value from the step function on the context, according to whether the component is currently blocked. The “inflight” block/value properties are the currently executing update, and the “enqueued” block/value properties are promises which represent the next step. Lastly, the run function calls the advance function in a Promise.prototype.finally callback to allow new steps to be enqueued.
/**
* Enqueues and executes the component associated with the context.
*/
function run(ctx) {
if (typeof ctx._ib === "undefined") {
try {
let [block, value] = step(ctx);
if (isPromiseLike(block)) {
ctx._ib = block
.catch((err) => {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
})
.finally(() => advance(ctx));
}
if (isPromiseLike(value)) {
ctx._iv = value;
ctx._el._inf = value;
}
return value;
}
catch (err) {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
throw err;
}
}
else if (ctx._f & AsyncGen) {
return ctx._iv;
}
else if (typeof ctx._eb === "undefined") {
let resolve;
ctx._eb = ctx._ib
.then(() => {
try {
const [block, value] = step(ctx);
resolve(value);
if (isPromiseLike(value)) {
ctx._el._inf = value;
}
if (isPromiseLike(block)) {
return block.catch((err) => {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
});
}
}
catch (err) {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
}
})
.finally(() => advance(ctx));
ctx._ev = new Promise((resolve1) => (resolve = resolve1));
}
return ctx._ev;
}
/**
* The step function is responsible for executing the component and handling all the different component types.
*
* @returns A tuple [block, value]
* block - A possible promise which represents the duration during which the component is blocked from updating.
* value - The actual rendered value of the children.
*
* @remarks
* Each component type will block/unblock according to the type of the component.
* Sync function components never block and will transparently pass updates to children.
* Async function components and async generator components block while executing itself, but will not block for async children.
* Sync generator components block while any children are executing, because they are expected to only resume when they’ve actually rendered. Additionally, they have no mechanism for awaiting async children.
*/
function step(ctx) {
const el = ctx._el;
if (ctx._f & Finished) {
return [undefined, getValue(el)];
}
let initial = false;
try {
ctx._f |= Stepping;
if (typeof ctx._it === "undefined") {
initial = true;
clearEventListeners(ctx);
const result = el.tag.call(ctx, el.props);
if (isIteratorLike(result)) {
ctx._it = result;
}
else if (isPromiseLike(result)) {
// async function component
const result1 = result instanceof Promise ? result : Promise.resolve(result);
const block = result1;
const value = result1.then((result) => updateCtxChildren(ctx, result));
return [block, value];
}
else {
// sync function component
return [undefined, updateCtxChildren(ctx, result)];
}
}
}
finally {
ctx._f &= ~Stepping;
}
let oldValue;
if (typeof ctx._el._inf === "object") {
oldValue = ctx._el._inf.then((value) => ctx._re.read(value), () => ctx._re.read(undefined));
}
else if (initial) {
oldValue = ctx._re.read(undefined);
}
else {
oldValue = ctx._re.read(getValue(el));
}
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.next(oldValue);
}
catch (err) {
ctx._f |= Finished;
throw err;
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
// async generator component
if (initial) {
ctx._f |= AsyncGen;
}
const block = iteration;
const value = iteration.then((iteration) => {
if (!(ctx._f & Iterating)) {
ctx._f &= ~Available;
}
ctx._f &= ~Iterating;
if (iteration.done) {
ctx._f |= Finished;
}
try {
const value = updateCtxChildren(ctx, iteration.value);
if (isPromiseLike(value)) {
return value.catch((err) => handleChildError(ctx, err));
}
return value;
}
catch (err) {
return handleChildError(ctx, err);
}
}, (err) => {
ctx._f |= Finished;
throw err;
});
return [block, value];
}
// sync generator component
if (initial) {
ctx._f |= SyncGen;
}
ctx._f &= ~Iterating;
if (iteration.done) {
ctx._f |= Finished;
}
let value;
try {
value = updateCtxChildren(ctx, iteration.value);
if (isPromiseLike(value)) {
value = value.catch((err) => handleChildError(ctx, err));
}
}
catch (err) {
value = handleChildError(ctx, err);
}
if (isPromiseLike(value)) {
return [value.catch(() => { }), value];
}
return [undefined, value];
}
/**
* @remarks
* Called when the inflight block promise settles.
*/
function advance(ctx) {
// _ib: inflightBlock
// _iv: inflightValue
// _eb: enqueuedBlock
// _ev: enqueuedValue
ctx._ib = ctx._eb;
ctx._iv = ctx._ev;
ctx._eb = undefined;
ctx._ev = undefined;
if (ctx._f & AsyncGen && !(ctx._f & Finished)) {
run(ctx);
}
}
// TODO: generator components which throw errors should be recoverable
function handleChildError(ctx, err) {
if (ctx._f & Finished ||
typeof ctx._it !== "object" ||
typeof ctx._it.throw !== "function") {
throw err;
}
resume(ctx);
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.throw(err);
}
catch (err) {
ctx._f |= Finished;
throw err;
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
return iteration.then((iteration) => {
if (iteration.done) {
ctx._f |= Finished;
}
return updateCtxChildren(ctx, iteration.value);
}, (err) => {
ctx._f |= Finished;
throw err;
});
}
if (iteration.done) {
ctx._f |= Finished;
}
return updateCtxChildren(ctx, iteration.value);
}
function propagateError(ctx, err) {
if (ctx === undefined) {
throw err;
}
let result;
try {
result = handleChildError(ctx, err);
}
catch (err) {
return propagateError(ctx._pa, err);
}
if (isPromiseLike(result)) {
return result.catch((err) => propagateError(ctx._pa, err));
}
return result;
}
function updateCtx(ctx) {
ctx._f |= Updating;
resume(ctx);
return run(ctx);
}
function updateCtxChildren(ctx, children) {
let child;
if (isNonStringIterable(children)) {
child = createElement(Fragment, null, children);
}
else {
child = children;
}
return updateChild(ctx._re, ctx._rt, // root
ctx._ho, // host
ctx, ctx._sc, // scope
ctx._el, // element
child);
}
function commitCtx(ctx, value) {
if (ctx._f & Unmounted) {
return;
}
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) {
for (const child of wrap(value)) {
if (isEventTarget(child)) {
for (const record of ctx._ls) {
child.addEventListener(record.type, record.callback, record.options);
}
}
}
}
if (!(ctx._f & Updating)) {
const listeners = getListeners(ctx._pa, ctx._ho);
if (listeners !== undefined && listeners.length > 0) {
for (let i = 0; i < listeners.length; i++) {
const record = listeners[i];
for (const v of wrap(value)) {
if (isEventTarget(v)) {
v.addEventListener(record.type, record.callback, record.options);
}
}
}
}
// TODO: we don’t need to call arrange if none of the nodes have changed or moved
const host = ctx._ho;
if (host._f & Committed) {
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, getChildValues(host));
}
ctx._re.complete(ctx._rt);
}
ctx._f &= ~Updating;
if (typeof ctx._ss === "object" && ctx._ss.size > 0) {
// NOTE: We have to clear the set of callbacks before calling them, because a callback which refreshes the component would otherwise cause a stack overflow.
const callbacks = Array.from(ctx._ss);
ctx._ss.clear();
const value1 = ctx._re.read(value);
for (const callback of callbacks) {
callback(value1);
}
}
}
// TODO: async unmounting
function unmountCtx(ctx) {
ctx._f |= Unmounted;
clearEventListeners(ctx);
if (typeof ctx._cs === "object") {
const value = ctx._re.read(getValue(ctx._el));
for (const cleanup of ctx._cs) {
cleanup(value);
}
ctx._cs = undefined;
}
if (!(ctx._f & Finished)) {
ctx._f |= Finished;
resume(ctx);
if (typeof ctx._it === "object" && typeof ctx._it.return === "function") {
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.return();
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
iteration.catch((err) => propagateError(ctx._pa, err));
}
}
}
}
var crank = require('./crank.js');
exports.Context = Context;
exports.Copy = Copy;
exports.Element = Element;
exports.Fragment = Fragment;
exports.Portal = Portal;
exports.Raw = Raw;
exports.Renderer = Renderer;
exports.cloneElement = cloneElement;
exports.createElement = createElement;
exports.isElement = isElement;
exports.Context = crank.Context;
exports.Copy = crank.Copy;
exports.Element = crank.Element;
exports.Fragment = crank.Fragment;
exports.Portal = crank.Portal;
exports.Raw = crank.Raw;
exports.Renderer = crank.Renderer;
exports.cloneElement = crank.cloneElement;
exports.createElement = crank.createElement;
exports.isElement = crank.isElement;
//# sourceMappingURL=index.js.map

@@ -1,6 +0,2 @@

import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./crank.js";
export declare class DOMRenderer extends Renderer<Node, string | undefined> {

@@ -15,1 +11,7 @@ render(children: Children, root: Node, ctx?: Context): Promise<ElementValue<Node>> | ElementValue<Node>;

export declare const renderer: DOMRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}

@@ -1,2 +0,3 @@

import { Renderer, Portal } from './index.js';
/// <reference types="./dom.d.ts" />
import { Renderer, Portal } from './crank.js';

@@ -3,0 +4,0 @@ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";

@@ -1,10 +0,6 @@

import { Element, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Element, ElementValue, Renderer } from "./crank.js";
interface Node {
value: string;
}
export declare class StringRenderer extends Renderer<Node | string, undefined, unknown, string> {
export declare class HTMLRenderer extends Renderer<Node | string, undefined, unknown, string> {
create(): Node;

@@ -15,3 +11,13 @@ escape(text: string): string;

}
export declare const renderer: StringRenderer;
export declare const renderer: HTMLRenderer;
/**
* @deprecated
*/
export declare const StringRenderer: typeof HTMLRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}
export {};

@@ -1,2 +0,3 @@

import { Renderer, Portal } from './index.js';
/// <reference types="./html.d.ts" />
import { Renderer, Portal } from './crank.js';

@@ -79,3 +80,3 @@ const voidTags = new Set([

}
class StringRenderer extends Renderer {
class HTMLRenderer extends Renderer {
create() {

@@ -122,5 +123,9 @@ return { value: "" };

}
const renderer = new StringRenderer();
const renderer = new HTMLRenderer();
/**
* @deprecated
*/
const StringRenderer = HTMLRenderer;
export { StringRenderer, renderer };
export { HTMLRenderer, StringRenderer, renderer };
//# sourceMappingURL=html.js.map

@@ -1,498 +0,1 @@

/**
* Represents all valid values which can be used for the tag of an element.
*
* @remarks
* Elements whose tags are strings or symbols are called “host” or “intrinsic” elements, and their behavior is determined by the renderer, while elements whose tags are components are called “component” elements, and their behavior is determined by the execution of the component.
*/
export declare type Tag = string | symbol | Component;
/**
* Maps the tag of an element to its expected props.
*
* @typeparam TTag - The element’s tag.
*/
export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown;
/***
* SPECIAL TAGS
* Crank provides a couple tags which have special meaning for the renderer.
***/
/**
* A special element tag for grouping multiple children within a parent.
*
* @remarks
* All iterables which appear in the element tree are implicitly wrapped in fragments. The Fragment tag is just the empty string, and you can use the empty string in createElement calls/transpiler options to avoid having to reference the Fragment export directly.
*/
export declare const Fragment = "";
export declare type Fragment = typeof Fragment;
/**
* A special element tag for creating a new element subtree with a different root, passed via the root prop.
*
* @remarks
* This element tag is useful for creating element trees with multiple roots. Renderer.prototype.render will implicitly wrap the children which have been passed in in an implicit Portal element.
*/
export declare const Portal: any;
export declare type Portal = typeof Portal;
/**
* A special element tag which copies whatever child appeared previously in the element’s position.
*
* @remarks
* Copy elements are useful when you want to prevent a subtree from updating as a performance optimization.
*/
export declare const Copy: any;
export declare type Copy = typeof Copy;
/**
* A special element tag for injecting raw nodes into an element tree via its value prop.
*
* @remarks
* If the value prop is a string, Renderer.prototype.parse will be called on the string and the element’s rendered value will be the result.
*/
export declare const Raw: any;
export declare type Raw = typeof Raw;
/**
* Describes all valid singular values of an element tree.
*
* @remarks
* Arbitrary objects can also be safely rendered but they will be converted to a string using the toString method. We exclude them from this type to catch potential type errors.
*/
export declare type Child = Element | string | number | boolean | null | undefined;
interface ChildIterable extends Iterable<Child | ChildIterable> {
}
/**
* Describes all valid values of an element tree, including arbitrarily nested iterables of such values.
*/
export declare type Children = Child | ChildIterable;
/**
* Represents all functions which can be used as a component.
*
* @typeparam TProps - The expected props for the component.
*
* @remarks
* The return type of iterator objects returned from components has to be void because typescript will infer most generators as having a void return type.
*/
export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>;
/**
* All nodes in the element tree are narrowed from the union in Child to NarrowedChild. This greatly simplifies element diffing.
*/
declare type NarrowedChild = Element | string | undefined;
declare type Key = unknown;
declare const ElementSymbol: unique symbol;
/**
* Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by special classes called renderers to produce and manage stateful nodes.
*
* @typeparam TTag - the type of the tag of the element.
*
* @example
* // specific element types
* let div: Element<"div">;
* let portal: Element<Portal>;
* let myEl: Element<MyComponent>;
*
* // general element types
* let host: Element<string | symbol>;
* let component: Element<Component>;
*
* @remarks
* Typically, you use the createElement function to create elements and not this class directly.
*/
export declare class Element<TTag extends Tag = Tag> {
/**
* @internal
* A unique symbol to identify elements as elements across versions and realms, and to protect against basic injection attacks.
* https://overreacted.io/why-do-react-elements-have-typeof-property/
*/
$$typeof: typeof ElementSymbol;
/**
* @internal
* flags - A bitmask. See ELEMENT FLAGS.
*/
_f: number;
/**
* The tag of the element. Can be a function, string or symbol depending on the kind of element.
*/
tag: TTag;
/**
* An object containing the “properties” of an element. These correspond to the attributes of the element when using JSX syntax.
*
* @remarks
* The props of an object are passed to most renderer host methods, and as the first argument to components.
*/
props: TagProps<TTag>;
/**
* A value which uniquely identifies an element from its siblings so that it can be added/updated/moved/removed by the identity of the key rather than its position within the parent.
*
* @remarks
* Passed to the element as the prop "crank-key".
*/
key: Key;
/**
* A callback which is called with the element’s value when the value is committed.
*
* @remarks
* Passed to the element as the prop "crank-ref".
*/
ref: Function | undefined;
/**
* @internal
* children - The rendered children of the element.
*/
_ch: Array<NarrowedChild> | NarrowedChild;
/**
* @internal
* node - The node associated with the element.
*
* @remarks
* Set by Renderer.prototype.create when the component is mounted. This property will only be set for host elements.
*/
_n: any;
/**
* @internal
* context - The Context object associated with this element.
*
* @remarks
* Created and assigned by the Renderer for component elements when it mounts the element tree.
*/
_ctx: Context<TagProps<TTag>> | undefined;
/**
* @internal
* fallback - The value of the element while it has never committed.
*
* @remarks
* If an element takes place of a previously rendered value but renders asynchronously, this property is set to the previously rendered value until the element commits. This allows asynchronously updating element trees to show something while pending.
*/
_fb: any;
/**
* @internal
* inflightPromise - The current async run of the element.
*
* @remarks
* This value is used to make sure element copies do not fulfill immediately, to set the fallback of the next element when the previous element commits, and as the yield value of async generator components with async children. It is unset when the element is committed.
*/
_inf: Promise<any> | undefined;
/**
* @internal
* onNewValues - the resolve function of a promise which represents the next child result. See the chase function for more info.
*/
_onv: Function | undefined;
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: Function | undefined);
}
export declare function isElement(value: any): value is Element;
/**
* Creates an element with the specified tag, props and children.
*
* @remarks
* This function is usually used as a transpilation target for JSX transpilers, but it can also be called directly. It additionally extracts the crank-key and crank-ref props so they aren’t accessible to the renderer methods or components, and assigns the children prop according to the remaining arguments passed to the function.
*/
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>;
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, children?: unknown): Element<TTag>;
/**
* Clones a given element.
*
* @remarks
* Mainly used internally to make sure we don’t accidentally reuse elements in an element tree, because elements are directly mutated by the renderer.
*/
export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>;
/*** ELEMENT VALUE UTILITIES ***/
/**
* A helper type which repesents all the possible rendered values of an element.
*
* @typeparam TNode - The node type for the element assigned by the renderer.
*
* @remarks
* When asking the question, what is the value of a specific element, the answer varies depending on the type of the element. For host or Raw elements, the answer is simply the nodes (DOM nodes in the case of the DOM renderer) created for the element. For fragments, the values are usually an array of nodes. For portals, the value is undefined, because a Portal element’s root and children are opaque to parents. For components, the value can be any of the above, because the value of a component is determined by its children. Rendered values can also be strings or arrays of nodes and strings, in the case of a component or fragment with strings for children. All of these possible values are reflected in this utility type.
*/
export declare type ElementValue<TNode> = Array<TNode | string> | TNode | string | undefined;
/**
* An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment.
*
* @typeparam TNode - The type of the node for a specific rendering environment.
* @typeparam TScope - Data which is passed down the tree.
* @typeparam TRoot - The type of the root for a specific rendering environment.
* @typeparam TResult - The type of the exposed values.
*/
export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> {
/**
* @internal
* A weakmap which stores element trees by root.
*/
_cache: WeakMap<object, Element<Portal>>;
constructor();
/**
* Renders an element tree into a specific root.
*
* @param children - An element tree. You can render null with a previously used root to delete the previously rendered element tree from the cache.
* @param root - The node to be rendered into. The renderer will cache element trees per root.
* @param ctx - An optional context that will be the ancestor context of all elements in the tree. Useful for connecting renderers which call each other so that events/provisions properly propagate. The context for a given root must be the same or an error will be thrown.
*
* @returns The read result of rendering the children, or a possible promise of the read result if the element tree renders asynchronously.
*/
render(children: Children, root?: TRoot | undefined, ctx?: Context | undefined): Promise<TResult> | TResult;
/**
* Called when an element’s value is exposed via render, schedule, refresh, refs, or generator yield expressions.
*
* @param value - The value of the element being read. Can be a node, a string, undefined, or an array of nodes and strings, depending on the element.
* @returns Varies according to the specific renderer subclass. By default, it exposes the element’s value.
*
* @remarks
* This is useful for renderers which don’t want to expose their internal nodes. For instance, the HTML renderer will convert all internal nodes to strings.
*
*/
read(value: ElementValue<TNode>): TResult;
/**
* Called in a preorder traversal for each host element.
*
* @remarks
* Useful for passing data down the element tree. For instance, the DOM renderer uses this method to keep track of whether we’re in an SVG subtree.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns The scope to be passed to create and scope for child host elements.
*
* @remarks
* This method sets the scope for child host elements, not the current host element.
*/
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope;
/**
* Called for each string in an element tree.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The escaped string.
*
* @remarks
* Rather than returning text nodes for whatever environment we’re rendering to, we defer that step for Renderer.prototype.arrange. We do this so that adjacent strings can be concatenated and the actual element tree can be rendered in a normalized form.
*/
escape(text: string, _scope: TScope): string;
/**
* Called for each Raw element whose value prop is a string.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The parsed node or string.
*/
parse(text: string, _scope: TScope): TNode | string;
/**
* Called for each host element when it is committed for the first time.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns A “node” which determines the value of the host element.
*/
create(_el: Element<string | symbol>, _scope: TScope): TNode;
/**
* Called for each host element when it is committed.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param scope - The current scope.
*
* @returns The return value is ignored.
*
* @remarks
* Used to mutate the node associated with an element when new props are passed.
*/
patch(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called for each host element after its children have committed with the actual values of the children.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param children - An array of nodes and strings from child elements.
*
* @returns The return value is ignored.
*
* @remarks
* This method is also called by child components contexts as the last step of a refresh.
*/
arrange(_el: Element<string | symbol>, _parent: TNode | TRoot, _children: Array<TNode | string>): unknown;
/**
* Called for each host element when it is unmounted.
*
* @param tag - The tag of the host element.
* @param props - The props of the host element.
* @param node - The node associated with the host element.
*
* @returns The return value is ignored.
*/
dispose(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called at the end of the rendering process for each root of the tree.
*
* @param root - The root prop passed to portals or the render method.
*
* @returns The return value is ignored.
*/
complete(_root: TRoot): unknown;
}
/**
* A map of event type strings to Event subclasses. Can be extended via TypeScript module augmentation to have strongly typed event listeners.
*/
export interface EventMap {
[type: string]: Event;
}
declare type MappedEventListener<T extends string> = (ev: EventMap[T]) => unknown;
declare type MappedEventListenerOrEventListenerObject<T extends string> = MappedEventListener<T> | {
handleEvent: MappedEventListener<T>;
};
interface EventListenerRecord {
type: string;
listener: MappedEventListenerOrEventListenerObject<any>;
callback: MappedEventListener<any>;
options: AddEventListenerOptions;
}
/**
* An interface which can be extended to provide strongly typed provisions (see Context.prototype.get and Context.prototype.set)
*/
export interface ProvisionMap {
}
/**
* A class which is instantiated and passed to every component as its this value. Contexts form a tree just like elements and all components in the element tree are connected via contexts. Components can use this tree to communicate data upwards via events and downwards via provisions.
* @typeparam TProps - The expected shape of the props passed to the component. Used to strongly type the Context iterator methods.
* @typeparam TResult - The readable element value type. It is used in places such as the return value of refresh and the argument passed to schedule/cleanup callbacks.
*/
export declare class Context<TProps = any, TResult = any> implements EventTarget {
/**
* @internal
* flags - A bitmask. See CONTEXT FLAGS above.
*/
_f: number;
/**
* @internal
* renderer - The renderer which created this context.
*/
_re: Renderer<unknown, unknown, unknown, TResult>;
/**
* @internal
* root - The root node set by an ancestor’s Portal prop.
*/
_rt: unknown;
/**
* @internal
* host - The nearest ancestor host element.
* @remarks
* When refresh is called, the host element will be arranged as the last step of the commit, to make sure the parent’s children properly reflects the components’s children.
*/
_ho: Element<string | symbol>;
/**
* @internal
* parent - The parent context.
*/
_pa: Context<unknown, TResult> | undefined;
/**
* @internal
* scope - The value of the scope at the point of element’s creation.
*/
_sc: unknown;
/**
* @internal
* el - The associated component element.
*/
_el: Element<Component>;
/**
* @internal
* iterator - The iterator returned by the component function.
*/
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined;
/**
* @internal
* onProps - A callback used in conjunction with the Available flag to implement the props async iterator. See the Symbol.asyncIterator method and the resume function.
*/
_op: ((props: any) => unknown) | undefined;
/**
* @internal
* inflightBlock
*/
_ib: Promise<unknown> | undefined;
/**
* @internal
* inflightValue
*/
_iv: Promise<ElementValue<any>> | undefined;
/**
* @internal
* enqueuedBlock
*/
_eb: Promise<unknown> | undefined;
/**
* @internal
* enqueuedValue
*/
_ev: Promise<ElementValue<any>> | undefined;
/**
* @internal
* listeners - An array of event listeners added to the context via Context.prototype.addEventListener
*/
_ls: Array<EventListenerRecord> | undefined;
/**
* @internal
* provisions - A map of values which can be set via Context.prototype.set and read from child contexts via Context.prototype.get
*/
_ps: Map<unknown, unknown> | undefined;
/**
* @internal
* schedules - a set of callbacks registered via Context.prototype.schedule, which fire when the component has committed.
*/
_ss: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
* cleanups - a set of callbacks registered via Context.prototype.cleanup, which fire when the component has unmounted.
*/
_cs: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
*/
constructor(renderer: Renderer<unknown, unknown, unknown, TResult>, root: unknown, host: Element<string | symbol>, parent: Context<unknown, TResult> | undefined, scope: unknown, el: Element<Component>);
consume<TKey extends keyof ProvisionMap>(key: TKey): ProvisionMap[TKey];
consume(key: unknown): any;
provide<TKey extends keyof ProvisionMap>(key: TKey, value: ProvisionMap[TKey]): void;
provide(key: unknown, value: any): void;
/**
* The current props of the associated element.
*
* @remarks
* Typically, you should read props either via the first parameter of the component or via the context iterator methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get props(): TProps;
/**
* The current value of the associated element.
*
* @remarks
* Typically, you should read values via refs, generator yield expressions, or the refresh, schedule or cleanup methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get value(): TResult;
[Symbol.iterator](): Generator<TProps>;
[Symbol.asyncIterator](): AsyncGenerator<TProps>;
/**
* Re-executes the component.
*
* @returns The rendered value of the component or a promise of the rendered value if the component or its children execute asynchronously.
*
* @remarks
* The refresh method works a little differently for async generator components, in that it will resume the Context async iterator rather than resuming execution. This is because async generator components are perpetually resumed independent of updates/refresh.
*/
refresh(): Promise<TResult> | TResult;
/**
* Registers a callback which fires when the component commits. Will only fire once per callback and update.
*/
schedule(callback: (value: TResult) => unknown): void;
/**
* Registers a callback which fires when the component unmounts. Will only fire once per callback.
*/
cleanup(callback: (value: TResult) => unknown): void;
addEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: boolean | AddEventListenerOptions): void;
removeEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: EventListenerOptions | boolean): void;
dispatchEvent(ev: Event): boolean;
}
declare global {
module JSX {
interface IntrinsicElements {
[tag: string]: any;
}
interface ElementChildrenAttribute {
children: {};
}
}
}
export {};
export * from "./crank.js";

@@ -1,1493 +0,3 @@

/*** UTILITIES ***/
function wrap(value) {
return value === undefined ? [] : Array.isArray(value) ? value : [value];
}
function unwrap(arr) {
return arr.length > 1 ? arr : arr[0];
}
function isIterable(value) {
return value != null && typeof value[Symbol.iterator] === "function";
}
function isNonStringIterable(value) {
return typeof value !== "string" && isIterable(value);
}
function isIteratorLike(value) {
return value != null && typeof value.next === "function";
}
function isPromiseLike(value) {
return value != null && typeof value.then === "function";
}
/***
* SPECIAL TAGS
* Crank provides a couple tags which have special meaning for the renderer.
***/
/**
* A special element tag for grouping multiple children within a parent.
*
* @remarks
* All iterables which appear in the element tree are implicitly wrapped in fragments. The Fragment tag is just the empty string, and you can use the empty string in createElement calls/transpiler options to avoid having to reference the Fragment export directly.
*/
const Fragment = "";
// NOTE: We assert the following symbol tags to be any because typescript support for symbol tags in JSX does not exist yet.
// https://github.com/microsoft/TypeScript/issues/38367
/**
* A special element tag for creating a new element subtree with a different root, passed via the root prop.
*
* @remarks
* This element tag is useful for creating element trees with multiple roots. Renderer.prototype.render will implicitly wrap the children which have been passed in in an implicit Portal element.
*/
const Portal = Symbol.for("crank.Portal");
/**
* A special element tag which copies whatever child appeared previously in the element’s position.
*
* @remarks
* Copy elements are useful when you want to prevent a subtree from updating as a performance optimization.
*/
const Copy = Symbol.for("crank.Copy");
/**
* A special element tag for injecting raw nodes into an element tree via its value prop.
*
* @remarks
* If the value prop is a string, Renderer.prototype.parse will be called on the string and the element’s rendered value will be the result.
*/
const Raw = Symbol.for("crank.Raw");
function narrow(child) {
if (child == null || typeof child === "boolean") {
return undefined;
}
else if (typeof child === "string" || isElement(child)) {
return child;
}
else {
return child.toString();
}
}
const ElementSymbol = Symbol.for("crank.Element");
/*** ELEMENT FLAGS ***/
/**
* A flag which is set when the component has been mounted. Used mainly to detect whether an element is being reused so that it can be cloned.
*/
const Mounted = 1 << 0;
/**
* A flag which is set when the component has committed at least once.
*/
const Committed = 1 << 1;
// NOTE: To save on filesize, we mangle the internal properties of Crank classes by hand. These internal properties are prefixed with an underscore. Refer to their definitions to see their unabbreviated names.
// NOTE: to maximize compatibility between Crank versions, starting with 0.2.0, the $$typeof property and the non-internal properties will not be changed, and any change to these properties will be considered a breaking change. This is to ensure maximum compatibility between components which use different Crank versions.
/**
* Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by special classes called renderers to produce and manage stateful nodes.
*
* @typeparam TTag - the type of the tag of the element.
*
* @example
* // specific element types
* let div: Element<"div">;
* let portal: Element<Portal>;
* let myEl: Element<MyComponent>;
*
* // general element types
* let host: Element<string | symbol>;
* let component: Element<Component>;
*
* @remarks
* Typically, you use the createElement function to create elements and not this class directly.
*/
class Element {
constructor(tag, props, key, ref) {
this.$$typeof = ElementSymbol;
this._f = 0;
this.tag = tag;
this.props = props;
this.key = key;
this.ref = ref;
this._ch = undefined;
this._n = undefined;
this._ctx = undefined;
}
}
function isElement(value) {
return value != null && value.$$typeof === ElementSymbol;
}
function createElement(tag, props, children) {
let key;
let ref;
const props1 = {};
if (props != null) {
for (const name in props) {
if (name === "crank-key") {
// NOTE: We have to make sure we don’t assign null to the key because we don’t check for null keys in the diffing functions.
if (props[name] != null) {
key = props[name];
}
}
else if (name === "crank-ref") {
if (typeof props["crank-ref"] === "function") {
ref = props[name];
}
}
else {
props1[name] = props[name];
}
}
}
let length = arguments.length;
if (length > 3) {
const children1 = [];
while (length-- > 2) {
children1[length - 2] = arguments[length];
}
props1.children = children1;
}
else if (length > 2) {
props1.children = children;
}
return new Element(tag, props1, key, ref);
}
/**
* Clones a given element.
*
* @remarks
* Mainly used internally to make sure we don’t accidentally reuse elements in an element tree, because elements are directly mutated by the renderer.
*/
function cloneElement(el) {
if (!isElement(el)) {
throw new TypeError("Cannot clone non-element");
}
return new Element(el.tag, { ...el.props }, el.key, el.ref);
}
/**
* Takes an array of element values and normalizes the output as an array of nodes and strings.
*
* @remarks
* Normalize will flatten only one level of nested arrays, because it is designed to be called once at each level of the tree. It will also concatenate adjacent strings and remove all undefineds.
*/
function normalize(values) {
const result = [];
let buffer;
for (let i = 0; i < values.length; i++) {
const value = values[i];
if (!value) ;
else if (typeof value === "string") {
buffer = (buffer || "") + value;
}
else if (!Array.isArray(value)) {
if (buffer) {
result.push(buffer);
buffer = undefined;
}
result.push(value);
}
else {
for (let j = 0; j < value.length; j++) {
const value1 = value[j];
if (!value1) ;
else if (typeof value1 === "string") {
buffer = (buffer || "") + value1;
}
else {
if (buffer) {
result.push(buffer);
buffer = undefined;
}
result.push(value1);
}
}
}
}
if (buffer) {
result.push(buffer);
}
return result;
}
/**
* Walks an element’s children to find its child values.
* @returns A normalized array of nodes and strings.
*/
function getChildValues(el) {
const values = [];
const children = wrap(el._ch);
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "string") {
values.push(child);
}
else if (typeof child !== "undefined") {
values.push(getValue(child));
}
}
return normalize(values);
}
/**
* Finds the value of the element according to its type.
* @returns The value of the element.
*/
function getValue(el) {
if (typeof el._fb !== "undefined") {
return el._fb;
}
else if (typeof el.tag === Portal) {
return undefined;
}
else if (typeof el.tag !== "function" && el.tag !== Fragment) {
return el._n;
}
return unwrap(getChildValues(el));
}
/**
* An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment.
*
* @typeparam TNode - The type of the node for a specific rendering environment.
* @typeparam TScope - Data which is passed down the tree.
* @typeparam TRoot - The type of the root for a specific rendering environment.
* @typeparam TResult - The type of the exposed values.
*/
class Renderer {
constructor() {
this._cache = new WeakMap();
}
/**
* Renders an element tree into a specific root.
*
* @param children - An element tree. You can render null with a previously used root to delete the previously rendered element tree from the cache.
* @param root - The node to be rendered into. The renderer will cache element trees per root.
* @param ctx - An optional context that will be the ancestor context of all elements in the tree. Useful for connecting renderers which call each other so that events/provisions properly propagate. The context for a given root must be the same or an error will be thrown.
*
* @returns The read result of rendering the children, or a possible promise of the read result if the element tree renders asynchronously.
*/
render(children, root, ctx) {
let portal;
if (typeof root === "object" && root !== null) {
portal = this._cache.get(root);
}
if (portal === undefined) {
portal = createElement(Portal, { children, root });
portal._ctx = ctx;
if (typeof root === "object" && root !== null && children != null) {
this._cache.set(root, portal);
}
}
else {
if (portal._ctx !== ctx) {
throw new Error("render must be called with the same context per root");
}
portal.props = { children, root };
if (typeof root === "object" && root !== null && children == null) {
this._cache.delete(root);
}
}
const value = update(this, root, portal, ctx, undefined, portal);
// NOTE: we return the read child values of the portal because portals themselves have no readable value.
if (isPromiseLike(value)) {
return value.then(() => {
const result = this.read(unwrap(getChildValues(portal)));
if (root == null) {
unmount(this, portal, undefined, portal);
}
return result;
});
}
const result = this.read(unwrap(getChildValues(portal)));
if (root == null) {
unmount(this, portal, undefined, portal);
}
return result;
}
/**
* Called when an element’s value is exposed via render, schedule, refresh, refs, or generator yield expressions.
*
* @param value - The value of the element being read. Can be a node, a string, undefined, or an array of nodes and strings, depending on the element.
* @returns Varies according to the specific renderer subclass. By default, it exposes the element’s value.
*
* @remarks
* This is useful for renderers which don’t want to expose their internal nodes. For instance, the HTML renderer will convert all internal nodes to strings.
*
*/
read(value) {
return value;
}
/**
* Called in a preorder traversal for each host element.
*
* @remarks
* Useful for passing data down the element tree. For instance, the DOM renderer uses this method to keep track of whether we’re in an SVG subtree.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns The scope to be passed to create and scope for child host elements.
*
* @remarks
* This method sets the scope for child host elements, not the current host element.
*/
scope(el, scope) {
return scope;
}
/**
* Called for each string in an element tree.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The escaped string.
*
* @remarks
* Rather than returning text nodes for whatever environment we’re rendering to, we defer that step for Renderer.prototype.arrange. We do this so that adjacent strings can be concatenated and the actual element tree can be rendered in a normalized form.
*/
escape(text, _scope) {
return text;
}
/**
* Called for each Raw element whose value prop is a string.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The parsed node or string.
*/
parse(text, _scope) {
return text;
}
/**
* Called for each host element when it is committed for the first time.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns A “node” which determines the value of the host element.
*/
create(_el, _scope) {
throw new Error("Not implemented");
}
/**
* Called for each host element when it is committed.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param scope - The current scope.
*
* @returns The return value is ignored.
*
* @remarks
* Used to mutate the node associated with an element when new props are passed.
*/
patch(_el, _node) {
return;
}
// TODO: pass hints into arrange about where the dirty children start and end
/**
* Called for each host element after its children have committed with the actual values of the children.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param children - An array of nodes and strings from child elements.
*
* @returns The return value is ignored.
*
* @remarks
* This method is also called by child components contexts as the last step of a refresh.
*/
arrange(_el, _parent, _children) {
return;
}
// TODO: remove(): a method which is called to remove a child from a parent to optimize arrange
/**
* Called for each host element when it is unmounted.
*
* @param tag - The tag of the host element.
* @param props - The props of the host element.
* @param node - The node associated with the host element.
*
* @returns The return value is ignored.
*/
dispose(_el, _node) {
return;
}
/**
* Called at the end of the rendering process for each root of the tree.
*
* @param root - The root prop passed to portals or the render method.
*
* @returns The return value is ignored.
*/
complete(_root) {
return;
}
}
/*** PRIVATE RENDERER FUNCTIONS ***/
// NOTE: to aid in the mangling of this module, we use functions rather than methods for internal logic.
function mount(renderer, root, host, ctx, scope, el) {
el._f |= Mounted;
if (typeof el.tag === "function") {
el._ctx = new Context(renderer, root, host, ctx, scope, el);
return updateCtx(el._ctx);
}
else if (el.tag === Raw) {
return commit(renderer, scope, el, []);
}
else if (el.tag !== Fragment) {
if (el.tag === Portal) {
root = el.props.root;
}
host = el;
scope = renderer.scope(host, scope);
}
// NOTE: The primary benefit of having a separate codepath for mounting is that it’s slightly faster because we don’t have to align and diff children against old children. But for singular child values, updateChild is sufficient.
if (isNonStringIterable(el.props.children)) {
return mountChildren(renderer, root, host, ctx, scope, el, el.props.children);
}
return updateChild(renderer, root, host, ctx, scope, el, el.props.children);
}
function mountChildren(renderer, root, host, ctx, scope, parent, children) {
const values = [];
const newChildren = Array.from(children);
let async = false;
for (let i = 0; i < newChildren.length; i++) {
let value;
let child = newChildren[i];
if (isNonStringIterable(child)) {
child = createElement(Fragment, null, child);
}
else {
child = narrow(child);
}
[child, value] = diff(renderer, root, host, ctx, scope, undefined, // oldChild
child);
newChildren[i] = child;
values.push(value);
if (!async && isPromiseLike(value)) {
async = true;
}
}
parent._ch = unwrap(newChildren);
let values1;
if (async) {
values1 = Promise.all(values);
}
else {
values1 = values;
}
return chase(renderer, host, ctx, scope, parent, values1);
}
function update(renderer, root, host, ctx, scope, el) {
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
return updateCtx(el._ctx);
}
return undefined;
}
else if (el.tag === Raw) {
return commit(renderer, scope, el, []);
}
else if (el.tag !== Fragment) {
host = el;
scope = renderer.scope(host, scope);
if (el.tag === Portal) {
root = el.props.root;
}
}
if (isNonStringIterable(el.props.children)) {
return updateChildren(renderer, root, host, ctx, scope, el, el.props.children);
}
else if (Array.isArray(el._ch)) {
return updateChildren(renderer, root, host, ctx, scope, el, [
el.props.children,
]);
}
return updateChild(renderer, root, host, ctx, scope, el, el.props.children);
}
function updateChild(renderer, root, host, ctx, scope, parent, child) {
let oldChild = parent._ch;
let newChild = narrow(child);
if (typeof oldChild === "object" &&
typeof newChild === "object" &&
oldChild.key !== newChild.key) {
oldChild = undefined;
}
let value;
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild);
if (typeof oldChild === "object" && oldChild !== newChild) {
unmount(renderer, host, ctx, oldChild);
}
parent._ch = newChild;
// TODO: allow single values to be passed to chase
const values = isPromiseLike(value) ? value.then(wrap) : wrap(value);
return chase(renderer, host, ctx, scope, parent, values);
}
function mapChildrenByKey(children) {
const childrenByKey = new Map();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "object" && typeof child.key !== "undefined") {
childrenByKey.set(child.key, child);
}
}
return childrenByKey;
}
function updateChildren(renderer, root, host, ctx, scope, parent, children) {
if (typeof parent._ch === "undefined") {
return mountChildren(renderer, root, host, ctx, scope, parent, children);
}
const values = [];
const oldChildren = wrap(parent._ch);
const newChildren = Array.from(children);
const graveyard = [];
let i = 0;
let async = false;
let seen;
let childrenByKey;
// TODO: switch to mountChildren if there are no more children
for (let j = 0; j < newChildren.length; j++) {
let oldChild = oldChildren[i];
let newChild = newChildren[j];
if (isNonStringIterable(newChild)) {
newChild = createElement(Fragment, null, newChild);
}
else {
newChild = narrow(newChild);
}
// ALIGNMENT
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined;
let newKey = typeof newChild === "object" ? newChild.key : undefined;
if (seen !== undefined && seen.has(newKey)) {
// TODO: warn about a duplicate key
newKey = undefined;
}
if (oldKey !== newKey) {
if (!childrenByKey) {
childrenByKey = mapChildrenByKey(oldChildren.slice(i));
}
if (newKey === undefined) {
while (oldChild !== undefined && oldKey !== undefined) {
i++;
oldChild = oldChildren[i];
oldKey = typeof oldChild === "object" ? oldChild.key : undefined;
}
i++;
}
else {
oldChild = childrenByKey.get(newKey);
if (oldChild !== undefined) {
childrenByKey.delete(newKey);
}
if (!seen) {
seen = new Set();
}
seen.add(newKey);
}
}
else {
if (childrenByKey !== undefined && newKey !== undefined) {
childrenByKey.delete(newKey);
}
i++;
}
// UPDATING
let value;
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild);
values.push(value);
newChildren[j] = newChild;
if (!async && isPromiseLike(value)) {
async = true;
}
if (typeof oldChild === "object" && oldChild !== newChild) {
graveyard.push(oldChild);
}
}
parent._ch = unwrap(newChildren);
// cleanup
for (; i < oldChildren.length; i++) {
const oldChild = oldChildren[i];
if (typeof oldChild === "object" && typeof oldChild.key === "undefined") {
graveyard.push(oldChild);
}
}
// TODO: async unmounting
if (childrenByKey !== undefined && childrenByKey.size > 0) {
graveyard.push(...childrenByKey.values());
}
let values1;
if (async) {
values1 = Promise.all(values).finally(() => graveyard.forEach((child) => unmount(renderer, host, ctx, child)));
}
else {
values1 = values;
graveyard.forEach((child) => unmount(renderer, host, ctx, child));
}
return chase(renderer, host, ctx, scope, parent, values1);
}
function diff(renderer, root, host, ctx, scope, oldChild, newChild) {
let value;
if (typeof oldChild === "object" &&
typeof newChild === "object" &&
oldChild.tag === newChild.tag) {
// TODO: implement Raw element parse caching
if (oldChild.tag === Portal) {
if (oldChild.props.root !== newChild.props.root) {
renderer.arrange(oldChild, oldChild.props.root, []);
}
}
if (oldChild !== newChild) {
oldChild.props = newChild.props;
oldChild.ref = newChild.ref;
newChild = oldChild;
}
value = update(renderer, root, host, ctx, scope, newChild);
}
else if (typeof newChild === "object") {
if (newChild.tag === Copy) {
if (typeof oldChild === "object") {
value = oldChild._inf || getValue(oldChild);
}
else {
value = oldChild;
}
if (typeof newChild.ref === "function") {
if (isPromiseLike(value)) {
value.then(newChild.ref).catch(() => { });
}
else {
newChild.ref(value);
}
}
newChild = oldChild;
}
else {
if (newChild._f & Mounted) {
newChild = cloneElement(newChild);
}
if (typeof oldChild === "object") {
newChild._fb = oldChild._n;
if (typeof oldChild._inf === "object") {
oldChild._inf
.then((value) => {
if (!(newChild._f & Committed)) {
newChild._fb = value;
}
})
.catch(() => { });
}
}
value = mount(renderer, root, host, ctx, scope, newChild);
}
}
else if (typeof newChild === "string") {
newChild = renderer.escape(newChild, scope);
value = newChild;
}
return [newChild, value];
}
/**
* A function to race current child values with future child values.
*
* @remarks
* When an element’s children update asynchronously, we race the resulting promise with the next update of the element’s children. By induction, this ensures that when any update to an element settles, all past updates to that same element will have settled as well. This prevents deadlocks and unnecessary awaiting when an element’s children have been cleared, for instance.
*/
function chase(renderer, host, ctx, scope, el, values) {
if (isPromiseLike(values)) {
let onNewValues;
const newValues = new Promise((resolve) => (onNewValues = resolve));
const valuesP = Promise.race([values, newValues]);
if (typeof el._onv === "function") {
el._onv(valuesP);
}
el._onv = onNewValues;
const value = valuesP.then((values) => commit(renderer, scope, el, normalize(values)));
el._inf = value;
return value;
}
if (typeof el._onv === "function") {
el._onv(values);
el._onv = undefined;
}
return commit(renderer, scope, el, normalize(values));
}
function commit(renderer, scope, el, values) {
let value = unwrap(values);
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
commitCtx(el._ctx, value);
}
}
else if (el.tag === Portal) {
renderer.arrange(el, el.props.root, values);
renderer.complete(el.props.root);
value = undefined;
}
else if (el.tag === Raw) {
if (typeof el.props.value === "string") {
el._n = renderer.parse(el.props.value, scope);
}
else {
el._n = el.props.value;
}
value = el._n;
}
else if (el.tag !== Fragment) {
if (!(el._f & Committed)) {
el._n = renderer.create(el, scope);
}
renderer.patch(el, el._n);
renderer.arrange(el, el._n, values);
value = el._n;
}
el._f |= Committed;
if (typeof el.ref === "function") {
el.ref(renderer.read(value));
}
if (typeof el._inf !== "undefined") {
el._inf = undefined;
}
if (typeof el._fb !== "undefined") {
el._fb = undefined;
}
return value;
}
function unmount(renderer, host, ctx, el) {
if (typeof el.tag === "function") {
if (typeof el._ctx === "object") {
unmountCtx(el._ctx);
}
ctx = el._ctx;
}
else if (el.tag === Portal) {
host = el;
renderer.arrange(host, host.props.root, []);
renderer.complete(el.props.root);
}
else if (el.tag !== Fragment) {
if (isEventTarget(el._n)) {
const listeners = getListeners(ctx, host);
if (listeners !== undefined && listeners.length > 0) {
for (let i = 0; i < listeners.length; i++) {
const record = listeners[i];
el._n.removeEventListener(record.type, record.callback, record.options);
}
}
}
host = el;
renderer.dispose(host, host._n);
}
const children = wrap(el._ch);
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === "object") {
unmount(renderer, host, ctx, child);
}
}
}
/*** EVENT UTILITIES ***/
// EVENT PHASE CONSTANTS (https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase)
const NONE = 0;
const CAPTURING_PHASE = 1;
const AT_TARGET = 2;
const BUBBLING_PHASE = 3;
function normalizeOptions(options) {
if (typeof options === "boolean") {
return { capture: options };
}
else if (options == null) {
return {};
}
else {
return options;
}
}
function isEventTarget(value) {
return (value != null &&
typeof value.addEventListener === "function" &&
typeof value.removeEventListener === "function" &&
typeof value.dispatchEvent === "function");
}
function setEventProperty(ev, key, value) {
Object.defineProperty(ev, key, { value, writable: false, configurable: true });
}
/**
* A function to reconstruct an array of every listener given a context and a host element.
*
* @remarks
* This function exploits the fact that contexts retain their nearest ancestor host element. We can determine all the contexts which are directly listening to an element by traversing up the context tree and checking that the host element passed in matches the context’s host property.
*/
function getListeners(ctx, host) {
let listeners;
while (ctx !== undefined && ctx._ho === host) {
if (typeof ctx._ls !== "undefined") {
listeners = (listeners || []).concat(ctx._ls);
}
ctx = ctx._pa;
}
return listeners;
}
function clearEventListeners(ctx) {
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) {
for (const value of getChildValues(ctx._el)) {
if (isEventTarget(value)) {
for (const record of ctx._ls) {
value.removeEventListener(record.type, record.callback, record.options);
}
}
}
ctx._ls = undefined;
}
}
// CONTEXT FLAGS
/**
* A flag which is set when the component is being updated by the parent and cleared when the component has committed. Used to determine whether the nearest host ancestor needs to be rearranged.
*/
const Updating = 1 << 0;
/**
* A flag which is set when the component is called or stepped through. It is used to ensure that a component which synchronously triggers a second update in the course of rendering does not cause an infinite loop or a generator error.
*/
const Stepping = 1 << 1;
/**
* A flag used to make sure multiple values are not pulled from context prop iterators without a yield.
*/
const Iterating = 1 << 2;
/**
* A flag used by async generator components in conjunction with the onProps functions (_op) to mark whether new props can be pulled via the context iterator methods.
*/
const Available = 1 << 3;
/**
* A flag which is set when generator components return. Set whenever an iterator returns an iteration with the done property set to true. Finished components will stick to their last rendered value and ignore further updates.
*/
const Finished = 1 << 4;
/**
* A flag which is set when the component is unmounted. Unmounted components are no longer in the element tree, and cannot run or refresh.
*/
const Unmounted = 1 << 5;
/**
* A flag which indicates that the component is a sync generator component.
*/
const SyncGen = 1 << 6;
/**
* A flag which indicates that the component is an async generator component.
*/
const AsyncGen = 1 << 7;
/**
* A class which is instantiated and passed to every component as its this value. Contexts form a tree just like elements and all components in the element tree are connected via contexts. Components can use this tree to communicate data upwards via events and downwards via provisions.
* @typeparam TProps - The expected shape of the props passed to the component. Used to strongly type the Context iterator methods.
* @typeparam TResult - The readable element value type. It is used in places such as the return value of refresh and the argument passed to schedule/cleanup callbacks.
*/
class Context {
/**
* @internal
*/
constructor(renderer, root, host, parent, scope, el) {
this._f = 0;
this._re = renderer;
this._rt = root;
this._ho = host;
this._pa = parent;
this._sc = scope;
this._el = el;
}
consume(key) {
for (let parent = this._pa; parent !== undefined; parent = parent._pa) {
if (typeof parent._ps === "object" && parent._ps.has(key)) {
return parent._ps.get(key);
}
}
}
provide(key, value) {
if (typeof this._ps === "undefined") {
this._ps = new Map();
}
this._ps.set(key, value);
}
/**
* The current props of the associated element.
*
* @remarks
* Typically, you should read props either via the first parameter of the component or via the context iterator methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get props() {
return this._el.props;
}
/**
* The current value of the associated element.
*
* @remarks
* Typically, you should read values via refs, generator yield expressions, or the refresh, schedule or cleanup methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get value() {
return this._re.read(getValue(this._el));
}
*[Symbol.iterator]() {
const el = this._el;
while (!(this._f & Unmounted)) {
if (this._f & Iterating) {
throw new Error("You must yield for each iteration of this.");
}
else if (this._f & AsyncGen) {
throw new Error("Use for await...of in async generator components.");
}
this._f |= Iterating;
yield el.props;
}
}
async *[Symbol.asyncIterator]() {
const el = this._el;
do {
if (this._f & Iterating) {
throw new Error("You must yield for each iteration of this.");
}
else if (this._f & SyncGen) {
throw new Error("Use for...of in sync generator components.");
}
this._f |= Iterating;
if (this._f & Available) {
this._f &= ~Available;
yield el.props;
}
else {
const props = await new Promise((resolve) => (this._op = resolve));
if (!(this._f & Unmounted)) {
yield props;
}
}
} while (!(this._f & Unmounted));
}
/**
* Re-executes the component.
*
* @returns The rendered value of the component or a promise of the rendered value if the component or its children execute asynchronously.
*
* @remarks
* The refresh method works a little differently for async generator components, in that it will resume the Context async iterator rather than resuming execution. This is because async generator components are perpetually resumed independent of updates/refresh.
*/
refresh() {
if (this._f & (Stepping | Unmounted)) {
// TODO: log an error
return this._re.read(undefined);
}
this._f &= ~Updating;
resume(this);
return this._re.read(run(this));
}
/**
* Registers a callback which fires when the component commits. Will only fire once per callback and update.
*/
schedule(callback) {
if (typeof this._ss === "undefined") {
this._ss = new Set();
}
this._ss.add(callback);
}
/**
* Registers a callback which fires when the component unmounts. Will only fire once per callback.
*/
cleanup(callback) {
if (typeof this._cs === "undefined") {
this._cs = new Set();
}
this._cs.add(callback);
}
addEventListener(type, listener, options) {
if (listener == null) {
return;
}
else if (typeof this._ls === "undefined") {
this._ls = [];
}
options = normalizeOptions(options);
let callback;
if (typeof listener === "object") {
callback = () => listener.handleEvent.apply(listener, arguments);
}
else {
callback = listener;
}
const record = { type, callback, listener, options };
if (options.once) {
const self = this;
record.callback = function () {
if (typeof self._ls !== "undefined") {
self._ls = self._ls.filter((record1) => record !== record1);
if (self._ls.length === 0) {
self._ls = undefined;
}
}
return callback.apply(this, arguments);
};
}
if (this._ls.some((record1) => record.type === record1.type &&
record.listener === record1.listener &&
!record.options.capture === !record1.options.capture)) {
return;
}
this._ls.push(record);
for (const value of getChildValues(this._el)) {
if (isEventTarget(value)) {
value.addEventListener(record.type, record.callback, record.options);
}
}
}
removeEventListener(type, listener, options) {
if (listener == null || typeof this._ls === "undefined") {
return;
}
const options1 = normalizeOptions(options);
const i = this._ls.findIndex((record) => record.type === type &&
record.listener === listener &&
!record.options.capture === !options1.capture);
if (i === -1) {
return;
}
const record = this._ls[i];
this._ls.splice(i, 1);
for (const value of getChildValues(this._el)) {
if (isEventTarget(value)) {
value.removeEventListener(record.type, record.callback, record.options);
}
}
if (this._ls.length === 0) {
this._ls = undefined;
}
}
dispatchEvent(ev) {
const path = [];
for (let parent = this._pa; parent !== undefined; parent = parent._pa) {
path.push(parent);
}
let stopped = false;
const stopImmediatePropagation = ev.stopImmediatePropagation;
setEventProperty(ev, "stopImmediatePropagation", () => {
stopped = true;
return stopImmediatePropagation.call(ev);
});
setEventProperty(ev, "target", this);
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
try {
for (let i = path.length - 1; i >= 0; i--) {
const et = path[i];
if (typeof et._ls !== "undefined") {
setEventProperty(ev, "currentTarget", et);
for (const record of et._ls) {
if (record.type === ev.type && record.options.capture) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
if (typeof this._ls !== "undefined") {
setEventProperty(ev, "eventPhase", AT_TARGET);
setEventProperty(ev, "currentTarget", this);
for (const record of this._ls) {
if (record.type === ev.type) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
if (ev.bubbles) {
setEventProperty(ev, "eventPhase", BUBBLING_PHASE);
for (const et of path) {
if (typeof et._ls !== "undefined") {
setEventProperty(ev, "currentTarget", et);
for (const record of et._ls) {
if (record.type === ev.type && !record.options.capture) {
try {
record.callback.call(this, ev);
}
catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
if (stopped) {
break;
}
}
}
}
if (stopped || ev.cancelBubble) {
return !ev.defaultPrevented;
}
}
}
return !ev.defaultPrevented;
}
finally {
setEventProperty(ev, "eventPhase", NONE);
setEventProperty(ev, "currentTarget", null);
}
}
}
/*** PRIVATE CONTEXT FUNCTIONS ***/
/**
* Called to make props available to the Context async iterator for async generator components.
*/
function resume(ctx) {
if (typeof ctx._op === "function") {
ctx._op(ctx._el.props);
ctx._op = undefined;
}
else {
ctx._f |= Available;
}
}
// NOTE: The functions run, step and advance work together to implement the async queueing behavior of components. The run function calls the step function, which returns two results in a tuple. The first result, called “block,” is a possible promise which settles when the component can accept new updates, and represents the duration during which the component is blocked from accepting new updates. The second result, called “value,” is the actual result of the update. The run function caches block/value from the step function on the context, according to whether the component is currently blocked. The “inflight” block/value properties are the currently executing update, and the “enqueued” block/value properties are promises which represent the next step. Lastly, the run function calls the advance function in a Promise.prototype.finally callback to allow new steps to be enqueued.
/**
* Enqueues and executes the component associated with the context.
*/
function run(ctx) {
if (typeof ctx._ib === "undefined") {
try {
let [block, value] = step(ctx);
if (isPromiseLike(block)) {
ctx._ib = block
.catch((err) => {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
})
.finally(() => advance(ctx));
}
if (isPromiseLike(value)) {
ctx._iv = value;
ctx._el._inf = value;
}
return value;
}
catch (err) {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
throw err;
}
}
else if (ctx._f & AsyncGen) {
return ctx._iv;
}
else if (typeof ctx._eb === "undefined") {
let resolve;
ctx._eb = ctx._ib
.then(() => {
try {
const [block, value] = step(ctx);
resolve(value);
if (isPromiseLike(value)) {
ctx._el._inf = value;
}
if (isPromiseLike(block)) {
return block.catch((err) => {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
});
}
}
catch (err) {
if (!(ctx._f & Updating)) {
return propagateError(ctx._pa, err);
}
}
})
.finally(() => advance(ctx));
ctx._ev = new Promise((resolve1) => (resolve = resolve1));
}
return ctx._ev;
}
/**
* The step function is responsible for executing the component and handling all the different component types.
*
* @returns A tuple [block, value]
* block - A possible promise which represents the duration during which the component is blocked from updating.
* value - The actual rendered value of the children.
*
* @remarks
* Each component type will block/unblock according to the type of the component.
* Sync function components never block and will transparently pass updates to children.
* Async function components and async generator components block while executing itself, but will not block for async children.
* Sync generator components block while any children are executing, because they are expected to only resume when they’ve actually rendered. Additionally, they have no mechanism for awaiting async children.
*/
function step(ctx) {
const el = ctx._el;
if (ctx._f & Finished) {
return [undefined, getValue(el)];
}
let initial = false;
try {
ctx._f |= Stepping;
if (typeof ctx._it === "undefined") {
initial = true;
clearEventListeners(ctx);
const result = el.tag.call(ctx, el.props);
if (isIteratorLike(result)) {
ctx._it = result;
}
else if (isPromiseLike(result)) {
// async function component
const result1 = result instanceof Promise ? result : Promise.resolve(result);
const block = result1;
const value = result1.then((result) => updateCtxChildren(ctx, result));
return [block, value];
}
else {
// sync function component
return [undefined, updateCtxChildren(ctx, result)];
}
}
}
finally {
ctx._f &= ~Stepping;
}
let oldValue;
if (typeof ctx._el._inf === "object") {
oldValue = ctx._el._inf.then((value) => ctx._re.read(value), () => ctx._re.read(undefined));
}
else if (initial) {
oldValue = ctx._re.read(undefined);
}
else {
oldValue = ctx._re.read(getValue(el));
}
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.next(oldValue);
}
catch (err) {
ctx._f |= Finished;
throw err;
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
// async generator component
if (initial) {
ctx._f |= AsyncGen;
}
const block = iteration;
const value = iteration.then((iteration) => {
if (!(ctx._f & Iterating)) {
ctx._f &= ~Available;
}
ctx._f &= ~Iterating;
if (iteration.done) {
ctx._f |= Finished;
}
try {
const value = updateCtxChildren(ctx, iteration.value);
if (isPromiseLike(value)) {
return value.catch((err) => handleChildError(ctx, err));
}
return value;
}
catch (err) {
return handleChildError(ctx, err);
}
}, (err) => {
ctx._f |= Finished;
throw err;
});
return [block, value];
}
// sync generator component
if (initial) {
ctx._f |= SyncGen;
}
ctx._f &= ~Iterating;
if (iteration.done) {
ctx._f |= Finished;
}
let value;
try {
value = updateCtxChildren(ctx, iteration.value);
if (isPromiseLike(value)) {
value = value.catch((err) => handleChildError(ctx, err));
}
}
catch (err) {
value = handleChildError(ctx, err);
}
if (isPromiseLike(value)) {
return [value.catch(() => { }), value];
}
return [undefined, value];
}
/**
* @remarks
* Called when the inflight block promise settles.
*/
function advance(ctx) {
// _ib: inflightBlock
// _iv: inflightValue
// _eb: enqueuedBlock
// _ev: enqueuedValue
ctx._ib = ctx._eb;
ctx._iv = ctx._ev;
ctx._eb = undefined;
ctx._ev = undefined;
if (ctx._f & AsyncGen && !(ctx._f & Finished)) {
run(ctx);
}
}
// TODO: generator components which throw errors should be recoverable
function handleChildError(ctx, err) {
if (ctx._f & Finished ||
typeof ctx._it !== "object" ||
typeof ctx._it.throw !== "function") {
throw err;
}
resume(ctx);
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.throw(err);
}
catch (err) {
ctx._f |= Finished;
throw err;
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
return iteration.then((iteration) => {
if (iteration.done) {
ctx._f |= Finished;
}
return updateCtxChildren(ctx, iteration.value);
}, (err) => {
ctx._f |= Finished;
throw err;
});
}
if (iteration.done) {
ctx._f |= Finished;
}
return updateCtxChildren(ctx, iteration.value);
}
function propagateError(ctx, err) {
if (ctx === undefined) {
throw err;
}
let result;
try {
result = handleChildError(ctx, err);
}
catch (err) {
return propagateError(ctx._pa, err);
}
if (isPromiseLike(result)) {
return result.catch((err) => propagateError(ctx._pa, err));
}
return result;
}
function updateCtx(ctx) {
ctx._f |= Updating;
resume(ctx);
return run(ctx);
}
function updateCtxChildren(ctx, children) {
let child;
if (isNonStringIterable(children)) {
child = createElement(Fragment, null, children);
}
else {
child = children;
}
return updateChild(ctx._re, ctx._rt, // root
ctx._ho, // host
ctx, ctx._sc, // scope
ctx._el, // element
child);
}
function commitCtx(ctx, value) {
if (ctx._f & Unmounted) {
return;
}
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) {
for (const child of wrap(value)) {
if (isEventTarget(child)) {
for (const record of ctx._ls) {
child.addEventListener(record.type, record.callback, record.options);
}
}
}
}
if (!(ctx._f & Updating)) {
const listeners = getListeners(ctx._pa, ctx._ho);
if (listeners !== undefined && listeners.length > 0) {
for (let i = 0; i < listeners.length; i++) {
const record = listeners[i];
for (const v of wrap(value)) {
if (isEventTarget(v)) {
v.addEventListener(record.type, record.callback, record.options);
}
}
}
}
// TODO: we don’t need to call arrange if none of the nodes have changed or moved
const host = ctx._ho;
if (host._f & Committed) {
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, getChildValues(host));
}
ctx._re.complete(ctx._rt);
}
ctx._f &= ~Updating;
if (typeof ctx._ss === "object" && ctx._ss.size > 0) {
// NOTE: We have to clear the set of callbacks before calling them, because a callback which refreshes the component would otherwise cause a stack overflow.
const callbacks = Array.from(ctx._ss);
ctx._ss.clear();
const value1 = ctx._re.read(value);
for (const callback of callbacks) {
callback(value1);
}
}
}
// TODO: async unmounting
function unmountCtx(ctx) {
ctx._f |= Unmounted;
clearEventListeners(ctx);
if (typeof ctx._cs === "object") {
const value = ctx._re.read(getValue(ctx._el));
for (const cleanup of ctx._cs) {
cleanup(value);
}
ctx._cs = undefined;
}
if (!(ctx._f & Finished)) {
ctx._f |= Finished;
resume(ctx);
if (typeof ctx._it === "object" && typeof ctx._it.return === "function") {
let iteration;
try {
ctx._f |= Stepping;
iteration = ctx._it.return();
}
finally {
ctx._f &= ~Stepping;
}
if (isPromiseLike(iteration)) {
iteration.catch((err) => propagateError(ctx._pa, err));
}
}
}
}
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, cloneElement, createElement, isElement };
/// <reference types="./index.d.ts" />
export { Context, Copy, Element, Fragment, Portal, Raw, Renderer, cloneElement, createElement, isElement } from './crank.js';
//# sourceMappingURL=index.js.map
{
"name": "@bikeshaving/crank",
"version": "0.3.0",
"version": "0.3.1-beta.0",
"description": "Write JSX-driven components with functions, promises and generators.",

@@ -15,4 +15,5 @@ "homepage": "https://crank.js.org",

"files": [
"crank.js",
"dom.js",
"index.js",
"dom.js",
"html.js",

@@ -34,2 +35,8 @@ "cjs",

},
"./cjs/crank": {
"require": "./cjs/crank.js"
},
"./cjs/crank.js": {
"require": "./cjs/crank.js"
},
"./cjs/dom": {

@@ -53,2 +60,10 @@ "require": "./cjs/dom.js"

},
"./crank": {
"import": "./crank.js",
"require": "./cjs/crank.js"
},
"./crank.js": {
"import": "./crank.js",
"require": "./cjs/crank.js"
},
"./dom": {

@@ -110,3 +125,2 @@ "import": "./dom.js",

"devDependencies": {
"@rollup/plugin-node-resolve": "^8.1.0",
"@types/jest": "^26.0.3",

@@ -124,2 +138,3 @@ "@typescript-eslint/eslint-plugin": "^3.5.0",

"lint-staged": "^10.2.11",
"magic-string": "^0.25.7",
"prettier": "^2.0.4",

@@ -130,2 +145,3 @@ "rollup": "^2.18.1",

"ts-jest": "^26.1.1",
"ts-transform-import-path-rewrite": "^0.2.1",
"typescript": "^3.9.6"

@@ -132,0 +148,0 @@ },

@@ -17,3 +17,3 @@ # Crank.js

### Lightweight
Crank has no dependencies, and its core is a single file. It currently measures at [4.5KB minified and gzipped]("https://bundlephobia.com/result?p=@bikeshaving/crank").
Crank has no dependencies, and its core is a single file. It currently measures at [4.5KB minified and gzipped](https://bundlephobia.com/result?p=@bikeshaving/crank).

@@ -20,0 +20,0 @@ ### Performant

@@ -1,6 +0,2 @@

import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./crank";
export declare class DOMRenderer extends Renderer<Node, string | undefined> {

@@ -15,1 +11,7 @@ render(children: Children, root: Node, ctx?: Context): Promise<ElementValue<Node>> | ElementValue<Node>;

export declare const renderer: DOMRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}

@@ -1,10 +0,6 @@

import { Element, ElementValue, Renderer } from "./index";
declare module "./index" {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
import { Element, ElementValue, Renderer } from "./crank";
interface Node {
value: string;
}
export declare class StringRenderer extends Renderer<Node | string, undefined, unknown, string> {
export declare class HTMLRenderer extends Renderer<Node | string, undefined, unknown, string> {
create(): Node;

@@ -15,3 +11,13 @@ escape(text: string): string;

}
export declare const renderer: StringRenderer;
export declare const renderer: HTMLRenderer;
/**
* @deprecated
*/
export declare const StringRenderer: typeof HTMLRenderer;
declare global {
module Crank {
interface EventMap extends GlobalEventHandlersEventMap {
}
}
}
export {};

@@ -1,498 +0,1 @@

/**
* Represents all valid values which can be used for the tag of an element.
*
* @remarks
* Elements whose tags are strings or symbols are called “host” or “intrinsic” elements, and their behavior is determined by the renderer, while elements whose tags are components are called “component” elements, and their behavior is determined by the execution of the component.
*/
export declare type Tag = string | symbol | Component;
/**
* Maps the tag of an element to its expected props.
*
* @typeparam TTag - The element’s tag.
*/
export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown;
/***
* SPECIAL TAGS
* Crank provides a couple tags which have special meaning for the renderer.
***/
/**
* A special element tag for grouping multiple children within a parent.
*
* @remarks
* All iterables which appear in the element tree are implicitly wrapped in fragments. The Fragment tag is just the empty string, and you can use the empty string in createElement calls/transpiler options to avoid having to reference the Fragment export directly.
*/
export declare const Fragment = "";
export declare type Fragment = typeof Fragment;
/**
* A special element tag for creating a new element subtree with a different root, passed via the root prop.
*
* @remarks
* This element tag is useful for creating element trees with multiple roots. Renderer.prototype.render will implicitly wrap the children which have been passed in in an implicit Portal element.
*/
export declare const Portal: any;
export declare type Portal = typeof Portal;
/**
* A special element tag which copies whatever child appeared previously in the element’s position.
*
* @remarks
* Copy elements are useful when you want to prevent a subtree from updating as a performance optimization.
*/
export declare const Copy: any;
export declare type Copy = typeof Copy;
/**
* A special element tag for injecting raw nodes into an element tree via its value prop.
*
* @remarks
* If the value prop is a string, Renderer.prototype.parse will be called on the string and the element’s rendered value will be the result.
*/
export declare const Raw: any;
export declare type Raw = typeof Raw;
/**
* Describes all valid singular values of an element tree.
*
* @remarks
* Arbitrary objects can also be safely rendered but they will be converted to a string using the toString method. We exclude them from this type to catch potential type errors.
*/
export declare type Child = Element | string | number | boolean | null | undefined;
interface ChildIterable extends Iterable<Child | ChildIterable> {
}
/**
* Describes all valid values of an element tree, including arbitrarily nested iterables of such values.
*/
export declare type Children = Child | ChildIterable;
/**
* Represents all functions which can be used as a component.
*
* @typeparam TProps - The expected props for the component.
*
* @remarks
* The return type of iterator objects returned from components has to be void because typescript will infer most generators as having a void return type.
*/
export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>;
/**
* All nodes in the element tree are narrowed from the union in Child to NarrowedChild. This greatly simplifies element diffing.
*/
declare type NarrowedChild = Element | string | undefined;
declare type Key = unknown;
declare const ElementSymbol: unique symbol;
/**
* Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by special classes called renderers to produce and manage stateful nodes.
*
* @typeparam TTag - the type of the tag of the element.
*
* @example
* // specific element types
* let div: Element<"div">;
* let portal: Element<Portal>;
* let myEl: Element<MyComponent>;
*
* // general element types
* let host: Element<string | symbol>;
* let component: Element<Component>;
*
* @remarks
* Typically, you use the createElement function to create elements and not this class directly.
*/
export declare class Element<TTag extends Tag = Tag> {
/**
* @internal
* A unique symbol to identify elements as elements across versions and realms, and to protect against basic injection attacks.
* https://overreacted.io/why-do-react-elements-have-typeof-property/
*/
$$typeof: typeof ElementSymbol;
/**
* @internal
* flags - A bitmask. See ELEMENT FLAGS.
*/
_f: number;
/**
* The tag of the element. Can be a function, string or symbol depending on the kind of element.
*/
tag: TTag;
/**
* An object containing the “properties” of an element. These correspond to the attributes of the element when using JSX syntax.
*
* @remarks
* The props of an object are passed to most renderer host methods, and as the first argument to components.
*/
props: TagProps<TTag>;
/**
* A value which uniquely identifies an element from its siblings so that it can be added/updated/moved/removed by the identity of the key rather than its position within the parent.
*
* @remarks
* Passed to the element as the prop "crank-key".
*/
key: Key;
/**
* A callback which is called with the element’s value when the value is committed.
*
* @remarks
* Passed to the element as the prop "crank-ref".
*/
ref: Function | undefined;
/**
* @internal
* children - The rendered children of the element.
*/
_ch: Array<NarrowedChild> | NarrowedChild;
/**
* @internal
* node - The node associated with the element.
*
* @remarks
* Set by Renderer.prototype.create when the component is mounted. This property will only be set for host elements.
*/
_n: any;
/**
* @internal
* context - The Context object associated with this element.
*
* @remarks
* Created and assigned by the Renderer for component elements when it mounts the element tree.
*/
_ctx: Context<TagProps<TTag>> | undefined;
/**
* @internal
* fallback - The value of the element while it has never committed.
*
* @remarks
* If an element takes place of a previously rendered value but renders asynchronously, this property is set to the previously rendered value until the element commits. This allows asynchronously updating element trees to show something while pending.
*/
_fb: any;
/**
* @internal
* inflightPromise - The current async run of the element.
*
* @remarks
* This value is used to make sure element copies do not fulfill immediately, to set the fallback of the next element when the previous element commits, and as the yield value of async generator components with async children. It is unset when the element is committed.
*/
_inf: Promise<any> | undefined;
/**
* @internal
* onNewValues - the resolve function of a promise which represents the next child result. See the chase function for more info.
*/
_onv: Function | undefined;
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: Function | undefined);
}
export declare function isElement(value: any): value is Element;
/**
* Creates an element with the specified tag, props and children.
*
* @remarks
* This function is usually used as a transpilation target for JSX transpilers, but it can also be called directly. It additionally extracts the crank-key and crank-ref props so they aren’t accessible to the renderer methods or components, and assigns the children prop according to the remaining arguments passed to the function.
*/
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>;
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, children?: unknown): Element<TTag>;
/**
* Clones a given element.
*
* @remarks
* Mainly used internally to make sure we don’t accidentally reuse elements in an element tree, because elements are directly mutated by the renderer.
*/
export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>;
/*** ELEMENT VALUE UTILITIES ***/
/**
* A helper type which repesents all the possible rendered values of an element.
*
* @typeparam TNode - The node type for the element assigned by the renderer.
*
* @remarks
* When asking the question, what is the value of a specific element, the answer varies depending on the type of the element. For host or Raw elements, the answer is simply the nodes (DOM nodes in the case of the DOM renderer) created for the element. For fragments, the values are usually an array of nodes. For portals, the value is undefined, because a Portal element’s root and children are opaque to parents. For components, the value can be any of the above, because the value of a component is determined by its children. Rendered values can also be strings or arrays of nodes and strings, in the case of a component or fragment with strings for children. All of these possible values are reflected in this utility type.
*/
export declare type ElementValue<TNode> = Array<TNode | string> | TNode | string | undefined;
/**
* An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment.
*
* @typeparam TNode - The type of the node for a specific rendering environment.
* @typeparam TScope - Data which is passed down the tree.
* @typeparam TRoot - The type of the root for a specific rendering environment.
* @typeparam TResult - The type of the exposed values.
*/
export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> {
/**
* @internal
* A weakmap which stores element trees by root.
*/
_cache: WeakMap<object, Element<Portal>>;
constructor();
/**
* Renders an element tree into a specific root.
*
* @param children - An element tree. You can render null with a previously used root to delete the previously rendered element tree from the cache.
* @param root - The node to be rendered into. The renderer will cache element trees per root.
* @param ctx - An optional context that will be the ancestor context of all elements in the tree. Useful for connecting renderers which call each other so that events/provisions properly propagate. The context for a given root must be the same or an error will be thrown.
*
* @returns The read result of rendering the children, or a possible promise of the read result if the element tree renders asynchronously.
*/
render(children: Children, root?: TRoot | undefined, ctx?: Context | undefined): Promise<TResult> | TResult;
/**
* Called when an element’s value is exposed via render, schedule, refresh, refs, or generator yield expressions.
*
* @param value - The value of the element being read. Can be a node, a string, undefined, or an array of nodes and strings, depending on the element.
* @returns Varies according to the specific renderer subclass. By default, it exposes the element’s value.
*
* @remarks
* This is useful for renderers which don’t want to expose their internal nodes. For instance, the HTML renderer will convert all internal nodes to strings.
*
*/
read(value: ElementValue<TNode>): TResult;
/**
* Called in a preorder traversal for each host element.
*
* @remarks
* Useful for passing data down the element tree. For instance, the DOM renderer uses this method to keep track of whether we’re in an SVG subtree.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns The scope to be passed to create and scope for child host elements.
*
* @remarks
* This method sets the scope for child host elements, not the current host element.
*/
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope;
/**
* Called for each string in an element tree.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The escaped string.
*
* @remarks
* Rather than returning text nodes for whatever environment we’re rendering to, we defer that step for Renderer.prototype.arrange. We do this so that adjacent strings can be concatenated and the actual element tree can be rendered in a normalized form.
*/
escape(text: string, _scope: TScope): string;
/**
* Called for each Raw element whose value prop is a string.
*
* @param text - The string child.
* @param scope - The current scope.
*
* @returns The parsed node or string.
*/
parse(text: string, _scope: TScope): TNode | string;
/**
* Called for each host element when it is committed for the first time.
*
* @param el - The host element.
* @param scope - The current scope.
*
* @returns A “node” which determines the value of the host element.
*/
create(_el: Element<string | symbol>, _scope: TScope): TNode;
/**
* Called for each host element when it is committed.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param scope - The current scope.
*
* @returns The return value is ignored.
*
* @remarks
* Used to mutate the node associated with an element when new props are passed.
*/
patch(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called for each host element after its children have committed with the actual values of the children.
*
* @param el - The host element.
* @param node - The node associated with the host element.
* @param children - An array of nodes and strings from child elements.
*
* @returns The return value is ignored.
*
* @remarks
* This method is also called by child components contexts as the last step of a refresh.
*/
arrange(_el: Element<string | symbol>, _parent: TNode | TRoot, _children: Array<TNode | string>): unknown;
/**
* Called for each host element when it is unmounted.
*
* @param tag - The tag of the host element.
* @param props - The props of the host element.
* @param node - The node associated with the host element.
*
* @returns The return value is ignored.
*/
dispose(_el: Element<string | symbol>, _node: TNode): unknown;
/**
* Called at the end of the rendering process for each root of the tree.
*
* @param root - The root prop passed to portals or the render method.
*
* @returns The return value is ignored.
*/
complete(_root: TRoot): unknown;
}
/**
* A map of event type strings to Event subclasses. Can be extended via TypeScript module augmentation to have strongly typed event listeners.
*/
export interface EventMap {
[type: string]: Event;
}
declare type MappedEventListener<T extends string> = (ev: EventMap[T]) => unknown;
declare type MappedEventListenerOrEventListenerObject<T extends string> = MappedEventListener<T> | {
handleEvent: MappedEventListener<T>;
};
interface EventListenerRecord {
type: string;
listener: MappedEventListenerOrEventListenerObject<any>;
callback: MappedEventListener<any>;
options: AddEventListenerOptions;
}
/**
* An interface which can be extended to provide strongly typed provisions (see Context.prototype.get and Context.prototype.set)
*/
export interface ProvisionMap {
}
/**
* A class which is instantiated and passed to every component as its this value. Contexts form a tree just like elements and all components in the element tree are connected via contexts. Components can use this tree to communicate data upwards via events and downwards via provisions.
* @typeparam TProps - The expected shape of the props passed to the component. Used to strongly type the Context iterator methods.
* @typeparam TResult - The readable element value type. It is used in places such as the return value of refresh and the argument passed to schedule/cleanup callbacks.
*/
export declare class Context<TProps = any, TResult = any> implements EventTarget {
/**
* @internal
* flags - A bitmask. See CONTEXT FLAGS above.
*/
_f: number;
/**
* @internal
* renderer - The renderer which created this context.
*/
_re: Renderer<unknown, unknown, unknown, TResult>;
/**
* @internal
* root - The root node set by an ancestor’s Portal prop.
*/
_rt: unknown;
/**
* @internal
* host - The nearest ancestor host element.
* @remarks
* When refresh is called, the host element will be arranged as the last step of the commit, to make sure the parent’s children properly reflects the components’s children.
*/
_ho: Element<string | symbol>;
/**
* @internal
* parent - The parent context.
*/
_pa: Context<unknown, TResult> | undefined;
/**
* @internal
* scope - The value of the scope at the point of element’s creation.
*/
_sc: unknown;
/**
* @internal
* el - The associated component element.
*/
_el: Element<Component>;
/**
* @internal
* iterator - The iterator returned by the component function.
*/
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined;
/**
* @internal
* onProps - A callback used in conjunction with the Available flag to implement the props async iterator. See the Symbol.asyncIterator method and the resume function.
*/
_op: ((props: any) => unknown) | undefined;
/**
* @internal
* inflightBlock
*/
_ib: Promise<unknown> | undefined;
/**
* @internal
* inflightValue
*/
_iv: Promise<ElementValue<any>> | undefined;
/**
* @internal
* enqueuedBlock
*/
_eb: Promise<unknown> | undefined;
/**
* @internal
* enqueuedValue
*/
_ev: Promise<ElementValue<any>> | undefined;
/**
* @internal
* listeners - An array of event listeners added to the context via Context.prototype.addEventListener
*/
_ls: Array<EventListenerRecord> | undefined;
/**
* @internal
* provisions - A map of values which can be set via Context.prototype.set and read from child contexts via Context.prototype.get
*/
_ps: Map<unknown, unknown> | undefined;
/**
* @internal
* schedules - a set of callbacks registered via Context.prototype.schedule, which fire when the component has committed.
*/
_ss: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
* cleanups - a set of callbacks registered via Context.prototype.cleanup, which fire when the component has unmounted.
*/
_cs: Set<(value: TResult) => unknown> | undefined;
/**
* @internal
*/
constructor(renderer: Renderer<unknown, unknown, unknown, TResult>, root: unknown, host: Element<string | symbol>, parent: Context<unknown, TResult> | undefined, scope: unknown, el: Element<Component>);
consume<TKey extends keyof ProvisionMap>(key: TKey): ProvisionMap[TKey];
consume(key: unknown): any;
provide<TKey extends keyof ProvisionMap>(key: TKey, value: ProvisionMap[TKey]): void;
provide(key: unknown, value: any): void;
/**
* The current props of the associated element.
*
* @remarks
* Typically, you should read props either via the first parameter of the component or via the context iterator methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get props(): TProps;
/**
* The current value of the associated element.
*
* @remarks
* Typically, you should read values via refs, generator yield expressions, or the refresh, schedule or cleanup methods. This property is mainly for plugins or utilities which wrap contexts.
*/
get value(): TResult;
[Symbol.iterator](): Generator<TProps>;
[Symbol.asyncIterator](): AsyncGenerator<TProps>;
/**
* Re-executes the component.
*
* @returns The rendered value of the component or a promise of the rendered value if the component or its children execute asynchronously.
*
* @remarks
* The refresh method works a little differently for async generator components, in that it will resume the Context async iterator rather than resuming execution. This is because async generator components are perpetually resumed independent of updates/refresh.
*/
refresh(): Promise<TResult> | TResult;
/**
* Registers a callback which fires when the component commits. Will only fire once per callback and update.
*/
schedule(callback: (value: TResult) => unknown): void;
/**
* Registers a callback which fires when the component unmounts. Will only fire once per callback.
*/
cleanup(callback: (value: TResult) => unknown): void;
addEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: boolean | AddEventListenerOptions): void;
removeEventListener<T extends string>(type: T, listener: MappedEventListenerOrEventListenerObject<T> | null, options?: EventListenerOptions | boolean): void;
dispatchEvent(ev: Event): boolean;
}
declare global {
module JSX {
interface IntrinsicElements {
[tag: string]: any;
}
interface ElementChildrenAttribute {
children: {};
}
}
}
export {};
export * from "./crank";

@@ -1,3 +0,3 @@

export * from "./src/index";
export * from "./src/crank";
export * as dom from "./src/dom";
export * as html from "./src/html";

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc