@bikeshaving/crank
Advanced tools
Comparing version 0.3.7 to 0.3.8
# Changelog | ||
## [0.3.8] - 2020-12-27 | ||
Various performance improvements. | ||
### Changed | ||
- `create()` and `patch()` calls have been moved to a pre-order traversal of the tree. | ||
- The DOM renderer now checks properties and attributes before mutating them. | ||
## [0.3.7] - 2020-11-16 | ||
@@ -3,0 +8,0 @@ Mostly some changes when trying to get Crank to play nicely with contenteditables |
/** | ||
* Represents all valid values which can be used for the tag of an element. | ||
* A type which represents all valid values that can be the tag of an element. | ||
* | ||
* @remarks | ||
* Elements whose tags are strings or symbols are called “host” or “intrinsic” | ||
@@ -12,5 +11,5 @@ * elements, and their behavior is determined by the renderer, while elements | ||
/** | ||
* Maps the tag of an element to its expected props. | ||
* A helper type to map the tag of an element to its expected props. | ||
* | ||
* @typeparam TTag - The element’s tag. | ||
* @template TTag - The element’s tag. | ||
*/ | ||
@@ -24,7 +23,6 @@ export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown; | ||
/** | ||
* A special tag for grouping multiple children within a parent. | ||
* A special tag for grouping multiple children within the same parent. | ||
* | ||
* @remarks | ||
* All iterables which appear in the element tree are implicitly wrapped in a | ||
* fragment element. | ||
* All non-string iterables which appear in the element tree are implicitly | ||
* wrapped in a fragment element. | ||
* | ||
@@ -40,8 +38,7 @@ * This tag is just the empty string, and you can use the empty string in | ||
* | ||
* @remarks | ||
* This tag is useful for creating element trees with multiple roots, for | ||
* things like modals or tooltips. | ||
* | ||
* Renderer.prototype.render will implicitly wrap the passed in element tree in | ||
* an implicit Portal element. | ||
* Renderer.prototype.render will implicitly wrap passed in element trees in an | ||
* implicit Portal element. | ||
*/ | ||
@@ -54,3 +51,2 @@ export declare const Portal: any; | ||
* | ||
* @remarks | ||
* Copy elements are useful for when you want to prevent a subtree from | ||
@@ -63,6 +59,5 @@ * rerendering as a performance optimization. Copy elements can also be keyed, | ||
/** | ||
* A special element tag for injecting raw nodes or strings via a value prop. | ||
* A special tag for injecting raw nodes or strings via a value prop. | ||
* | ||
* @remarks | ||
* If the value prop is a string, Renderer.prototype.parse will be called on | ||
* If the value prop is a string, Renderer.prototype.parse() will be called on | ||
* the string and the result of that method will be inserted. | ||
@@ -75,3 +70,2 @@ */ | ||
* | ||
* @remarks | ||
* Arbitrary objects can also be safely rendered, but will be converted to a | ||
@@ -92,3 +86,3 @@ * string using the toString method. We exclude them from this type to catch | ||
* | ||
* @typeparam TProps - The expected props for the component. | ||
* @template [TProps=*] - The expected props for the component. | ||
*/ | ||
@@ -103,3 +97,3 @@ export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>; | ||
* | ||
* @typeparam TTag - The type of the tag of the element. | ||
* @template {Tag} [TTag=Tag] - The type of the tag of the element. | ||
* | ||
@@ -116,5 +110,4 @@ * @example | ||
* | ||
* @remarks | ||
* Typically, you use the createElement function to create elements rather than | ||
* instatiating this class directly. | ||
* Typically, you use a helper function like createElement to create elements | ||
* rather than instatiating this class directly. | ||
*/ | ||
@@ -127,10 +120,8 @@ export declare class Element<TTag extends Tag = Tag> { | ||
* https://overreacted.io/why-do-react-elements-have-typeof-property/ | ||
* | ||
* This property is defined on the element prototype rather than per | ||
* instance, because it is the same for every Element. | ||
*/ | ||
$$typeof: typeof ElementSymbol; | ||
/** | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* The tag of the element. Can be a string, symbol or function. | ||
@@ -148,3 +139,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-key". | ||
@@ -156,3 +146,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-ref". | ||
@@ -163,2 +152,7 @@ */ | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* @internal | ||
* children - The rendered children of the element. | ||
@@ -169,7 +163,14 @@ */ | ||
* @internal | ||
* node - The node associated with the element. | ||
* node - The node or context associated with the element. | ||
* | ||
* @remarks | ||
* Set by Renderer.prototype.create when the component is mounted. | ||
* This property will only be set for host elements. | ||
* For host elements, this property is set to the return value of | ||
* Renderer.prototype.create when the component is mounted, i.e. DOM nodes | ||
* for the DOM renderer. | ||
* | ||
* For component elements, this property is set to a Context instance | ||
* (Context<TagProps<TTag>>). | ||
* | ||
* We assign both of these to the same property because they are mutually | ||
* exclusive. We use any because the Element type has no knowledge of | ||
* renderer nodes. | ||
*/ | ||
@@ -179,17 +180,7 @@ _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 element which this element is replacing. | ||
* | ||
* @remarks | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
* If an element renders asynchronously, we show any previously rendered | ||
* values in its place until it has committed for the first time. This | ||
* property is set to the previously rendered child. | ||
*/ | ||
@@ -199,17 +190,17 @@ _fb: NarrowedChild; | ||
* @internal | ||
* inflight - The current async run of the element. | ||
* inflightChildren - The current async run of the element’s children. | ||
* | ||
* @remarks | ||
* This value is used to make sure Copy element refs fire at the correct | ||
* time, and is also used as the yield value of async generator components | ||
* with async children. It is unset when the element is committed. | ||
* This property is used to make sure Copy element refs fire at the correct | ||
* time, and is also used to create yield values for async generator | ||
* components with async children. It is unset when the element is committed. | ||
*/ | ||
_inf: Promise<any> | undefined; | ||
_ic: Promise<any> | undefined; | ||
/** | ||
* @internal | ||
* onvalues - The resolve function of a promise which represents the next | ||
* children result. | ||
* onvalue(s) - This property is set to the resolve function of a promise | ||
* which represents the next children, so that renderings can be raced. | ||
*/ | ||
_onv: Function | undefined; | ||
_ov: Function | undefined; | ||
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: ((value: unknown) => unknown) | undefined); | ||
get hadChildren(): boolean; | ||
} | ||
@@ -220,17 +211,14 @@ export declare function isElement(value: any): value is Element; | ||
* | ||
* @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 renderer methods or | ||
* components, and assigns the children prop according to the arguments passed | ||
* to the function. | ||
* components, and assigns the children prop according to any additional | ||
* arguments passed to the function. | ||
*/ | ||
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>; | ||
/** | ||
* Clones a given element. Will also shallow copy the props object. | ||
* Clones a given element, shallowly copying the props object. | ||
* | ||
* @remarks | ||
* Mainly used internally to make sure we don’t accidentally reuse elements in | ||
* an element tree, because element have internal properties which are directly | ||
* mutated by the renderer. | ||
* Used internally to make sure we don’t accidentally reuse elements when | ||
* rendering. | ||
*/ | ||
@@ -247,5 +235,4 @@ export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>; | ||
* | ||
* @typeparam TNode - The node type for the element assigned by the renderer. | ||
* @template 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 | ||
@@ -269,6 +256,6 @@ * answer varies depending on its tag. For host or Raw elements, the answer is | ||
* | ||
* @typeparam TNode - The type of the node for a rendering environment. | ||
* @typeparam TScope - Data which is passed down the tree. | ||
* @typeparam TRoot - The type of the root for a rendering environment. | ||
* @typeparam TResult - The type of exposed values. | ||
* @template TNode - The type of the node for a rendering environment. | ||
* @template TScope - Data which is passed down the tree. | ||
* @template TRoot - The type of the root for a rendering environment. | ||
* @template TResult - The type of exposed values. | ||
*/ | ||
@@ -309,3 +296,2 @@ export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> { | ||
* | ||
* @remarks | ||
* This is useful for renderers which don’t want to expose their internal | ||
@@ -320,3 +306,2 @@ * nodes. For instance, the HTML renderer will convert all internal nodes to | ||
* | ||
* @remarks | ||
* Useful for passing data down the element tree. For instance, the DOM | ||
@@ -332,7 +317,6 @@ * renderer uses this method to keep track of whether we’re in an SVG | ||
* | ||
* @remarks | ||
* This method sets the scope for child host elements, not the current host | ||
* element. | ||
*/ | ||
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
scope(_el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
/** | ||
@@ -346,3 +330,2 @@ * Called for each string in an element tree. | ||
* | ||
* @remarks | ||
* Rather than returning text nodes for whatever environment we’re rendering | ||
@@ -380,3 +363,2 @@ * to, we defer that step for Renderer.prototype.arrange. We do this so that | ||
* | ||
* @remarks | ||
* Used to mutate the node associated with an element when new props are | ||
@@ -395,3 +377,2 @@ * passed. | ||
* | ||
* @remarks | ||
* This method is also called by child components contexts as the last step | ||
@@ -419,10 +400,10 @@ * of a refresh. | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
* An interface which can be extended to provide strongly typed provisions (see | ||
* Context.prototype.get and Context.prototype.set) | ||
* Context.prototype.consume and Context.prototype.provide) | ||
*/ | ||
export interface ProvisionMap extends Crank.ProvisionMap { | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
@@ -434,7 +415,7 @@ * A class which is instantiated and passed to every component as its this | ||
* | ||
* @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. | ||
* @template [TProps=*] - The expected shape of the props passed to the | ||
* component. Used to strongly type the Context iterator methods. | ||
* @template [TResult=*] - The readable element value type. It is used in | ||
* places such as the return value of refresh and the argument passed to | ||
* schedule and cleanup callbacks. | ||
*/ | ||
@@ -460,3 +441,3 @@ export declare class Context<TProps = any, TResult = any> implements EventTarget { | ||
* host - The nearest ancestor host element. | ||
* @remarks | ||
* | ||
* When refresh is called, the host element will be arranged as the last step | ||
@@ -487,2 +468,3 @@ * of the commit, to make sure the parent’s children properly reflects the | ||
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined; | ||
/*** async properties ***/ | ||
/** | ||
@@ -494,3 +476,3 @@ * @internal | ||
*/ | ||
_oa: (() => unknown) | undefined; | ||
_oa: Function | undefined; | ||
/** | ||
@@ -518,27 +500,4 @@ * @internal | ||
* @internal | ||
* listeners - An array of event listeners added to the context via | ||
* Context.prototype.addEventListener | ||
* Contexts should never be instantiated directly. | ||
*/ | ||
_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>); | ||
@@ -548,3 +507,2 @@ /** | ||
* | ||
* @remarks | ||
* Typically, you should read props either via the first parameter of the | ||
@@ -558,3 +516,2 @@ * component or via the context iterator methods. This property is mainly for | ||
* | ||
* @remarks | ||
* Typically, you should read values via refs, generator yield expressions, | ||
@@ -568,12 +525,12 @@ * or the refresh, schedule or cleanup methods. This property is mainly for | ||
/** | ||
* Re-executes the component. | ||
* Re-executes a component. | ||
* | ||
* @returns The rendered value of the component or a promise of the rendered | ||
* value if the component or its children execute asynchronously. | ||
* @returns The rendered value of the component or a promise thereof 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. | ||
* components, in that it will resume the Context’s props async iterator | ||
* rather than resuming execution. This is because async generator components | ||
* are perpetually resumed independent of updates, and rely on the props | ||
* async iterator to suspend. | ||
*/ | ||
@@ -610,8 +567,2 @@ refresh(): Promise<TResult> | TResult; | ||
}; | ||
interface EventListenerRecord { | ||
type: string; | ||
listener: MappedEventListenerOrEventListenerObject<any>; | ||
callback: MappedEventListener<any>; | ||
options: AddEventListenerOptions; | ||
} | ||
declare global { | ||
@@ -618,0 +569,0 @@ module Crank { |
809
cjs/crank.js
@@ -15,6 +15,4 @@ 'use strict'; | ||
* | ||
* @remarks | ||
* This function pretty much does the same thing as wrap above except it | ||
* handles nulls and iterables, and shallowly clones arrays, so it is | ||
* appropriate for wrapping user-provided children from el.props. | ||
* This function does the same thing as wrap() above except it handles nulls | ||
* and iterables, so it is appropriate for wrapping user-provided children. | ||
*/ | ||
@@ -25,3 +23,3 @@ function arrayify(value) { | ||
: Array.isArray(value) | ||
? value.slice() | ||
? value | ||
: typeof value === "string" || | ||
@@ -44,7 +42,6 @@ typeof value[Symbol.iterator] !== "function" | ||
/** | ||
* A special tag for grouping multiple children within a parent. | ||
* A special tag for grouping multiple children within the same parent. | ||
* | ||
* @remarks | ||
* All iterables which appear in the element tree are implicitly wrapped in a | ||
* fragment element. | ||
* All non-string iterables which appear in the element tree are implicitly | ||
* wrapped in a fragment element. | ||
* | ||
@@ -56,4 +53,4 @@ * This tag is just the empty string, and you can use the empty string in | ||
const Fragment = ""; | ||
// NOTE: We assert the following symbol tags as any because typescript support | ||
// for symbol tags in JSX does not exist yet. | ||
// TODO: We assert the following symbol tags as any because typescript support | ||
// for symbol tags in JSX doesn’t exist yet. | ||
// https://github.com/microsoft/TypeScript/issues/38367 | ||
@@ -63,8 +60,7 @@ /** | ||
* | ||
* @remarks | ||
* This tag is useful for creating element trees with multiple roots, for | ||
* things like modals or tooltips. | ||
* | ||
* Renderer.prototype.render will implicitly wrap the passed in element tree in | ||
* an implicit Portal element. | ||
* Renderer.prototype.render will implicitly wrap passed in element trees in an | ||
* implicit Portal element. | ||
*/ | ||
@@ -76,3 +72,2 @@ const Portal = Symbol.for("crank.Portal"); | ||
* | ||
* @remarks | ||
* Copy elements are useful for when you want to prevent a subtree from | ||
@@ -84,6 +79,5 @@ * rerendering as a performance optimization. Copy elements can also be keyed, | ||
/** | ||
* A special element tag for injecting raw nodes or strings via a value prop. | ||
* A special tag for injecting raw nodes or strings via a value prop. | ||
* | ||
* @remarks | ||
* If the value prop is a string, Renderer.prototype.parse will be called on | ||
* If the value prop is a string, Renderer.prototype.parse() will be called on | ||
* the string and the result of that method will be inserted. | ||
@@ -95,15 +89,20 @@ */ | ||
/** | ||
* A flag which is set when the element has been mounted. Used mainly to | ||
* detect whether an element is being reused so that we clone it. | ||
* A flag which is set when the element is mounted, used to detect whether an | ||
* element is being reused so that we clone it rather than accidentally | ||
* overwriting its state. | ||
* | ||
* IMPORTANT: Changing this flag value would likely be a breaking changes in terms | ||
* of interop between elements created by different versions of Crank. | ||
*/ | ||
const IsMounted = 1 << 0; | ||
const IsInUse = 1 << 0; | ||
/** | ||
* A flag which is set when the element has committed at least once. | ||
* A flag which tracks whether the element has previously rendered children, | ||
* used to clear elements which no longer render children in the next render. | ||
* We may deprecate this and make elements without explicit children | ||
* uncontrolled. | ||
*/ | ||
const IsCommitted = 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, | ||
// any change to Element’s properties will be considered a breaking change. | ||
const HadChildren = 1 << 1; | ||
// 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. | ||
/** | ||
@@ -114,3 +113,3 @@ * Elements are the basic building blocks of Crank applications. They are | ||
* | ||
* @typeparam TTag - The type of the tag of the element. | ||
* @template {Tag} [TTag=Tag] - The type of the tag of the element. | ||
* | ||
@@ -127,9 +126,7 @@ * @example | ||
* | ||
* @remarks | ||
* Typically, you use the createElement function to create elements rather than | ||
* instatiating this class directly. | ||
* Typically, you use a helper function like createElement to create elements | ||
* rather than instatiating this class directly. | ||
*/ | ||
class Element { | ||
constructor(tag, props, key, ref) { | ||
this.$$typeof = ElementSymbol; | ||
this._f = 0; | ||
@@ -142,12 +139,11 @@ this.tag = tag; | ||
this._n = undefined; | ||
this._ctx = undefined; | ||
// NOTE: We don’t assign fallback (_fb), inflight (_inf) or onvalues (_onv) | ||
// in the constructor to save on the shallow size of elements. This saves a | ||
// couple bytes per element, especially when we aren’t rendering | ||
// asynchronous components. This may or may not be a good idea. | ||
//this._fb = undefined; | ||
//this._inf = undefined; | ||
//this._onv = undefined; | ||
this._fb = undefined; | ||
this._ic = undefined; | ||
this._ov = undefined; | ||
} | ||
get hadChildren() { | ||
return (this._f & HadChildren) !== 0; | ||
} | ||
} | ||
Element.prototype.$$typeof = ElementSymbol; | ||
function isElement(value) { | ||
@@ -159,8 +155,7 @@ return value != null && value.$$typeof === ElementSymbol; | ||
* | ||
* @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 renderer methods or | ||
* components, and assigns the children prop according to the arguments passed | ||
* to the function. | ||
* components, and assigns the children prop according to any additional | ||
* arguments passed to the function. | ||
*/ | ||
@@ -173,17 +168,18 @@ function createElement(tag, props, ...children) { | ||
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["crank-key"] != null) { | ||
key = props["crank-key"]; | ||
} | ||
switch (name) { | ||
case "crank-key": | ||
// 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["crank-key"] != null) { | ||
key = props["crank-key"]; | ||
} | ||
break; | ||
case "crank-ref": | ||
if (typeof props["crank-ref"] === "function") { | ||
ref = props["crank-ref"]; | ||
} | ||
break; | ||
default: | ||
props1[name] = props[name]; | ||
} | ||
else if (name === "crank-ref") { | ||
if (typeof props["crank-ref"] === "function") { | ||
ref = props["crank-ref"]; | ||
} | ||
} | ||
else { | ||
props1[name] = props[name]; | ||
} | ||
} | ||
@@ -200,8 +196,6 @@ } | ||
/** | ||
* Clones a given element. Will also shallow copy the props object. | ||
* Clones a given element, shallowly copying the props object. | ||
* | ||
* @remarks | ||
* Mainly used internally to make sure we don’t accidentally reuse elements in | ||
* an element tree, because element have internal properties which are directly | ||
* mutated by the renderer. | ||
* Used internally to make sure we don’t accidentally reuse elements when | ||
* rendering. | ||
*/ | ||
@@ -232,3 +226,2 @@ function cloneElement(el) { | ||
* | ||
* @remarks | ||
* Normalize will flatten only one level of nested arrays, because it is | ||
@@ -282,7 +275,4 @@ * designed to be called once at each level of the tree. It will also | ||
function getValue(el) { | ||
if (el._fb) { | ||
if (typeof el._fb === "object") { | ||
return getValue(el._fb); | ||
} | ||
return el._fb; | ||
if (typeof el._fb !== "undefined") { | ||
return typeof el._fb === "object" ? getValue(el._fb) : el._fb; | ||
} | ||
@@ -297,2 +287,5 @@ else if (el.tag === Portal) { | ||
} | ||
function getInflightValue(el) { | ||
return ((typeof el.tag === "function" && el._n._iv) || el._ic || getValue(el)); | ||
} | ||
/** | ||
@@ -320,6 +313,6 @@ * Walks an element’s children to find its child values. | ||
* | ||
* @typeparam TNode - The type of the node for a rendering environment. | ||
* @typeparam TScope - Data which is passed down the tree. | ||
* @typeparam TRoot - The type of the root for a rendering environment. | ||
* @typeparam TResult - The type of exposed values. | ||
* @template TNode - The type of the node for a rendering environment. | ||
* @template TScope - Data which is passed down the tree. | ||
* @template TRoot - The type of the root for a rendering environment. | ||
* @template TResult - The type of exposed values. | ||
*/ | ||
@@ -352,3 +345,3 @@ class Renderer { | ||
portal = createElement(Portal, { children, root }); | ||
portal._ctx = ctx; | ||
portal._n = ctx; | ||
if (typeof root === "object" && root !== null && children != null) { | ||
@@ -359,3 +352,3 @@ this._cache.set(root, portal); | ||
else { | ||
if (portal._ctx !== ctx) { | ||
if (portal._n !== ctx) { | ||
throw new Error("Context mismatch"); | ||
@@ -369,3 +362,3 @@ } | ||
const value = update(this, root, portal, ctx, undefined, portal); | ||
// NOTE: We return the child values of the portal because portal elements | ||
// We return the child values of the portal because portal elements | ||
// themselves have no readable value. | ||
@@ -398,3 +391,2 @@ if (isPromiseLike(value)) { | ||
* | ||
* @remarks | ||
* This is useful for renderers which don’t want to expose their internal | ||
@@ -411,3 +403,2 @@ * nodes. For instance, the HTML renderer will convert all internal nodes to | ||
* | ||
* @remarks | ||
* Useful for passing data down the element tree. For instance, the DOM | ||
@@ -423,7 +414,6 @@ * renderer uses this method to keep track of whether we’re in an SVG | ||
* | ||
* @remarks | ||
* This method sets the scope for child host elements, not the current host | ||
* element. | ||
*/ | ||
scope(el, scope) { | ||
scope(_el, scope) { | ||
return scope; | ||
@@ -439,3 +429,2 @@ } | ||
* | ||
* @remarks | ||
* Rather than returning text nodes for whatever environment we’re rendering | ||
@@ -479,3 +468,2 @@ * to, we defer that step for Renderer.prototype.arrange. We do this so that | ||
* | ||
* @remarks | ||
* Used to mutate the node associated with an element when new props are | ||
@@ -497,3 +485,2 @@ * passed. | ||
* | ||
* @remarks | ||
* This method is also called by child components contexts as the last step | ||
@@ -530,58 +517,7 @@ * of a refresh. | ||
/*** PRIVATE RENDERER FUNCTIONS ***/ | ||
function diff(renderer, root, host, ctx, scope, oldChild, newChild) { | ||
let value; | ||
if (typeof oldChild === "object" && | ||
typeof newChild === "object" && | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
// TODO: implement Raw element parse caching | ||
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(NOOP); | ||
} | ||
else { | ||
newChild.ref(value); | ||
} | ||
} | ||
newChild = oldChild; | ||
} | ||
else { | ||
if (newChild._f & IsMounted) { | ||
newChild = cloneElement(newChild); | ||
} | ||
value = mount(renderer, root, host, ctx, scope, newChild); | ||
if (isPromiseLike(value)) { | ||
newChild._fb = oldChild; | ||
} | ||
} | ||
} | ||
else if (typeof newChild === "string") { | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
return [newChild, value]; | ||
} | ||
function mount(renderer, root, host, ctx, scope, el) { | ||
el._f |= IsMounted; | ||
el._f |= IsInUse; | ||
if (typeof el.tag === "function") { | ||
el._ctx = new Context(renderer, root, host, ctx, scope, el); | ||
return updateCtx(el._ctx); | ||
el._n = new Context(renderer, root, host, ctx, scope, el); | ||
return updateCtx(el._n); | ||
} | ||
@@ -595,51 +531,14 @@ else if (el.tag === Raw) { | ||
} | ||
else { | ||
el._n = renderer.create(el, scope); | ||
renderer.patch(el, el._n); | ||
} | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
} | ||
return mountChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
return updateChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
} | ||
function mountChildren(renderer, root, host, ctx, scope, el, children) { | ||
const newChildren = arrayify(children); | ||
const values = []; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
for (let i = 0; i < newChildren.length; i++) { | ||
let child = narrow(newChildren[i]); | ||
const key = child && child.key; | ||
if (key !== undefined) { | ||
if (seen.has(key)) { | ||
console.error("Duplicate key", key); | ||
} | ||
seen.add(key); | ||
} | ||
let value; | ||
[child, value] = diff(renderer, root, host, ctx, scope, undefined, // oldChild | ||
child); | ||
newChildren[i] = child; | ||
values.push(value); | ||
isAsync = isAsync || isPromiseLike(value); | ||
} | ||
el._ch = unwrap(newChildren); | ||
if (isAsync) { | ||
let onvalues; | ||
const values1 = Promise.race([ | ||
Promise.all(values), | ||
new Promise((resolve) => (onvalues = resolve)), | ||
]); | ||
if (el._onv) { | ||
el._onv(values1); | ||
} | ||
el._onv = onvalues; | ||
el._inf = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
return el._inf; | ||
} | ||
if (el._onv) { | ||
el._onv(values); | ||
el._onv = undefined; | ||
} | ||
return commit(renderer, scope, el, normalize(values)); | ||
} | ||
function update(renderer, root, host, ctx, scope, el) { | ||
if (el._ctx) { | ||
return updateCtx(el._ctx); | ||
if (typeof el.tag === "function") { | ||
return updateCtx(el._n); | ||
} | ||
@@ -650,7 +549,10 @@ else if (el.tag === Raw) { | ||
else if (el.tag !== Fragment) { | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
if (el.tag === Portal) { | ||
root = el.props.root; | ||
} | ||
else { | ||
renderer.patch(el, el._n); | ||
} | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
} | ||
@@ -670,25 +572,28 @@ return updateChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
function updateChildren(renderer, root, host, ctx, scope, el, children) { | ||
if (typeof el._ch === "undefined") { | ||
return mountChildren(renderer, root, host, ctx, scope, el, children); | ||
} | ||
const oldChildren = wrap(el._ch); | ||
const newChildren = arrayify(children); | ||
const newChildren1 = []; | ||
const values = []; | ||
const graveyard = []; | ||
let graveyard; | ||
let seenKeys; | ||
let childrenByKey; | ||
let isAsync = false; | ||
let i = 0; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
let childrenByKey; | ||
// TODO: switch to mountChildren if there are no more children | ||
for (let j = 0; j < newChildren.length; j++) { | ||
let oldChild = oldChildren[i]; | ||
for (let j = 0, il = oldChildren.length, jl = newChildren.length; j < jl; j++) { | ||
let oldChild = i >= il ? undefined : oldChildren[i]; | ||
let newChild = narrow(newChildren[j]); | ||
// ALIGNMENT | ||
let oldKey = oldChild && oldChild.key; | ||
let newKey = newChild && newChild.key; | ||
if (newKey !== undefined && seen.has(newKey)) { | ||
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined; | ||
let newKey = typeof newChild === "object" ? newChild.key : undefined; | ||
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) { | ||
console.error("Duplicate key", newKey); | ||
newKey = undefined; | ||
} | ||
if (oldKey !== newKey) { | ||
if (oldKey === newKey) { | ||
if (childrenByKey !== undefined && newKey !== undefined) { | ||
childrenByKey.delete(newKey); | ||
} | ||
i++; | ||
} | ||
else { | ||
if (!childrenByKey) { | ||
@@ -710,22 +615,66 @@ childrenByKey = createChildrenByKey(oldChildren.slice(i)); | ||
} | ||
seen.add(newKey); | ||
if (!seenKeys) { | ||
seenKeys = new Set(); | ||
} | ||
seenKeys.add(newKey); | ||
} | ||
} | ||
else { | ||
if (childrenByKey !== undefined && newKey !== undefined) { | ||
childrenByKey.delete(newKey); | ||
// UPDATING | ||
let value; | ||
if (typeof oldChild === "object" && | ||
typeof newChild === "object" && | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
i++; | ||
// TODO: implement Raw element parse caching | ||
if (oldChild !== newChild) { | ||
oldChild.props = newChild.props; | ||
oldChild.ref = newChild.ref; | ||
newChild = oldChild; | ||
} | ||
value = update(renderer, root, host, ctx, scope, newChild); | ||
} | ||
// UPDATING | ||
let value; | ||
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild); | ||
values.push(value); | ||
newChildren[j] = newChild; | ||
else if (typeof newChild === "object") { | ||
if (newChild.tag === Copy) { | ||
value = | ||
typeof oldChild === "object" | ||
? getInflightValue(oldChild) | ||
: oldChild; | ||
if (typeof newChild.ref === "function") { | ||
if (isPromiseLike(value)) { | ||
value.then(newChild.ref).catch(NOOP); | ||
} | ||
else { | ||
newChild.ref(value); | ||
} | ||
} | ||
newChild = oldChild; | ||
} | ||
else { | ||
if (newChild._f & IsInUse) { | ||
newChild = cloneElement(newChild); | ||
} | ||
value = mount(renderer, root, host, ctx, scope, newChild); | ||
if (isPromiseLike(value)) { | ||
newChild._fb = oldChild; | ||
} | ||
} | ||
} | ||
else if (typeof newChild === "string") { | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
newChildren1[j] = newChild; | ||
values[j] = value; | ||
isAsync = isAsync || isPromiseLike(value); | ||
if (typeof oldChild === "object" && oldChild !== newChild) { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(oldChild); | ||
} | ||
} | ||
el._ch = unwrap(newChildren); | ||
el._ch = unwrap(newChildren1); | ||
// cleanup | ||
@@ -735,2 +684,5 @@ for (; i < oldChildren.length; i++) { | ||
if (typeof oldChild === "object" && typeof oldChild.key === "undefined") { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(oldChild); | ||
@@ -740,2 +692,5 @@ } | ||
if (childrenByKey !== undefined && childrenByKey.size > 0) { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(...childrenByKey.values()); | ||
@@ -745,3 +700,7 @@ } | ||
let values1 = Promise.all(values).finally(() => { | ||
graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
if (graveyard) { | ||
for (let i = 0; i < graveyard.length; i++) { | ||
unmount(renderer, host, ctx, graveyard[i]); | ||
} | ||
} | ||
}); | ||
@@ -753,19 +712,23 @@ let onvalues; | ||
]); | ||
if (el._onv) { | ||
el._onv(values1); | ||
if (el._ov) { | ||
el._ov(values1); | ||
} | ||
el._onv = onvalues; | ||
el._inf = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
return el._inf; | ||
el._ic = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
el._ov = onvalues; | ||
return el._ic; | ||
} | ||
graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
if (el._onv) { | ||
el._onv(values); | ||
el._onv = undefined; | ||
if (graveyard) { | ||
for (let i = 0; i < graveyard.length; i++) { | ||
unmount(renderer, host, ctx, graveyard[i]); | ||
} | ||
} | ||
if (el._ov) { | ||
el._ov(values); | ||
el._ov = undefined; | ||
} | ||
return commit(renderer, scope, el, normalize(values)); | ||
} | ||
function commit(renderer, scope, el, values) { | ||
if (el._inf) { | ||
el._inf = undefined; | ||
if (el._ic) { | ||
el._ic = undefined; | ||
} | ||
@@ -776,12 +739,5 @@ if (el._fb) { | ||
let value; | ||
if (el._ctx) { | ||
value = commitCtx(el._ctx, values); | ||
if (typeof el.tag === "function") { | ||
value = commitCtx(el._n, values); | ||
} | ||
else if (el.tag === Portal) { | ||
if (!(el._f & IsCommitted)) { | ||
el._f |= IsCommitted; | ||
} | ||
renderer.arrange(el, el.props.root, values); | ||
renderer.complete(el.props.root); | ||
} | ||
else if (el.tag === Raw) { | ||
@@ -800,9 +756,16 @@ if (typeof el.props.value === "string") { | ||
else { | ||
if (!(el._f & IsCommitted)) { | ||
el._n = renderer.create(el, scope); | ||
el._f |= IsCommitted; | ||
if (el.tag === Portal) { | ||
renderer.arrange(el, el.props.root, values); | ||
renderer.complete(el.props.root); | ||
} | ||
renderer.patch(el, el._n); | ||
renderer.arrange(el, el._n, values); | ||
else { | ||
renderer.arrange(el, el._n, values); | ||
} | ||
value = el._n; | ||
if (values.length) { | ||
el._f |= HadChildren; | ||
} | ||
else { | ||
el._f &= ~HadChildren; | ||
} | ||
} | ||
@@ -815,5 +778,5 @@ if (el.ref) { | ||
function unmount(renderer, host, ctx, el) { | ||
if (el._ctx) { | ||
unmountCtx(el._ctx); | ||
ctx = el._ctx; | ||
if (typeof el.tag === "function") { | ||
unmountCtx(el._n); | ||
ctx = el._n; | ||
} | ||
@@ -828,7 +791,5 @@ else if (el.tag === Portal) { | ||
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); | ||
} | ||
for (let i = 0; i < listeners.length; i++) { | ||
const record = listeners[i]; | ||
el._n.removeEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -847,3 +808,3 @@ } | ||
} | ||
// CONTEXT FLAGS | ||
/*** CONTEXT FLAGS ***/ | ||
/** | ||
@@ -856,6 +817,6 @@ * A flag which is set when the component is being updated by the parent and | ||
/** | ||
* A flag which is set when the component function is called or the component | ||
* generator is resumed. This flags is used to ensure that a component which | ||
* synchronously triggers a second update in the course of rendering does not | ||
* cause an stack overflow or a generator error. | ||
* A flag which is set when the component function or generator is | ||
* synchronously executing. This flags is used to ensure that a component which | ||
* triggers a second update in the course of rendering does not cause an stack | ||
* overflow or a generator error. | ||
*/ | ||
@@ -870,11 +831,11 @@ const IsExecuting = 1 << 1; | ||
* A flag used by async generator components in conjunction with the | ||
* onIsAvailable (_oa) callback to mark whether new props can be pulled via the | ||
* context async iterator. | ||
* onavailable (_oa) callback to mark whether new props can be pulled via the | ||
* context async iterator. See the Symbol.asyncIterator method and the | ||
* resumeCtx function. | ||
*/ | ||
const IsAvailable = 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 or throws. | ||
* Done components will stick to their last rendered value and ignore further | ||
* updates. | ||
* A flag which is set when a generator components returns, i.e. the done | ||
* property on the generator is set to true or throws. Done components will | ||
* stick to their last rendered value and ignore further updates. | ||
*/ | ||
@@ -884,3 +845,3 @@ const IsDone = 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. | ||
* are no longer in the element tree and cannot refresh or rerender. | ||
*/ | ||
@@ -896,2 +857,5 @@ const IsUnmounted = 1 << 5; | ||
const IsAsyncGen = 1 << 7; | ||
const provisionMaps = new WeakMap(); | ||
const scheduleMap = new WeakMap(); | ||
const cleanupMap = new WeakMap(); | ||
/** | ||
@@ -903,7 +867,7 @@ * A class which is instantiated and passed to every component as its this | ||
* | ||
* @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. | ||
* @template [TProps=*] - The expected shape of the props passed to the | ||
* component. Used to strongly type the Context iterator methods. | ||
* @template [TResult=*] - The readable element value type. It is used in | ||
* places such as the return value of refresh and the argument passed to | ||
* schedule and cleanup callbacks. | ||
*/ | ||
@@ -913,2 +877,3 @@ class Context { | ||
* @internal | ||
* Contexts should never be instantiated directly. | ||
*/ | ||
@@ -923,2 +888,8 @@ constructor(renderer, root, host, parent, scope, el) { | ||
this._el = el; | ||
this._it = undefined; | ||
this._oa = undefined; | ||
this._ib = undefined; | ||
this._iv = undefined; | ||
this._eb = undefined; | ||
this._ev = undefined; | ||
} | ||
@@ -928,3 +899,2 @@ /** | ||
* | ||
* @remarks | ||
* Typically, you should read props either via the first parameter of the | ||
@@ -940,3 +910,2 @@ * component or via the context iterator methods. This property is mainly for | ||
* | ||
* @remarks | ||
* Typically, you should read values via refs, generator yield expressions, | ||
@@ -985,12 +954,12 @@ * or the refresh, schedule or cleanup methods. This property is mainly for | ||
/** | ||
* Re-executes the component. | ||
* Re-executes a component. | ||
* | ||
* @returns The rendered value of the component or a promise of the rendered | ||
* value if the component or its children execute asynchronously. | ||
* @returns The rendered value of the component or a promise thereof 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. | ||
* components, in that it will resume the Context’s props async iterator | ||
* rather than resuming execution. This is because async generator components | ||
* are perpetually resumed independent of updates, and rely on the props | ||
* async iterator to suspend. | ||
*/ | ||
@@ -1014,6 +983,8 @@ refresh() { | ||
schedule(callback) { | ||
if (!this._ss) { | ||
this._ss = new Set(); | ||
let callbacks = scheduleMap.get(this); | ||
if (!callbacks) { | ||
callbacks = new Set(); | ||
scheduleMap.set(this, callbacks); | ||
} | ||
this._ss.add(callback); | ||
callbacks.add(callback); | ||
} | ||
@@ -1025,11 +996,14 @@ /** | ||
cleanup(callback) { | ||
if (!this._cs) { | ||
this._cs = new Set(); | ||
let callbacks = cleanupMap.get(this); | ||
if (!callbacks) { | ||
callbacks = new Set(); | ||
cleanupMap.set(this, callbacks); | ||
} | ||
this._cs.add(callback); | ||
callbacks.add(callback); | ||
} | ||
consume(key) { | ||
for (let parent = this._pa; parent !== undefined; parent = parent._pa) { | ||
if (parent._ps && parent._ps.has(key)) { | ||
return parent._ps.get(key); | ||
const provisions = provisionMaps.get(parent); | ||
if (provisions && provisions.has(key)) { | ||
return provisions.get(key); | ||
} | ||
@@ -1039,13 +1013,23 @@ } | ||
provide(key, value) { | ||
if (!this._ps) { | ||
this._ps = new Map(); | ||
let provisions = provisionMaps.get(this); | ||
if (!provisions) { | ||
provisions = new Map(); | ||
provisionMaps.set(this, provisions); | ||
} | ||
this._ps.set(key, value); | ||
provisions.set(key, value); | ||
} | ||
addEventListener(type, listener, options) { | ||
let listeners; | ||
if (listener == null) { | ||
return; | ||
} | ||
else if (!this._ls) { | ||
this._ls = []; | ||
else { | ||
const listeners1 = listenersMap.get(this); | ||
if (listeners1) { | ||
listeners = listeners1; | ||
} | ||
else { | ||
listeners = []; | ||
listenersMap.set(this, listeners); | ||
} | ||
} | ||
@@ -1062,9 +1046,6 @@ options = normalizeOptions(options); | ||
if (options.once) { | ||
const self = this; | ||
record.callback = function () { | ||
if (self._ls) { | ||
self._ls = self._ls.filter((record1) => record !== record1); | ||
if (self._ls.length === 0) { | ||
self._ls = undefined; | ||
} | ||
const i = listeners.indexOf(record); | ||
if (i !== -1) { | ||
listeners.splice(i, 1); | ||
} | ||
@@ -1074,3 +1055,3 @@ return callback.apply(this, arguments); | ||
} | ||
if (this._ls.some((record1) => record.type === record1.type && | ||
if (listeners.some((record1) => record.type === record1.type && | ||
record.listener === record1.listener && | ||
@@ -1080,3 +1061,3 @@ !record.options.capture === !record1.options.capture)) { | ||
} | ||
this._ls.push(record); | ||
listeners.push(record); | ||
for (const value of getChildValues(this._el)) { | ||
@@ -1089,7 +1070,8 @@ if (isEventTarget(value)) { | ||
removeEventListener(type, listener, options) { | ||
if (listener == null || !this._ls) { | ||
const listeners = listenersMap.get(this); | ||
if (listener == null || listeners == null) { | ||
return; | ||
} | ||
const options1 = normalizeOptions(options); | ||
const i = this._ls.findIndex((record) => record.type === type && | ||
const i = listeners.findIndex((record) => record.type === type && | ||
record.listener === listener && | ||
@@ -1100,7 +1082,4 @@ !record.options.capture === !options1.capture); | ||
} | ||
const record = this._ls[i]; | ||
this._ls.splice(i, 1); | ||
if (this._ls.length === 0) { | ||
this._ls = undefined; | ||
} | ||
const record = listeners[i]; | ||
listeners.splice(i, 1); | ||
for (const value of getChildValues(this._el)) { | ||
@@ -1118,3 +1097,4 @@ if (isEventTarget(value)) { | ||
// We patch the stopImmediatePropagation method because ev.cancelBubble | ||
// only informs us if stopPropagation was called. | ||
// only informs us if stopPropagation was called and there are no | ||
// properties which inform us if stopImmediatePropagation was called. | ||
let immediateCancelBubble = false; | ||
@@ -1127,14 +1107,18 @@ const stopImmediatePropagation = ev.stopImmediatePropagation; | ||
setEventProperty(ev, "target", this); | ||
// The only possible errors in this block are errors thrown by listener | ||
// callbacks, and dispatchEvent will only log the error rather than | ||
// rethrowing it. We return true because the return value is overridden in | ||
// the finally block but TypeScript (justifiably) does not recognize the | ||
// unsafe return statement. | ||
// The only possible errors in this block are errors thrown in callbacks, | ||
// and dispatchEvent is designed to only these errors rather than throwing | ||
// them. Therefore, we place all code in a try block, log errors in the | ||
// catch block use unsafe return statement in the finally block. | ||
// | ||
// Each early return within the try block returns true because while the | ||
// return value is overridden in the finally block, TypeScript | ||
// (justifiably) does not recognize the unsafe return statement. | ||
try { | ||
setEventProperty(ev, "eventPhase", CAPTURING_PHASE); | ||
for (let i = path.length - 1; i >= 0; i--) { | ||
const et = path[i]; | ||
if (et._ls) { | ||
setEventProperty(ev, "currentTarget", et); | ||
for (const record of et._ls) { | ||
const target = path[i]; | ||
const listeners = listenersMap.get(target); | ||
if (listeners) { | ||
setEventProperty(ev, "currentTarget", target); | ||
for (const record of listeners) { | ||
if (record.type === ev.type && record.options.capture) { | ||
@@ -1152,23 +1136,28 @@ record.callback.call(this, ev); | ||
} | ||
if (this._ls) { | ||
setEventProperty(ev, "eventPhase", AT_TARGET); | ||
setEventProperty(ev, "currentTarget", this); | ||
for (const record of this._ls) { | ||
if (record.type === ev.type) { | ||
record.callback.call(this, ev); | ||
if (immediateCancelBubble) { | ||
return true; | ||
{ | ||
const listeners = listenersMap.get(this); | ||
if (listeners) { | ||
setEventProperty(ev, "eventPhase", AT_TARGET); | ||
setEventProperty(ev, "currentTarget", this); | ||
for (const record of listeners) { | ||
if (record.type === ev.type) { | ||
record.callback.call(this, ev); | ||
if (immediateCancelBubble) { | ||
return true; | ||
} | ||
} | ||
} | ||
if (ev.cancelBubble) { | ||
return true; | ||
} | ||
} | ||
if (ev.cancelBubble) { | ||
return true; | ||
} | ||
} | ||
if (ev.bubbles) { | ||
setEventProperty(ev, "eventPhase", BUBBLING_PHASE); | ||
for (const et of path) { | ||
if (et._ls) { | ||
setEventProperty(ev, "currentTarget", et); | ||
for (const record of et._ls) { | ||
for (let i = 0; i < path.length; i++) { | ||
const target = path[i]; | ||
const listeners = listenersMap.get(target); | ||
if (listeners) { | ||
setEventProperty(ev, "currentTarget", target); | ||
for (const record of listeners) { | ||
if (record.type === ev.type && !record.options.capture) { | ||
@@ -1200,15 +1189,2 @@ record.callback.call(this, ev); | ||
/*** PRIVATE CONTEXT FUNCTIONS ***/ | ||
/* | ||
* NOTE: The functions stepCtx, advanceCtx and runCtx work together to | ||
* implement the async queueing behavior of components. The runCtx function | ||
* calls the stepCtx function, which returns two results in a tuple. The first | ||
* result, called the “block,” is a possible promise which represents the | ||
* duration for which the component is blocked from accepting new updates. The | ||
* second result, called the “value,” is the actual result of the update. The | ||
* runCtx function caches block/value from the stepCtx 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 represent an enqueued next stepCtx. | ||
* Enqueued steps are dequeued in a finally callback on the blocking promise. | ||
*/ | ||
/** | ||
@@ -1218,3 +1194,3 @@ * This function is responsible for executing the component and handling all | ||
* | ||
* @returns A tuple [block, value] | ||
* @returns {[block, value]} A tuple where | ||
* block - A possible promise which represents the duration during which the | ||
@@ -1224,11 +1200,9 @@ * component is blocked from updating. | ||
* | ||
* @remarks | ||
* Each component type will block 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 | ||
* - 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 | ||
* - 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. | ||
*/ | ||
@@ -1241,5 +1215,5 @@ function stepCtx(ctx) { | ||
const initial = !ctx._it; | ||
try { | ||
ctx._f |= IsExecuting; | ||
if (initial) { | ||
if (initial) { | ||
try { | ||
ctx._f |= IsExecuting; | ||
clearEventListeners(ctx); | ||
@@ -1261,12 +1235,17 @@ const result = el.tag.call(ctx, el.props); | ||
} | ||
finally { | ||
ctx._f &= ~IsExecuting; | ||
} | ||
} | ||
finally { | ||
ctx._f &= ~IsExecuting; | ||
} | ||
// The value passed back into the generator as the argument to the next | ||
// method is a promise if an async generator component has async children. | ||
// Sync generator components only resume when their children have fulfilled | ||
// so ctx._el._ic (the element’s inflight children) will never be defined. | ||
let oldValue; | ||
if (ctx._el._inf) { | ||
oldValue = ctx._el._inf.then((value) => ctx._re.read(value), () => ctx._re.read(undefined)); | ||
if (initial) { | ||
// The argument passed to the first call to next is ignored. | ||
oldValue = undefined; | ||
} | ||
else if (initial) { | ||
oldValue = ctx._re.read(undefined); | ||
else if (ctx._el._ic) { | ||
oldValue = ctx._el._ic.then(ctx._re.read, () => ctx._re.read(undefined)); | ||
} | ||
@@ -1341,10 +1320,9 @@ else { | ||
/** | ||
* @remarks | ||
* Called when the inflight block promise settles. | ||
*/ | ||
function advanceCtx(ctx) { | ||
// _ib: inflightBlock | ||
// _iv: inflightValue | ||
// _eb: enqueuedBlock | ||
// _ev: enqueuedValue | ||
// _ib - inflightBlock | ||
// _iv - inflightValue | ||
// _eb - enqueuedBlock | ||
// _ev - enqueuedValue | ||
ctx._ib = ctx._eb; | ||
@@ -1360,2 +1338,14 @@ ctx._iv = ctx._ev; | ||
* Enqueues and executes the component associated with the context. | ||
* | ||
* The functions stepCtx, advanceCtx and runCtx work together to implement the | ||
* async queueing behavior of components. The runCtx function calls the stepCtx | ||
* function, which returns two results in a tuple. The first result, called the | ||
* “block,” is a possible promise which represents the duration for which the | ||
* component is blocked from accepting new updates. The second result, called | ||
* the “value,” is the actual result of the update. The runCtx function caches | ||
* block/value from the stepCtx function on the context, according to whether | ||
* the component blocks. The “inflight” block/value properties are the | ||
* currently executing update, and the “enqueued” block/value properties | ||
* represent an enqueued next stepCtx. Enqueued steps are dequeued every time | ||
* the current block promise settles. | ||
*/ | ||
@@ -1365,3 +1355,3 @@ function runCtx(ctx) { | ||
try { | ||
let [block, value] = stepCtx(ctx); | ||
const [block, value] = stepCtx(ctx); | ||
if (block) { | ||
@@ -1375,6 +1365,4 @@ ctx._ib = block | ||
.finally(() => advanceCtx(ctx)); | ||
} | ||
if (isPromiseLike(value)) { | ||
// stepCtx will only return a block if the value is asynchronous | ||
ctx._iv = value; | ||
ctx._el._inf = value; | ||
} | ||
@@ -1395,3 +1383,2 @@ return value; | ||
let resolve; | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
ctx._eb = ctx._ib | ||
@@ -1402,5 +1389,2 @@ .then(() => { | ||
resolve(value); | ||
if (isPromiseLike(value)) { | ||
ctx._el._inf = value; | ||
} | ||
if (block) { | ||
@@ -1421,2 +1405,3 @@ return block.catch((err) => { | ||
.finally(() => advanceCtx(ctx)); | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
} | ||
@@ -1426,3 +1411,3 @@ return ctx._ev; | ||
/** | ||
* Called to make props available to the Context async iterator for async | ||
* Called to make props available to the props async iterator for async | ||
* generator components. | ||
@@ -1445,7 +1430,3 @@ */ | ||
function updateCtxChildren(ctx, children) { | ||
return updateChildren(ctx._re, ctx._rt, // root | ||
ctx._ho, // host | ||
ctx, ctx._sc, // scope | ||
ctx._el, // element | ||
narrow(children)); | ||
return updateChildren(ctx._re, ctx._rt, ctx._ho, ctx, ctx._sc, ctx._el, narrow(children)); | ||
} | ||
@@ -1456,7 +1437,10 @@ function commitCtx(ctx, values) { | ||
} | ||
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
for (const record of ctx._ls) { | ||
v.addEventListener(record.type, record.callback, record.options); | ||
const listeners = listenersMap.get(ctx); | ||
if (listeners && listeners.length) { | ||
for (let i = 0; i < values.length; i++) { | ||
const value = values[i]; | ||
if (isEventTarget(value)) { | ||
for (let j = 0; j < listeners.length; j++) { | ||
const record = listeners[j]; | ||
value.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1468,8 +1452,9 @@ } | ||
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 values) { | ||
if (isEventTarget(v)) { | ||
v.addEventListener(record.type, record.callback, record.options); | ||
if (listeners.length) { | ||
for (let i = 0; i < values.length; i++) { | ||
const value = values[i]; | ||
if (isEventTarget(value)) { | ||
for (let j = 0; j < listeners.length; j++) { | ||
const record = listeners[j]; | ||
value.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1479,7 +1464,11 @@ } | ||
} | ||
// TODO: avoid calling arrange if none of the nodes have changed or moved | ||
const host = ctx._ho; | ||
if (host._f & IsCommitted) { | ||
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, getChildValues(host)); | ||
const hostValues = getChildValues(host); | ||
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, hostValues); | ||
if (hostValues.length) { | ||
host._f |= HadChildren; | ||
} | ||
else { | ||
host._f &= ~HadChildren; | ||
} | ||
ctx._re.complete(ctx._rt); | ||
@@ -1489,10 +1478,11 @@ } | ||
const value = unwrap(values); | ||
if (ctx._ss && 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 | ||
const callbacks = scheduleMap.get(ctx); | ||
if (callbacks && callbacks.size) { | ||
// We must 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 callbacks1 = Array.from(callbacks); | ||
callbacks.clear(); | ||
const value1 = ctx._re.read(value); | ||
for (const callback of callbacks) { | ||
for (const callback of callbacks1) { | ||
callback(value1); | ||
@@ -1507,8 +1497,9 @@ } | ||
clearEventListeners(ctx); | ||
if (ctx._cs) { | ||
const callbacks = cleanupMap.get(ctx); | ||
if (callbacks && callbacks.size) { | ||
const value = ctx._re.read(getValue(ctx._el)); | ||
for (const cleanup of ctx._cs) { | ||
for (const cleanup of callbacks) { | ||
cleanup(value); | ||
} | ||
ctx._cs = undefined; | ||
callbacks.clear(); | ||
} | ||
@@ -1519,6 +1510,8 @@ if (!(ctx._f & IsDone)) { | ||
if (ctx._it && typeof ctx._it.return === "function") { | ||
let iteration; | ||
try { | ||
ctx._f |= IsExecuting; | ||
iteration = ctx._it.return(); | ||
const iteration = ctx._it.return(); | ||
if (isPromiseLike(iteration)) { | ||
iteration.catch((err) => propagateError(ctx._pa, err)); | ||
} | ||
} | ||
@@ -1528,5 +1521,2 @@ finally { | ||
} | ||
if (isPromiseLike(iteration)) { | ||
iteration.catch((err) => propagateError(ctx._pa, err)); | ||
} | ||
} | ||
@@ -1542,2 +1532,3 @@ } | ||
const BUBBLING_PHASE = 3; | ||
const listenersMap = new WeakMap(); | ||
function normalizeOptions(options) { | ||
@@ -1565,13 +1556,16 @@ if (typeof options === "boolean") { | ||
* | ||
* @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. | ||
* element passed in matches the parent context’s host element. | ||
* | ||
* TODO: Maybe we can pass in the current context directly, rather than | ||
* starting from the parent? | ||
*/ | ||
function getListeners(ctx, host) { | ||
let listeners; | ||
let listeners = []; | ||
while (ctx !== undefined && ctx._ho === host) { | ||
if (typeof ctx._ls !== "undefined") { | ||
listeners = (listeners || []).concat(ctx._ls); | ||
const listeners1 = listenersMap.get(ctx); | ||
if (listeners1) { | ||
listeners = listeners.concat(listeners1); | ||
} | ||
@@ -1583,11 +1577,12 @@ ctx = ctx._pa; | ||
function clearEventListeners(ctx) { | ||
if (ctx._ls && ctx._ls.length > 0) { | ||
const listeners = listenersMap.get(ctx); | ||
if (listeners && listeners.length) { | ||
for (const value of getChildValues(ctx._el)) { | ||
if (isEventTarget(value)) { | ||
for (const { type, callback, options } of ctx._ls) { | ||
value.removeEventListener(type, callback, options); | ||
for (const record of listeners) { | ||
value.removeEventListener(record.type, record.callback, record.options); | ||
} | ||
} | ||
} | ||
ctx._ls = undefined; | ||
listeners.length = 0; | ||
} | ||
@@ -1594,0 +1589,0 @@ } |
@@ -8,4 +8,2 @@ 'use strict'; | ||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | ||
// TODO: maybe the HasChildren flag can be defined on (Crank) Element flags... | ||
const HasChildren = Symbol.for("crank.HasChildren"); | ||
class DOMRenderer extends crank.Renderer { | ||
@@ -47,3 +45,3 @@ render(children, root, ctx) { | ||
} | ||
if (el.tag === "svg") { | ||
else if (el.tag === "svg") { | ||
ns = SVG_NAMESPACE; | ||
@@ -73,3 +71,5 @@ } | ||
else if (typeof value === "string") { | ||
style.cssText = value; | ||
if (style.cssText !== value) { | ||
style.cssText = value; | ||
} | ||
} | ||
@@ -82,3 +82,3 @@ else { | ||
} | ||
else { | ||
else if (style.getPropertyValue(styleName) !== styleValue) { | ||
style.setProperty(styleName, styleValue); | ||
@@ -100,5 +100,7 @@ } | ||
else if (!isSVG) { | ||
node["className"] = value; | ||
if (node.className !== value) { | ||
node["className"] = value; | ||
} | ||
} | ||
else { | ||
else if (node.getAttribute("class") !== value) { | ||
node.setAttribute("class", value); | ||
@@ -120,8 +122,9 @@ } | ||
} | ||
else if (typeof value === "function" || typeof value === "object") { | ||
node[name] = value; | ||
else if (typeof value === "function" || | ||
typeof value === "object" || | ||
(!forceAttribute && !isSVG && name in node)) { | ||
if (node[name] !== value) { | ||
node[name] = value; | ||
} | ||
} | ||
else if (!forceAttribute && !isSVG && name in node) { | ||
node[name] = value; | ||
} | ||
else if (value === true) { | ||
@@ -133,3 +136,3 @@ node.setAttribute(name, ""); | ||
} | ||
else { | ||
else if (node.getAttribute(name) !== value) { | ||
node.setAttribute(name, value); | ||
@@ -146,4 +149,4 @@ } | ||
} | ||
if (("children" in el.props || node[HasChildren]) && | ||
!("innerHTML" in el.props)) { | ||
if (!("innerHTML" in el.props) && | ||
("children" in el.props || el.hadChildren)) { | ||
if (children.length === 0) { | ||
@@ -202,8 +205,2 @@ node.textContent = ""; | ||
} | ||
if (children.length > 0) { | ||
node[HasChildren] = true; | ||
} | ||
else if (node[HasChildren]) { | ||
node[HasChildren] = false; | ||
} | ||
} | ||
@@ -210,0 +207,0 @@ } |
199
crank.d.ts
/** | ||
* Represents all valid values which can be used for the tag of an element. | ||
* A type which represents all valid values that can be the tag of an element. | ||
* | ||
* @remarks | ||
* Elements whose tags are strings or symbols are called “host” or “intrinsic” | ||
@@ -12,5 +11,5 @@ * elements, and their behavior is determined by the renderer, while elements | ||
/** | ||
* Maps the tag of an element to its expected props. | ||
* A helper type to map the tag of an element to its expected props. | ||
* | ||
* @typeparam TTag - The element’s tag. | ||
* @template TTag - The element’s tag. | ||
*/ | ||
@@ -24,7 +23,6 @@ export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown; | ||
/** | ||
* A special tag for grouping multiple children within a parent. | ||
* A special tag for grouping multiple children within the same parent. | ||
* | ||
* @remarks | ||
* All iterables which appear in the element tree are implicitly wrapped in a | ||
* fragment element. | ||
* All non-string iterables which appear in the element tree are implicitly | ||
* wrapped in a fragment element. | ||
* | ||
@@ -40,8 +38,7 @@ * This tag is just the empty string, and you can use the empty string in | ||
* | ||
* @remarks | ||
* This tag is useful for creating element trees with multiple roots, for | ||
* things like modals or tooltips. | ||
* | ||
* Renderer.prototype.render will implicitly wrap the passed in element tree in | ||
* an implicit Portal element. | ||
* Renderer.prototype.render will implicitly wrap passed in element trees in an | ||
* implicit Portal element. | ||
*/ | ||
@@ -54,3 +51,2 @@ export declare const Portal: any; | ||
* | ||
* @remarks | ||
* Copy elements are useful for when you want to prevent a subtree from | ||
@@ -63,6 +59,5 @@ * rerendering as a performance optimization. Copy elements can also be keyed, | ||
/** | ||
* A special element tag for injecting raw nodes or strings via a value prop. | ||
* A special tag for injecting raw nodes or strings via a value prop. | ||
* | ||
* @remarks | ||
* If the value prop is a string, Renderer.prototype.parse will be called on | ||
* If the value prop is a string, Renderer.prototype.parse() will be called on | ||
* the string and the result of that method will be inserted. | ||
@@ -75,3 +70,2 @@ */ | ||
* | ||
* @remarks | ||
* Arbitrary objects can also be safely rendered, but will be converted to a | ||
@@ -92,3 +86,3 @@ * string using the toString method. We exclude them from this type to catch | ||
* | ||
* @typeparam TProps - The expected props for the component. | ||
* @template [TProps=*] - The expected props for the component. | ||
*/ | ||
@@ -103,3 +97,3 @@ export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>; | ||
* | ||
* @typeparam TTag - The type of the tag of the element. | ||
* @template {Tag} [TTag=Tag] - The type of the tag of the element. | ||
* | ||
@@ -116,5 +110,4 @@ * @example | ||
* | ||
* @remarks | ||
* Typically, you use the createElement function to create elements rather than | ||
* instatiating this class directly. | ||
* Typically, you use a helper function like createElement to create elements | ||
* rather than instatiating this class directly. | ||
*/ | ||
@@ -127,10 +120,8 @@ export declare class Element<TTag extends Tag = Tag> { | ||
* https://overreacted.io/why-do-react-elements-have-typeof-property/ | ||
* | ||
* This property is defined on the element prototype rather than per | ||
* instance, because it is the same for every Element. | ||
*/ | ||
$$typeof: typeof ElementSymbol; | ||
/** | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* The tag of the element. Can be a string, symbol or function. | ||
@@ -148,3 +139,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-key". | ||
@@ -156,3 +146,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-ref". | ||
@@ -163,2 +152,7 @@ */ | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* @internal | ||
* children - The rendered children of the element. | ||
@@ -169,7 +163,14 @@ */ | ||
* @internal | ||
* node - The node associated with the element. | ||
* node - The node or context associated with the element. | ||
* | ||
* @remarks | ||
* Set by Renderer.prototype.create when the component is mounted. | ||
* This property will only be set for host elements. | ||
* For host elements, this property is set to the return value of | ||
* Renderer.prototype.create when the component is mounted, i.e. DOM nodes | ||
* for the DOM renderer. | ||
* | ||
* For component elements, this property is set to a Context instance | ||
* (Context<TagProps<TTag>>). | ||
* | ||
* We assign both of these to the same property because they are mutually | ||
* exclusive. We use any because the Element type has no knowledge of | ||
* renderer nodes. | ||
*/ | ||
@@ -179,17 +180,7 @@ _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 element which this element is replacing. | ||
* | ||
* @remarks | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
* If an element renders asynchronously, we show any previously rendered | ||
* values in its place until it has committed for the first time. This | ||
* property is set to the previously rendered child. | ||
*/ | ||
@@ -199,17 +190,17 @@ _fb: NarrowedChild; | ||
* @internal | ||
* inflight - The current async run of the element. | ||
* inflightChildren - The current async run of the element’s children. | ||
* | ||
* @remarks | ||
* This value is used to make sure Copy element refs fire at the correct | ||
* time, and is also used as the yield value of async generator components | ||
* with async children. It is unset when the element is committed. | ||
* This property is used to make sure Copy element refs fire at the correct | ||
* time, and is also used to create yield values for async generator | ||
* components with async children. It is unset when the element is committed. | ||
*/ | ||
_inf: Promise<any> | undefined; | ||
_ic: Promise<any> | undefined; | ||
/** | ||
* @internal | ||
* onvalues - The resolve function of a promise which represents the next | ||
* children result. | ||
* onvalue(s) - This property is set to the resolve function of a promise | ||
* which represents the next children, so that renderings can be raced. | ||
*/ | ||
_onv: Function | undefined; | ||
_ov: Function | undefined; | ||
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: ((value: unknown) => unknown) | undefined); | ||
get hadChildren(): boolean; | ||
} | ||
@@ -220,17 +211,14 @@ export declare function isElement(value: any): value is Element; | ||
* | ||
* @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 renderer methods or | ||
* components, and assigns the children prop according to the arguments passed | ||
* to the function. | ||
* components, and assigns the children prop according to any additional | ||
* arguments passed to the function. | ||
*/ | ||
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>; | ||
/** | ||
* Clones a given element. Will also shallow copy the props object. | ||
* Clones a given element, shallowly copying the props object. | ||
* | ||
* @remarks | ||
* Mainly used internally to make sure we don’t accidentally reuse elements in | ||
* an element tree, because element have internal properties which are directly | ||
* mutated by the renderer. | ||
* Used internally to make sure we don’t accidentally reuse elements when | ||
* rendering. | ||
*/ | ||
@@ -247,5 +235,4 @@ export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>; | ||
* | ||
* @typeparam TNode - The node type for the element assigned by the renderer. | ||
* @template 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 | ||
@@ -269,6 +256,6 @@ * answer varies depending on its tag. For host or Raw elements, the answer is | ||
* | ||
* @typeparam TNode - The type of the node for a rendering environment. | ||
* @typeparam TScope - Data which is passed down the tree. | ||
* @typeparam TRoot - The type of the root for a rendering environment. | ||
* @typeparam TResult - The type of exposed values. | ||
* @template TNode - The type of the node for a rendering environment. | ||
* @template TScope - Data which is passed down the tree. | ||
* @template TRoot - The type of the root for a rendering environment. | ||
* @template TResult - The type of exposed values. | ||
*/ | ||
@@ -309,3 +296,2 @@ export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> { | ||
* | ||
* @remarks | ||
* This is useful for renderers which don’t want to expose their internal | ||
@@ -320,3 +306,2 @@ * nodes. For instance, the HTML renderer will convert all internal nodes to | ||
* | ||
* @remarks | ||
* Useful for passing data down the element tree. For instance, the DOM | ||
@@ -332,7 +317,6 @@ * renderer uses this method to keep track of whether we’re in an SVG | ||
* | ||
* @remarks | ||
* This method sets the scope for child host elements, not the current host | ||
* element. | ||
*/ | ||
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
scope(_el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
/** | ||
@@ -346,3 +330,2 @@ * Called for each string in an element tree. | ||
* | ||
* @remarks | ||
* Rather than returning text nodes for whatever environment we’re rendering | ||
@@ -380,3 +363,2 @@ * to, we defer that step for Renderer.prototype.arrange. We do this so that | ||
* | ||
* @remarks | ||
* Used to mutate the node associated with an element when new props are | ||
@@ -395,3 +377,2 @@ * passed. | ||
* | ||
* @remarks | ||
* This method is also called by child components contexts as the last step | ||
@@ -419,10 +400,10 @@ * of a refresh. | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
* An interface which can be extended to provide strongly typed provisions (see | ||
* Context.prototype.get and Context.prototype.set) | ||
* Context.prototype.consume and Context.prototype.provide) | ||
*/ | ||
export interface ProvisionMap extends Crank.ProvisionMap { | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
@@ -434,7 +415,7 @@ * A class which is instantiated and passed to every component as its this | ||
* | ||
* @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. | ||
* @template [TProps=*] - The expected shape of the props passed to the | ||
* component. Used to strongly type the Context iterator methods. | ||
* @template [TResult=*] - The readable element value type. It is used in | ||
* places such as the return value of refresh and the argument passed to | ||
* schedule and cleanup callbacks. | ||
*/ | ||
@@ -460,3 +441,3 @@ export declare class Context<TProps = any, TResult = any> implements EventTarget { | ||
* host - The nearest ancestor host element. | ||
* @remarks | ||
* | ||
* When refresh is called, the host element will be arranged as the last step | ||
@@ -487,2 +468,3 @@ * of the commit, to make sure the parent’s children properly reflects the | ||
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined; | ||
/*** async properties ***/ | ||
/** | ||
@@ -494,3 +476,3 @@ * @internal | ||
*/ | ||
_oa: (() => unknown) | undefined; | ||
_oa: Function | undefined; | ||
/** | ||
@@ -518,27 +500,4 @@ * @internal | ||
* @internal | ||
* listeners - An array of event listeners added to the context via | ||
* Context.prototype.addEventListener | ||
* Contexts should never be instantiated directly. | ||
*/ | ||
_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>); | ||
@@ -548,3 +507,2 @@ /** | ||
* | ||
* @remarks | ||
* Typically, you should read props either via the first parameter of the | ||
@@ -558,3 +516,2 @@ * component or via the context iterator methods. This property is mainly for | ||
* | ||
* @remarks | ||
* Typically, you should read values via refs, generator yield expressions, | ||
@@ -568,12 +525,12 @@ * or the refresh, schedule or cleanup methods. This property is mainly for | ||
/** | ||
* Re-executes the component. | ||
* Re-executes a component. | ||
* | ||
* @returns The rendered value of the component or a promise of the rendered | ||
* value if the component or its children execute asynchronously. | ||
* @returns The rendered value of the component or a promise thereof 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. | ||
* components, in that it will resume the Context’s props async iterator | ||
* rather than resuming execution. This is because async generator components | ||
* are perpetually resumed independent of updates, and rely on the props | ||
* async iterator to suspend. | ||
*/ | ||
@@ -610,8 +567,2 @@ refresh(): Promise<TResult> | TResult; | ||
}; | ||
interface EventListenerRecord { | ||
type: string; | ||
listener: MappedEventListenerOrEventListenerObject<any>; | ||
callback: MappedEventListener<any>; | ||
options: AddEventListenerOptions; | ||
} | ||
declare global { | ||
@@ -618,0 +569,0 @@ module Crank { |
809
crank.js
@@ -12,6 +12,4 @@ /// <reference types="./crank.d.ts" /> | ||
* | ||
* @remarks | ||
* This function pretty much does the same thing as wrap above except it | ||
* handles nulls and iterables, and shallowly clones arrays, so it is | ||
* appropriate for wrapping user-provided children from el.props. | ||
* This function does the same thing as wrap() above except it handles nulls | ||
* and iterables, so it is appropriate for wrapping user-provided children. | ||
*/ | ||
@@ -22,3 +20,3 @@ function arrayify(value) { | ||
: Array.isArray(value) | ||
? value.slice() | ||
? value | ||
: typeof value === "string" || | ||
@@ -41,7 +39,6 @@ typeof value[Symbol.iterator] !== "function" | ||
/** | ||
* A special tag for grouping multiple children within a parent. | ||
* A special tag for grouping multiple children within the same parent. | ||
* | ||
* @remarks | ||
* All iterables which appear in the element tree are implicitly wrapped in a | ||
* fragment element. | ||
* All non-string iterables which appear in the element tree are implicitly | ||
* wrapped in a fragment element. | ||
* | ||
@@ -53,4 +50,4 @@ * This tag is just the empty string, and you can use the empty string in | ||
const Fragment = ""; | ||
// NOTE: We assert the following symbol tags as any because typescript support | ||
// for symbol tags in JSX does not exist yet. | ||
// TODO: We assert the following symbol tags as any because typescript support | ||
// for symbol tags in JSX doesn’t exist yet. | ||
// https://github.com/microsoft/TypeScript/issues/38367 | ||
@@ -60,8 +57,7 @@ /** | ||
* | ||
* @remarks | ||
* This tag is useful for creating element trees with multiple roots, for | ||
* things like modals or tooltips. | ||
* | ||
* Renderer.prototype.render will implicitly wrap the passed in element tree in | ||
* an implicit Portal element. | ||
* Renderer.prototype.render will implicitly wrap passed in element trees in an | ||
* implicit Portal element. | ||
*/ | ||
@@ -73,3 +69,2 @@ const Portal = Symbol.for("crank.Portal"); | ||
* | ||
* @remarks | ||
* Copy elements are useful for when you want to prevent a subtree from | ||
@@ -81,6 +76,5 @@ * rerendering as a performance optimization. Copy elements can also be keyed, | ||
/** | ||
* A special element tag for injecting raw nodes or strings via a value prop. | ||
* A special tag for injecting raw nodes or strings via a value prop. | ||
* | ||
* @remarks | ||
* If the value prop is a string, Renderer.prototype.parse will be called on | ||
* If the value prop is a string, Renderer.prototype.parse() will be called on | ||
* the string and the result of that method will be inserted. | ||
@@ -92,15 +86,20 @@ */ | ||
/** | ||
* A flag which is set when the element has been mounted. Used mainly to | ||
* detect whether an element is being reused so that we clone it. | ||
* A flag which is set when the element is mounted, used to detect whether an | ||
* element is being reused so that we clone it rather than accidentally | ||
* overwriting its state. | ||
* | ||
* IMPORTANT: Changing this flag value would likely be a breaking changes in terms | ||
* of interop between elements created by different versions of Crank. | ||
*/ | ||
const IsMounted = 1 << 0; | ||
const IsInUse = 1 << 0; | ||
/** | ||
* A flag which is set when the element has committed at least once. | ||
* A flag which tracks whether the element has previously rendered children, | ||
* used to clear elements which no longer render children in the next render. | ||
* We may deprecate this and make elements without explicit children | ||
* uncontrolled. | ||
*/ | ||
const IsCommitted = 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, | ||
// any change to Element’s properties will be considered a breaking change. | ||
const HadChildren = 1 << 1; | ||
// 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. | ||
/** | ||
@@ -111,3 +110,3 @@ * Elements are the basic building blocks of Crank applications. They are | ||
* | ||
* @typeparam TTag - The type of the tag of the element. | ||
* @template {Tag} [TTag=Tag] - The type of the tag of the element. | ||
* | ||
@@ -124,9 +123,7 @@ * @example | ||
* | ||
* @remarks | ||
* Typically, you use the createElement function to create elements rather than | ||
* instatiating this class directly. | ||
* Typically, you use a helper function like createElement to create elements | ||
* rather than instatiating this class directly. | ||
*/ | ||
class Element { | ||
constructor(tag, props, key, ref) { | ||
this.$$typeof = ElementSymbol; | ||
this._f = 0; | ||
@@ -139,12 +136,11 @@ this.tag = tag; | ||
this._n = undefined; | ||
this._ctx = undefined; | ||
// NOTE: We don’t assign fallback (_fb), inflight (_inf) or onvalues (_onv) | ||
// in the constructor to save on the shallow size of elements. This saves a | ||
// couple bytes per element, especially when we aren’t rendering | ||
// asynchronous components. This may or may not be a good idea. | ||
//this._fb = undefined; | ||
//this._inf = undefined; | ||
//this._onv = undefined; | ||
this._fb = undefined; | ||
this._ic = undefined; | ||
this._ov = undefined; | ||
} | ||
get hadChildren() { | ||
return (this._f & HadChildren) !== 0; | ||
} | ||
} | ||
Element.prototype.$$typeof = ElementSymbol; | ||
function isElement(value) { | ||
@@ -156,8 +152,7 @@ return value != null && value.$$typeof === ElementSymbol; | ||
* | ||
* @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 renderer methods or | ||
* components, and assigns the children prop according to the arguments passed | ||
* to the function. | ||
* components, and assigns the children prop according to any additional | ||
* arguments passed to the function. | ||
*/ | ||
@@ -170,17 +165,18 @@ function createElement(tag, props, ...children) { | ||
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["crank-key"] != null) { | ||
key = props["crank-key"]; | ||
} | ||
switch (name) { | ||
case "crank-key": | ||
// 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["crank-key"] != null) { | ||
key = props["crank-key"]; | ||
} | ||
break; | ||
case "crank-ref": | ||
if (typeof props["crank-ref"] === "function") { | ||
ref = props["crank-ref"]; | ||
} | ||
break; | ||
default: | ||
props1[name] = props[name]; | ||
} | ||
else if (name === "crank-ref") { | ||
if (typeof props["crank-ref"] === "function") { | ||
ref = props["crank-ref"]; | ||
} | ||
} | ||
else { | ||
props1[name] = props[name]; | ||
} | ||
} | ||
@@ -197,8 +193,6 @@ } | ||
/** | ||
* Clones a given element. Will also shallow copy the props object. | ||
* Clones a given element, shallowly copying the props object. | ||
* | ||
* @remarks | ||
* Mainly used internally to make sure we don’t accidentally reuse elements in | ||
* an element tree, because element have internal properties which are directly | ||
* mutated by the renderer. | ||
* Used internally to make sure we don’t accidentally reuse elements when | ||
* rendering. | ||
*/ | ||
@@ -229,3 +223,2 @@ function cloneElement(el) { | ||
* | ||
* @remarks | ||
* Normalize will flatten only one level of nested arrays, because it is | ||
@@ -279,7 +272,4 @@ * designed to be called once at each level of the tree. It will also | ||
function getValue(el) { | ||
if (el._fb) { | ||
if (typeof el._fb === "object") { | ||
return getValue(el._fb); | ||
} | ||
return el._fb; | ||
if (typeof el._fb !== "undefined") { | ||
return typeof el._fb === "object" ? getValue(el._fb) : el._fb; | ||
} | ||
@@ -294,2 +284,5 @@ else if (el.tag === Portal) { | ||
} | ||
function getInflightValue(el) { | ||
return ((typeof el.tag === "function" && el._n._iv) || el._ic || getValue(el)); | ||
} | ||
/** | ||
@@ -317,6 +310,6 @@ * Walks an element’s children to find its child values. | ||
* | ||
* @typeparam TNode - The type of the node for a rendering environment. | ||
* @typeparam TScope - Data which is passed down the tree. | ||
* @typeparam TRoot - The type of the root for a rendering environment. | ||
* @typeparam TResult - The type of exposed values. | ||
* @template TNode - The type of the node for a rendering environment. | ||
* @template TScope - Data which is passed down the tree. | ||
* @template TRoot - The type of the root for a rendering environment. | ||
* @template TResult - The type of exposed values. | ||
*/ | ||
@@ -349,3 +342,3 @@ class Renderer { | ||
portal = createElement(Portal, { children, root }); | ||
portal._ctx = ctx; | ||
portal._n = ctx; | ||
if (typeof root === "object" && root !== null && children != null) { | ||
@@ -356,3 +349,3 @@ this._cache.set(root, portal); | ||
else { | ||
if (portal._ctx !== ctx) { | ||
if (portal._n !== ctx) { | ||
throw new Error("Context mismatch"); | ||
@@ -366,3 +359,3 @@ } | ||
const value = update(this, root, portal, ctx, undefined, portal); | ||
// NOTE: We return the child values of the portal because portal elements | ||
// We return the child values of the portal because portal elements | ||
// themselves have no readable value. | ||
@@ -395,3 +388,2 @@ if (isPromiseLike(value)) { | ||
* | ||
* @remarks | ||
* This is useful for renderers which don’t want to expose their internal | ||
@@ -408,3 +400,2 @@ * nodes. For instance, the HTML renderer will convert all internal nodes to | ||
* | ||
* @remarks | ||
* Useful for passing data down the element tree. For instance, the DOM | ||
@@ -420,7 +411,6 @@ * renderer uses this method to keep track of whether we’re in an SVG | ||
* | ||
* @remarks | ||
* This method sets the scope for child host elements, not the current host | ||
* element. | ||
*/ | ||
scope(el, scope) { | ||
scope(_el, scope) { | ||
return scope; | ||
@@ -436,3 +426,2 @@ } | ||
* | ||
* @remarks | ||
* Rather than returning text nodes for whatever environment we’re rendering | ||
@@ -476,3 +465,2 @@ * to, we defer that step for Renderer.prototype.arrange. We do this so that | ||
* | ||
* @remarks | ||
* Used to mutate the node associated with an element when new props are | ||
@@ -494,3 +482,2 @@ * passed. | ||
* | ||
* @remarks | ||
* This method is also called by child components contexts as the last step | ||
@@ -527,58 +514,7 @@ * of a refresh. | ||
/*** PRIVATE RENDERER FUNCTIONS ***/ | ||
function diff(renderer, root, host, ctx, scope, oldChild, newChild) { | ||
let value; | ||
if (typeof oldChild === "object" && | ||
typeof newChild === "object" && | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
// TODO: implement Raw element parse caching | ||
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(NOOP); | ||
} | ||
else { | ||
newChild.ref(value); | ||
} | ||
} | ||
newChild = oldChild; | ||
} | ||
else { | ||
if (newChild._f & IsMounted) { | ||
newChild = cloneElement(newChild); | ||
} | ||
value = mount(renderer, root, host, ctx, scope, newChild); | ||
if (isPromiseLike(value)) { | ||
newChild._fb = oldChild; | ||
} | ||
} | ||
} | ||
else if (typeof newChild === "string") { | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
return [newChild, value]; | ||
} | ||
function mount(renderer, root, host, ctx, scope, el) { | ||
el._f |= IsMounted; | ||
el._f |= IsInUse; | ||
if (typeof el.tag === "function") { | ||
el._ctx = new Context(renderer, root, host, ctx, scope, el); | ||
return updateCtx(el._ctx); | ||
el._n = new Context(renderer, root, host, ctx, scope, el); | ||
return updateCtx(el._n); | ||
} | ||
@@ -592,51 +528,14 @@ else if (el.tag === Raw) { | ||
} | ||
else { | ||
el._n = renderer.create(el, scope); | ||
renderer.patch(el, el._n); | ||
} | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
} | ||
return mountChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
return updateChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
} | ||
function mountChildren(renderer, root, host, ctx, scope, el, children) { | ||
const newChildren = arrayify(children); | ||
const values = []; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
for (let i = 0; i < newChildren.length; i++) { | ||
let child = narrow(newChildren[i]); | ||
const key = child && child.key; | ||
if (key !== undefined) { | ||
if (seen.has(key)) { | ||
console.error("Duplicate key", key); | ||
} | ||
seen.add(key); | ||
} | ||
let value; | ||
[child, value] = diff(renderer, root, host, ctx, scope, undefined, // oldChild | ||
child); | ||
newChildren[i] = child; | ||
values.push(value); | ||
isAsync = isAsync || isPromiseLike(value); | ||
} | ||
el._ch = unwrap(newChildren); | ||
if (isAsync) { | ||
let onvalues; | ||
const values1 = Promise.race([ | ||
Promise.all(values), | ||
new Promise((resolve) => (onvalues = resolve)), | ||
]); | ||
if (el._onv) { | ||
el._onv(values1); | ||
} | ||
el._onv = onvalues; | ||
el._inf = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
return el._inf; | ||
} | ||
if (el._onv) { | ||
el._onv(values); | ||
el._onv = undefined; | ||
} | ||
return commit(renderer, scope, el, normalize(values)); | ||
} | ||
function update(renderer, root, host, ctx, scope, el) { | ||
if (el._ctx) { | ||
return updateCtx(el._ctx); | ||
if (typeof el.tag === "function") { | ||
return updateCtx(el._n); | ||
} | ||
@@ -647,7 +546,10 @@ else if (el.tag === Raw) { | ||
else if (el.tag !== Fragment) { | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
if (el.tag === Portal) { | ||
root = el.props.root; | ||
} | ||
else { | ||
renderer.patch(el, el._n); | ||
} | ||
host = el; | ||
scope = renderer.scope(host, scope); | ||
} | ||
@@ -667,25 +569,28 @@ return updateChildren(renderer, root, host, ctx, scope, el, el.props.children); | ||
function updateChildren(renderer, root, host, ctx, scope, el, children) { | ||
if (typeof el._ch === "undefined") { | ||
return mountChildren(renderer, root, host, ctx, scope, el, children); | ||
} | ||
const oldChildren = wrap(el._ch); | ||
const newChildren = arrayify(children); | ||
const newChildren1 = []; | ||
const values = []; | ||
const graveyard = []; | ||
let graveyard; | ||
let seenKeys; | ||
let childrenByKey; | ||
let isAsync = false; | ||
let i = 0; | ||
let isAsync = false; | ||
let seen = new Set(); | ||
let childrenByKey; | ||
// TODO: switch to mountChildren if there are no more children | ||
for (let j = 0; j < newChildren.length; j++) { | ||
let oldChild = oldChildren[i]; | ||
for (let j = 0, il = oldChildren.length, jl = newChildren.length; j < jl; j++) { | ||
let oldChild = i >= il ? undefined : oldChildren[i]; | ||
let newChild = narrow(newChildren[j]); | ||
// ALIGNMENT | ||
let oldKey = oldChild && oldChild.key; | ||
let newKey = newChild && newChild.key; | ||
if (newKey !== undefined && seen.has(newKey)) { | ||
let oldKey = typeof oldChild === "object" ? oldChild.key : undefined; | ||
let newKey = typeof newChild === "object" ? newChild.key : undefined; | ||
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) { | ||
console.error("Duplicate key", newKey); | ||
newKey = undefined; | ||
} | ||
if (oldKey !== newKey) { | ||
if (oldKey === newKey) { | ||
if (childrenByKey !== undefined && newKey !== undefined) { | ||
childrenByKey.delete(newKey); | ||
} | ||
i++; | ||
} | ||
else { | ||
if (!childrenByKey) { | ||
@@ -707,22 +612,66 @@ childrenByKey = createChildrenByKey(oldChildren.slice(i)); | ||
} | ||
seen.add(newKey); | ||
if (!seenKeys) { | ||
seenKeys = new Set(); | ||
} | ||
seenKeys.add(newKey); | ||
} | ||
} | ||
else { | ||
if (childrenByKey !== undefined && newKey !== undefined) { | ||
childrenByKey.delete(newKey); | ||
// UPDATING | ||
let value; | ||
if (typeof oldChild === "object" && | ||
typeof newChild === "object" && | ||
oldChild.tag === newChild.tag) { | ||
if (oldChild.tag === Portal && | ||
oldChild.props.root !== newChild.props.root) { | ||
renderer.arrange(oldChild, oldChild.props.root, []); | ||
renderer.complete(oldChild.props.root); | ||
} | ||
i++; | ||
// TODO: implement Raw element parse caching | ||
if (oldChild !== newChild) { | ||
oldChild.props = newChild.props; | ||
oldChild.ref = newChild.ref; | ||
newChild = oldChild; | ||
} | ||
value = update(renderer, root, host, ctx, scope, newChild); | ||
} | ||
// UPDATING | ||
let value; | ||
[newChild, value] = diff(renderer, root, host, ctx, scope, oldChild, newChild); | ||
values.push(value); | ||
newChildren[j] = newChild; | ||
else if (typeof newChild === "object") { | ||
if (newChild.tag === Copy) { | ||
value = | ||
typeof oldChild === "object" | ||
? getInflightValue(oldChild) | ||
: oldChild; | ||
if (typeof newChild.ref === "function") { | ||
if (isPromiseLike(value)) { | ||
value.then(newChild.ref).catch(NOOP); | ||
} | ||
else { | ||
newChild.ref(value); | ||
} | ||
} | ||
newChild = oldChild; | ||
} | ||
else { | ||
if (newChild._f & IsInUse) { | ||
newChild = cloneElement(newChild); | ||
} | ||
value = mount(renderer, root, host, ctx, scope, newChild); | ||
if (isPromiseLike(value)) { | ||
newChild._fb = oldChild; | ||
} | ||
} | ||
} | ||
else if (typeof newChild === "string") { | ||
newChild = value = renderer.escape(newChild, scope); | ||
} | ||
newChildren1[j] = newChild; | ||
values[j] = value; | ||
isAsync = isAsync || isPromiseLike(value); | ||
if (typeof oldChild === "object" && oldChild !== newChild) { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(oldChild); | ||
} | ||
} | ||
el._ch = unwrap(newChildren); | ||
el._ch = unwrap(newChildren1); | ||
// cleanup | ||
@@ -732,2 +681,5 @@ for (; i < oldChildren.length; i++) { | ||
if (typeof oldChild === "object" && typeof oldChild.key === "undefined") { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(oldChild); | ||
@@ -737,2 +689,5 @@ } | ||
if (childrenByKey !== undefined && childrenByKey.size > 0) { | ||
if (!graveyard) { | ||
graveyard = []; | ||
} | ||
graveyard.push(...childrenByKey.values()); | ||
@@ -742,3 +697,7 @@ } | ||
let values1 = Promise.all(values).finally(() => { | ||
graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
if (graveyard) { | ||
for (let i = 0; i < graveyard.length; i++) { | ||
unmount(renderer, host, ctx, graveyard[i]); | ||
} | ||
} | ||
}); | ||
@@ -750,19 +709,23 @@ let onvalues; | ||
]); | ||
if (el._onv) { | ||
el._onv(values1); | ||
if (el._ov) { | ||
el._ov(values1); | ||
} | ||
el._onv = onvalues; | ||
el._inf = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
return el._inf; | ||
el._ic = values1.then((values) => commit(renderer, scope, el, normalize(values))); | ||
el._ov = onvalues; | ||
return el._ic; | ||
} | ||
graveyard.forEach((child) => unmount(renderer, host, ctx, child)); | ||
if (el._onv) { | ||
el._onv(values); | ||
el._onv = undefined; | ||
if (graveyard) { | ||
for (let i = 0; i < graveyard.length; i++) { | ||
unmount(renderer, host, ctx, graveyard[i]); | ||
} | ||
} | ||
if (el._ov) { | ||
el._ov(values); | ||
el._ov = undefined; | ||
} | ||
return commit(renderer, scope, el, normalize(values)); | ||
} | ||
function commit(renderer, scope, el, values) { | ||
if (el._inf) { | ||
el._inf = undefined; | ||
if (el._ic) { | ||
el._ic = undefined; | ||
} | ||
@@ -773,12 +736,5 @@ if (el._fb) { | ||
let value; | ||
if (el._ctx) { | ||
value = commitCtx(el._ctx, values); | ||
if (typeof el.tag === "function") { | ||
value = commitCtx(el._n, values); | ||
} | ||
else if (el.tag === Portal) { | ||
if (!(el._f & IsCommitted)) { | ||
el._f |= IsCommitted; | ||
} | ||
renderer.arrange(el, el.props.root, values); | ||
renderer.complete(el.props.root); | ||
} | ||
else if (el.tag === Raw) { | ||
@@ -797,9 +753,16 @@ if (typeof el.props.value === "string") { | ||
else { | ||
if (!(el._f & IsCommitted)) { | ||
el._n = renderer.create(el, scope); | ||
el._f |= IsCommitted; | ||
if (el.tag === Portal) { | ||
renderer.arrange(el, el.props.root, values); | ||
renderer.complete(el.props.root); | ||
} | ||
renderer.patch(el, el._n); | ||
renderer.arrange(el, el._n, values); | ||
else { | ||
renderer.arrange(el, el._n, values); | ||
} | ||
value = el._n; | ||
if (values.length) { | ||
el._f |= HadChildren; | ||
} | ||
else { | ||
el._f &= ~HadChildren; | ||
} | ||
} | ||
@@ -812,5 +775,5 @@ if (el.ref) { | ||
function unmount(renderer, host, ctx, el) { | ||
if (el._ctx) { | ||
unmountCtx(el._ctx); | ||
ctx = el._ctx; | ||
if (typeof el.tag === "function") { | ||
unmountCtx(el._n); | ||
ctx = el._n; | ||
} | ||
@@ -825,7 +788,5 @@ else if (el.tag === Portal) { | ||
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); | ||
} | ||
for (let i = 0; i < listeners.length; i++) { | ||
const record = listeners[i]; | ||
el._n.removeEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -844,3 +805,3 @@ } | ||
} | ||
// CONTEXT FLAGS | ||
/*** CONTEXT FLAGS ***/ | ||
/** | ||
@@ -853,6 +814,6 @@ * A flag which is set when the component is being updated by the parent and | ||
/** | ||
* A flag which is set when the component function is called or the component | ||
* generator is resumed. This flags is used to ensure that a component which | ||
* synchronously triggers a second update in the course of rendering does not | ||
* cause an stack overflow or a generator error. | ||
* A flag which is set when the component function or generator is | ||
* synchronously executing. This flags is used to ensure that a component which | ||
* triggers a second update in the course of rendering does not cause an stack | ||
* overflow or a generator error. | ||
*/ | ||
@@ -867,11 +828,11 @@ const IsExecuting = 1 << 1; | ||
* A flag used by async generator components in conjunction with the | ||
* onIsAvailable (_oa) callback to mark whether new props can be pulled via the | ||
* context async iterator. | ||
* onavailable (_oa) callback to mark whether new props can be pulled via the | ||
* context async iterator. See the Symbol.asyncIterator method and the | ||
* resumeCtx function. | ||
*/ | ||
const IsAvailable = 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 or throws. | ||
* Done components will stick to their last rendered value and ignore further | ||
* updates. | ||
* A flag which is set when a generator components returns, i.e. the done | ||
* property on the generator is set to true or throws. Done components will | ||
* stick to their last rendered value and ignore further updates. | ||
*/ | ||
@@ -881,3 +842,3 @@ const IsDone = 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. | ||
* are no longer in the element tree and cannot refresh or rerender. | ||
*/ | ||
@@ -893,2 +854,5 @@ const IsUnmounted = 1 << 5; | ||
const IsAsyncGen = 1 << 7; | ||
const provisionMaps = new WeakMap(); | ||
const scheduleMap = new WeakMap(); | ||
const cleanupMap = new WeakMap(); | ||
/** | ||
@@ -900,7 +864,7 @@ * A class which is instantiated and passed to every component as its this | ||
* | ||
* @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. | ||
* @template [TProps=*] - The expected shape of the props passed to the | ||
* component. Used to strongly type the Context iterator methods. | ||
* @template [TResult=*] - The readable element value type. It is used in | ||
* places such as the return value of refresh and the argument passed to | ||
* schedule and cleanup callbacks. | ||
*/ | ||
@@ -910,2 +874,3 @@ class Context { | ||
* @internal | ||
* Contexts should never be instantiated directly. | ||
*/ | ||
@@ -920,2 +885,8 @@ constructor(renderer, root, host, parent, scope, el) { | ||
this._el = el; | ||
this._it = undefined; | ||
this._oa = undefined; | ||
this._ib = undefined; | ||
this._iv = undefined; | ||
this._eb = undefined; | ||
this._ev = undefined; | ||
} | ||
@@ -925,3 +896,2 @@ /** | ||
* | ||
* @remarks | ||
* Typically, you should read props either via the first parameter of the | ||
@@ -937,3 +907,2 @@ * component or via the context iterator methods. This property is mainly for | ||
* | ||
* @remarks | ||
* Typically, you should read values via refs, generator yield expressions, | ||
@@ -982,12 +951,12 @@ * or the refresh, schedule or cleanup methods. This property is mainly for | ||
/** | ||
* Re-executes the component. | ||
* Re-executes a component. | ||
* | ||
* @returns The rendered value of the component or a promise of the rendered | ||
* value if the component or its children execute asynchronously. | ||
* @returns The rendered value of the component or a promise thereof 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. | ||
* components, in that it will resume the Context’s props async iterator | ||
* rather than resuming execution. This is because async generator components | ||
* are perpetually resumed independent of updates, and rely on the props | ||
* async iterator to suspend. | ||
*/ | ||
@@ -1011,6 +980,8 @@ refresh() { | ||
schedule(callback) { | ||
if (!this._ss) { | ||
this._ss = new Set(); | ||
let callbacks = scheduleMap.get(this); | ||
if (!callbacks) { | ||
callbacks = new Set(); | ||
scheduleMap.set(this, callbacks); | ||
} | ||
this._ss.add(callback); | ||
callbacks.add(callback); | ||
} | ||
@@ -1022,11 +993,14 @@ /** | ||
cleanup(callback) { | ||
if (!this._cs) { | ||
this._cs = new Set(); | ||
let callbacks = cleanupMap.get(this); | ||
if (!callbacks) { | ||
callbacks = new Set(); | ||
cleanupMap.set(this, callbacks); | ||
} | ||
this._cs.add(callback); | ||
callbacks.add(callback); | ||
} | ||
consume(key) { | ||
for (let parent = this._pa; parent !== undefined; parent = parent._pa) { | ||
if (parent._ps && parent._ps.has(key)) { | ||
return parent._ps.get(key); | ||
const provisions = provisionMaps.get(parent); | ||
if (provisions && provisions.has(key)) { | ||
return provisions.get(key); | ||
} | ||
@@ -1036,13 +1010,23 @@ } | ||
provide(key, value) { | ||
if (!this._ps) { | ||
this._ps = new Map(); | ||
let provisions = provisionMaps.get(this); | ||
if (!provisions) { | ||
provisions = new Map(); | ||
provisionMaps.set(this, provisions); | ||
} | ||
this._ps.set(key, value); | ||
provisions.set(key, value); | ||
} | ||
addEventListener(type, listener, options) { | ||
let listeners; | ||
if (listener == null) { | ||
return; | ||
} | ||
else if (!this._ls) { | ||
this._ls = []; | ||
else { | ||
const listeners1 = listenersMap.get(this); | ||
if (listeners1) { | ||
listeners = listeners1; | ||
} | ||
else { | ||
listeners = []; | ||
listenersMap.set(this, listeners); | ||
} | ||
} | ||
@@ -1059,9 +1043,6 @@ options = normalizeOptions(options); | ||
if (options.once) { | ||
const self = this; | ||
record.callback = function () { | ||
if (self._ls) { | ||
self._ls = self._ls.filter((record1) => record !== record1); | ||
if (self._ls.length === 0) { | ||
self._ls = undefined; | ||
} | ||
const i = listeners.indexOf(record); | ||
if (i !== -1) { | ||
listeners.splice(i, 1); | ||
} | ||
@@ -1071,3 +1052,3 @@ return callback.apply(this, arguments); | ||
} | ||
if (this._ls.some((record1) => record.type === record1.type && | ||
if (listeners.some((record1) => record.type === record1.type && | ||
record.listener === record1.listener && | ||
@@ -1077,3 +1058,3 @@ !record.options.capture === !record1.options.capture)) { | ||
} | ||
this._ls.push(record); | ||
listeners.push(record); | ||
for (const value of getChildValues(this._el)) { | ||
@@ -1086,7 +1067,8 @@ if (isEventTarget(value)) { | ||
removeEventListener(type, listener, options) { | ||
if (listener == null || !this._ls) { | ||
const listeners = listenersMap.get(this); | ||
if (listener == null || listeners == null) { | ||
return; | ||
} | ||
const options1 = normalizeOptions(options); | ||
const i = this._ls.findIndex((record) => record.type === type && | ||
const i = listeners.findIndex((record) => record.type === type && | ||
record.listener === listener && | ||
@@ -1097,7 +1079,4 @@ !record.options.capture === !options1.capture); | ||
} | ||
const record = this._ls[i]; | ||
this._ls.splice(i, 1); | ||
if (this._ls.length === 0) { | ||
this._ls = undefined; | ||
} | ||
const record = listeners[i]; | ||
listeners.splice(i, 1); | ||
for (const value of getChildValues(this._el)) { | ||
@@ -1115,3 +1094,4 @@ if (isEventTarget(value)) { | ||
// We patch the stopImmediatePropagation method because ev.cancelBubble | ||
// only informs us if stopPropagation was called. | ||
// only informs us if stopPropagation was called and there are no | ||
// properties which inform us if stopImmediatePropagation was called. | ||
let immediateCancelBubble = false; | ||
@@ -1124,14 +1104,18 @@ const stopImmediatePropagation = ev.stopImmediatePropagation; | ||
setEventProperty(ev, "target", this); | ||
// The only possible errors in this block are errors thrown by listener | ||
// callbacks, and dispatchEvent will only log the error rather than | ||
// rethrowing it. We return true because the return value is overridden in | ||
// the finally block but TypeScript (justifiably) does not recognize the | ||
// unsafe return statement. | ||
// The only possible errors in this block are errors thrown in callbacks, | ||
// and dispatchEvent is designed to only these errors rather than throwing | ||
// them. Therefore, we place all code in a try block, log errors in the | ||
// catch block use unsafe return statement in the finally block. | ||
// | ||
// Each early return within the try block returns true because while the | ||
// return value is overridden in the finally block, TypeScript | ||
// (justifiably) does not recognize the unsafe return statement. | ||
try { | ||
setEventProperty(ev, "eventPhase", CAPTURING_PHASE); | ||
for (let i = path.length - 1; i >= 0; i--) { | ||
const et = path[i]; | ||
if (et._ls) { | ||
setEventProperty(ev, "currentTarget", et); | ||
for (const record of et._ls) { | ||
const target = path[i]; | ||
const listeners = listenersMap.get(target); | ||
if (listeners) { | ||
setEventProperty(ev, "currentTarget", target); | ||
for (const record of listeners) { | ||
if (record.type === ev.type && record.options.capture) { | ||
@@ -1149,23 +1133,28 @@ record.callback.call(this, ev); | ||
} | ||
if (this._ls) { | ||
setEventProperty(ev, "eventPhase", AT_TARGET); | ||
setEventProperty(ev, "currentTarget", this); | ||
for (const record of this._ls) { | ||
if (record.type === ev.type) { | ||
record.callback.call(this, ev); | ||
if (immediateCancelBubble) { | ||
return true; | ||
{ | ||
const listeners = listenersMap.get(this); | ||
if (listeners) { | ||
setEventProperty(ev, "eventPhase", AT_TARGET); | ||
setEventProperty(ev, "currentTarget", this); | ||
for (const record of listeners) { | ||
if (record.type === ev.type) { | ||
record.callback.call(this, ev); | ||
if (immediateCancelBubble) { | ||
return true; | ||
} | ||
} | ||
} | ||
if (ev.cancelBubble) { | ||
return true; | ||
} | ||
} | ||
if (ev.cancelBubble) { | ||
return true; | ||
} | ||
} | ||
if (ev.bubbles) { | ||
setEventProperty(ev, "eventPhase", BUBBLING_PHASE); | ||
for (const et of path) { | ||
if (et._ls) { | ||
setEventProperty(ev, "currentTarget", et); | ||
for (const record of et._ls) { | ||
for (let i = 0; i < path.length; i++) { | ||
const target = path[i]; | ||
const listeners = listenersMap.get(target); | ||
if (listeners) { | ||
setEventProperty(ev, "currentTarget", target); | ||
for (const record of listeners) { | ||
if (record.type === ev.type && !record.options.capture) { | ||
@@ -1197,15 +1186,2 @@ record.callback.call(this, ev); | ||
/*** PRIVATE CONTEXT FUNCTIONS ***/ | ||
/* | ||
* NOTE: The functions stepCtx, advanceCtx and runCtx work together to | ||
* implement the async queueing behavior of components. The runCtx function | ||
* calls the stepCtx function, which returns two results in a tuple. The first | ||
* result, called the “block,” is a possible promise which represents the | ||
* duration for which the component is blocked from accepting new updates. The | ||
* second result, called the “value,” is the actual result of the update. The | ||
* runCtx function caches block/value from the stepCtx 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 represent an enqueued next stepCtx. | ||
* Enqueued steps are dequeued in a finally callback on the blocking promise. | ||
*/ | ||
/** | ||
@@ -1215,3 +1191,3 @@ * This function is responsible for executing the component and handling all | ||
* | ||
* @returns A tuple [block, value] | ||
* @returns {[block, value]} A tuple where | ||
* block - A possible promise which represents the duration during which the | ||
@@ -1221,11 +1197,9 @@ * component is blocked from updating. | ||
* | ||
* @remarks | ||
* Each component type will block 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 | ||
* - 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 | ||
* - 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. | ||
*/ | ||
@@ -1238,5 +1212,5 @@ function stepCtx(ctx) { | ||
const initial = !ctx._it; | ||
try { | ||
ctx._f |= IsExecuting; | ||
if (initial) { | ||
if (initial) { | ||
try { | ||
ctx._f |= IsExecuting; | ||
clearEventListeners(ctx); | ||
@@ -1258,12 +1232,17 @@ const result = el.tag.call(ctx, el.props); | ||
} | ||
finally { | ||
ctx._f &= ~IsExecuting; | ||
} | ||
} | ||
finally { | ||
ctx._f &= ~IsExecuting; | ||
} | ||
// The value passed back into the generator as the argument to the next | ||
// method is a promise if an async generator component has async children. | ||
// Sync generator components only resume when their children have fulfilled | ||
// so ctx._el._ic (the element’s inflight children) will never be defined. | ||
let oldValue; | ||
if (ctx._el._inf) { | ||
oldValue = ctx._el._inf.then((value) => ctx._re.read(value), () => ctx._re.read(undefined)); | ||
if (initial) { | ||
// The argument passed to the first call to next is ignored. | ||
oldValue = undefined; | ||
} | ||
else if (initial) { | ||
oldValue = ctx._re.read(undefined); | ||
else if (ctx._el._ic) { | ||
oldValue = ctx._el._ic.then(ctx._re.read, () => ctx._re.read(undefined)); | ||
} | ||
@@ -1338,10 +1317,9 @@ else { | ||
/** | ||
* @remarks | ||
* Called when the inflight block promise settles. | ||
*/ | ||
function advanceCtx(ctx) { | ||
// _ib: inflightBlock | ||
// _iv: inflightValue | ||
// _eb: enqueuedBlock | ||
// _ev: enqueuedValue | ||
// _ib - inflightBlock | ||
// _iv - inflightValue | ||
// _eb - enqueuedBlock | ||
// _ev - enqueuedValue | ||
ctx._ib = ctx._eb; | ||
@@ -1357,2 +1335,14 @@ ctx._iv = ctx._ev; | ||
* Enqueues and executes the component associated with the context. | ||
* | ||
* The functions stepCtx, advanceCtx and runCtx work together to implement the | ||
* async queueing behavior of components. The runCtx function calls the stepCtx | ||
* function, which returns two results in a tuple. The first result, called the | ||
* “block,” is a possible promise which represents the duration for which the | ||
* component is blocked from accepting new updates. The second result, called | ||
* the “value,” is the actual result of the update. The runCtx function caches | ||
* block/value from the stepCtx function on the context, according to whether | ||
* the component blocks. The “inflight” block/value properties are the | ||
* currently executing update, and the “enqueued” block/value properties | ||
* represent an enqueued next stepCtx. Enqueued steps are dequeued every time | ||
* the current block promise settles. | ||
*/ | ||
@@ -1362,3 +1352,3 @@ function runCtx(ctx) { | ||
try { | ||
let [block, value] = stepCtx(ctx); | ||
const [block, value] = stepCtx(ctx); | ||
if (block) { | ||
@@ -1372,6 +1362,4 @@ ctx._ib = block | ||
.finally(() => advanceCtx(ctx)); | ||
} | ||
if (isPromiseLike(value)) { | ||
// stepCtx will only return a block if the value is asynchronous | ||
ctx._iv = value; | ||
ctx._el._inf = value; | ||
} | ||
@@ -1392,3 +1380,2 @@ return value; | ||
let resolve; | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
ctx._eb = ctx._ib | ||
@@ -1399,5 +1386,2 @@ .then(() => { | ||
resolve(value); | ||
if (isPromiseLike(value)) { | ||
ctx._el._inf = value; | ||
} | ||
if (block) { | ||
@@ -1418,2 +1402,3 @@ return block.catch((err) => { | ||
.finally(() => advanceCtx(ctx)); | ||
ctx._ev = new Promise((resolve1) => (resolve = resolve1)); | ||
} | ||
@@ -1423,3 +1408,3 @@ return ctx._ev; | ||
/** | ||
* Called to make props available to the Context async iterator for async | ||
* Called to make props available to the props async iterator for async | ||
* generator components. | ||
@@ -1442,7 +1427,3 @@ */ | ||
function updateCtxChildren(ctx, children) { | ||
return updateChildren(ctx._re, ctx._rt, // root | ||
ctx._ho, // host | ||
ctx, ctx._sc, // scope | ||
ctx._el, // element | ||
narrow(children)); | ||
return updateChildren(ctx._re, ctx._rt, ctx._ho, ctx, ctx._sc, ctx._el, narrow(children)); | ||
} | ||
@@ -1453,7 +1434,10 @@ function commitCtx(ctx, values) { | ||
} | ||
if (typeof ctx._ls !== "undefined" && ctx._ls.length > 0) { | ||
for (const v of values) { | ||
if (isEventTarget(v)) { | ||
for (const record of ctx._ls) { | ||
v.addEventListener(record.type, record.callback, record.options); | ||
const listeners = listenersMap.get(ctx); | ||
if (listeners && listeners.length) { | ||
for (let i = 0; i < values.length; i++) { | ||
const value = values[i]; | ||
if (isEventTarget(value)) { | ||
for (let j = 0; j < listeners.length; j++) { | ||
const record = listeners[j]; | ||
value.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1465,8 +1449,9 @@ } | ||
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 values) { | ||
if (isEventTarget(v)) { | ||
v.addEventListener(record.type, record.callback, record.options); | ||
if (listeners.length) { | ||
for (let i = 0; i < values.length; i++) { | ||
const value = values[i]; | ||
if (isEventTarget(value)) { | ||
for (let j = 0; j < listeners.length; j++) { | ||
const record = listeners[j]; | ||
value.addEventListener(record.type, record.callback, record.options); | ||
} | ||
@@ -1476,7 +1461,11 @@ } | ||
} | ||
// TODO: avoid calling arrange if none of the nodes have changed or moved | ||
const host = ctx._ho; | ||
if (host._f & IsCommitted) { | ||
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, getChildValues(host)); | ||
const hostValues = getChildValues(host); | ||
ctx._re.arrange(host, host.tag === Portal ? host.props.root : host._n, hostValues); | ||
if (hostValues.length) { | ||
host._f |= HadChildren; | ||
} | ||
else { | ||
host._f &= ~HadChildren; | ||
} | ||
ctx._re.complete(ctx._rt); | ||
@@ -1486,10 +1475,11 @@ } | ||
const value = unwrap(values); | ||
if (ctx._ss && 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 | ||
const callbacks = scheduleMap.get(ctx); | ||
if (callbacks && callbacks.size) { | ||
// We must 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 callbacks1 = Array.from(callbacks); | ||
callbacks.clear(); | ||
const value1 = ctx._re.read(value); | ||
for (const callback of callbacks) { | ||
for (const callback of callbacks1) { | ||
callback(value1); | ||
@@ -1504,8 +1494,9 @@ } | ||
clearEventListeners(ctx); | ||
if (ctx._cs) { | ||
const callbacks = cleanupMap.get(ctx); | ||
if (callbacks && callbacks.size) { | ||
const value = ctx._re.read(getValue(ctx._el)); | ||
for (const cleanup of ctx._cs) { | ||
for (const cleanup of callbacks) { | ||
cleanup(value); | ||
} | ||
ctx._cs = undefined; | ||
callbacks.clear(); | ||
} | ||
@@ -1516,6 +1507,8 @@ if (!(ctx._f & IsDone)) { | ||
if (ctx._it && typeof ctx._it.return === "function") { | ||
let iteration; | ||
try { | ||
ctx._f |= IsExecuting; | ||
iteration = ctx._it.return(); | ||
const iteration = ctx._it.return(); | ||
if (isPromiseLike(iteration)) { | ||
iteration.catch((err) => propagateError(ctx._pa, err)); | ||
} | ||
} | ||
@@ -1525,5 +1518,2 @@ finally { | ||
} | ||
if (isPromiseLike(iteration)) { | ||
iteration.catch((err) => propagateError(ctx._pa, err)); | ||
} | ||
} | ||
@@ -1539,2 +1529,3 @@ } | ||
const BUBBLING_PHASE = 3; | ||
const listenersMap = new WeakMap(); | ||
function normalizeOptions(options) { | ||
@@ -1562,13 +1553,16 @@ if (typeof options === "boolean") { | ||
* | ||
* @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. | ||
* element passed in matches the parent context’s host element. | ||
* | ||
* TODO: Maybe we can pass in the current context directly, rather than | ||
* starting from the parent? | ||
*/ | ||
function getListeners(ctx, host) { | ||
let listeners; | ||
let listeners = []; | ||
while (ctx !== undefined && ctx._ho === host) { | ||
if (typeof ctx._ls !== "undefined") { | ||
listeners = (listeners || []).concat(ctx._ls); | ||
const listeners1 = listenersMap.get(ctx); | ||
if (listeners1) { | ||
listeners = listeners.concat(listeners1); | ||
} | ||
@@ -1580,11 +1574,12 @@ ctx = ctx._pa; | ||
function clearEventListeners(ctx) { | ||
if (ctx._ls && ctx._ls.length > 0) { | ||
const listeners = listenersMap.get(ctx); | ||
if (listeners && listeners.length) { | ||
for (const value of getChildValues(ctx._el)) { | ||
if (isEventTarget(value)) { | ||
for (const { type, callback, options } of ctx._ls) { | ||
value.removeEventListener(type, callback, options); | ||
for (const record of listeners) { | ||
value.removeEventListener(record.type, record.callback, record.options); | ||
} | ||
} | ||
} | ||
ctx._ls = undefined; | ||
listeners.length = 0; | ||
} | ||
@@ -1591,0 +1586,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./crank.js"; | ||
import { Children, Context, Element as CrankElement, ElementValue, Renderer } from "./crank"; | ||
export declare class DOMRenderer extends Renderer<Node, string | undefined> { | ||
@@ -3,0 +3,0 @@ render(children: Children, root: Node, ctx?: Context): Promise<ElementValue<Node>> | ElementValue<Node>; |
39
dom.js
@@ -5,4 +5,2 @@ /// <reference types="./dom.d.ts" /> | ||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | ||
// TODO: maybe the HasChildren flag can be defined on (Crank) Element flags... | ||
const HasChildren = Symbol.for("crank.HasChildren"); | ||
class DOMRenderer extends Renderer { | ||
@@ -44,3 +42,3 @@ render(children, root, ctx) { | ||
} | ||
if (el.tag === "svg") { | ||
else if (el.tag === "svg") { | ||
ns = SVG_NAMESPACE; | ||
@@ -70,3 +68,5 @@ } | ||
else if (typeof value === "string") { | ||
style.cssText = value; | ||
if (style.cssText !== value) { | ||
style.cssText = value; | ||
} | ||
} | ||
@@ -79,3 +79,3 @@ else { | ||
} | ||
else { | ||
else if (style.getPropertyValue(styleName) !== styleValue) { | ||
style.setProperty(styleName, styleValue); | ||
@@ -97,5 +97,7 @@ } | ||
else if (!isSVG) { | ||
node["className"] = value; | ||
if (node.className !== value) { | ||
node["className"] = value; | ||
} | ||
} | ||
else { | ||
else if (node.getAttribute("class") !== value) { | ||
node.setAttribute("class", value); | ||
@@ -117,8 +119,9 @@ } | ||
} | ||
else if (typeof value === "function" || typeof value === "object") { | ||
node[name] = value; | ||
else if (typeof value === "function" || | ||
typeof value === "object" || | ||
(!forceAttribute && !isSVG && name in node)) { | ||
if (node[name] !== value) { | ||
node[name] = value; | ||
} | ||
} | ||
else if (!forceAttribute && !isSVG && name in node) { | ||
node[name] = value; | ||
} | ||
else if (value === true) { | ||
@@ -130,3 +133,3 @@ node.setAttribute(name, ""); | ||
} | ||
else { | ||
else if (node.getAttribute(name) !== value) { | ||
node.setAttribute(name, value); | ||
@@ -143,4 +146,4 @@ } | ||
} | ||
if (("children" in el.props || node[HasChildren]) && | ||
!("innerHTML" in el.props)) { | ||
if (!("innerHTML" in el.props) && | ||
("children" in el.props || el.hadChildren)) { | ||
if (children.length === 0) { | ||
@@ -199,8 +202,2 @@ node.textContent = ""; | ||
} | ||
if (children.length > 0) { | ||
node[HasChildren] = true; | ||
} | ||
else if (node[HasChildren]) { | ||
node[HasChildren] = false; | ||
} | ||
} | ||
@@ -207,0 +204,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { Element, ElementValue, Renderer } from "./crank.js"; | ||
import { Element, ElementValue, Renderer } from "./crank"; | ||
interface Node { | ||
@@ -3,0 +3,0 @@ value: string; |
@@ -1,1 +0,1 @@ | ||
export * from "./crank.js"; | ||
export * from "./crank"; |
{ | ||
"name": "@bikeshaving/crank", | ||
"version": "0.3.7", | ||
"version": "0.3.8", | ||
"description": "Write JSX-driven components with functions, promises and generators.", | ||
@@ -122,17 +122,17 @@ "homepage": "https://crank.js.org", | ||
"devDependencies": { | ||
"@types/jest": "^26.0.15", | ||
"@typescript-eslint/eslint-plugin": "^4.7.0", | ||
"@typescript-eslint/parser": "^4.7.0", | ||
"core-js": "^3.7.0", | ||
"eslint": "^7.13.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"@types/jest": "^26.0.19", | ||
"@typescript-eslint/eslint-plugin": "^4.11.0", | ||
"@typescript-eslint/parser": "^4.11.0", | ||
"core-js": "^3.8.1", | ||
"eslint": "^7.16.0", | ||
"eslint-config-prettier": "^7.1.0", | ||
"eslint-plugin-jest": "^24.1.3", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"eslint-plugin-prettier": "^3.3.0", | ||
"eslint-plugin-react": "^7.21.5", | ||
"husky": "^4.3.0", | ||
"husky": "^4.3.6", | ||
"jest": "^26.6.3", | ||
"lint-staged": "^10.5.1", | ||
"lint-staged": "^10.5.3", | ||
"magic-string": "^0.25.7", | ||
"prettier": "^2.0.4", | ||
"rollup": "^2.33.1", | ||
"prettier": "^2.2.1", | ||
"rollup": "^2.35.1", | ||
"rollup-plugin-typescript2": "^0.29.0", | ||
@@ -142,3 +142,3 @@ "shx": "^0.3.3", | ||
"ts-transform-import-path-rewrite": "^0.3.0", | ||
"typescript": "^4.0.5" | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -145,0 +145,0 @@ "publishConfig": { |
/** | ||
* Represents all valid values which can be used for the tag of an element. | ||
* A type which represents all valid values that can be the tag of an element. | ||
* | ||
* @remarks | ||
* Elements whose tags are strings or symbols are called “host” or “intrinsic” | ||
@@ -12,5 +11,5 @@ * elements, and their behavior is determined by the renderer, while elements | ||
/** | ||
* Maps the tag of an element to its expected props. | ||
* A helper type to map the tag of an element to its expected props. | ||
* | ||
* @typeparam TTag - The element’s tag. | ||
* @template TTag - The element’s tag. | ||
*/ | ||
@@ -24,7 +23,6 @@ export declare type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : unknown; | ||
/** | ||
* A special tag for grouping multiple children within a parent. | ||
* A special tag for grouping multiple children within the same parent. | ||
* | ||
* @remarks | ||
* All iterables which appear in the element tree are implicitly wrapped in a | ||
* fragment element. | ||
* All non-string iterables which appear in the element tree are implicitly | ||
* wrapped in a fragment element. | ||
* | ||
@@ -40,8 +38,7 @@ * This tag is just the empty string, and you can use the empty string in | ||
* | ||
* @remarks | ||
* This tag is useful for creating element trees with multiple roots, for | ||
* things like modals or tooltips. | ||
* | ||
* Renderer.prototype.render will implicitly wrap the passed in element tree in | ||
* an implicit Portal element. | ||
* Renderer.prototype.render will implicitly wrap passed in element trees in an | ||
* implicit Portal element. | ||
*/ | ||
@@ -54,3 +51,2 @@ export declare const Portal: any; | ||
* | ||
* @remarks | ||
* Copy elements are useful for when you want to prevent a subtree from | ||
@@ -63,6 +59,5 @@ * rerendering as a performance optimization. Copy elements can also be keyed, | ||
/** | ||
* A special element tag for injecting raw nodes or strings via a value prop. | ||
* A special tag for injecting raw nodes or strings via a value prop. | ||
* | ||
* @remarks | ||
* If the value prop is a string, Renderer.prototype.parse will be called on | ||
* If the value prop is a string, Renderer.prototype.parse() will be called on | ||
* the string and the result of that method will be inserted. | ||
@@ -75,3 +70,2 @@ */ | ||
* | ||
* @remarks | ||
* Arbitrary objects can also be safely rendered, but will be converted to a | ||
@@ -92,3 +86,3 @@ * string using the toString method. We exclude them from this type to catch | ||
* | ||
* @typeparam TProps - The expected props for the component. | ||
* @template [TProps=*] - The expected props for the component. | ||
*/ | ||
@@ -103,3 +97,3 @@ export declare type Component<TProps = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>; | ||
* | ||
* @typeparam TTag - The type of the tag of the element. | ||
* @template {Tag} [TTag=Tag] - The type of the tag of the element. | ||
* | ||
@@ -116,5 +110,4 @@ * @example | ||
* | ||
* @remarks | ||
* Typically, you use the createElement function to create elements rather than | ||
* instatiating this class directly. | ||
* Typically, you use a helper function like createElement to create elements | ||
* rather than instatiating this class directly. | ||
*/ | ||
@@ -127,10 +120,8 @@ export declare class Element<TTag extends Tag = Tag> { | ||
* https://overreacted.io/why-do-react-elements-have-typeof-property/ | ||
* | ||
* This property is defined on the element prototype rather than per | ||
* instance, because it is the same for every Element. | ||
*/ | ||
$$typeof: typeof ElementSymbol; | ||
/** | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* The tag of the element. Can be a string, symbol or function. | ||
@@ -148,3 +139,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-key". | ||
@@ -156,3 +146,2 @@ */ | ||
* | ||
* @remarks | ||
* Passed in createElement() as the prop "crank-ref". | ||
@@ -163,2 +152,7 @@ */ | ||
* @internal | ||
* flags - A bitmask. See ELEMENT FLAGS. | ||
*/ | ||
_f: number; | ||
/** | ||
* @internal | ||
* children - The rendered children of the element. | ||
@@ -169,7 +163,14 @@ */ | ||
* @internal | ||
* node - The node associated with the element. | ||
* node - The node or context associated with the element. | ||
* | ||
* @remarks | ||
* Set by Renderer.prototype.create when the component is mounted. | ||
* This property will only be set for host elements. | ||
* For host elements, this property is set to the return value of | ||
* Renderer.prototype.create when the component is mounted, i.e. DOM nodes | ||
* for the DOM renderer. | ||
* | ||
* For component elements, this property is set to a Context instance | ||
* (Context<TagProps<TTag>>). | ||
* | ||
* We assign both of these to the same property because they are mutually | ||
* exclusive. We use any because the Element type has no knowledge of | ||
* renderer nodes. | ||
*/ | ||
@@ -179,17 +180,7 @@ _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 element which this element is replacing. | ||
* | ||
* @remarks | ||
* Until an element commits for the first time, we show any previously | ||
* rendered values in its place. This allows the nearest host to be | ||
* rearranged concurrently. | ||
* If an element renders asynchronously, we show any previously rendered | ||
* values in its place until it has committed for the first time. This | ||
* property is set to the previously rendered child. | ||
*/ | ||
@@ -199,17 +190,17 @@ _fb: NarrowedChild; | ||
* @internal | ||
* inflight - The current async run of the element. | ||
* inflightChildren - The current async run of the element’s children. | ||
* | ||
* @remarks | ||
* This value is used to make sure Copy element refs fire at the correct | ||
* time, and is also used as the yield value of async generator components | ||
* with async children. It is unset when the element is committed. | ||
* This property is used to make sure Copy element refs fire at the correct | ||
* time, and is also used to create yield values for async generator | ||
* components with async children. It is unset when the element is committed. | ||
*/ | ||
_inf: Promise<any> | undefined; | ||
_ic: Promise<any> | undefined; | ||
/** | ||
* @internal | ||
* onvalues - The resolve function of a promise which represents the next | ||
* children result. | ||
* onvalue(s) - This property is set to the resolve function of a promise | ||
* which represents the next children, so that renderings can be raced. | ||
*/ | ||
_onv: Function | undefined; | ||
_ov: Function | undefined; | ||
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref: ((value: unknown) => unknown) | undefined); | ||
get hadChildren(): boolean; | ||
} | ||
@@ -220,17 +211,14 @@ export declare function isElement(value: any): value is Element; | ||
* | ||
* @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 renderer methods or | ||
* components, and assigns the children prop according to the arguments passed | ||
* to the function. | ||
* components, and assigns the children prop according to any additional | ||
* arguments passed to the function. | ||
*/ | ||
export declare function createElement<TTag extends Tag>(tag: TTag, props?: TagProps<TTag> | null | undefined, ...children: Array<unknown>): Element<TTag>; | ||
/** | ||
* Clones a given element. Will also shallow copy the props object. | ||
* Clones a given element, shallowly copying the props object. | ||
* | ||
* @remarks | ||
* Mainly used internally to make sure we don’t accidentally reuse elements in | ||
* an element tree, because element have internal properties which are directly | ||
* mutated by the renderer. | ||
* Used internally to make sure we don’t accidentally reuse elements when | ||
* rendering. | ||
*/ | ||
@@ -247,5 +235,4 @@ export declare function cloneElement<TTag extends Tag>(el: Element<TTag>): Element<TTag>; | ||
* | ||
* @typeparam TNode - The node type for the element assigned by the renderer. | ||
* @template 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 | ||
@@ -269,6 +256,6 @@ * answer varies depending on its tag. For host or Raw elements, the answer is | ||
* | ||
* @typeparam TNode - The type of the node for a rendering environment. | ||
* @typeparam TScope - Data which is passed down the tree. | ||
* @typeparam TRoot - The type of the root for a rendering environment. | ||
* @typeparam TResult - The type of exposed values. | ||
* @template TNode - The type of the node for a rendering environment. | ||
* @template TScope - Data which is passed down the tree. | ||
* @template TRoot - The type of the root for a rendering environment. | ||
* @template TResult - The type of exposed values. | ||
*/ | ||
@@ -309,3 +296,2 @@ export declare class Renderer<TNode, TScope, TRoot = TNode, TResult = ElementValue<TNode>> { | ||
* | ||
* @remarks | ||
* This is useful for renderers which don’t want to expose their internal | ||
@@ -320,3 +306,2 @@ * nodes. For instance, the HTML renderer will convert all internal nodes to | ||
* | ||
* @remarks | ||
* Useful for passing data down the element tree. For instance, the DOM | ||
@@ -332,7 +317,6 @@ * renderer uses this method to keep track of whether we’re in an SVG | ||
* | ||
* @remarks | ||
* This method sets the scope for child host elements, not the current host | ||
* element. | ||
*/ | ||
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
scope(_el: Element<string | symbol>, scope: TScope | undefined): TScope; | ||
/** | ||
@@ -346,3 +330,2 @@ * Called for each string in an element tree. | ||
* | ||
* @remarks | ||
* Rather than returning text nodes for whatever environment we’re rendering | ||
@@ -380,3 +363,2 @@ * to, we defer that step for Renderer.prototype.arrange. We do this so that | ||
* | ||
* @remarks | ||
* Used to mutate the node associated with an element when new props are | ||
@@ -395,3 +377,2 @@ * passed. | ||
* | ||
* @remarks | ||
* This method is also called by child components contexts as the last step | ||
@@ -419,10 +400,10 @@ * of a refresh. | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
* An interface which can be extended to provide strongly typed provisions (see | ||
* Context.prototype.get and Context.prototype.set) | ||
* Context.prototype.consume and Context.prototype.provide) | ||
*/ | ||
export interface ProvisionMap extends Crank.ProvisionMap { | ||
} | ||
export interface Context extends Crank.Context { | ||
} | ||
/** | ||
@@ -434,7 +415,7 @@ * A class which is instantiated and passed to every component as its this | ||
* | ||
* @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. | ||
* @template [TProps=*] - The expected shape of the props passed to the | ||
* component. Used to strongly type the Context iterator methods. | ||
* @template [TResult=*] - The readable element value type. It is used in | ||
* places such as the return value of refresh and the argument passed to | ||
* schedule and cleanup callbacks. | ||
*/ | ||
@@ -460,3 +441,3 @@ export declare class Context<TProps = any, TResult = any> implements EventTarget { | ||
* host - The nearest ancestor host element. | ||
* @remarks | ||
* | ||
* When refresh is called, the host element will be arranged as the last step | ||
@@ -487,2 +468,3 @@ * of the commit, to make sure the parent’s children properly reflects the | ||
_it: Iterator<Children, Children | void, unknown> | AsyncIterator<Children, Children | void, unknown> | undefined; | ||
/*** async properties ***/ | ||
/** | ||
@@ -494,3 +476,3 @@ * @internal | ||
*/ | ||
_oa: (() => unknown) | undefined; | ||
_oa: Function | undefined; | ||
/** | ||
@@ -518,27 +500,4 @@ * @internal | ||
* @internal | ||
* listeners - An array of event listeners added to the context via | ||
* Context.prototype.addEventListener | ||
* Contexts should never be instantiated directly. | ||
*/ | ||
_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>); | ||
@@ -548,3 +507,2 @@ /** | ||
* | ||
* @remarks | ||
* Typically, you should read props either via the first parameter of the | ||
@@ -558,3 +516,2 @@ * component or via the context iterator methods. This property is mainly for | ||
* | ||
* @remarks | ||
* Typically, you should read values via refs, generator yield expressions, | ||
@@ -568,12 +525,12 @@ * or the refresh, schedule or cleanup methods. This property is mainly for | ||
/** | ||
* Re-executes the component. | ||
* Re-executes a component. | ||
* | ||
* @returns The rendered value of the component or a promise of the rendered | ||
* value if the component or its children execute asynchronously. | ||
* @returns The rendered value of the component or a promise thereof 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. | ||
* components, in that it will resume the Context’s props async iterator | ||
* rather than resuming execution. This is because async generator components | ||
* are perpetually resumed independent of updates, and rely on the props | ||
* async iterator to suspend. | ||
*/ | ||
@@ -610,8 +567,2 @@ refresh(): Promise<TResult> | TResult; | ||
}; | ||
interface EventListenerRecord { | ||
type: string; | ||
listener: MappedEventListenerOrEventListenerObject<any>; | ||
callback: MappedEventListener<any>; | ||
options: AddEventListenerOptions; | ||
} | ||
declare global { | ||
@@ -618,0 +569,0 @@ module Crank { |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
651865
7456