UNPKG

nano-jsx

Version:

SSR first, lightweight 1kB JSX library.

157 lines (156 loc) 4.73 kB
import { onNodeRemove } from './helpers.js'; import { tick, _render, render, h } from './core.js'; import { _state } from './state.js'; export class Component { constructor(props) { this._elements = []; this._skipUnmount = false; this._hasUnmounted = false; this.props = props || {}; this.id = this._getHash(); } static get isClass() { return true; } get isClass() { return true; } setState(state, shouldUpdate = false) { const isObject = typeof state === 'object' && state !== null; // if state is an object, we merge the objects if (isObject && this.state !== undefined) this.state = { ...this.state, ...state }; // else, we just overwrite it else this.state = state; if (shouldUpdate) this.update(); } set state(state) { _state.set(this.id, state); } get state() { return _state.get(this.id); } set initState(state) { if (this.state === undefined) this.state = state; } /** Returns all currently rendered node elements */ get elements() { return this._elements || []; } set elements(elements) { if (!Array.isArray(elements)) elements = [elements]; elements.forEach(element => { this._elements.push(element); }); } _addNodeRemoveListener() { // check if didUnmount is unused if (/^[^{]+{\s+}$/gm.test(this.didUnmount.toString())) return; // listen if the root elements gets removed onNodeRemove(this.elements[0], () => { if (!this._skipUnmount) this._didUnmount(); }); } // @ts-ignore _didMount() { this._addNodeRemoveListener(); this.didMount(); } _willUpdate() { this.willUpdate(); } _didUpdate() { this.didUpdate(); } _didUnmount() { if (this._hasUnmounted) return; this.didUnmount(); this._hasUnmounted = true; } willMount() { } didMount() { } willUpdate() { } didUpdate() { } didUnmount() { } render(_update) { } /** Will forceRender the component */ update(update) { this._skipUnmount = true; this._willUpdate(); // get all current rendered node elements const oldElements = [...this.elements]; // clear this._elements = []; let el = this.render(update); el = _render(el); this.elements = el; // console.log('old: ', oldElements) // console.log('new: ', this.elements) // get valid parent node const parent = oldElements[0].parentNode; // make sure we have a parent if (!parent) console.warn('Component needs a parent element to get updated!'); // add all new node elements this.elements.forEach((child) => { if (parent) parent.insertBefore(child, oldElements[0]); }); // remove all elements oldElements.forEach((child) => { // wee keep the element if it is the same, for example if passed as a child if (!this.elements.includes(child)) { child.remove(); // @ts-ignore child = null; } }); // listen for node removal this._addNodeRemoveListener(); // @ts-ignore tick(() => { this._skipUnmount = false; if (!this.elements[0].isConnected) this._didUnmount(); else this._didUpdate(); }); } _getHash() { } } /** * Renders a class component and returns its reference. * * @param C Class Component * @param props Properties * @param children Children * @param parent HTMLElement * @returns Reference to Class Component * * @example const componentReference = await renderComponentGetReference( App, // Component { name: 'Joe' }, // Props [Child, 'Some text', child], // Children (Component, string, FC) parentElement // HTMLElement ) componentReference.hello() componentReference.setState({ age: 40 }) componentReference.update() parentElement.remove() */ export function renderComponentGetReference(C, props, children, parent) { return new Promise(resolve => { render(h(C, { ...props, ref: (el) => resolve(el) }, ...children), parent); // This is the same as the above, but written in JSX. // render(<C {...props} ref={(el: any) => resolve(el)} />, parent) }); } //# sourceMappingURL=component.js.map