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.0.0-alpha.0 to 4.0.0

.eslintrc.cjs

27

CHANGELOG.md

@@ -0,5 +1,32 @@

# 3.2.2
- Fix #76: Add support for TypeScript 4.8
# 3.2.1
- Feature #73: Add a function to unmount a component tree from outside the Forgo
- Fix #50: Components that returned a fragment saw their `mount` lifecycle
method called after the first child element had been created instead of after
the render had completed.
- Fix #70: Calling `component.update()` during a mount lifecycle handler
resulted in the component recursively mounting ad infinitum.
- Fix #75: ESLint plugin `eslint-plugin-import` could not resolve imports of Forgo
- Add TSX support for custom element tag names. Non-string attributes are not
yet supported
# 3.2.0
- #59: Forgo's legacy component syntax (component syntax used through v3.1.1)
has been deprecated, and will be removed in v4.0. For more details, please see
the deprecation notice on https://forgojs.org.
- Fix #62: ensure that a child component's `mount()` lifecycle method is only
called after its parent has completely finished rendering
- Feature: Allow components to return `null` or `undefined` from their
`render()` method (#39)
# 3.1.1
- Fix: components that changed their root HTML tag were erroneously unmounted
# 3.0.2
- Fix: component teardown left old DOM elements in memory (#47)

@@ -6,0 +33,0 @@

163

dist/index.d.ts

@@ -1,8 +0,8 @@

export declare type ForgoRef<T> = {
export type ForgoRef<T> = {
value?: T;
};
export declare type ForgoElementProps = {
export type ForgoElementProps = {
children?: ForgoNode | ForgoNode[];
};
export declare type ForgoDOMElementProps = {
export type ForgoDOMElementProps = {
xmlns?: string;

@@ -14,20 +14,22 @@ ref?: ForgoRef<Element>;

} & ForgoElementProps;
export declare type ForgoComponentProps = ForgoElementProps;
export declare type ForgoComponentCtor<Props extends {} = {}> = (props: Props & ForgoComponentProps) => Component<Props>;
export declare type ForgoElementArg = {
export type ForgoComponentProps = ForgoElementProps;
export type ForgoComponentCtor<Props extends object = object> = (props: Props & ForgoComponentProps) => ForgoComponent<Props>;
export type ForgoNewComponentCtor<Props extends object = object> = (props: Props & ForgoComponentProps) => Component<Props>;
export type ForgoElementArg = {
node?: ChildNode;
componentIndex: number;
};
export declare type ForgoElementBase<TProps extends ForgoElementProps> = {
key?: any;
export type ForgoKeyType = string | number;
export type ForgoElementBase<TProps extends ForgoElementProps> = {
key?: ForgoKeyType;
props: TProps;
__is_forgo_element__: true;
};
export declare type ForgoDOMElement<TProps extends ForgoDOMElementProps> = ForgoElementBase<TProps> & {
export type ForgoDOMElement<TProps extends ForgoDOMElementProps> = ForgoElementBase<TProps> & {
type: string;
};
export declare type ForgoComponentElement<TProps extends ForgoComponentProps> = ForgoElementBase<TProps> & {
type: ForgoComponentCtor<TProps>;
export type ForgoComponentElement<TProps extends ForgoComponentProps> = ForgoElementBase<TProps> & {
type: ForgoNewComponentCtor<TProps>;
};
export declare type ForgoFragment = {
export type ForgoFragment = {
type: typeof Fragment;

@@ -39,5 +41,5 @@ props: {

};
export declare type ForgoElement<TProps> = ForgoDOMElement<TProps> | ForgoComponentElement<TProps>;
export declare type ForgoNonEmptyPrimitiveNode = string | number | boolean | object | BigInt;
export declare type ForgoPrimitiveNode = ForgoNonEmptyPrimitiveNode | null | undefined;
export type ForgoElement<TProps extends ForgoDOMElementProps> = ForgoDOMElement<TProps> | ForgoComponentElement<TProps>;
export type ForgoNonEmptyPrimitiveNode = string | number | boolean | object | bigint | null | undefined;
export type ForgoPrimitiveNode = ForgoNonEmptyPrimitiveNode | null | undefined;
/**

@@ -47,6 +49,6 @@ * Anything renderable by Forgo, whether from an external source (e.g.,

*/
export declare type ForgoNode = ForgoPrimitiveNode | ForgoElement<any> | ForgoFragment;
export declare type NodeAttachedComponentState<TProps> = {
key?: any;
ctor: ForgoComponentCtor<TProps>;
export type ForgoNode = ForgoPrimitiveNode | ForgoElement<any> | ForgoFragment;
export type ComponentState<TProps extends object> = {
key?: string | number;
ctor: ForgoNewComponentCtor<TProps> | ForgoComponentCtor<TProps>;
component: Component<TProps>;

@@ -57,3 +59,3 @@ props: TProps;

};
export declare type NodeAttachedState = {
export type NodeAttachedState = {
key?: string | number;

@@ -63,3 +65,3 @@ props?: {

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

@@ -69,7 +71,17 @@ [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;
};
};
export declare type DOMCSSProperties = {
export type DOMCSSProperties = {
[key in keyof Omit<CSSStyleDeclaration, "item" | "setProperty" | "removeProperty" | "getPropertyValue" | "getPropertyPriority">]?: string | number | null | undefined;
};
export declare type AllCSSProperties = {
export type AllCSSProperties = {
[key: string]: string | number | null | undefined;

@@ -80,3 +92,3 @@ };

}
export declare type ForgoEnvType = {
export type ForgoEnvType = {
window: Window;

@@ -90,5 +102,5 @@ document: Document;

/**
* Nodes will be created as detached DOM nodes, and will not be attached to the parent
* Nodes will be created as detached DOM nodes, and will not be attached to a parent.
*/
export declare type DetachedNodeInsertionOptions = {
export type DetachedNodeInsertionOptions = {
type: "detached";

@@ -100,3 +112,3 @@ };

*/
export declare type SearchableNodeInsertionOptions = {
export type DOMNodeInsertionOptions = {
type: "search";

@@ -117,14 +129,7 @@ /**

};
/**
* Decides how the called function attaches nodes to the supplied parent
*/
export declare type NodeInsertionOptions = DetachedNodeInsertionOptions | SearchableNodeInsertionOptions;
export declare type UnloadableChildNode = {
node: ChildNode;
pendingAttachStates: NodeAttachedComponentState<any>[];
};
export declare type RenderResult = {
export type NodeInsertionOptions = DetachedNodeInsertionOptions | DOMNodeInsertionOptions;
export type RenderResult = {
nodes: ChildNode[];
};
export declare type DeletedNode = {
export type DeletedNode = {
node: ChildNode;

@@ -135,13 +140,5 @@ };

__forgo?: NodeAttachedState;
__forgo_deletedNodes?: DeletedNode[];
}
}
export declare const Fragment: unique symbol;
/**
* 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<Props extends ForgoComponentProps> {

@@ -156,3 +153,3 @@ render: (props: Props & ForgoComponentProps, component: Component<Props>) => ForgoNode | ForgoNode[];

*/
declare type ComponentEventListenerBase = {
type ComponentEventListenerBase = {
[event in keyof typeof lifecycleEmitters]: Array<Function>;

@@ -165,9 +162,10 @@ };

*/
interface ComponentEventListeners<Props> extends ComponentEventListenerBase {
mount: Array<(props: Props & ForgoComponentProps, component: Component<Props>) => void>;
unmount: Array<(props: Props & ForgoComponentProps, component: Component<Props>) => void>;
afterRender: Array<(props: Props & ForgoComponentProps, previousNode: ChildNode | undefined, component: Component<Props>) => void>;
interface ComponentEventListeners<Props extends object> extends ComponentEventListenerBase {
mount: Array<(props: Props & ForgoComponentProps, component: Component<Props>) => any>;
remount: Array<(props: Props & ForgoComponentProps, component: Component<Props>) => any>;
unmount: Array<(props: Props & ForgoComponentProps, component: Component<Props>) => any>;
afterRender: Array<(props: Props & ForgoComponentProps, previousNode: ChildNode | undefined, component: Component<Props>) => any>;
shouldUpdate: Array<(newProps: Props & ForgoComponentProps, oldProps: Props & ForgoComponentProps, component: Component<Props>) => boolean>;
}
interface ComponentInternal<Props> {
interface ComponentInternal<Props extends object> {
unmounted: boolean;

@@ -179,6 +177,7 @@ registeredMethods: ForgoComponentMethods<Props>;

declare const lifecycleEmitters: {
mount<Props, Return = void>(component: Component<Props>, props: Props): void;
unmount<Props_1>(component: Component<Props_1>, props: Props_1): void;
shouldUpdate<Props_2>(component: Component<Props_2>, newProps: Props_2, oldProps: Props_2): boolean;
afterRender<Props_3>(component: Component<Props_3>, props: Props_3, previousNode: ChildNode | undefined): void;
mount<Props extends object>(component: Component<Props>, props: Props): void;
remount<Props_1 extends object>(component: Component<Props_1>, props: Props_1): void;
unmount<Props_2 extends object>(component: Component<Props_2>, props: Props_2): void;
shouldUpdate<Props_3 extends object>(component: Component<Props_3>, newProps: Props_3, oldProps: Props_3): boolean;
afterRender<Props_4 extends object>(component: Component<Props_4>, props: Props_4, previousNode: ChildNode | undefined): void;
};

@@ -190,3 +189,3 @@ /**

*/
export declare class Component<Props extends {} = {}> {
export declare class Component<Props extends object = object> {
/** @internal */

@@ -200,9 +199,16 @@ __internal: ComponentInternal<Props>;

constructor(registeredMethods: ForgoComponentMethods<Props>);
update(props?: Props): void;
addEventListener<Event extends keyof ComponentEventListeners<Props>>(event: Event, listener: ComponentEventListeners<Props>[Event][number]): void;
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;
}
/**
* jsxFactory function
*/
export declare function createElement<TProps extends ForgoElementProps & {
key?: any;
}>(type: string | ForgoComponentCtor<TProps>, props: TProps): {
type: string | ForgoComponentCtor<TProps>;
}>(type: string | ForgoNewComponentCtor<TProps> | ForgoComponentCtor<TProps>, props: TProps, ...args: any[]): {
type: string | ForgoNewComponentCtor<TProps> | ForgoComponentCtor<TProps>;
props: TProps;

@@ -219,2 +225,3 @@ key: any;

mount: (forgoNode: ForgoNode, container: Element | string | null) => RenderResult;
unmount: (container: Element | string | null) => void;
render: (forgoNode: ForgoNode) => {

@@ -227,3 +234,11 @@ node: ChildNode;

export declare function setCustomEnv(customEnv: any): void;
/**
* Attach a new Forgo application to a DOM element
*/
export declare function mount(forgoNode: ForgoNode, container: Element | string | null): RenderResult;
/**
* Unmount a Forgo application from outside.
* @param container The root element that the Forgo app was mounted onto
*/
export declare function unmount(container: Element | string | null): void;
export declare function render(forgoNode: ForgoNode): {

@@ -234,4 +249,30 @@ node: ChildNode;

export declare function rerender(element: ForgoElementArg | undefined, props?: any): RenderResult;
export declare function getForgoState(node: ChildNode): NodeAttachedState | undefined;
export declare function getForgoState(node: ChildNode): NodeAttachedState;
export declare function setForgoState(node: ChildNode, state: NodeAttachedState): void;
/**
* We bridge the old component syntax to the new syntax until our next breaking release
*/
export type ForgoComponent<TProps extends ForgoComponentProps> = {
render: (props: TProps, args: ForgoRenderArgs) => ForgoNode | ForgoNode[];
afterRender?: (props: TProps, args: ForgoAfterRenderArgs) => void;
error?: (props: TProps, args: ForgoErrorArgs) => ForgoNode;
mount?: (props: TProps, args: ForgoRenderArgs) => void;
remount?: (props: TProps, args: ForgoRenderArgs) => void;
unmount?: (props: TProps, args: ForgoRenderArgs) => void;
shouldUpdate?: (newProps: TProps, oldProps: TProps) => boolean;
__forgo?: {
unmounted?: boolean;
};
};
export type ForgoRenderArgs = {
element: ForgoElementArg;
update: (props?: any) => RenderResult;
};
export type ForgoAfterRenderArgs = ForgoRenderArgs & {
previousNode?: ChildNode;
};
export type ForgoErrorArgs = ForgoRenderArgs & {
error: any;
};
export declare const legacyComponentSyntaxCompat: <Props extends object>(legacyComponent: ForgoComponent<Props>) => Component<Props>;
export * as JSX from "./jsxTypes.js";

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

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

@@ -15,6 +15,6 @@ } ? Defaultize<Props, Defaults> : Props;

}
export declare type DOMCSSProperties = {
export type DOMCSSProperties = {
[key in keyof Omit<CSSStyleDeclaration, "item" | "setProperty" | "removeProperty" | "getPropertyValue" | "getPropertyPriority">]?: string | number | null | undefined;
};
export declare type AllCSSProperties = {
export type AllCSSProperties = {
[key: string]: string | number | null | undefined;

@@ -269,17 +269,17 @@ };

}
export declare type TargetedEvent<Target extends EventTarget = EventTarget, TypedEvent extends Event = Event> = Omit<TypedEvent, "currentTarget"> & {
export type TargetedEvent<Target extends EventTarget = EventTarget, TypedEvent extends Event = Event> = Omit<TypedEvent, "currentTarget"> & {
readonly currentTarget: Target;
};
export declare type TargetedAnimationEvent<Target extends EventTarget> = TargetedEvent<Target, AnimationEvent>;
export declare type TargetedClipboardEvent<Target extends EventTarget> = TargetedEvent<Target, ClipboardEvent>;
export declare type TargetedCompositionEvent<Target extends EventTarget> = TargetedEvent<Target, CompositionEvent>;
export declare type TargetedDragEvent<Target extends EventTarget> = TargetedEvent<Target, DragEvent>;
export declare type TargetedFocusEvent<Target extends EventTarget> = TargetedEvent<Target, FocusEvent>;
export declare type TargetedKeyboardEvent<Target extends EventTarget> = TargetedEvent<Target, KeyboardEvent>;
export declare type TargetedMouseEvent<Target extends EventTarget> = TargetedEvent<Target, MouseEvent>;
export declare type TargetedPointerEvent<Target extends EventTarget> = TargetedEvent<Target, PointerEvent>;
export declare type TargetedTouchEvent<Target extends EventTarget> = TargetedEvent<Target, TouchEvent>;
export declare type TargetedTransitionEvent<Target extends EventTarget> = TargetedEvent<Target, TransitionEvent>;
export declare type TargetedUIEvent<Target extends EventTarget> = TargetedEvent<Target, UIEvent>;
export declare type TargetedWheelEvent<Target extends EventTarget> = TargetedEvent<Target, WheelEvent>;
export type TargetedAnimationEvent<Target extends EventTarget> = TargetedEvent<Target, AnimationEvent>;
export type TargetedClipboardEvent<Target extends EventTarget> = TargetedEvent<Target, ClipboardEvent>;
export type TargetedCompositionEvent<Target extends EventTarget> = TargetedEvent<Target, CompositionEvent>;
export type TargetedDragEvent<Target extends EventTarget> = TargetedEvent<Target, DragEvent>;
export type TargetedFocusEvent<Target extends EventTarget> = TargetedEvent<Target, FocusEvent>;
export type TargetedKeyboardEvent<Target extends EventTarget> = TargetedEvent<Target, KeyboardEvent>;
export type TargetedMouseEvent<Target extends EventTarget> = TargetedEvent<Target, MouseEvent>;
export type TargetedPointerEvent<Target extends EventTarget> = TargetedEvent<Target, PointerEvent>;
export type TargetedTouchEvent<Target extends EventTarget> = TargetedEvent<Target, TouchEvent>;
export type TargetedTransitionEvent<Target extends EventTarget> = TargetedEvent<Target, TransitionEvent>;
export type TargetedUIEvent<Target extends EventTarget> = TargetedEvent<Target, UIEvent>;
export type TargetedWheelEvent<Target extends EventTarget> = TargetedEvent<Target, WheelEvent>;
export interface EventHandler<E extends TargetedEvent> {

@@ -292,15 +292,15 @@ /**

}
export declare type AnimationEventHandler<Target extends EventTarget> = EventHandler<TargetedAnimationEvent<Target>>;
export declare type ClipboardEventHandler<Target extends EventTarget> = EventHandler<TargetedClipboardEvent<Target>>;
export declare type CompositionEventHandler<Target extends EventTarget> = EventHandler<TargetedCompositionEvent<Target>>;
export declare type DragEventHandler<Target extends EventTarget> = EventHandler<TargetedDragEvent<Target>>;
export declare type FocusEventHandler<Target extends EventTarget> = EventHandler<TargetedFocusEvent<Target>>;
export declare type GenericEventHandler<Target extends EventTarget> = EventHandler<TargetedEvent<Target>>;
export declare type KeyboardEventHandler<Target extends EventTarget> = EventHandler<TargetedKeyboardEvent<Target>>;
export declare type MouseEventHandler<Target extends EventTarget> = EventHandler<TargetedMouseEvent<Target>>;
export declare type PointerEventHandler<Target extends EventTarget> = EventHandler<TargetedPointerEvent<Target>>;
export declare type TouchEventHandler<Target extends EventTarget> = EventHandler<TargetedTouchEvent<Target>>;
export declare type TransitionEventHandler<Target extends EventTarget> = EventHandler<TargetedTransitionEvent<Target>>;
export declare type UIEventHandler<Target extends EventTarget> = EventHandler<TargetedUIEvent<Target>>;
export declare type WheelEventHandler<Target extends EventTarget> = EventHandler<TargetedWheelEvent<Target>>;
export type AnimationEventHandler<Target extends EventTarget> = EventHandler<TargetedAnimationEvent<Target>>;
export type ClipboardEventHandler<Target extends EventTarget> = EventHandler<TargetedClipboardEvent<Target>>;
export type CompositionEventHandler<Target extends EventTarget> = EventHandler<TargetedCompositionEvent<Target>>;
export type DragEventHandler<Target extends EventTarget> = EventHandler<TargetedDragEvent<Target>>;
export type FocusEventHandler<Target extends EventTarget> = EventHandler<TargetedFocusEvent<Target>>;
export type GenericEventHandler<Target extends EventTarget> = EventHandler<TargetedEvent<Target>>;
export type KeyboardEventHandler<Target extends EventTarget> = EventHandler<TargetedKeyboardEvent<Target>>;
export type MouseEventHandler<Target extends EventTarget> = EventHandler<TargetedMouseEvent<Target>>;
export type PointerEventHandler<Target extends EventTarget> = EventHandler<TargetedPointerEvent<Target>>;
export type TouchEventHandler<Target extends EventTarget> = EventHandler<TargetedTouchEvent<Target>>;
export type TransitionEventHandler<Target extends EventTarget> = EventHandler<TargetedTransitionEvent<Target>>;
export type UIEventHandler<Target extends EventTarget> = EventHandler<TargetedUIEvent<Target>>;
export type WheelEventHandler<Target extends EventTarget> = EventHandler<TargetedWheelEvent<Target>>;
export interface DOMAttributes<Target extends EventTarget> extends ForgoDOMElementProps {

@@ -545,164 +545,7 @@ onload?: GenericEventHandler<Target>;

}
export interface IntrinsicElements {
a: HTMLAttributes<HTMLAnchorElement>;
abbr: HTMLAttributes<HTMLElement>;
address: HTMLAttributes<HTMLElement>;
area: HTMLAttributes<HTMLAreaElement>;
article: HTMLAttributes<HTMLElement>;
aside: HTMLAttributes<HTMLElement>;
audio: HTMLAttributes<HTMLAudioElement>;
b: HTMLAttributes<HTMLElement>;
base: HTMLAttributes<HTMLBaseElement>;
bdi: HTMLAttributes<HTMLElement>;
bdo: HTMLAttributes<HTMLElement>;
big: HTMLAttributes<HTMLElement>;
blockquote: HTMLAttributes<HTMLQuoteElement>;
body: HTMLAttributes<HTMLBodyElement>;
br: HTMLAttributes<HTMLBRElement>;
button: HTMLAttributes<HTMLButtonElement>;
canvas: HTMLAttributes<HTMLCanvasElement>;
caption: HTMLAttributes<HTMLTableCaptionElement>;
cite: HTMLAttributes<HTMLElement>;
code: HTMLAttributes<HTMLElement>;
col: HTMLAttributes<HTMLTableColElement>;
colgroup: HTMLAttributes<HTMLTableColElement>;
data: HTMLAttributes<HTMLDataElement>;
datalist: HTMLAttributes<HTMLDataListElement>;
dd: HTMLAttributes<HTMLElement>;
del: HTMLAttributes<HTMLModElement>;
details: HTMLAttributes<HTMLDetailsElement>;
dfn: HTMLAttributes<HTMLElement>;
dialog: HTMLAttributes<HTMLDialogElement>;
div: HTMLAttributes<HTMLDivElement>;
dl: HTMLAttributes<HTMLDListElement>;
dt: HTMLAttributes<HTMLElement>;
em: HTMLAttributes<HTMLElement>;
embed: HTMLAttributes<HTMLEmbedElement>;
fieldset: HTMLAttributes<HTMLFieldSetElement>;
figcaption: HTMLAttributes<HTMLElement>;
figure: HTMLAttributes<HTMLElement>;
footer: HTMLAttributes<HTMLElement>;
form: HTMLAttributes<HTMLFormElement>;
h1: HTMLAttributes<HTMLHeadingElement>;
h2: HTMLAttributes<HTMLHeadingElement>;
h3: HTMLAttributes<HTMLHeadingElement>;
h4: HTMLAttributes<HTMLHeadingElement>;
h5: HTMLAttributes<HTMLHeadingElement>;
h6: HTMLAttributes<HTMLHeadingElement>;
head: HTMLAttributes<HTMLHeadElement>;
header: HTMLAttributes<HTMLElement>;
hgroup: HTMLAttributes<HTMLElement>;
hr: HTMLAttributes<HTMLHRElement>;
html: HTMLAttributes<HTMLHtmlElement>;
i: HTMLAttributes<HTMLElement>;
iframe: HTMLAttributes<HTMLIFrameElement>;
img: HTMLAttributes<HTMLImageElement>;
input: HTMLAttributes<HTMLInputElement>;
ins: HTMLAttributes<HTMLModElement>;
kbd: HTMLAttributes<HTMLElement>;
keygen: HTMLAttributes<HTMLUnknownElement>;
label: HTMLAttributes<HTMLLabelElement>;
legend: HTMLAttributes<HTMLLegendElement>;
li: HTMLAttributes<HTMLLIElement>;
link: HTMLAttributes<HTMLLinkElement>;
main: HTMLAttributes<HTMLElement>;
map: HTMLAttributes<HTMLMapElement>;
mark: HTMLAttributes<HTMLElement>;
marquee: HTMLAttributes<HTMLMarqueeElement>;
menu: HTMLAttributes<HTMLMenuElement>;
menuitem: HTMLAttributes<HTMLUnknownElement>;
meta: HTMLAttributes<HTMLMetaElement>;
meter: HTMLAttributes<HTMLMeterElement>;
nav: HTMLAttributes<HTMLElement>;
noscript: HTMLAttributes<HTMLElement>;
object: HTMLAttributes<HTMLObjectElement>;
ol: HTMLAttributes<HTMLOListElement>;
optgroup: HTMLAttributes<HTMLOptGroupElement>;
option: HTMLAttributes<HTMLOptionElement>;
output: HTMLAttributes<HTMLOutputElement>;
p: HTMLAttributes<HTMLParagraphElement>;
param: HTMLAttributes<HTMLParamElement>;
picture: HTMLAttributes<HTMLPictureElement>;
pre: HTMLAttributes<HTMLPreElement>;
progress: HTMLAttributes<HTMLProgressElement>;
q: HTMLAttributes<HTMLQuoteElement>;
rp: HTMLAttributes<HTMLElement>;
rt: HTMLAttributes<HTMLElement>;
ruby: HTMLAttributes<HTMLElement>;
s: HTMLAttributes<HTMLElement>;
samp: HTMLAttributes<HTMLElement>;
script: HTMLAttributes<HTMLScriptElement>;
section: HTMLAttributes<HTMLElement>;
select: HTMLAttributes<HTMLSelectElement>;
slot: HTMLAttributes<HTMLSlotElement>;
small: HTMLAttributes<HTMLElement>;
source: HTMLAttributes<HTMLSourceElement>;
span: HTMLAttributes<HTMLSpanElement>;
strong: HTMLAttributes<HTMLElement>;
style: HTMLAttributes<HTMLStyleElement>;
sub: HTMLAttributes<HTMLElement>;
summary: HTMLAttributes<HTMLElement>;
sup: HTMLAttributes<HTMLElement>;
table: HTMLAttributes<HTMLTableElement>;
tbody: HTMLAttributes<HTMLTableSectionElement>;
td: HTMLAttributes<HTMLTableCellElement>;
textarea: HTMLAttributes<HTMLTextAreaElement>;
tfoot: HTMLAttributes<HTMLTableSectionElement>;
th: HTMLAttributes<HTMLTableCellElement>;
thead: HTMLAttributes<HTMLTableSectionElement>;
time: HTMLAttributes<HTMLTimeElement>;
title: HTMLAttributes<HTMLTitleElement>;
tr: HTMLAttributes<HTMLTableRowElement>;
track: HTMLAttributes<HTMLTrackElement>;
u: HTMLAttributes<HTMLElement>;
ul: HTMLAttributes<HTMLUListElement>;
var: HTMLAttributes<HTMLElement>;
video: HTMLAttributes<HTMLVideoElement>;
wbr: HTMLAttributes<HTMLElement>;
svg: SVGAttributes<SVGSVGElement>;
animate: SVGAttributes<SVGAnimateElement>;
circle: SVGAttributes<SVGCircleElement>;
animateTransform: SVGAttributes<SVGAnimateElement>;
clipPath: SVGAttributes<SVGClipPathElement>;
defs: SVGAttributes<SVGDefsElement>;
desc: SVGAttributes<SVGDescElement>;
ellipse: SVGAttributes<SVGEllipseElement>;
feBlend: SVGAttributes<SVGFEBlendElement>;
feColorMatrix: SVGAttributes<SVGFEColorMatrixElement>;
feComponentTransfer: SVGAttributes<SVGFEComponentTransferElement>;
feComposite: SVGAttributes<SVGFECompositeElement>;
feConvolveMatrix: SVGAttributes<SVGFEConvolveMatrixElement>;
feDiffuseLighting: SVGAttributes<SVGFEDiffuseLightingElement>;
feDisplacementMap: SVGAttributes<SVGFEDisplacementMapElement>;
feDropShadow: SVGAttributes<SVGFEDropShadowElement>;
feFlood: SVGAttributes<SVGFEFloodElement>;
feGaussianBlur: SVGAttributes<SVGFEGaussianBlurElement>;
feImage: SVGAttributes<SVGFEImageElement>;
feMerge: SVGAttributes<SVGFEMergeElement>;
feMergeNode: SVGAttributes<SVGFEMergeNodeElement>;
feMorphology: SVGAttributes<SVGFEMorphologyElement>;
feOffset: SVGAttributes<SVGFEOffsetElement>;
feSpecularLighting: SVGAttributes<SVGFESpecularLightingElement>;
feTile: SVGAttributes<SVGFETileElement>;
feTurbulence: SVGAttributes<SVGFETurbulenceElement>;
filter: SVGAttributes<SVGFilterElement>;
foreignObject: SVGAttributes<SVGForeignObjectElement>;
g: SVGAttributes<SVGGElement>;
image: SVGAttributes<SVGImageElement>;
line: SVGAttributes<SVGLineElement>;
linearGradient: SVGAttributes<SVGLinearGradientElement>;
marker: SVGAttributes<SVGMarkerElement>;
mask: SVGAttributes<SVGMaskElement>;
path: SVGAttributes<SVGPathElement>;
pattern: SVGAttributes<SVGPatternElement>;
polygon: SVGAttributes<SVGPolygonElement>;
polyline: SVGAttributes<SVGPolylineElement>;
radialGradient: SVGAttributes<SVGRadialGradientElement>;
rect: SVGAttributes<SVGRectElement>;
stop: SVGAttributes<SVGStopElement>;
symbol: SVGAttributes<SVGSymbolElement>;
text: SVGAttributes<SVGTextElement>;
tspan: SVGAttributes<SVGTSpanElement>;
use: SVGAttributes<SVGUseElement>;
}
export type IntrinsicElements = {
[el in keyof Omit<SVGElementTagNameMap, "a">]: HTMLAttributes<SVGElementTagNameMap[el]>;
} & {
[el in keyof HTMLElementTagNameMap]: HTMLAttributes<HTMLElementTagNameMap[el]>;
};
export {};
{
"name": "forgo",
"version": "4.0.0-alpha.0",
"main": "./dist/index.js",
"version": "4.0.0",
"main": "./dist/forgo.min.js",
"type": "module",

@@ -14,13 +14,16 @@ "author": "Jeswin Kumar<jeswinpk@agilehead.com>",

"devDependencies": {
"@types/jsdom": "^16.2.14",
"@types/mocha": "^9.1.0",
"@types/jsdom": "^20.0.0",
"@types/mocha": "^9.1.1",
"@types/should": "^13.0.0",
"@types/source-map-support": "^0.5.4",
"esbuild": "^0.14.25",
"jsdom": "^19.0.0",
"mocha": "^9.2.1",
"@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",
"should": "^13.2.3",
"source-map-support": "^0.5.21",
"typescript": "^4.6.2"
"typescript": "^4.9.4"
},

@@ -30,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": "npx tsc",
"build-dev": "npm run clean && npx tsc",
"test": "npx tsc && npx mocha dist/test/test.js"

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

@@ -9,5 +9,6 @@ # forgo

- There are no synthetic events
- Use closures for maintaining component state
- Use closures and ordinary variables for maintaining component state
- There's no vDOM or DOM diffing
- Renders are manually triggered
- Declarative DOM updates

@@ -21,3 +22,3 @@ ## We'll be tiny. Always.

```
npm i forgo
npm install forgo
```

@@ -42,6 +43,6 @@

```sh
# switch to the project directory
# Switch to the project directory
cd my-project
# run!
# Run!
npm start

@@ -55,33 +56,142 @@

A Forgo Component must have a function (called Component Constructor) that returns an object with a render() function (called Component).
Forgo components are functions that return a Component instance, which has a
`render()` method that returns JSX. Components hold their state using ordinary
variables held in the closure scope of the function (called a Component Constructor).
Here's an Example.
Forgo likes to keep things simple and explicit. It avoids automatic behavior,
prefers basic functions and variables instead of implicit constructs, and tries
not to come between you and the DOM.
Here's the smallest Forgo component you can make:
```jsx
import { rerender } from "forgo";
import * as forgo from "forgo";
function SimpleTimer(initialProps) {
const HelloWorld = () => {
return new forgo.Component({
render() {
return <p>Hello, world!</p>;
},
});
};
```
When a component is created (either by being mounted onto the DOM when the page
loads, or because it was rendered by a component higher up in your app), Forgo
calls the Component Constructor to generate the component instance. This is
where you can put closure variables to hold the component's state.
Then Forgo calls the component's `render()` method to generate the HTML that
Forgo will show on the page.
After the component's first render, the Constructor won't be called again, but
the `render()` method will be called each time the component (or one of its
ancestors) rerenders.
Forgo will pass any props (i.e., HTML attributes from your JSX) to both the
Constructor and the `render()` method.
Here's a bigger example - the component below increments a counter when a button is
pressed, and counts how many seconds the component has been alive.
```jsx
import * as forgo from "forgo";
const ClickCounter = (initialProps) => {
let seconds = 0; // Just a regular variable, no hooks!
let clickCounter = 0;
return {
render(props, args) {
setTimeout(() => {
seconds++;
rerender(args.element); // rerender
}, 1000);
const component = new forgo.Component({
// Every component has a render() method, which declares what HTML Forgo
// needs to generate.
render(props) {
const { firstName } = props;
// You can declare any DOM event handlers you need inside the render()
// method.
const onclick = (_event: Event) => {
// Forgo doesn't know or care how you manage your state. This frees you
// to use any library or code pattern that suits your situation, not
// only tools designed to integrate with the framework.
clickCounter += 1;
// When you're ready to rerender, just call component.update(). Manual
// updates mean the framework only does what you tell it to, putting you
// in control of efficiency and business logic.
//
// An optional package, forgo-state, can automate this for simple scenarios.
component.update();
};
// Forgo uses JSX, like React or Solid, to generate HTML declaratively.
// JSX is a special syntax for JavaScript, which means you can treat it
// like ordinary code (assign it to variables, type check it, etc.).
return (
<div>
{seconds} seconds have elapsed... {props.firstName}!
<p>Hello, {firstName}!</p>
<button type="button" onclick={onclick}>
The button has been clicked {clickCounter} times in {seconds}{" "}
seconds
</button>
</div>
);
},
};
});
// You can add callbacks to react to lifecycle events,
// like mounting and unmounting
component.mount(() => {
const timeout = setTimeout(() => {
seconds++;
component.update();
}, 1000);
component.unmount(() => clearTimeout(timeout));
});
return component;
};
```
Here's how the API looks when using TypeScript (which is optional):
```tsx
import * as forgo from "forgo";
import type { ForgoNewComponentCtor } from "forgo";
// The constructor generic type accepts the shape of your component's props
const HelloWorld: ForgoNewComponentCtor<{ name: string }> = () => {
return new forgo.Component({
render({ name }) {
return <p>Hello, {name}!</p>;
},
});
};
```
If you assign the component to a variable (such as when adding lifecycle event
handlers), you'll need to annotate the generic types on both the constructor and
the component.
_If you're handy with TypeScript, [we'd love a PR to infer the types!](https://github.com/forgojs/forgo/issues/68)_
```tsx
import * as forgo from "forgo";
import type { ForgoNewComponentCtor } from "forgo";
interface HelloWorldProps {
name: string;
}
const HelloWorld: ForgoNewComponentCtor<HelloWorldProps> = () => {
const component = new forgo.Component<HelloWorldProps>({
render({ name }) {
return <p>Hello, {name}!</p>;
},
});
component.mount(() => console.log("Mounted!"));
return component;
};
```
The Component Constructor function and the Component's render() method are both called during the first render with the initial set of props. But for subsequent rerenders of the same component, only the render() gets called (with new props). So if you're using props, remember to get it from the render() method.
## Launching your components when the page loads
## Mounting the Component
Use the mount() function once your document has loaded.

@@ -92,2 +202,3 @@

// Wait for the page DOM to be ready for changes
function ready(fn) {

@@ -102,2 +213,3 @@ if (document.readyState !== "loading") {

ready(() => {
// Attach your app's root component to a specific DOM element
mount(<App />, document.getElementById("root"));

@@ -107,3 +219,4 @@ });

You could also pass a selector instead of an element to the mount() function.
Instead of retrieving the DOM element yoursely, you could pass a CSS selector
and Forgo will find the element for you:

@@ -118,8 +231,17 @@ ```js

That works just as you'd have seen in React.
Props and children work just like in React and similar frameworks:
```jsx
function Parent(initialProps) {
return {
render(props, args) {
// Component Constructors will receive the props passed the *first* time the
// component renders. But beware! This value won't be updated on later renders.
// Props passod to the Constructor are useful for one-time setup, but to read
// the latest props you'll need to use the value passed to render().
const Parent = (_initialProps) => {
return new forgo.Component({
// The props passed here will always be up-to-date.
//
// All lifecycle methods (render, mount, etc.) receive a reference to the
// component. This makes it easy to create reusable logic that works for
// many different components.
render(_props, _component) {
return (

@@ -132,12 +254,54 @@ <div>

},
};
}
});
};
function Greeter(initialProps) {
return {
render(props, args) {
const Greeter = (_initialProps) => {
return new forgo.Component({
render(props, _component) {
return <div>Hello {props.firstName}</div>;
},
};
});
};
```
You can pass any kind of value as a prop - not just strings! You just have to
use curly braces instead of quotes:
```jsx
const MyComponent = () => {
return new forgo.Component({
render(_props) {
return <NumberComponent myNumber={2} />;
},
});
};
```
You can have one component wrap JSX provided by another. To do this, just render `props.children`.
```jsx
const Parent = () => {
return new forgo.Component({
render(_props) {
return
<Child>
<p>Hello, world!</p>
</Child>
)
}
});
}
const Child = () => {
return new forgo.Component({
render(props) {
return (
<div>
<p>Here's what the parent told us to render:</p>
{props.children}
</div>
)
}
});
}
```

@@ -147,16 +311,25 @@

To access the actual DOM elements corresponding to your markup (and the values contained within them), you need to use the ref attribute in the markup. An object referenced by the ref attribute in an element's markup will have its 'value' property set to the actual DOM element when it gets created.
Forgo encourages you to use the vanilla DOM API when you need to read form field
values, by directly accessing the DOM elements in the form.
To access the actual DOM elements corresponding to your markup (and the values
contained within them), you need to use the `ref` attribute in the JSX markup of
the element you want to reference. An element referenced by the `ref` attribute
will have its 'value' property set to the actual DOM element when it gets
created.
Here's an example:
```jsx
function Component(initialProps) {
const MyComponent = (_initialProps) => {
// This starts as an empty object. After the element is created, this object
// will have a `value` field holding the element.
const myInputRef = {};
return {
render(props, args) {
function onClick() {
return new forgo.Component({
render(_props, _component) {
const onClick = () => {
const inputElement = myInputRef.value;
alert(inputElement.value); // Read the text input.
}
};

@@ -166,29 +339,33 @@ return (

<input type="text" ref={myInputRef} />
<button onclick={onClick}>Click me!</button>
<button type="button" onclick={onClick}>
Click me!
</button>
</div>
);
},
};
}
});
};
```
You can access and read form input elements using regular DOM APIs as well. For example, the following code will work just fine if you assign an id to the input element.
If you want, you can bypass Forgo entirely when reading form field values. If
you set the `id` field on the form field, then you could use the vanilla DOM API
to access that element directly:
```jsx
function onClick() {
const inputElement = document.getElementById("myinput");
const onClick = () => {
const inputElement = document.getElementById("my-input");
alert(inputElement.value);
}
};
```
Lastly, you can pass an event handler to an input and extract the current value from the input event:
Lastly, DOM events like key presses and clicks pass the affected element to the
event handler as `event.target`:
```jsx
function Component(initialProps) {
return {
render(props, args) {
function onInput(e) {
e.preventDefault();
alert(e.target.value);
}
const Component = (_initialProps) => {
return new forgo.Component({
render(_props, _component) {
const onInput = (event) => {
alert(event.target.value);
};

@@ -201,19 +378,32 @@ return (

},
};
}
});
};
```
## Lists and Keys
## Rendering Lists and using Keys
Keys help Forgo identify which items in a list have changed, are added, or are removed. While Forgo works well without keys, it is a good idea to add them since it avoids unnecessary component mounting and unmounting in some cases.
Forgo will render any arrays it sees in the JSX. To create a list of elements,
just use the array's `myArray.map()` method to generate JSX for each item in the array.
As long as they are unique, there is no restriction on what data type you may use for the key; keys could be strings, numbers or even objects. For string keys and numeric keys, Forgo compares them by value; while for object keys, a reference equality check is used.
Each item in the array may be given a `key` attribute. Keys help Forgo identify
which items in a list have changed, are added, or are removed. While Forgo works
well without keys, it is a good idea to add them since it lets Forgo be more
efficient by only mounting or unmounting components that actually need it.
You can use any data type for a key strings, numbers or even objects. The key
values only need to be unique. Forgo compares keys using `===` (reference
equality), so be careful when using mutable objects as keys.
When looping over an array, don't use the array index as a key - keys should be
something tied to the specific value being rendered (like a permanent ID field).
The same array index might be associated with different values if you reorder
the array, and so using the array index as a key will cause unexpected behavior.
```jsx
function Parent() {
return {
render(props, args) {
const Parent = () => {
return new forgo.Component({
render(_props, _component) {
const people = [
{ firstName: "jeswin", id: 1 },
{ firstName: "kai", id: 2 },
{ firstName: "jeswin", id: 123 },
{ firstName: "kai", id: 456 },
];

@@ -228,12 +418,12 @@ return (

},
};
}
});
};
function Child(initialProps) {
return {
const Child = (initialProps) => {
return new forgo.Component({
render(props) {
return <div>Hello {props.firstName}</div>;
},
};
}
});
};
```

@@ -243,28 +433,20 @@

Parts of your application might need to fetch data asynchronously, and refresh your component accordingly.
Your component might need to load data asynchronously (such as making a network
request). Here's how to do that:
Here's an example of how to do this:
```jsx
async function getMessages() {
const data = await fetchMessagesFromServer();
return data;
}
export function InboxComponent(initialProps) {
// This will be empty initially.
export const InboxComponent = (_initialProps) => {
// This will be empty at first, and will get filled in sometime after the
// component first mounts.
let messages = undefined;
return {
render(props, args) {
const component = new forgo.Component({
render(_props, _component) {
// Messages are empty. Let's fetch them.
if (!messages) {
getMessages().then((data) => {
messages = data.messages;
rerender(args.element);
});
return <p>Loading data...</p>;
}
// We have messages to show.
// After messages are fetched, the component will rerender and now we can
// show the data.
return (

@@ -281,21 +463,11 @@ <div>

},
};
}
```
});
## The Unmount Event
component.mount(async () => {
messages = await fetchMessagesFromServer();
component.update();
});
When a component is unmounted, Forgo will invoke the unmount() function if defined for a component. It receives the current props and args as arguments, just as in the render() function. This can be used for any tear down you might want to do.
```jsx
function Greeter(initialProps) {
return {
render(props, args) {
return <div>Hello {props.firstName}</div>;
},
unmount(props, args) {
console.log("Got unloaded.");
},
};
}
return component;
};
```

@@ -305,83 +477,86 @@

If you're an application developer, you'd rarely have to use this. It might however be useful if you're developing libraries or frameworks which use Forgo. mount() gets called with the same arguments as render(), but after getting mounted on a real DOM node. It gets called only once.
The mount event is fired just once per component, when the component has just
been created. This is useful for set-up logic like starting a timer, fetching
data, or opening a WebSocket.
You can register multiple mount callbacks, which is useful if you want to have
reusable logic that you apply to a number of components.
```jsx
function Greeter(initialProps) {
return {
render(props, args) {
const Greeter = (_initialProps) => {
const component = new forgo.Component({
render(_props, _component) {
return <div id="hello">Hello {props.firstName}</div>;
},
mount(props, args) {
console.log(`Mounted on node with id ${args.element.node.id}`);
},
};
}
});
component.mount((_props, _component) => {
console.log("The component has been mounted.");
});
return component;
};
```
## The AfterRender Event
## The Unmount Event
Again, if you're an application developer you'd rarely need to use this. The afterRender() event runs every time after the render() runs, but after the rendered elements have been attached to actual DOM nodes. The 'previousNode' property of args will give you the node to which the component was previously attached, if it has changed due to the render().
A component is unmounted when your app no longer renders it (such as when a
parent component chooses to display something different, or when an item is
removed from a list you're rendering).
```jsx
function Greeter(initialProps) {
return {
render(props, args) {
return <div id="hello">Hello {props.firstName}</div>;
},
afterRender(props, args) {
console.log(
`This component is mounted on ${args.element.node.id}, and previously to ${args.previousNode.id}`
);
},
};
}
```
When a component is unmounted, you might want to do tear-down, like canceling a
timer or closing a WebSocket. To do this, you can register unmount callbacks
on your component, which will be called when the component is unmounted.
## Bailing out of a render
The callbacks are passed the current props and the component instance, just like
the `render()` method.
When the shouldUpdate() function is defined for a component, Forgo will call it with newProps and oldProps and check if the return value is true before rendering the component. Returning false will skip rendering the component.
```jsx
function Greeter(initialProps) {
return {
render(props, args) {
const Greeter = (_initialProps) => {
const component = new forgo.Component({
render(props, _component) {
return <div>Hello {props.firstName}</div>;
},
shouldUpdate(newProps, oldProps) {
return newProps.firstName !== oldProps.firstName;
},
};
}
});
component.unmount((props, _component) => {
console.log("The component has been unloaded.");
});
return component;
};
```
### Rendering nothing
## Skipping renders
A stateless component may elect to render nothing by returning `null` from the `render()` method:
Sometimes you have a reason why a component shouldn't be rendered right now. For
example, if you're using immutable data structures, you may want to only
rerender if the data structure has changed.
```jsx
function EmptyComponent(initialProps) {
return {
render(props, args) {
return null;
}
};
}
```
Forgo components accept `shouldUpdate` callbacks, which return true/false to
signal whether the component should / should not be rerendered. If any
`shouldUpdate` callbacks return true, the component will be rerendered. If they
all return false (or if none are registered), the component's `render()` method
won't be called, skipping all DOM operations for the component and its
decendants.
This does not work for stateful components. Forgo tracks component state by attaching it to the component's DOM element. This allows Forgo to operate without a virtual DOM (like React has), but means that if your component render returns `null` Forgo will unmount your component. If the component unmounts itself, there is not a way to request it remount itself, although it will be recreated if the parent rerenders.
The callbacks receive the new props for the proposed render, and the old props
used in the last render.
If you want a component to skip rendering its usual output, return an empty element, like so:
Using `shouldUpdate` is completely optional, and typically isn't necessary.
```jsx
function EmptyComponent(initialProps) {
// This will be discarded if the render returns null.
// To keep it, always return an element.
const myState = Math.random();
return {
render(props, args) {
return <div></div>;
}
};
}
const Greeter = (_initialProps) => {
const component = new forgo.Component({
render(props, component) {
return <div>Hello {props.firstName}</div>;
},
});
component.shouldUpdate((newProps, oldProps) => {
return newProps.firstName !== oldProps.firstName;
});
return component;
};
```

@@ -391,17 +566,23 @@

By defining the error() function, Forgo lets you catch errors in child components (at any level, and not necessarily immediate children).
Forgo lets components define an `error()` method, which is run any time the
component (or any of its decendants) throws an exception while running the
component's `render()` method. The error method can return JSX that is rendered
in place of the render output, to display an error message to the user.
If no ancestors have an `error()` method registered, the render will abort and
Forgo will print an error to the console.
```jsx
// Here's a component which throws an error.
function BadComponent() {
return {
const BadComponent = () => {
return new forgo.Component({
render() {
throw new Error("Some error occurred :(");
},
};
}
});
};
// Parent can catch the error by defining the error() function.
function Parent(initialProps) {
return {
// The first ancestor with an error() method defined will catch the error
const Parent = (initialProps) => {
return new forgo.Component({
render() {

@@ -414,38 +595,70 @@ return (

},
error(props, args) {
error(props, error, _component) {
return (
<p>
Error in {props.name}: {args.error.message}
Error in {props.name}: {error.message}
</p>
);
},
};
}
});
};
```
## Additional Rerender options
## The AfterRender Event
The most straight forward way to do rerender is by invoking it with `args.element` as the only argument - as follows.
If you're an application developer you'll rarely need to use this - it's
provided for building libraries that wrap Forgo.
The `afterRender` event runs after `render()` has been called and the rendered
elements have been created in the DOM. The callback is passed the previous DOM
element the component was attached to, if it changed in the latest render.
```jsx
function TodoList(initialProps) {
const Greeter = (_initialProps) => {
const component = new forgo.Component({
render(props, component) {
return <div id="hello">Hello {props.firstName}</div>;
},
});
component.afterRender((_props, previousNode, _component) => {
console.log(
`This component is mounted on ${component.__internal.element.node.id}, and was previously mounted on ${previousNode.id}`
);
});
return component;
};
```
## Passing new props when rerendering
The most straight forward way to do rerender is by invoking it with `component.update()`, as follows:
```jsx
const TodoList = (initialProps) => {
let todos = [];
return {
render(props, args) {
function addTodos(text) {
return new forgo.Component({
render(props, component) {
const addTodos = (text) => {
todos.push(text);
rerender(args.element);
}
return <div>markup goes here...</div>;
component.update();
};
return (
<button type="button" onclick={addTodos}>
Add a Todo
</button>
);
},
};
}
});
};
```
But you could pass newProps as well while rerendering. If you'd like previous props to be used, pass undefined here.
`component.update()` may optionally receive new props to use in the render.
Omitting the props parameter will rerender leave the props unchanged.
```js
const newProps = { name: "Kai" };
rerender(args.element, newProps);
component.update(newProps);
```

@@ -463,3 +676,3 @@

window.addEventListener("load", () => {
document.getElementById("root")!.firstElementChild!.replaceWith(node);
document.getElementById("root").firstElementChild.replaceWith(node);
});

@@ -470,11 +683,13 @@ ```

Forgo Router (forgo-router) is a tiny router for Forgo, and is just around 1KB gzipped. Read more at https://github.com/forgojs/forgo-router
Forgo offers an optional package (`forgo-router`) for handling client-side
navigation. Forgo Router is just around 1KB gzipped. Read more at
https://github.com/forgojs/forgo-router
Here's an example:
```js
```jsx
import { Router, Link, matchExactUrl, matchUrl } from "forgo-router";
function App() {
return {
const App = () => {
return new forgo.Component({
render() {

@@ -490,4 +705,4 @@ return (

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

@@ -497,7 +712,9 @@

Forgo State (forgo-state) is an easy-to-use application state management solution for Forgo (like Redux or MobX), and is less than 1KB gzipped. Read more at https://github.com/forgojs/forgo-state
Forgo offers an optional package (`forgo-state`) with an easy-to-use application
state management solution for Forgo. This solves a similar problem to Redux or
MobX. It's than 1KB gzipped. Read more at https://github.com/forgojs/forgo-state
Here's an example:
```js
```jsx
import { bindToStates, defineState } from "forgo-state";

@@ -507,2 +724,3 @@

const mailboxState = defineState({
username: "Bender B. Rodriguez",
messages: [],

@@ -514,20 +732,33 @@ drafts: [],

// A Forgo component
function MailboxView() {
const component = {
render(props: any, args: ForgoRenderArgs) {
// A Forgo component that should react to state changes
const MailboxView = () => {
const component = new forgo.Component({
render() {
if (mailboxState.messages.length > 0) {
return (
<div>
{mailboxState.messages.map((m) => (
<p>{m}</p>
))}
</div>
);
}
return (
<div>
{mailboxState.messages.length ? (
mailboxState.messages.map((m) => <p>{m}</p>)
) : (
<p>There are no messages for {signinState.username}.</p>
)}
<p>There are no messages for {mailboxState.username}.</p>
</div>
);
},
};
});
component.mount(() => updateInbox());
// MailboxView must change whenever mailboxState changes.
return bindToStates([mailboxState], component);
}
//
// Under the hood, this registers component.mount() and component.unmount()
// even handlers
bindToStates([mailboxState], component);
return component;
};

@@ -543,5 +774,9 @@ async function updateInbox() {

You can achieve lazy loading with the forgo-lazy package. Read more at https://github.com/jacob-ebey/forgo-lazy
If you want to lazy load a component, you can use the community-provided
`forgo-lazy` package. This is useful for code splitting, where you want the
initial page load to be quick (loading the smallest JS possible), and then load
in more components only when the user needs them. Read more at
https://github.com/jacob-ebey/forgo-lazy
It's as simple as this:
It's works like this:

@@ -553,9 +788,13 @@ ```jsx

const App = () => ({
render: () => (
<Suspense fallback={() => "Loading..."}>
<LazyComponent title="It's that easy :D" />
</Suspense>
),
});
const App = () => {
return new forgo.Component({
render() {
return (
<Suspense fallback={() => "Loading..."}>
<LazyComponent title="It's that easy :D" />
</Suspense>
);
},
});
};
```

@@ -565,21 +804,26 @@

Forgo is quite easy to integrate into an existing web app written with other frameworks or with older libraries like jQuery.
Forgo can be integrated into an existing web app written with other frameworks
(React, Vue, etc.), or with lower-level libraries like jQuery.
To help with that, the forgo-powertoys library (less than 1KB in size) exposes a rerenderElement() function which can rerender a mounted Forgo component with just a CSS selector (from outside the Forgo app). Read more at https://github.com/forgojs/forgo-powertoys
To help with that, the `forgo-powertoys` package (less than 1KB in size) exposes
a `rerenderElement()` function which can receive a CSS selector and rerender the
Forgo component associated with that element. This works from outside the Forgo
app, so you can drive Forgo components using your framework/library of choice.
Read more at https://github.com/forgojs/forgo-powertoys
Here's an example:
```js
```jsx
import { rerenderElement } from "forgo-powertoys";
// A forgo component.
function LiveScores() {
return {
const LiveScores = () => {
return new forgo.Component({
render(props) {
return <p id="live-scores">Top score is {props.topscore}</p>;
},
};
}
});
};
//mount it on a DOM node as usual
// Mount it on a DOM node usual
window.addEventListener("load", () => {

@@ -589,3 +833,4 @@ mount(<SimpleTimer />, document.getElementById("root"));

// Now you can rerender the component from anywhere, anytime!
// Now you can rerender the component from anywhere, anytime! Pass in the ID of
// the root element the component returns, as well as new props.
rerenderElement("#live-scores", { topscore: 244 });

@@ -596,17 +841,20 @@ ```

You can render components to an html (string) with the forgo-ssr package. This allows you to prerender components on the server and will work with Node.JS servers like Koa, Express etc. Read more at https://github.com/forgojs/forgo-ssr
From Node.js you can render components to an HTML string with the `forgo-ssr`
package. This allows you to prerender components on the server, from server-side
frameworks like Koa, Express etc. Read more at
https://github.com/forgojs/forgo-ssr
Here's an example:
```js
```jsx
import render from "forgo-ssr";
// A forgo component.
function MyComponent() {
return {
const MyComponent = () => {
return new forgo.Component({
render() {
return <div>Hello world</div>;
},
};
}
});
};

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

## Manually adding elements to the DOM
Forgo allows you to insert elements into the DOM, inside a Forgo component,
using the vanilla JS DOM API. These elements will be completely unmanaged and
ignored by Forgo. This is useful for enabling charting libraries, such as D3.
If you add unmanaged elements as siblings to managed nodes, Forgo pushes the
unmanaged nodes towards the bottom of the sibling list as 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

@@ -636,31 +873,5 @@

const chartElement = {};
let interval;
return {
mount(_props, args) {
const chartOptions = {
chart: {
type: "line"
},
series: [
{
name: "sales",
data: [30, 40, 35, 50, 49, 60, 70, 91, 125]
}
],
xaxis: {
categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
}
};
const chart = new ApexCharts(chartElement.value, chartOptions);
chart.render();
interval = setInterval(() => args.update(), 1_000);
},
unmount() {
if (interval) clearInterval(interval);
},
render(_props, args) {
const component = new forgo.Component({
render(_props, component) {
const now = new Date();

@@ -680,4 +891,30 @@ return (

);
}
};
},
});
component.mount(() => {
const chartOptions = {
chart: {
type: "line",
},
series: [
{
name: "sales",
data: [30, 40, 35, 50, 49, 60, 70, 91, 125],
},
],
xaxis: {
categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999],
},
};
const chart = new ApexCharts(chartElement.value, chartOptions);
chart.render();
const interval = setInterval(() => component.update(), 1_000);
component.unmount(() => clearInterval(interval));
});
return component;
};

@@ -690,3 +927,3 @@ ```

Or if you prefer Typescript, try [Forgo TodoList in TypeScript](https://codesandbox.io/s/forgo-todos-typescript-9v0iy).
Or if you prefer TypeScript, try [Forgo TodoList in TypeScript](https://codesandbox.io/s/forgo-todos-typescript-9v0iy).

@@ -697,5 +934,8 @@ There is also an example for using [Forgo with forgo-router](https://codesandbox.io/s/forgo-router-typescript-px4sg).

Most users are better off just using create-forgo-app to create the project skeleton - in which case all of this is already set up for you. We strongly recommend doing it this way.
Most users should use create-forgo-app to create the project skeleton - in which
case all of this is already set up for you. This is the easiest way to get
started.
But for people who are doing it manually, we'll cover webpack-specific configuration here. Other bundlers would need similar configuration.
If you want to stand up a project manually, we'll cover webpack-specific
configuration here. Other bundlers would need similar configuration.

@@ -793,6 +1033,3 @@ ### esbuild-loader with JavaScript/JSX

"plugins": [
[
"@babel/plugin-transform-react-jsx",
{ "pragma": "forgo.createElement" }
]
["@babel/plugin-transform-react-jsx", { "pragma": "forgo.createElement" }]
]

@@ -833,8 +1070,2 @@ }

### Breaking changes in 2.0
Forgo 2.0 drops support for the new JSX transform introduced via "jsx-runtime".
This never worked with esbuild loader, and more importantly doesn't play well with ES modules.
If you were using this previously, switch to the configurations discussed above.
## Core Team

@@ -847,4 +1078,73 @@

If you find issues, please file a bug on [Github](https://github.com/forgojs/forgo/issues). You can also reach out to us via Twitter (@forgojs).
If you find issues, please file a bug on
[Github](https://github.com/forgojs/forgo/issues). You can also reach out to us
via Twitter (@forgojs).
## Deprecation of legacy component syntax is 3.2.0
In version 3.2.0, Forgo introduced a new syntax for components. This change
makes Forgo easier to extend with reusable libraries, and makes it
straightforward to colocate logic that spans mounts & unmounts.
The legacy component syntax will be removed in v4.0. Until then, Forgo will
print a warning to the console whenever it sees a legacy component. You can
suppress these warnings by setting `window.FORGO_NO_LEGACY_WARN = true;`.
### Migrating
Forgo components are now instances of the `Component` class, rather than
freestanding object values. The `new Component` constructor accepts an object
holding a `render()` an optional `error()` method. All other methods have been
converted to lifecycle methods on the component instance. You may register
multiple handlers for each lifecycle event, and you may register new handlers
from inside a handler (e.g., a mount handler that registers its own unmount
logic).
`args` has been replaced by a reference to the component instance, in all
lifecycle event handlers. This simplifies writing reusable component logic.
The `error()` method now receives the error object as a function parameter,
rather than as a property on `args`.
The `afterRender` lifecycle event now receives the `previousNode` as a function
parameter, instead of a property on `args`.
Before:
```jsx
const MyComponent = () => {
return {
render() {},
error() {},
mount() {},
unmount() {},
shouldUpdate() {},
afterRender() {},
};
};
```
After:
```jsx
const MyComponent = () => {
const component = new Component({
render() {},
error() {},
});
component.mount(() => {});
component.unmount(() => {});
component.shouldUpdate(() => {});
component.afterRender(() => {});
return component;
};
```
## Breaking changes in 2.0
Forgo 2.0 drops support for the new JSX transform introduced via "jsx-runtime".
This never worked with esbuild loader, and more importantly doesn't play well
with ES modules. If you were using this previously, switch to the configurations
discussed above.

@@ -703,166 +703,14 @@ import type { ForgoDOMElementProps } from "./index.js";

export interface IntrinsicElements {
// HTML
a: HTMLAttributes<HTMLAnchorElement>;
abbr: HTMLAttributes<HTMLElement>;
address: HTMLAttributes<HTMLElement>;
area: HTMLAttributes<HTMLAreaElement>;
article: HTMLAttributes<HTMLElement>;
aside: HTMLAttributes<HTMLElement>;
audio: HTMLAttributes<HTMLAudioElement>;
b: HTMLAttributes<HTMLElement>;
base: HTMLAttributes<HTMLBaseElement>;
bdi: HTMLAttributes<HTMLElement>;
bdo: HTMLAttributes<HTMLElement>;
big: HTMLAttributes<HTMLElement>;
blockquote: HTMLAttributes<HTMLQuoteElement>;
body: HTMLAttributes<HTMLBodyElement>;
br: HTMLAttributes<HTMLBRElement>;
button: HTMLAttributes<HTMLButtonElement>;
canvas: HTMLAttributes<HTMLCanvasElement>;
caption: HTMLAttributes<HTMLTableCaptionElement>;
cite: HTMLAttributes<HTMLElement>;
code: HTMLAttributes<HTMLElement>;
col: HTMLAttributes<HTMLTableColElement>;
colgroup: HTMLAttributes<HTMLTableColElement>;
data: HTMLAttributes<HTMLDataElement>;
datalist: HTMLAttributes<HTMLDataListElement>;
dd: HTMLAttributes<HTMLElement>;
del: HTMLAttributes<HTMLModElement>;
details: HTMLAttributes<HTMLDetailsElement>;
dfn: HTMLAttributes<HTMLElement>;
dialog: HTMLAttributes<HTMLDialogElement>;
div: HTMLAttributes<HTMLDivElement>;
dl: HTMLAttributes<HTMLDListElement>;
dt: HTMLAttributes<HTMLElement>;
em: HTMLAttributes<HTMLElement>;
embed: HTMLAttributes<HTMLEmbedElement>;
fieldset: HTMLAttributes<HTMLFieldSetElement>;
figcaption: HTMLAttributes<HTMLElement>;
figure: HTMLAttributes<HTMLElement>;
footer: HTMLAttributes<HTMLElement>;
form: HTMLAttributes<HTMLFormElement>;
h1: HTMLAttributes<HTMLHeadingElement>;
h2: HTMLAttributes<HTMLHeadingElement>;
h3: HTMLAttributes<HTMLHeadingElement>;
h4: HTMLAttributes<HTMLHeadingElement>;
h5: HTMLAttributes<HTMLHeadingElement>;
h6: HTMLAttributes<HTMLHeadingElement>;
head: HTMLAttributes<HTMLHeadElement>;
header: HTMLAttributes<HTMLElement>;
hgroup: HTMLAttributes<HTMLElement>;
hr: HTMLAttributes<HTMLHRElement>;
html: HTMLAttributes<HTMLHtmlElement>;
i: HTMLAttributes<HTMLElement>;
iframe: HTMLAttributes<HTMLIFrameElement>;
img: HTMLAttributes<HTMLImageElement>;
input: HTMLAttributes<HTMLInputElement>;
ins: HTMLAttributes<HTMLModElement>;
kbd: HTMLAttributes<HTMLElement>;
keygen: HTMLAttributes<HTMLUnknownElement>;
label: HTMLAttributes<HTMLLabelElement>;
legend: HTMLAttributes<HTMLLegendElement>;
li: HTMLAttributes<HTMLLIElement>;
link: HTMLAttributes<HTMLLinkElement>;
main: HTMLAttributes<HTMLElement>;
map: HTMLAttributes<HTMLMapElement>;
mark: HTMLAttributes<HTMLElement>;
marquee: HTMLAttributes<HTMLMarqueeElement>;
menu: HTMLAttributes<HTMLMenuElement>;
menuitem: HTMLAttributes<HTMLUnknownElement>;
meta: HTMLAttributes<HTMLMetaElement>;
meter: HTMLAttributes<HTMLMeterElement>;
nav: HTMLAttributes<HTMLElement>;
noscript: HTMLAttributes<HTMLElement>;
object: HTMLAttributes<HTMLObjectElement>;
ol: HTMLAttributes<HTMLOListElement>;
optgroup: HTMLAttributes<HTMLOptGroupElement>;
option: HTMLAttributes<HTMLOptionElement>;
output: HTMLAttributes<HTMLOutputElement>;
p: HTMLAttributes<HTMLParagraphElement>;
param: HTMLAttributes<HTMLParamElement>;
picture: HTMLAttributes<HTMLPictureElement>;
pre: HTMLAttributes<HTMLPreElement>;
progress: HTMLAttributes<HTMLProgressElement>;
q: HTMLAttributes<HTMLQuoteElement>;
rp: HTMLAttributes<HTMLElement>;
rt: HTMLAttributes<HTMLElement>;
ruby: HTMLAttributes<HTMLElement>;
s: HTMLAttributes<HTMLElement>;
samp: HTMLAttributes<HTMLElement>;
script: HTMLAttributes<HTMLScriptElement>;
section: HTMLAttributes<HTMLElement>;
select: HTMLAttributes<HTMLSelectElement>;
slot: HTMLAttributes<HTMLSlotElement>;
small: HTMLAttributes<HTMLElement>;
source: HTMLAttributes<HTMLSourceElement>;
span: HTMLAttributes<HTMLSpanElement>;
strong: HTMLAttributes<HTMLElement>;
style: HTMLAttributes<HTMLStyleElement>;
sub: HTMLAttributes<HTMLElement>;
summary: HTMLAttributes<HTMLElement>;
sup: HTMLAttributes<HTMLElement>;
table: HTMLAttributes<HTMLTableElement>;
tbody: HTMLAttributes<HTMLTableSectionElement>;
td: HTMLAttributes<HTMLTableCellElement>;
textarea: HTMLAttributes<HTMLTextAreaElement>;
tfoot: HTMLAttributes<HTMLTableSectionElement>;
th: HTMLAttributes<HTMLTableCellElement>;
thead: HTMLAttributes<HTMLTableSectionElement>;
time: HTMLAttributes<HTMLTimeElement>;
title: HTMLAttributes<HTMLTitleElement>;
tr: HTMLAttributes<HTMLTableRowElement>;
track: HTMLAttributes<HTMLTrackElement>;
u: HTMLAttributes<HTMLElement>;
ul: HTMLAttributes<HTMLUListElement>;
var: HTMLAttributes<HTMLElement>;
video: HTMLAttributes<HTMLVideoElement>;
wbr: HTMLAttributes<HTMLElement>;
//SVG
svg: SVGAttributes<SVGSVGElement>;
animate: SVGAttributes<SVGAnimateElement>;
circle: SVGAttributes<SVGCircleElement>;
animateTransform: SVGAttributes<SVGAnimateElement>;
clipPath: SVGAttributes<SVGClipPathElement>;
defs: SVGAttributes<SVGDefsElement>;
desc: SVGAttributes<SVGDescElement>;
ellipse: SVGAttributes<SVGEllipseElement>;
feBlend: SVGAttributes<SVGFEBlendElement>;
feColorMatrix: SVGAttributes<SVGFEColorMatrixElement>;
feComponentTransfer: SVGAttributes<SVGFEComponentTransferElement>;
feComposite: SVGAttributes<SVGFECompositeElement>;
feConvolveMatrix: SVGAttributes<SVGFEConvolveMatrixElement>;
feDiffuseLighting: SVGAttributes<SVGFEDiffuseLightingElement>;
feDisplacementMap: SVGAttributes<SVGFEDisplacementMapElement>;
feDropShadow: SVGAttributes<SVGFEDropShadowElement>;
feFlood: SVGAttributes<SVGFEFloodElement>;
feGaussianBlur: SVGAttributes<SVGFEGaussianBlurElement>;
feImage: SVGAttributes<SVGFEImageElement>;
feMerge: SVGAttributes<SVGFEMergeElement>;
feMergeNode: SVGAttributes<SVGFEMergeNodeElement>;
feMorphology: SVGAttributes<SVGFEMorphologyElement>;
feOffset: SVGAttributes<SVGFEOffsetElement>;
feSpecularLighting: SVGAttributes<SVGFESpecularLightingElement>;
feTile: SVGAttributes<SVGFETileElement>;
feTurbulence: SVGAttributes<SVGFETurbulenceElement>;
filter: SVGAttributes<SVGFilterElement>;
foreignObject: SVGAttributes<SVGForeignObjectElement>;
g: SVGAttributes<SVGGElement>;
image: SVGAttributes<SVGImageElement>;
line: SVGAttributes<SVGLineElement>;
linearGradient: SVGAttributes<SVGLinearGradientElement>;
marker: SVGAttributes<SVGMarkerElement>;
mask: SVGAttributes<SVGMaskElement>;
path: SVGAttributes<SVGPathElement>;
pattern: SVGAttributes<SVGPatternElement>;
polygon: SVGAttributes<SVGPolygonElement>;
polyline: SVGAttributes<SVGPolylineElement>;
radialGradient: SVGAttributes<SVGRadialGradientElement>;
rect: SVGAttributes<SVGRectElement>;
stop: SVGAttributes<SVGStopElement>;
symbol: SVGAttributes<SVGSymbolElement>;
text: SVGAttributes<SVGTextElement>;
tspan: SVGAttributes<SVGTSpanElement>;
use: SVGAttributes<SVGUseElement>;
}
export type IntrinsicElements = {
// We have to omit the SVG anchor element because both HTML and SVG have an
// 'a' tag, and TSX gets confused about which one you mean if you have onclick
// handlers. Sadly that means things get weird if you try to use the SVG
// anchor tag, but I don't know how to support both.
[el in keyof Omit<SVGElementTagNameMap, "a">]: HTMLAttributes<
SVGElementTagNameMap[el]
>;
} & {
[el in keyof HTMLElementTagNameMap]: HTMLAttributes<
HTMLElementTagNameMap[el]
>;
};

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