nano-jsx
Version:
SSR first, lightweight 1kB JSX library.
157 lines (156 loc) • 4.73 kB
JavaScript
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