Socket
Socket
Sign inDemoInstall

forgo

Package Overview
Dependencies
Maintainers
2
Versions
140
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

forgo - npm Package Compare versions

Comparing version 4.1.1 to 4.1.2

151

dist/index.d.ts
export type ForgoRef<T> = {
value?: T;
};
export type ForgoElementProps = {
export type ForgoElementBaseProps = {
children?: ForgoNode | ForgoNode[];
ref?: ForgoRef<Element>;
};
export type ForgoDOMElementProps = {
xmlns?: string;
ref?: ForgoRef<Element>;
dangerouslySetInnerHTML?: {
__html: string;
};
};
export type ForgoComponentCtor<Props extends object = object> = (props: Props & ForgoElementProps) => ForgoComponent<Props>;
export type ForgoNewComponentCtor<Props extends object = object> = (props: Props & ForgoElementProps) => Component<Props>;
} & ForgoElementBaseProps;
export type ForgoLegacyComponentCtor<TProps extends object> = (props: TProps & ForgoElementBaseProps) => ForgoLegacyComponent<TProps>;
export type ForgoNewComponentCtor<TProps extends object> = (props: TProps & ForgoElementBaseProps) => Component<TProps>;
export type ForgoElementArg = {
node?: ChildNode;
nodeIndex: number;
componentIndex: number;
};
export type ForgoKeyType = string | number;
export type ForgoDOMElement<Props extends object> = {
key?: ForgoKeyType;
props: Props & ForgoElementProps;
export type ForgoElementBase<TProps extends ForgoElementBaseProps> = {
key?: any;
props: TProps;
__is_forgo_element__: true;
};
export type ForgoDOMElement<TProps extends ForgoDOMElementProps> = ForgoElementBase<TProps> & {
type: string;
};
export type ForgoComponentElement<Props extends object> = {
key?: ForgoKeyType;
props: Props & ForgoElementProps;
__is_forgo_element__: true;
type: ForgoNewComponentCtor<Props>;
export type ForgoComponentElement<TProps extends ForgoElementBaseProps> = ForgoElementBase<TProps> & {
type: ForgoNewComponentCtor<TProps>;
};

@@ -39,4 +38,4 @@ export type ForgoFragment = {

};
export type ForgoElement<Props extends object> = ForgoDOMElement<Props> | ForgoComponentElement<Props>;
export type ForgoNonEmptyPrimitiveNode = string | number | boolean | object | bigint | null | undefined;
export type ForgoElement<TProps extends ForgoElementBaseProps> = ForgoDOMElement<TProps> | ForgoComponentElement<TProps>;
export type ForgoNonEmptyPrimitiveNode = string | number | boolean | object | bigint;
export type ForgoPrimitiveNode = ForgoNonEmptyPrimitiveNode | null | undefined;

@@ -48,7 +47,7 @@ /**

export type ForgoNode = ForgoPrimitiveNode | ForgoElement<any> | ForgoFragment;
export type ComponentState<Props extends object = object> = {
key?: string | number;
ctor: ForgoNewComponentCtor<Props> | ForgoComponentCtor<Props>;
component: Component<Props>;
props: Props & ForgoElementProps;
export type NodeAttachedComponentState<TProps extends ForgoElementBaseProps> = {
key?: any;
ctor: ForgoNewComponentCtor<TProps> | ForgoLegacyComponentCtor<TProps>;
component: Component<TProps>;
props: TProps;
nodes: ChildNode[];

@@ -62,3 +61,3 @@ isMounted: boolean;

};
components: ComponentState<any>[];
components: NodeAttachedComponentState<any>[];
style?: {

@@ -68,12 +67,2 @@ [key: string]: any;

deleted?: boolean;
lookups: {
deletedUnkeyedNodes: DeletedNode[];
deletedKeyedComponentNodes: Map<string | number, ChildNode[]>;
keyedComponentNodes: Map<string | number, ChildNode[]>;
newlyAddedKeyedComponentNodes: Map<string | number, ChildNode[]>;
deletedKeyedElementNodes: Map<string | number, ChildNode>;
keyedElementNodes: Map<string | number, ChildNode>;
newlyAddedKeyedElementNodes: Map<string | number, ChildNode>;
renderCount: number;
};
};

@@ -98,3 +87,3 @@ export type DOMCSSProperties = {

/**
* Nodes will be created as detached DOM nodes, and will not be attached to a parent.
* Nodes will be created as detached DOM nodes, and will not be attached to the parent
*/

@@ -108,4 +97,4 @@ export type DetachedNodeInsertionOptions = {

*/
export type DOMNodeInsertionOptions = {
type: "search";
export type SearchableNodeInsertionOptions = {
type: "new-component";
/**

@@ -125,5 +114,9 @@ * The element that holds the previously-rendered version of this component

};
export type NodeInsertionOptions = DetachedNodeInsertionOptions | DOMNodeInsertionOptions;
/**
* Decides how the called function attaches nodes to the supplied parent
*/
export type NodeInsertionOptions = DetachedNodeInsertionOptions | SearchableNodeInsertionOptions;
export type RenderResult = {
nodes: ChildNode[];
pendingMounts: (() => void)[];
};

@@ -136,8 +129,16 @@ export type DeletedNode = {

__forgo?: NodeAttachedState;
__forgo_deletedNodes?: DeletedNode[];
}
}
export declare const Fragment: unique symbol;
export interface ForgoComponentMethods<Props extends object> {
render: (props: Props & ForgoElementProps, component: Component<Props>) => ForgoNode | ForgoNode[];
error?: (props: Props & ForgoElementProps, error: unknown, component: Component<Props>) => ForgoNode;
/**
* These are methods that a component may implement. Every component is required
* to have a render method.
* 1. render() returns the actual DOM to render.
* 2. error() is called when this component, or one of its children, throws an
* error.
*/
export interface ForgoComponentMethods<TProps extends object> {
render: (props: TProps & ForgoElementBaseProps, component: Component<TProps>) => ForgoNode | ForgoNode[];
error?: (props: TProps & ForgoElementBaseProps, error: unknown, component: Component<TProps>) => ForgoNode;
}

@@ -157,21 +158,19 @@ /**

*/
interface ComponentEventListeners<Props extends object> extends ComponentEventListenerBase {
mount: Array<(props: Props & ForgoElementProps, component: Component<Props>) => any>;
remount: Array<(props: Props & ForgoElementProps, component: Component<Props>) => any>;
unmount: Array<(props: Props & ForgoElementProps, component: Component<Props>) => any>;
afterRender: Array<(props: Props & ForgoElementProps, previousNode: ChildNode | undefined, component: Component<Props>) => any>;
shouldUpdate: Array<(newProps: Props & ForgoElementProps, oldProps: Props & ForgoElementProps, component: Component<Props>) => boolean>;
interface ComponentEventListeners<TProps extends object> extends ComponentEventListenerBase {
mount: Array<(props: TProps & ForgoElementBaseProps, component: Component<TProps>) => void>;
unmount: Array<(props: TProps & ForgoElementBaseProps, component: Component<TProps>) => void>;
afterRender: Array<(props: TProps & ForgoElementBaseProps, previousNode: ChildNode | undefined, component: Component<TProps>) => void>;
shouldUpdate: Array<(newProps: TProps & ForgoElementBaseProps, oldProps: TProps & ForgoElementBaseProps, component: Component<TProps>) => boolean>;
}
interface ComponentInternal<Props extends object = object> {
interface ComponentInternal<TProps extends object> {
unmounted: boolean;
registeredMethods: ForgoComponentMethods<Props>;
eventListeners: ComponentEventListeners<Props>;
registeredMethods: ForgoComponentMethods<TProps>;
eventListeners: ComponentEventListeners<TProps>;
element: ForgoElementArg;
}
declare const lifecycleEmitters: {
mount<Props extends object = object>(component: Component<Props>, props: Props & ForgoElementProps): void;
remount<Props_1 extends object = object>(component: Component<Props_1>, props: Props_1 & ForgoElementProps): void;
unmount<Props_2 extends object = object>(component: Component<Props_2>, props: Props_2 & ForgoElementProps): void;
shouldUpdate<Props_3 extends object = object>(component: Component<Props_3>, newProps: Props_3 & ForgoElementProps, oldProps: Props_3 & ForgoElementProps): boolean;
afterRender<Props_4 extends object = object>(component: Component<Props_4>, props: Props_4 & ForgoElementProps, previousNode: ChildNode | undefined): void;
mount<TProps extends object>(component: Component<TProps>, props: TProps): void;
unmount<TProps_1 extends object>(component: Component<TProps_1>, props: TProps_1): void;
shouldUpdate<TProps_2 extends object>(component: Component<TProps_2>, newProps: TProps_2, oldProps: TProps_2): boolean;
afterRender<TProps_3 extends object>(component: Component<TProps_3>, props: TProps_3, previousNode: ChildNode | undefined): void;
};

@@ -183,5 +182,5 @@ /**

*/
export declare class Component<Props extends object = object> {
export declare class Component<TProps extends object> {
/** @internal */
__internal: ComponentInternal<Props>;
__internal: ComponentInternal<TProps>;
/**

@@ -192,9 +191,8 @@ * @params methods The render method is mandatory. It receives your current

*/
constructor(registeredMethods: ForgoComponentMethods<Props>);
update(props?: Props): RenderResult;
mount(listener: ComponentEventListeners<Props>["mount"][number]): void;
remount(listener: ComponentEventListeners<Props>["remount"][number]): void;
unmount(listener: ComponentEventListeners<Props>["unmount"][number]): void;
shouldUpdate(listener: ComponentEventListeners<Props>["shouldUpdate"][number]): void;
afterRender(listener: ComponentEventListeners<Props>["afterRender"][number]): void;
constructor(registeredMethods: ForgoComponentMethods<TProps>);
update(props?: TProps & ForgoElementBaseProps): RenderResult;
mount(listener: ComponentEventListeners<TProps>["mount"][number]): void;
unmount(listener: ComponentEventListeners<TProps>["unmount"][number]): void;
shouldUpdate(listener: ComponentEventListeners<TProps>["shouldUpdate"][number]): void;
afterRender(listener: ComponentEventListeners<TProps>["afterRender"][number]): void;
}

@@ -204,7 +202,7 @@ /**

*/
export declare function createElement<Props extends object & {
export declare function createElement<TProps extends ForgoDOMElementProps & {
key?: any;
}>(type: string | ForgoNewComponentCtor<Props> | ForgoComponentCtor<Props>, props: Props & ForgoElementProps, ...args: any[]): {
type: string | ForgoNewComponentCtor<Props> | ForgoComponentCtor<Props>;
props: Props & ForgoElementProps;
}>(type: string | ForgoNewComponentCtor<TProps> | ForgoLegacyComponentCtor<TProps>, props: TProps, ...args: any[]): {
type: string | ForgoNewComponentCtor<TProps> | ForgoLegacyComponentCtor<TProps>;
props: TProps;
key: any;

@@ -242,3 +240,3 @@ __is_forgo_element__: boolean;

export declare function rerender(element: ForgoElementArg | undefined, props?: any): RenderResult;
export declare function getForgoState(node: ChildNode): NodeAttachedState;
export declare function getForgoState(node: ChildNode): NodeAttachedState | undefined;
export declare function setForgoState(node: ChildNode, state: NodeAttachedState): void;

@@ -248,10 +246,9 @@ /**

*/
export type ForgoComponent<Props extends object = object> = {
render: (props: Props & ForgoElementProps, args: ForgoRenderArgs) => ForgoNode | ForgoNode[];
afterRender?: (props: Props & ForgoElementProps, args: ForgoAfterRenderArgs) => void;
error?: (props: Props & ForgoElementProps, args: ForgoErrorArgs) => ForgoNode;
mount?: (props: Props & ForgoElementProps, args: ForgoRenderArgs) => void;
remount?: (props: Props & ForgoElementProps, args: ForgoRenderArgs) => void;
unmount?: (props: Props & ForgoElementProps, args: ForgoRenderArgs) => void;
shouldUpdate?: (newProps: Props & ForgoElementProps, oldProps: Props & ForgoElementProps) => boolean;
export type ForgoLegacyComponent<TProps extends object> = {
render: (props: TProps & ForgoElementBaseProps, args: ForgoRenderArgs) => ForgoNode | ForgoNode[];
afterRender?: (props: TProps & ForgoElementBaseProps, args: ForgoAfterRenderArgs) => void;
error?: (props: TProps & ForgoElementBaseProps, args: ForgoErrorArgs) => ForgoNode;
mount?: (props: TProps & ForgoElementBaseProps, args: ForgoRenderArgs) => void;
unmount?: (props: TProps & ForgoElementBaseProps, args: ForgoRenderArgs) => void;
shouldUpdate?: (newProps: TProps & ForgoElementBaseProps, oldProps: TProps & ForgoElementBaseProps) => boolean;
__forgo?: {

@@ -271,3 +268,3 @@ unmounted?: boolean;

};
export declare const legacyComponentSyntaxCompat: <Props extends object = object>(legacyComponent: ForgoComponent<Props>) => Component<Props>;
export declare const legacyComponentSyntaxCompat: <TProps extends object>(legacyComponent: ForgoLegacyComponent<TProps>) => Component<TProps>;
export * as JSX from "./jsxTypes.js";

@@ -274,0 +271,0 @@ import * as JSXTypes from "./jsxTypes.js";

@@ -5,21 +5,21 @@ // Since we'll set any attribute the user passes us, we need to be sure not to

/*
* Fragment constructor.
* We simply use it as a marker in jsx-runtime.
*/
Fragment constructor.
We simply use it as a marker in jsx-runtime.
*/
export const Fragment = Symbol.for("FORGO_FRAGMENT");
/*
* HTML Namespaces
*/
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
HTML Namespaces
*/
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
const MISSING_COMPONENT_INDEX = -1;
const MISSING_NODE_INDEX = -1;
/*
* These come from the browser's Node interface, which defines an enum of node
* types. We'd like to just reference Node.<whatever>, but JSDOM makes us jump
* through hoops to do that because it hates adding new globals. Getting around
* that is more complex, and more bytes on the wire, than just hardcoding the
* constants we care about.
*/
These come from the browser's Node interface, which defines an enum of node
types. We'd like to just reference Node.<whatever>, but JSDOM makes us jump
through hoops to do that because it hates adding new globals. Getting around
that is more complex, and more bytes on the wire, than just hardcoding the
constants we care about.
*/
const ELEMENT_NODE_TYPE = 1;

@@ -32,5 +32,2 @@ const TEXT_NODE_TYPE = 3;

},
remount(component, props) {
component.__internal.eventListeners.remount.forEach((cb) => cb(props, component));
},
unmount(component, props) {

@@ -69,7 +66,8 @@ component.__internal.eventListeners.unmount.forEach((cb) => cb(props, component));

mount: [],
remount: [],
unmount: [],
shouldUpdate: [],
},
element: { componentIndex: -1, nodeIndex: -1 },
element: {
componentIndex: MISSING_COMPONENT_INDEX,
},
};

@@ -86,5 +84,2 @@ }

}
remount(listener) {
this.__internal.eventListeners["remount"].push(listener);
}
unmount(listener) {

@@ -117,6 +112,6 @@ this.__internal.eventListeners["unmount"].push(listener);

/*
* HACK: Chrome fires onblur (if defined) immediately after a node.remove().
* This is bad news for us, since a rerender() inside the onblur handler will
* run on an unattached node. So, disable onblur if node is set to be removed.
*/
HACK: Chrome fires onblur (if defined) immediately after a node.remove().
This is bad news for us, since a rerender() inside the onblur handler
will run on an unattached node. So, disable onblur if node is set to be removed.
*/
function handlerDisabledOnNodeDelete(node, value) {

@@ -141,43 +136,66 @@ return (e) => {

/**
* This is the main render function.
*
* @param forgoNode The node to render. Can be any value renderable by Forgo,
* not just DOM nodes.
* @param insertionOptions Which nodes need to be replaced by the new node(s),
* or whether the new node should be created detached from the DOM (without
* replacement).
* @param pendingAttachStates The list of Component State objects which will
* be attached to the element.
*/
function internalRender(forgoNode, insertionOptions, pendingAttachStates, mountOnPreExistingDOM) {
* This is the main render function.
* @param forgoNode The node to render. Can be any value renderable by Forgo,
* not just DOM nodes.
* @param insertionOptions Which nodes need to be replaced by the new
* node(s), or whether the new node should be created detached from the DOM
* (without replacement).
* @param statesAwaitingAttach The list of Component State objects which will
* be attached to the element.
*/
function internalRender(forgoNode, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM) {
// Array of Nodes, or Fragment
if (Array.isArray(forgoNode) || isForgoFragment(forgoNode)) {
return renderArray(flatten(forgoNode), insertionOptions, pendingAttachStates, mountOnPreExistingDOM);
return renderArray(flatten(forgoNode), insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM);
}
// Primitive Nodes
else if (!isForgoElement(forgoNode)) {
return renderNonElement(forgoNode, insertionOptions, pendingAttachStates);
return renderNonElement(forgoNode, insertionOptions, statesAwaitingAttach);
}
// HTML Element
else if (isForgoDOMElement(forgoNode)) {
return renderDOMElement(forgoNode, insertionOptions, pendingAttachStates, mountOnPreExistingDOM);
return renderDOMElement(forgoNode, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM);
}
// Component
else {
return renderComponent(forgoNode, insertionOptions, pendingAttachStates, mountOnPreExistingDOM);
const result = renderComponent(forgoNode, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM);
// In order to prevent issue #50 (Fragments having mount() called before
// *all* child elements have finished rendering), we delay calling mount
// until a subtree's render has completed
//
// Ideally this would encompass both mounts and unmounts, but an unmounted
// component doesn't get `renderComponent()` called on it, so we need to
// continue unmounting inside each of the type-specific render functions.
// That's fine since the problem is elements not existing at mount time,
// whereas unmount timing isn't sensitive to that.
result.pendingMounts.forEach((fn) => fn());
result.pendingMounts.length = 0;
return result;
}
}
function renderNonElement(forgoNode, insertionOptions, pendingAttachStates) {
/*
Render a string.
* Such as in the render function below:
* function MyComponent() {
* return new forgo.Component({
* render() {
* return "Hello world"
* }
* })
* }
*/
function renderNonElement(forgoNode, insertionOptions, statesAwaitingAttach) {
var _a;
// Text and comment nodes will always be recreated (why?).
let node;
if (isNullOrUndefined(forgoNode)) {
if (forgoNode === null || forgoNode === undefined) {
node = env.document.createComment("null component render");
}
else {
node = env.document.createTextNode(stringOfPrimitiveNode(forgoNode));
node = env.document.createTextNode(stringOfNode(forgoNode));
}
let oldComponentState = undefined;
// We have to find a node to replace.
if (insertionOptions.type === "search") {
if (insertionOptions.type === "new-component") {
const childNodes = insertionOptions.parentElement.childNodes;

@@ -194,3 +212,3 @@ // If we're searching in a list, we replace if the current node is a text node.

const nextNode = childNodes[insertionOptions.currentNodeIndex];
insertionOptions.parentElement.insertBefore(node, nextNode !== null && nextNode !== void 0 ? nextNode : null);
insertionOptions.parentElement.insertBefore(node, nextNode);
}

@@ -205,17 +223,31 @@ }

const nextNode = childNodes[insertionOptions.currentNodeIndex];
insertionOptions.parentElement.insertBefore(node, nextNode !== null && nextNode !== void 0 ? nextNode : null);
insertionOptions.parentElement.insertBefore(node, nextNode);
}
syncAttrsAndState(forgoNode, node, insertionOptions.currentNodeIndex, true, pendingAttachStates);
}
else {
syncAttrsAndState(forgoNode, node, -1, true, pendingAttachStates);
}
syncAttrsAndState(forgoNode, node, true, statesAwaitingAttach);
unmountComponents(statesAwaitingAttach, oldComponentState);
return {
nodes: [node],
pendingMounts: [
() => mountComponents(statesAwaitingAttach, oldComponentState),
],
};
}
function renderDOMElement(forgoElement, insertionOptions, pendingAttachStates, mountOnPreExistingDOM) {
/*
Render a DOM element. Will find + update an existing DOM element (if
appropriate), or insert a new element.
Such as in the render function below:
function MyComponent() {
return {
render() {
return <div>Hello world</div>
}
}
}
*/
function renderDOMElement(forgoElement, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM) {
// We need to create a detached node
if (insertionOptions.type === "detached") {
return addElement(undefined, undefined);
return addElement(undefined, null);
}

@@ -225,36 +257,71 @@ // We have to find a node to replace.

const childNodes = insertionOptions.parentElement.childNodes;
const found = findReplacementCandidateForElement(forgoElement, insertionOptions, pendingAttachStates);
const renderResult = found
? renderExistingElement(insertionOptions)
: addElement(insertionOptions.parentElement, insertionOptions.currentNodeIndex);
return renderResult;
if (insertionOptions.length) {
const searchResult = findReplacementCandidateForElement(forgoElement, insertionOptions.parentElement, insertionOptions.currentNodeIndex, insertionOptions.length);
if (searchResult.found) {
return renderExistingElement(searchResult.index, childNodes, insertionOptions);
}
}
return addElement(insertionOptions.parentElement, childNodes[insertionOptions.currentNodeIndex]);
}
function renderChildNodes(element) {
function renderChildNodes(parentElement) {
// If the user gave us exact HTML to stuff into this parent, we can
// skip/ignore the usual rendering logic
if (forgoElement.props.dangerouslySetInnerHTML) {
element.innerHTML = forgoElement.props.dangerouslySetInnerHTML.__html;
parentElement.innerHTML =
forgoElement.props.dangerouslySetInnerHTML.__html;
}
else {
const state = getForgoState(element);
initKeyLookupLoop(state);
// Coerce children to always be an array, for simplicity
const forgoChildren = flatten([forgoElement.props.children]).filter(
// Children may or may not be specified
(x) => !isNullOrUndefined(x));
let currentNodeIndex = 0;
(x) => x !== undefined && x !== null);
// Make sure that if the user prepends non-Forgo DOM children under this
// parent that we start with the correct offset, otherwise we'll do DOM
// transformations that don't make any sense for the given input.
const firstForgoChildIndex = Array.from(parentElement.childNodes).findIndex((child) => getForgoState(child));
// Each node we render will push any leftover children further down the
// parent's list of children. After rendering everything, we can clean
// up anything extra. We'll know what's extra because all nodes we want
// to preserve come before this index.
let lastRenderedNodeIndex = firstForgoChildIndex === -1 ? 0 : firstForgoChildIndex;
for (const forgoChild of forgoChildren) {
const { nodes: nodesJustRendered } = internalRender(forgoChild, {
type: "search",
parentElement: element,
currentNodeIndex,
length: element.childNodes.length - currentNodeIndex,
const { nodes: nodesAfterRender } = internalRender(forgoChild, {
type: "new-component",
parentElement,
currentNodeIndex: lastRenderedNodeIndex,
length: parentElement.childNodes.length - lastRenderedNodeIndex,
}, [], mountOnPreExistingDOM);
currentNodeIndex += nodesJustRendered.length;
// Continue down the children list to wherever's right after the stuff
// we just added. Because users are allowed to add arbitrary stuff to
// the DOM manually, we can't just jump by the count of rendered
// elements, since that's the count of *managed* elements, which might
// be interspersed with unmanaged elements that we also need to skip
// past.
if (nodesAfterRender.length) {
while (parentElement.childNodes[lastRenderedNodeIndex] !==
nodesAfterRender[nodesAfterRender.length - 1]) {
lastRenderedNodeIndex += 1;
}
// Move the counter *past* the last node we inserted. E.g., if we just
// inserted our first node, we need to increment from 0 -> 1, where
// we'll start searching for the next thing we insert
lastRenderedNodeIndex += 1;
// If we're updating an existing DOM element, it's possible that the
// user manually added some DOM nodes somewhere in the middle of our
// managed nodes. If that happened, we need to scan forward until we
// pass them and find the next managed node, which we'll use as the
// starting point for whatever we render next. We still need the +1
// above to make sure we always progress the index, in case this is
// our first render pass and there's nothing to scan forward to.
while (lastRenderedNodeIndex < parentElement.childNodes.length) {
if (getForgoState(parentElement.childNodes[lastRenderedNodeIndex])) {
break;
}
lastRenderedNodeIndex += 1;
}
}
}
// Clear nodes remaining after currentNodeIndex
// eg: if currentNodeIndex = 10 (and length = 20), remove everything > 10
markNodesForUnloading(element.childNodes, currentNodeIndex, element.childNodes.length);
unloadMarkedNodes(element);
finalizeKeyLookups(state);
// Remove all nodes that don't correspond to the rendered output of a
// live component
markNodesForUnloading(parentElement.childNodes, lastRenderedNodeIndex, parentElement.childNodes.length);
}

@@ -266,99 +333,49 @@ }

*/
function renderExistingElement(insertionOptions) {
const childNodes = insertionOptions.parentElement.childNodes;
const parentState = getForgoState(insertionOptions.parentElement);
function renderExistingElement(insertAt, childNodes, insertionOptions) {
var _a;
// Get rid of unwanted nodes.
markNodesForUnloading(childNodes, insertionOptions.currentNodeIndex, insertAt);
const targetElement = childNodes[insertionOptions.currentNodeIndex];
pendingAttachStates.forEach((pendingAttachState, i) => {
var _a;
if (pendingAttachState.key !== undefined) {
const key = deriveComponentKey(pendingAttachState.key, i);
const nodesForKey = (_a = parentState.lookups.newlyAddedKeyedComponentNodes.get(key)) !== null && _a !== void 0 ? _a : [];
nodesForKey.push(targetElement);
parentState.lookups.newlyAddedKeyedComponentNodes.set(key, nodesForKey);
}
});
if (forgoElement.key !== undefined) {
parentState.lookups.newlyAddedKeyedElementNodes.set(forgoElement.key, targetElement);
}
syncAttrsAndState(forgoElement, targetElement, insertionOptions.currentNodeIndex, false, pendingAttachStates);
const oldComponentState = (_a = getForgoState(targetElement)) === null || _a === void 0 ? void 0 : _a.components;
syncAttrsAndState(forgoElement, targetElement, false, statesAwaitingAttach);
renderChildNodes(targetElement);
unloadMarkedNodes(targetElement, statesAwaitingAttach);
unmountComponents(statesAwaitingAttach, oldComponentState);
return {
nodes: [targetElement],
pendingMounts: [
() => mountComponents(statesAwaitingAttach, oldComponentState),
],
};
}
function addElement(parentElement, position) {
function addElement(parentElement, oldNode) {
const newElement = createElement(forgoElement, parentElement);
if (parentElement) {
parentElement.insertBefore(newElement, oldNode);
}
if (forgoElement.props.ref) {
forgoElement.props.ref.value = newElement;
}
const oldNode = position !== undefined
? parentElement.childNodes[position]
: null;
if (parentElement) {
const parentState = getForgoState(parentElement);
pendingAttachStates.forEach((pendingAttachState, i) => {
if (pendingAttachState.key !== undefined) {
const key = deriveComponentKey(pendingAttachState.key, i);
parentState.lookups.newlyAddedKeyedComponentNodes.set(key, [
newElement,
]);
}
});
if (forgoElement.key !== undefined) {
parentState.lookups.newlyAddedKeyedElementNodes.set(forgoElement.key, newElement);
}
}
if (parentElement) {
parentElement.insertBefore(newElement, oldNode !== null && oldNode !== void 0 ? oldNode : null);
syncAttrsAndState(forgoElement, newElement, insertionOptions.type === "search"
? insertionOptions.currentNodeIndex
: findNodeIndex(parentElement.childNodes, newElement, 0), true, pendingAttachStates);
}
else {
syncAttrsAndState(forgoElement, newElement, insertionOptions.type === "search"
? insertionOptions.currentNodeIndex
: -1, true, pendingAttachStates);
}
syncAttrsAndState(forgoElement, newElement, true, statesAwaitingAttach);
renderChildNodes(newElement);
return { nodes: [newElement] };
unmountComponents(statesAwaitingAttach, undefined);
return {
nodes: [newElement],
pendingMounts: [() => mountComponents(statesAwaitingAttach, undefined)],
};
}
}
function initKeyLookupLoop(state) {
state.lookups.renderCount++;
}
function finalizeKeyLookups(state) {
state.lookups.renderCount--;
if (state.lookups.renderCount === 0) {
state.lookups.keyedComponentNodes =
state.lookups.newlyAddedKeyedComponentNodes;
state.lookups.keyedElementNodes =
state.lookups.newlyAddedKeyedElementNodes;
state.lookups.newlyAddedKeyedComponentNodes = new Map();
state.lookups.newlyAddedKeyedElementNodes = new Map();
state.lookups.deletedKeyedComponentNodes = new Map();
state.lookups.deletedKeyedElementNodes = new Map();
state.lookups.deletedUnkeyedNodes = [];
}
}
function renderComponent(forgoComponent, insertionOptions, pendingAttachStates, mountOnPreExistingDOM
// boundary: ForgoComponent<object> | undefined
/*
Render a Component.
Such as <MySideBar size="large" />
*/
function renderComponent(forgoComponent, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM
// boundary: ForgoComponent<any> | undefined
) {
const componentIndex = pendingAttachStates.length;
if (
// We need to create a detached node.
insertionOptions.type !== "detached" &&
!mountOnPreExistingDOM) {
const childNodes = insertionOptions.parentElement.childNodes;
const found = findReplacementCandidateForComponent(forgoComponent, insertionOptions, pendingAttachStates.length);
if (found) {
return renderExistingComponent(childNodes, insertionOptions);
}
}
// No nodes in target node list, or no matching node found.
// Nothing to unload.
return addComponent();
function renderExistingComponent(childNodes, insertionOptions) {
const targetNode = childNodes[insertionOptions.currentNodeIndex];
const state = getForgoState(targetNode);
function renderExistingComponent(insertAt, childNodes, insertionOptions) {
const targetNode = childNodes[insertAt];
const state = getExistingForgoState(targetNode);
const componentState = state.components[componentIndex];
// Get rid of unwanted nodes.
markNodesForUnloading(childNodes, insertionOptions.currentNodeIndex, insertAt);
if (lifecycleEmitters.shouldUpdate(componentState.component, forgoComponent.props, componentState.props)) {

@@ -370,4 +387,3 @@ // Since we have compatible state already stored,

const newForgoNode = updatedComponentState.component.__internal.registeredMethods.render(forgoComponent.props, updatedComponentState.component);
const componentIndex = pendingAttachStates.length;
const statesToAttach = pendingAttachStates.concat(updatedComponentState);
const statesToAttach = statesAwaitingAttach.concat(updatedComponentState);
const previousNode = componentState.component.__internal.element.node;

@@ -381,3 +397,3 @@ const boundary = updatedComponentState.component.__internal

const newInsertionOptions = {
type: "search",
type: "new-component",
currentNodeIndex: insertionOptions.currentNodeIndex,

@@ -387,3 +403,3 @@ length: updatedComponentState.nodes.length,

};
return renderComponentAndRemoveStaleNodes(newForgoNode, newInsertionOptions, statesToAttach, componentIndex, updatedComponentState.nodes.length, mountOnPreExistingDOM);
return renderComponentAndRemoveStaleNodes(newForgoNode, newInsertionOptions, statesToAttach, updatedComponentState, mountOnPreExistingDOM);
});

@@ -395,4 +411,6 @@ lifecycleEmitters.afterRender(updatedComponentState.component, forgoComponent.props, previousNode);

else {
const indexOfNode = findNodeIndex(insertionOptions.parentElement.childNodes, componentState.component.__internal.element.node);
return {
nodes: componentState.nodes,
nodes: sliceNodes(insertionOptions.parentElement.childNodes, indexOfNode, indexOfNode + componentState.nodes.length),
pendingMounts: [],
};

@@ -403,3 +421,3 @@ }

const ctor = forgoComponent.type;
const component = assertIsComponent(ctor, ctor(forgoComponent.props), env.window.FORGO_NO_LEGACY_WARN !== true);
const component = assertIsComponent(ctor, ctor(forgoComponent.props));
component.__internal.element.componentIndex = componentIndex;

@@ -410,3 +428,3 @@ const boundary = component.__internal.registeredMethods.error

// Create new component state
// ... and push it to pendingAttachStates
// ... and push it to statesAwaitAttach[]
const newComponentState = {

@@ -420,4 +438,3 @@ key: forgoComponent.key,

};
const indexOfNewComponentState = pendingAttachStates.length;
const statesToAttach = pendingAttachStates.concat(newComponentState);
const statesToAttach = statesAwaitingAttach.concat(newComponentState);
return withErrorBoundary(forgoComponent.props, statesToAttach, boundary, () => {

@@ -430,3 +447,3 @@ // Create an element by rendering the component

: {
type: "search",
type: "new-component",
currentNodeIndex: insertionOptions.currentNodeIndex,

@@ -438,9 +455,7 @@ length: mountOnPreExistingDOM ? insertionOptions.length : 0,

const renderResult = internalRender(newForgoElement, newInsertionOptions, statesToAttach, mountOnPreExistingDOM);
const nodeAttachedState = getForgoState(renderResult.nodes[0]);
const componentStateAttached = nodeAttachedState.components[indexOfNewComponentState];
componentStateAttached.nodes = renderResult.nodes;
setNodeInfo(componentStateAttached.component.__internal.element, renderResult.nodes[0], insertionOptions.type !== "detached"
? insertionOptions.currentNodeIndex
: -1);
lifecycleEmitters.mount(newComponentState.component, forgoComponent.props);
// In case we rendered an array, set the node to the first node.
// We do this because args.element.node would be set to the last node otherwise.
newComponentState.nodes = renderResult.nodes;
newComponentState.component.__internal.element.node =
renderResult.nodes[0];
// No previousNode since new component. So just args and not

@@ -466,13 +481,23 @@ // afterRenderArgs.

}
const componentIndex = statesAwaitingAttach.length;
if (
// We need to create a detached node.
insertionOptions.type !== "detached" &&
// We have to find a node to replace.
insertionOptions.length &&
!mountOnPreExistingDOM) {
const childNodes = insertionOptions.parentElement.childNodes;
const searchResult = findReplacementCandidateForComponent(forgoComponent, insertionOptions.parentElement, insertionOptions.currentNodeIndex, insertionOptions.length, statesAwaitingAttach.length);
if (searchResult.found) {
return renderExistingComponent(searchResult.index, childNodes, insertionOptions);
}
}
// No nodes in target node list, or no matching node found.
// Nothing to unload.
return addComponent();
}
function renderComponentAndRemoveStaleNodes(forgoNode, insertionOptions, statesToAttach, componentIndex, previousNodeCount, mountOnPreExistingDOM) {
function renderComponentAndRemoveStaleNodes(forgoNode, insertionOptions, statesToAttach, componentState, mountOnPreExistingDOM) {
const totalNodesBeforeRender = insertionOptions.parentElement.childNodes.length;
const componentState = statesToAttach.slice(-1)[0];
const previousNode = componentState.component.__internal.element.node;
// Pass it on for rendering...
const renderResult = internalRender(forgoNode, insertionOptions, statesToAttach, mountOnPreExistingDOM);
const newNode = componentState.component.__internal.element.node;
if (previousNode !== newNode) {
lifecycleEmitters.remount(componentState.component, componentState.props);
}
const totalNodesAfterRender = insertionOptions.parentElement.childNodes.length;

@@ -487,16 +512,29 @@ const numNodesReused = totalNodesBeforeRender +

const deleteFromIndex = insertionOptions.currentNodeIndex + renderResult.nodes.length;
markNodesForUnloading(insertionOptions.parentElement.childNodes, deleteFromIndex, deleteFromIndex + previousNodeCount - numNodesReused);
// In case we rendered an array, set the node to the first node. We do this
// because args.element.node would be set to the last node otherwise.
// There's also a chance that renderResult might have no nodes. For example,
// if render returned an empty fragment.
if (renderResult.nodes.length) {
const nodeAttachedState = getForgoState(renderResult.nodes[0]);
const componentStateAttached = nodeAttachedState.components[componentIndex];
componentStateAttached.nodes = renderResult.nodes;
setNodeInfo(componentStateAttached.component.__internal.element, renderResult.nodes[0], insertionOptions.currentNodeIndex);
const deletedNodes = markNodesForUnloading(insertionOptions.parentElement.childNodes, deleteFromIndex, deleteFromIndex + componentState.nodes.length - numNodesReused);
/*
* transferredState is the state that's already been remounted on a different node.
* Components in transferredState should not be unmounted, since this is already
* being tracked on a different node. Hence transferredState needs to be removed
* from deletedNodes.
*/
const transferredState = renderResult.nodes.length > 0 ? statesToAttach : [];
// Patch state in deletedNodes to exclude what's been already transferred.
for (const deletedNode of deletedNodes) {
const state = getForgoState(deletedNode);
if (state) {
const indexOfFirstIncompatibleState = findIndexOfFirstIncompatibleState(transferredState, state.components);
state.components = state.components.slice(indexOfFirstIncompatibleState);
}
}
// In case we rendered an array, set the node to the first node.
// We do this because args.element.node would be set to the last node otherwise.
componentState.nodes = renderResult.nodes;
componentState.component.__internal.element.node = renderResult.nodes[0];
return renderResult;
}
function renderArray(forgoNodes, insertionOptions, pendingAttachStates, mountOnPreExistingDOM) {
/*
Render an array of components.
Called when a Component returns an array (or fragment) in its render method.
*/
function renderArray(forgoNodes, insertionOptions, statesAwaitingAttach, mountOnPreExistingDOM) {
const flattenedNodes = flatten(forgoNodes);

@@ -507,12 +545,11 @@ if (insertionOptions.type === "detached") {

else {
const renderResults = { nodes: [] };
const renderResults = { nodes: [], pendingMounts: [] };
let currentNodeIndex = insertionOptions.currentNodeIndex;
let numNodes = insertionOptions.length;
const parentState = getForgoState(insertionOptions.parentElement);
initKeyLookupLoop(parentState);
for (const forgoNode of flattenedNodes) {
const totalNodesBeforeRender = insertionOptions.parentElement.childNodes.length;
const newInsertionOptions = Object.assign(Object.assign({}, insertionOptions), { currentNodeIndex, length: numNodes });
const renderResult = internalRender(forgoNode, newInsertionOptions, pendingAttachStates, mountOnPreExistingDOM);
const renderResult = internalRender(forgoNode, newInsertionOptions, statesAwaitingAttach, mountOnPreExistingDOM);
renderResults.nodes.push(...renderResult.nodes);
renderResults.pendingMounts.push(...renderResult.pendingMounts);
const totalNodesAfterRender = insertionOptions.parentElement.childNodes.length;

@@ -525,3 +562,2 @@ const numNodesRemoved = totalNodesBeforeRender +

}
finalizeKeyLookups(parentState);
return renderResults;

@@ -532,5 +568,5 @@ }

* This doesn't unmount components attached to these nodes, but moves the node
* itself from the DOM to deletedXYXNodes under parentNode.lookups. We sort of
* "mark" it for deletion, but it may be resurrected if it's matched by a
* keyed forgo node that has been reordered.
* itself from the DOM to parentNode.__forgo_deletedNodes. We sort of "mark"
* it for deletion, but it may be resurrected if it's matched by a keyed forgo
* node that has been reordered.
*

@@ -540,189 +576,213 @@ * Nodes in between `from` and `to` (not inclusive of `to`) will be marked for

* we're sure we don't need to resurrect them.
*
* We don't want to remove DOM nodes that aren't owned by Forgo. I.e., if the
* user grabs a reference to a DOM element and manually adds children under
* it, we don't want to remove those children. That'll mess up e.g., charting
* libraries.
*/
function markNodesForUnloading(nodes, from, to) {
const removedNodes = [];
if (to > from) {
const parentElement = nodes[from].parentElement;
const parentState = getForgoState(parentElement);
for (let i = from; i < to; i++) {
const node = nodes[from];
const justDeletedNodes = [];
const nodesToRemove = sliceNodes(nodes, from, to);
if (nodesToRemove.length) {
const parentElement = nodesToRemove[0].parentElement;
const deletedNodes = getDeletedNodes(parentElement);
for (const node of nodesToRemove) {
// If the consuming application has manually mucked with the DOM don't
// remove things it added
const state = getForgoState(node);
// Remove the node from DOM
if (!state)
continue;
node.remove();
// If the component is keyed, we have to remove the entry in key-map
state.components.forEach((component, i) => {
var _a;
if (component.key !== undefined) {
const key = deriveComponentKey(component.key, i);
const nodesForKey = parentState.lookups.keyedComponentNodes.get(key);
if (nodesForKey !== undefined) {
const updatedNodesForKey = nodesForKey.filter((x) => x !== node);
if (updatedNodesForKey.length) {
parentState.lookups.keyedComponentNodes.set(key, updatedNodesForKey);
}
else {
parentState.lookups.keyedComponentNodes.delete(key);
}
}
const deletedNodesForKey = (_a = parentState.lookups.deletedKeyedComponentNodes.get(key)) !== null && _a !== void 0 ? _a : [];
deletedNodesForKey.push(node);
parentState.lookups.deletedKeyedComponentNodes.set(key, deletedNodesForKey);
}
});
if (state.key !== undefined) {
parentState.lookups.keyedComponentNodes.delete(state.key);
parentState.lookups.deletedKeyedComponentNodes.set(state.key, [node]);
}
else {
parentState.lookups.deletedUnkeyedNodes.push({ node });
}
removedNodes.push(node);
justDeletedNodes.push(node);
deletedNodes.push({ node });
}
}
return removedNodes;
return justDeletedNodes;
}
/*
* Unmount components from nodes. If a componentState is attached to the node
* that is about to be unloaded, then we should unmount the component.
*/
function unloadMarkedNodes(parentElement) {
function unloadNode(node) {
Unmount components from nodes.
We unmount only after first incompatible state, since compatible states
will be reattached to new candidate node.
*/
function unloadMarkedNodes(parentElement, statesAwaitingAttach) {
const deletedNodes = getDeletedNodes(parentElement);
for (const { node } of deletedNodes) {
const state = getForgoState(node);
state.deleted = true;
for (const componentState of state.components) {
if (componentState.component.__internal.element.node === node) {
if (!componentState.component.__internal.unmounted) {
lifecycleEmitters.unmount(componentState.component, componentState.props);
}
if (state) {
state.deleted = true;
const oldComponentStates = state.components;
unmountComponents(statesAwaitingAttach, oldComponentStates);
}
}
clearDeletedNodes(parentElement);
}
/*
When states are attached to a new node or when states are reattached,
some of the old component states need to go away. The corresponding components
will need to be unmounted.
While rendering, the component gets reused if the ctor is the same. If the
ctor is different, the component is discarded. And hence needs to be unmounted.
So we check the ctor type in old and new.
*/
function findIndexOfFirstIncompatibleState(newStates, oldStates) {
let i = 0;
for (const newState of newStates) {
if (oldStates.length > i) {
const oldState = oldStates[i];
if (oldState.component !== newState.component) {
break;
}
i++;
}
else {
break;
}
}
const parentState = getForgoState(parentElement);
for (const nodeList of parentState.lookups.deletedKeyedComponentNodes.values()) {
for (const node of nodeList) {
if (node.isConnected) {
unloadNode(node);
return i;
}
/**
* Unmount components above an index. This is going to be passed a stale
* state[].
*
* The `unmount` lifecycle event will be called.
*/
function unmountComponents(statesAwaitingAttach, oldComponentStates) {
if (!oldComponentStates)
return;
// If the parent has already unmounted, we can skip checks on children.
let parentHasUnmounted = false;
const indexOfFirstIncompatibleState = findIndexOfFirstIncompatibleState(statesAwaitingAttach, oldComponentStates);
for (let i = indexOfFirstIncompatibleState; i < oldComponentStates.length; i++) {
const state = oldComponentStates[i];
const component = state.component;
// Render if:
// - parent has already unmounted
// - OR for all nodes:
// - node is disconnected
// - OR node connected to a different component
if (parentHasUnmounted ||
state.nodes.every((x) => {
if (!x.isConnected) {
return true;
}
else {
const stateOnCurrentNode = getExistingForgoState(x);
return (stateOnCurrentNode.components[i] === undefined ||
stateOnCurrentNode.components[i].component !== state.component);
}
})) {
if (!component.__internal.unmounted) {
component.__internal.unmounted = true;
lifecycleEmitters.unmount(component, state.props);
}
parentHasUnmounted = true;
}
}
for (const { node } of parentState.lookups.deletedUnkeyedNodes) {
unloadNode(node);
}
/**
* Mount components above an index. This is going to be passed the new
* state[].
*/
function mountComponents(statesAwaitingAttach, oldComponentStates) {
const indexOfFirstIncompatibleState = oldComponentStates
? findIndexOfFirstIncompatibleState(statesAwaitingAttach, oldComponentStates)
: 0;
for (let i = indexOfFirstIncompatibleState; i < statesAwaitingAttach.length; i++) {
const state = statesAwaitingAttach[i];
// This function is called in every syncStateAndProps() call, so many of
// the calls will be for already-mounted components. Only fire the mount
// lifecycle events when appropriate.
if (!state.isMounted) {
state.isMounted = true;
// Set this before calling the lifecycle handlers to fix #70
lifecycleEmitters.mount(state.component, state.props);
}
}
// Clear deleted nodes
parentState.lookups.deletedKeyedComponentNodes.clear();
parentState.lookups.deletedUnkeyedNodes = [];
}
function findReplacementCandidateForElement(forgoElement, insertionOptions, pendingAttachStates) {
function isCompatibleElement(node, forgoElement, pendingAttachStates) {
/**
* When we try to find replacement candidates for DOM nodes,
* we try to:
* a) match by the key
* b) match by the tagname
*/
function findReplacementCandidateForElement(forgoElement, parentElement, searchFrom, length) {
const nodes = parentElement.childNodes;
for (let i = searchFrom; i < searchFrom + length; i++) {
const node = nodes[i];
if (nodeIsElement(node)) {
const state = getForgoState(node);
return (node.tagName.toLowerCase() === forgoElement.type &&
state.components.every((componentState, i) => pendingAttachStates[i] !== undefined &&
pendingAttachStates[i].component === componentState.component));
}
else {
return false;
}
}
function findReplacementCandidateForKeyedElement(forgoElement, insertionOptions, pendingAttachStates) {
const { parentElement, currentNodeIndex: searchFrom } = insertionOptions;
// First let's check active nodes.
const parentState = getForgoState(parentElement);
// See if the node is in our key lookup
const nodeFromKeyLookup = parentState.lookups.keyedElementNodes.get(forgoElement.key);
if (nodeFromKeyLookup !== undefined) {
if (isCompatibleElement(nodeFromKeyLookup, forgoElement, pendingAttachStates)) {
// Let's insert the nodes at the corresponding position.
const firstNodeInSearchList = parentElement.childNodes[searchFrom];
if (nodeFromKeyLookup !== firstNodeInSearchList) {
parentElement.insertBefore(nodeFromKeyLookup, firstNodeInSearchList !== null && firstNodeInSearchList !== void 0 ? firstNodeInSearchList : null);
}
return true;
const stateOnNode = getForgoState(node);
// If the user stuffs random elements into the DOM manually, we don't
// want to treat them as replacement candidates - they should be left
// alone.
if (!stateOnNode)
continue;
if (forgoElement.key !== undefined &&
(stateOnNode === null || stateOnNode === void 0 ? void 0 : stateOnNode.key) === forgoElement.key) {
return { found: true, index: i };
}
else {
// Node is mismatched. No point in keeping it in key lookup.
parentState.lookups.keyedComponentNodes.delete(forgoElement.key);
return false;
}
}
// Not found in active nodes. Check deleted nodes.
else {
const nodeFromKeyLookup = parentState.lookups.deletedKeyedElementNodes.get(forgoElement.key);
if (nodeFromKeyLookup !== undefined) {
const nodes = parentElement.childNodes;
// Delete key from lookup since we're either going to resurrect the node or discard it.
parentState.lookups.deletedKeyedComponentNodes.delete(forgoElement.key);
if (isCompatibleElement(nodeFromKeyLookup, forgoElement, pendingAttachStates)) {
// Let's insert the nodes at the corresponding position.
const firstNodeInSearchList = nodes[searchFrom];
if (nodeFromKeyLookup !== firstNodeInSearchList) {
parentElement.insertBefore(nodeFromKeyLookup, firstNodeInSearchList !== null && firstNodeInSearchList !== void 0 ? firstNodeInSearchList : null);
}
return true;
// If the candidate has a key defined,
// we don't match it with an unkeyed forgo element
if (node.tagName.toLowerCase() === forgoElement.type &&
(stateOnNode === undefined || stateOnNode.key === undefined)) {
return { found: true, index: i };
}
}
return false;
}
}
function findReplacementCandidateForUnKeyedElement(forgoElement, insertionOptions, pendingAttachStates) {
var _a;
const { parentElement, currentNodeIndex: searchFrom, length, } = insertionOptions;
const nodes = parentElement.childNodes;
for (let i = searchFrom; i < searchFrom + length; i++) {
const node = nodes[i];
if (nodeIsElement(node)) {
const state = getForgoState(node);
// If the candidate has a key defined, we don't match it with
// an unkeyed forgo element
if (node.tagName.toLowerCase() === forgoElement.type &&
state.key === undefined &&
isCompatibleElement(node, forgoElement, pendingAttachStates)) {
const elementAtSearchIndex = (_a = parentElement.childNodes[searchFrom]) !== null && _a !== void 0 ? _a : null;
if (node !== elementAtSearchIndex) {
parentElement.insertBefore(node, elementAtSearchIndex);
}
return true;
// Let's check deleted nodes as well.
if (forgoElement.key !== undefined) {
const deletedNodes = getDeletedNodes(parentElement);
for (let i = 0; i < deletedNodes.length; i++) {
const { node } = deletedNodes[i];
const stateOnNode = getForgoState(node);
if ((stateOnNode === null || stateOnNode === void 0 ? void 0 : stateOnNode.key) === forgoElement.key) {
// Remove it from deletedNodes.
deletedNodes.splice(i, 1);
// Append it to the beginning of the node list.
const firstNodeInSearchList = nodes[searchFrom];
if (!isNullOrUndefined(firstNodeInSearchList)) {
parentElement.insertBefore(node, firstNodeInSearchList);
}
else {
parentElement.appendChild(node);
}
return { found: true, index: searchFrom };
}
}
return false;
}
if (isKeyedElement(forgoElement)) {
return findReplacementCandidateForKeyedElement(forgoElement, insertionOptions, pendingAttachStates);
}
else {
return findReplacementCandidateForUnKeyedElement(forgoElement, insertionOptions, pendingAttachStates);
}
return { found: false };
}
function findReplacementCandidateForComponent(forgoComponent, insertionOptions, componentIndex) {
function findReplacementCandidateForKeyedComponent(forgoComponent, insertionOptions, componentIndex) {
const { parentElement, currentNodeIndex: searchFrom } = insertionOptions;
const key = deriveComponentKey(forgoComponent.key, componentIndex);
// If forgo element has a key, we gotta find it in the childNodeMap (under active and deleted).
const parentState = getForgoState(parentElement);
// Check active nodes first
const nodesForKey = parentState.lookups.keyedComponentNodes.get(key);
if (nodesForKey !== undefined) {
// Let's insert the nodes at the corresponding position.
const elementAtIndex = parentElement.childNodes[searchFrom];
for (const node of nodesForKey) {
if (node !== elementAtIndex) {
parentElement.insertBefore(node, elementAtIndex !== null && elementAtIndex !== void 0 ? elementAtIndex : null);
/**
* When we try to find replacement candidates for Components,
* we try to:
* a) match by the key
* b) match by the component constructor
*/
function findReplacementCandidateForComponent(forgoElement, parentElement, searchFrom, length, componentIndex) {
const nodes = parentElement.childNodes;
for (let i = searchFrom; i < searchFrom + length; i++) {
const node = nodes[i];
const stateOnNode = getForgoState(node);
if (stateOnNode && stateOnNode.components.length > componentIndex) {
if (forgoElement.key !== undefined) {
if (stateOnNode.components[componentIndex].ctor === forgoElement.type &&
stateOnNode.components[componentIndex].key === forgoElement.key) {
return { found: true, index: i };
}
}
return true;
else {
if (stateOnNode.components[componentIndex].ctor === forgoElement.type) {
return { found: true, index: i };
}
}
}
// Not found in active nodes. Check deleted nodes.
else {
const matchingNodes = parentState.lookups.deletedKeyedComponentNodes.get(key);
if (matchingNodes !== undefined) {
// Delete key from lookup since we're either going to resurrect these nodes
parentState.lookups.deletedKeyedComponentNodes.delete(key);
// Append it to the beginning of the node list.
for (const node of matchingNodes) {
const firstNodeInSearchList = parentElement.childNodes[searchFrom];
if (node !== firstNodeInSearchList) {
parentElement.insertBefore(node, firstNodeInSearchList !== null && firstNodeInSearchList !== void 0 ? firstNodeInSearchList : null);
}
}
}
// Check if a keyed component is mounted on this node.
function nodeBelongsToKeyedComponent(node, forgoElement, componentIndex) {
const stateOnNode = getForgoState(node);
if (stateOnNode && stateOnNode.components.length > componentIndex) {
if (stateOnNode.components[componentIndex].ctor === forgoElement.type &&
stateOnNode.components[componentIndex].key === forgoElement.key) {
return true;

@@ -733,33 +793,42 @@ }

}
function findReplacementCandidateForUnkeyedComponent(forgoComponent, insertionOptions, componentIndex) {
var _a;
const { parentElement, currentNodeIndex: searchFrom, length, } = insertionOptions;
const nodes = parentElement.childNodes;
for (let i = searchFrom; i < searchFrom + length; i++) {
const node = nodes[i];
const state = getForgoState(node);
if (state !== undefined && state.components.length > componentIndex) {
if (state.components[componentIndex].ctor === forgoComponent.type) {
const elementAtSearchIndex = (_a = parentElement.childNodes[searchFrom]) !== null && _a !== void 0 ? _a : null;
if (node !== elementAtSearchIndex) {
parentElement.insertBefore(node, elementAtSearchIndex);
// Let's check deleted nodes as well.
if (forgoElement.key !== undefined) {
const deletedNodes = getDeletedNodes(parentElement);
for (let i = 0; i < deletedNodes.length; i++) {
const { node: deletedNode } = deletedNodes[i];
if (nodeBelongsToKeyedComponent(deletedNode, forgoElement, componentIndex)) {
const nodesToResurrect = [deletedNode];
// Found a match!
// Collect all consecutive matching nodes.
for (let j = i + 1; j < deletedNodes.length; j++) {
const { node: subsequentNode } = deletedNodes[j];
if (nodeBelongsToKeyedComponent(subsequentNode, forgoElement, componentIndex)) {
nodesToResurrect.push(subsequentNode);
}
return true;
}
// Remove them from deletedNodes.
deletedNodes.splice(i, nodesToResurrect.length);
// Append resurrected nodes to the beginning of the node list.
const insertBeforeNode = nodes[searchFrom];
if (!isNullOrUndefined(insertBeforeNode)) {
for (const node of nodesToResurrect) {
parentElement.insertBefore(node, insertBeforeNode);
}
}
else {
for (const node of nodesToResurrect) {
parentElement.appendChild(node);
}
}
return { found: true, index: searchFrom };
}
}
return false;
}
if (isKeyedElement(forgoComponent)) {
return findReplacementCandidateForKeyedComponent(forgoComponent, insertionOptions, componentIndex);
}
else {
return findReplacementCandidateForUnkeyedComponent(forgoComponent, insertionOptions, componentIndex);
}
return { found: false };
}
/**
* Attach props from the forgoElement onto the DOM node. We also need to attach
* states from pendingAttachStates
* states from statesAwaitingAttach
*/
function syncAttrsAndState(forgoNode, node, nodeIndex, isNewNode, pendingAttachStates) {
function syncAttrsAndState(forgoNode, node, isNewNode, statesAwaitingAttach) {
var _a;

@@ -769,5 +838,4 @@ // We have to inject node into the args object.

// They don't know yet that args.element.node is undefined.
if (pendingAttachStates.length > 0) {
setNodeInfo(pendingAttachStates[pendingAttachStates.length - 1].component.__internal
.element, node, nodeIndex);
if (statesAwaitingAttach.length > 0) {
statesAwaitingAttach[statesAwaitingAttach.length - 1].component.__internal.element.node = node;
}

@@ -777,3 +845,3 @@ if (isForgoElement(forgoNode)) {

// Remove props which don't exist
if (existingState !== undefined && existingState.props) {
if (existingState && existingState.props) {
for (const key in existingState.props) {

@@ -867,3 +935,7 @@ if (!(key in forgoNode.props)) {

// Now attach the internal forgo state.
const state = Object.assign(Object.assign({}, existingState), { key: forgoNode.key, props: forgoNode.props, components: pendingAttachStates });
const state = {
key: forgoNode.key,
props: forgoNode.props,
components: statesAwaitingAttach,
};
setForgoState(node, state);

@@ -874,13 +946,3 @@ }

const state = {
components: pendingAttachStates,
lookups: {
deletedKeyedComponentNodes: new Map(),
deletedUnkeyedNodes: [],
keyedComponentNodes: new Map(),
newlyAddedKeyedComponentNodes: new Map(),
deletedKeyedElementNodes: new Map(),
newlyAddedKeyedElementNodes: new Map(),
keyedElementNodes: new Map(),
renderCount: 0,
},
components: statesAwaitingAttach,
};

@@ -895,3 +957,3 @@ setForgoState(node, state);

const parentElement = (isString(container) ? env.document.querySelector(container) : container);
if (parentElement == undefined) {
if (isNullOrUndefined(parentElement)) {
throw new Error(`The mount() function was called on a non-element (${typeof container === "string" ? container : container === null || container === void 0 ? void 0 : container.tagName}).`);

@@ -904,3 +966,3 @@ }

const result = internalRender(forgoNode, {
type: "search",
type: "new-component",
currentNodeIndex: 0,

@@ -921,6 +983,4 @@ length: parentElement.childNodes.length,

function unmount(container) {
const parentElement = isString(container)
? env.document.querySelector(container)
: container;
if (parentElement === null) {
const parentElement = (isString(container) ? env.document.querySelector(container) : container);
if (isNullOrUndefined(parentElement)) {
throw new Error(`The unmount() function was called on a non-element (${typeof container === "string" ? container : container === null || container === void 0 ? void 0 : container.tagName}).`);

@@ -932,3 +992,3 @@ }

markNodesForUnloading(parentElement.childNodes, 0, parentElement.childNodes.length);
unloadMarkedNodes(parentElement);
unloadMarkedNodes(parentElement, []);
}

@@ -959,16 +1019,15 @@ /*

function rerender(element, props) {
if (element === undefined || element.node === undefined) {
if (!(element === null || element === void 0 ? void 0 : element.node)) {
throw new Error(`Missing node information in rerender() argument.`);
}
if (element.node.parentElement !== null && element.nodeIndex === -1) {
element.nodeIndex = findNodeIndex(element.node.parentElement.childNodes, element.node, 0);
}
const parentElement = element.node.parentElement;
if (!isNullOrUndefined(parentElement)) {
const state = getForgoState(element.node);
if (parentElement !== null) {
const state = getExistingForgoState(element.node);
const originalComponentState = state.components[element.componentIndex];
const effectiveProps = props !== null && props !== void 0 ? props : originalComponentState.props;
if (!lifecycleEmitters.shouldUpdate(originalComponentState.component, effectiveProps, originalComponentState.props)) {
const indexOfNode = findNodeIndex(parentElement.childNodes, element.node);
return {
nodes: sliceNodes(parentElement.childNodes, element.nodeIndex, element.nodeIndex + originalComponentState.nodes.length),
nodes: sliceNodes(parentElement.childNodes, indexOfNode, indexOfNode + originalComponentState.nodes.length),
pendingMounts: [],
};

@@ -978,16 +1037,16 @@ }

const parentStates = state.components.slice(0, element.componentIndex);
const componentIndex = parentStates.length;
const statesToAttach = parentStates.concat(componentStateWithUpdatedProps);
const previousNode = originalComponentState.component.__internal.element.node;
const forgoNode = originalComponentState.component.__internal.registeredMethods.render(effectiveProps, originalComponentState.component);
const nodeIndex = findNodeIndex(parentElement.childNodes, element.node);
const insertionOptions = {
type: "search",
currentNodeIndex: element.nodeIndex,
type: "new-component",
currentNodeIndex: nodeIndex,
length: originalComponentState.nodes.length,
parentElement,
};
const renderResult = renderComponentAndRemoveStaleNodes(forgoNode, insertionOptions, statesToAttach, componentIndex, originalComponentState.nodes.length, false);
const renderResult = renderComponentAndRemoveStaleNodes(forgoNode, insertionOptions, statesToAttach, componentStateWithUpdatedProps, false);
// We have to propagate node changes up the component Tree.
// Reason 1:
// Imagine a Parent rendering Child1 & Child2
// Imaging Parent rendering Child1 & Child2
// Child1 renders [div1, div2], and Child2 renders [div3, div4].

@@ -1013,6 +1072,11 @@ // When Child1's rerender is called, it might return [p1] instead of [div1, div2]

// The root node might have changed, so fix it up just in case.
setNodeInfo(parentState.component.__internal.element, parentState.nodes[0], indexOfOriginalRootNode);
parentState.component.__internal.element.node = parentState.nodes[0];
}
}
unloadMarkedNodes(parentElement);
// Unload marked nodes.
unloadMarkedNodes(parentElement, renderResult.nodes.length > 0 ? statesToAttach : []);
// Unmount rendered component itself if all nodes are gone.
// if (renderResult.nodes.length === 0) {
// unmountComponents([newComponentState], 0);
// }
// Run afterRender() if defined.

@@ -1023,12 +1087,12 @@ lifecycleEmitters.afterRender(originalComponentState.component, effectiveProps, previousNode);

else {
return { nodes: [] };
return { nodes: [], pendingMounts: [] };
}
}
function createElement(forgoElement, element) {
const namespaceURI = !isNullOrUndefined(forgoElement.props.xmlns)
function createElement(forgoElement, parentElement) {
const namespaceURI = forgoElement.props.xmlns !== undefined
? forgoElement.props.xmlns
: forgoElement.type === "svg"
? SVG_NAMESPACE
: element !== undefined
? element.namespaceURI
: parentElement
? parentElement.namespaceURI
: null;

@@ -1115,3 +1179,3 @@ if (forgoElement.props.is !== undefined) {

*/
function stringOfPrimitiveNode(node) {
function stringOfNode(node) {
return node.toString();

@@ -1123,3 +1187,4 @@ }

function isForgoElement(forgoNode) {
return (!isNullOrUndefined(forgoNode) &&
return (forgoNode !== undefined &&
forgoNode !== null &&
forgoNode.__is_forgo_element__ === true);

@@ -1131,3 +1196,3 @@ }

function isForgoFragment(node) {
return !isNullOrUndefined(node) && node.type === Fragment;
return node !== undefined && node !== null && node.type === Fragment;
}

@@ -1138,20 +1203,16 @@ /*

export function getForgoState(node) {
if (node.__forgo === undefined) {
node.__forgo = {
components: [],
lookups: {
deletedKeyedComponentNodes: new Map(),
deletedUnkeyedNodes: [],
keyedComponentNodes: new Map(),
newlyAddedKeyedComponentNodes: new Map(),
deletedKeyedElementNodes: new Map(),
keyedElementNodes: new Map(),
newlyAddedKeyedElementNodes: new Map(),
renderCount: 0,
},
};
}
return node.__forgo;
}
/*
Same as above, but throws if undefined. (Caller must make sure.)
*/
function getExistingForgoState(node) {
if (node.__forgo) {
return node.__forgo;
}
else {
throw new Error("Missing forgo state on node.");
}
}
/*
Sets the state (NodeAttachedState) on an element.

@@ -1162,2 +1223,17 @@ */

}
/*
We maintain a list of deleted childNodes on an element.
In case we need to resurrect it - on account of a subsequent out-of-order key referring that node.
*/
function getDeletedNodes(element) {
if (!element.__forgo_deletedNodes) {
element.__forgo_deletedNodes = [];
}
return element.__forgo_deletedNodes;
}
function clearDeletedNodes(element) {
if (element.__forgo_deletedNodes) {
element.__forgo_deletedNodes = [];
}
}
// We export this so forgo-state & friends can publish non-breaking

@@ -1190,7 +1266,2 @@ // compatibility releases

}
if (legacyComponent.remount) {
component.remount((props) => {
legacyComponent.remount(props, mkRenderArgs(component));
});
}
if (legacyComponent.unmount) {

@@ -1213,16 +1284,7 @@ component.unmount((props) => {

};
function deriveComponentKey(key, componentIndex) {
return `$Component${componentIndex}_${key}`;
}
/*
Throw if component is a non-component
*/
function assertIsComponent(ctor, component, warnOnLegacySyntax) {
function assertIsComponent(ctor, component) {
if (!(component instanceof Component) && Reflect.has(component, "render")) {
if (warnOnLegacySyntax) {
console.warn("Legacy component syntax is deprecated since v3.2.0 and will be removed in v5.0. The affected component was found here:");
// Minification mangles component names so we have to settle for a
// stacktrace.
console.warn(new Error().stack);
}
return legacyComponentSyntaxCompat(component);

@@ -1263,25 +1325,17 @@ }

}
function isKeyedElement(t) {
return t.key !== undefined;
}
/**
* node.childNodes is some funky data structure that's not really not an array,
* so we can't just slice it like normal
*/
function sliceNodes(nodes, from, to) {
const result = [];
for (let i = from; i < to; i++) {
result.push(nodes[i]);
}
return result;
return Array.from(nodes).slice(from, to);
}
function setNodeInfo(element, node, nodeIndex) {
element.node = node;
element.nodeIndex = nodeIndex;
}
function findNodeIndex(nodes, element, startSearchFrom) {
if (element === undefined)
/**
* node.childNodes is some funky data structure that's not really not an array,
* so we can't just search for the value like normal
*/
function findNodeIndex(nodes, element) {
if (!element)
return -1;
for (let i = startSearchFrom; i < nodes.length; i++) {
if (nodes[i] === element) {
return i;
}
}
return -1;
return Array.from(nodes).indexOf(element);
}

@@ -1306,3 +1360,2 @@ /* JSX Types */

// This covers a consuming project using the forgo.createElement jsxFactory
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export * as JSX from "./jsxTypes.js";

@@ -1316,3 +1369,3 @@ // If jsxTypes is imported using named imports, esbuild doesn't know how to

// The createElement namespace exists so that users can set their TypeScript
// jsxFactory to createElement instead of forgo.createElement.
// jsxFactory to createElement instead of forgo.createElement.// eslint-disable-next-line @typescript-eslint/no-namespace
// eslint-disable-next-line @typescript-eslint/no-namespace

@@ -1319,0 +1372,0 @@ (function (createElement) {

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

import type { ForgoElementProps } from "./index.js";
import { ForgoDOMElementProps } from ".";
type Defaultize<Props, Defaults> = Props extends any ? Partial<Pick<Props, Extract<keyof Props, keyof Defaults>>> & Pick<Props, Exclude<keyof Props, keyof Defaults>> : never;

@@ -303,3 +303,3 @@ export type LibraryManagedAttributes<Component, Props> = Component extends {

export type WheelEventHandler<Target extends EventTarget> = EventHandler<TargetedWheelEvent<Target>>;
export interface DOMAttributes<Target extends EventTarget> extends ForgoElementProps {
export interface DOMAttributes<Target extends EventTarget> extends ForgoDOMElementProps {
onload?: GenericEventHandler<Target>;

@@ -306,0 +306,0 @@ onerror?: GenericEventHandler<Target>;

{
"name": "forgo",
"version": "4.1.1",
"version": "4.1.2",
"main": "./dist/forgo.min.js",

@@ -14,16 +14,16 @@ "type": "module",

"devDependencies": {
"@types/jsdom": "^20.0.0",
"@types/mocha": "^9.1.1",
"@types/jsdom": "^21.1.0",
"@types/mocha": "^10.0.1",
"@types/should": "^13.0.0",
"@types/source-map-support": "^0.5.6",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"esbuild": "^0.15.5",
"eslint": "^8.32.0",
"jsdom": "^20.0.0",
"mocha": "^10.0.0",
"rimraf": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"esbuild": "^0.17.8",
"eslint": "^8.34.0",
"jsdom": "^21.1.0",
"mocha": "^10.2.0",
"rimraf": "^4.1.2",
"should": "^13.2.3",
"source-map-support": "^0.5.21",
"typescript": "^4.9.4"
"typescript": "^4.9.5"
},

@@ -33,3 +33,3 @@ "scripts": {

"build": "npm run clean && npx tsc --emitDeclarationOnly && npx esbuild ./src/index.ts --minify --bundle --format=esm --sourcemap --target=es2015 --outfile=dist/forgo.min.js",
"build-dev": "npm run clean && npx tsc",
"build-dev": "npx tsc",
"test": "npx tsc && npx mocha dist/test/test.js"

@@ -36,0 +36,0 @@ },

@@ -70,3 +70,3 @@ # forgo

return <p>Hello, world!</p>;
},
}
});

@@ -128,8 +128,7 @@ };

<button type="button" onclick={onclick}>
The button has been clicked {clickCounter} times in {seconds}{" "}
seconds
The button has been clicked {clickCounter} times in {seconds} seconds
</button>
</div>
);
},
}
});

@@ -162,3 +161,3 @@

return <p>Hello, {name}!</p>;
},
}
});

@@ -170,4 +169,40 @@ };

handlers), you'll need to annotate the generic types on both the constructor and
the component.
the component.
Generic props can also be used:
```tsx
import * as forgo from "forgo";
import type { ForgoNewComponentCtor, Component } from "forgo";
// Props have to be assigned to the initial props for TSX to recognize the generic
type ListProps<T extends string | number> = {
data: T[],
render: (item: T) => Component
}
const List = <T extends string | number> = (
initial: ListProps<T>
): Component<ListProps<T>> => new forgo.Component<ListProps<T>>({
render(props) {
return (
<ul>
{props.data.map(item => props.render(item))}
</ul>
}
});
const App: ForgoNewComponentCtor = () => new forgo.Component({
render(props) {
return (
<List
data={[1, '2', 3]}
// item: number | string
render={item => <li>{item}</li>}
/>
)
}
})
```
_If you're handy with TypeScript, [we'd love a PR to infer the types!](https://github.com/forgojs/forgo/issues/68)_

@@ -186,3 +221,3 @@

return <p>Hello, {name}!</p>;
},
}
});

@@ -250,3 +285,3 @@

);
},
}
});

@@ -259,3 +294,3 @@ };

return <div>Hello {props.firstName}</div>;
},
}
});

@@ -273,3 +308,3 @@ };

return <NumberComponent myNumber={2} />;
},
}
});

@@ -337,8 +372,6 @@ };

<input type="text" ref={myInputRef} />
<button type="button" onclick={onClick}>
Click me!
</button>
<button type="button" onclick={onClick}>Click me!</button>
</div>
);
},
}
});

@@ -375,3 +408,3 @@ };

);
},
}
});

@@ -415,3 +448,3 @@ };

);
},
}
});

@@ -459,3 +492,3 @@ };

);
},
}
});

@@ -486,3 +519,3 @@

return <div id="hello">Hello {props.firstName}</div>;
},
}
});

@@ -516,3 +549,3 @@

return <div>Hello {props.firstName}</div>;
},
}
});

@@ -551,3 +584,3 @@

return <div>Hello {props.firstName}</div>;
},
}
});

@@ -560,3 +593,3 @@

return component;
};
}
```

@@ -580,5 +613,5 @@

throw new Error("Some error occurred :(");
},
}
});
};
}

@@ -601,5 +634,5 @@ // The first ancestor with an error() method defined will catch the error

);
},
}
});
};
}
```

@@ -621,3 +654,3 @@

return <div id="hello">Hello {props.firstName}</div>;
},
}
});

@@ -654,5 +687,5 @@

);
},
}
});
};
}
```

@@ -704,5 +737,5 @@

);
},
}
});
};
}
```

@@ -727,3 +760,3 @@

spam: [],
unread: 0,
unread: 0
});

@@ -738,5 +771,3 @@

<div>
{mailboxState.messages.map((m) => (
<p>{m}</p>
))}
{mailboxState.messages.map((m) => <p>{m}</p>)}
</div>

@@ -751,3 +782,3 @@ );

);
},
}
});

@@ -763,3 +794,3 @@

return component;
};
}

@@ -796,5 +827,5 @@ async function updateInbox() {

);
},
}
});
};
}
```

@@ -823,5 +854,5 @@

return <p id="live-scores">Top score is {props.topscore}</p>;
},
}
});
};
}

@@ -855,5 +886,5 @@ // Mount it on a DOM node usual

return <div>Hello world</div>;
},
}
});
};
}

@@ -864,2 +895,13 @@ // Get the html (string) and serve it via koa, express etc.

## Manually adding elements to the DOM
Forgo allows you to use the built-in browser DOM API to insert elements into the
DOM tree rendered by a Forgo component. Forgo will ignore these elements. This
is useful for working with charting libraries, such as D3.
If you add unmanaged nodes as siblings to nodes which Forgo manages, Forgo
pushes the unmanaged nodes towards the bottom of the sibling list when managed
nodes are added and removed. If you don't add/remove managed nodes, the
unmanaged nodes will stay in their original positions.
### ApexCharts example

@@ -889,3 +931,3 @@

);
},
}
});

@@ -1077,3 +1119,2 @@

## Deprecation of legacy component syntax is 3.2.0
In version 3.2.0, Forgo introduced a new syntax for components. This change

@@ -1088,3 +1129,2 @@ makes Forgo easier to extend with reusable libraries, and makes it

### Migrating
Forgo components are now instances of the `Component` class, rather than

@@ -1108,3 +1148,2 @@ freestanding object values. The `new Component` constructor accepts an object

Before:
```jsx

@@ -1120,7 +1159,6 @@ const MyComponent = () => {

};
};
}
```
After:
```jsx

@@ -1130,3 +1168,3 @@ const MyComponent = () => {

render() {},
error() {},
error() {}
});

@@ -1140,3 +1178,3 @@

return component;
};
}
```

@@ -1149,2 +1187,2 @@

with ES modules. If you were using this previously, switch to the configurations
discussed above.
discussed above.

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

import type { ForgoElementProps } from "./index.js";
import { ForgoDOMElementProps } from ".";

@@ -423,3 +423,3 @@ /* JSX Definitions */

export interface DOMAttributes<Target extends EventTarget>
extends ForgoElementProps {
extends ForgoDOMElementProps {
// Image Events

@@ -426,0 +426,0 @@ onload?: GenericEventHandler<Target>;

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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc