@teaui/react
Version:
React Reconciler and renderer for TeaUI
410 lines • 14.9 kB
JavaScript
import ReactReconciler from 'react-reconciler';
import { DefaultEventPriority } from 'react-reconciler/constants.js';
import { Accordion, Align, AlignRow, At, Alert, Badge, Box, Breadcrumb, Button, Callout, Calendar, Canvas, Checkbox, Collapsible, CollapsibleText, ConsoleLog, Digits, Drawer, Dropdown, Geometry, H1, HotKey, Modal, Keyboard, Mouse, Pane, H2, H3, H4, H5, H6, Input, Legend, AutoLegend, Progress, Screen, Scrollable, Separator, Slider, Space, Spinner, Logo, Page, ZStack, Stack, ScrollableList, Table, Tabs, Toggle, ToggleGroup, Tree, Window, } from '@teaui/core';
import { TextContainer, TextLiteral, TextProvider, TextStyle, } from './components/TextReact.js';
import { isSame } from './isSame.js';
const customElements = new Map();
/**
* Register a custom element type for the React reconciler.
* External packages (e.g. @teaui/subprocess) can call this to add new JSX elements.
*
* @example
* registerElement('tui-subprocess', (props) => new SubprocessView(props))
*/
export function registerElement(type, factory) {
customElements.set(type, factory);
}
function createInstance(type, props) {
const factory = customElements.get(type);
if (factory) {
return factory(props);
}
switch (type) {
// views
case 'at':
case 'tui-at':
return new At(props);
case 'align':
case 'tui-align':
return new Align(props);
case 'badge':
case 'tui-badge':
return new Badge(props);
case 'align-row':
case 'tui-align-row':
return new AlignRow(props);
case 'br':
case 'tui-br':
return new TextLiteral('\n');
case 'breadcrumb':
case 'tui-breadcrumb':
return new Breadcrumb(props);
case 'calendar':
case 'tui-calendar':
return new Calendar(props);
case 'canvas':
case 'tui-canvas':
return new Canvas(props);
case 'checkbox':
case 'tui-checkbox':
return new Checkbox(props);
case 'collapsible-text':
case 'tui-collapsible-text':
return new CollapsibleText(props);
case 'console':
case 'tui-console':
return new ConsoleLog(props);
case 'digits':
case 'tui-digits':
return new Digits(props);
case 'dropdown':
case 'tui-dropdown':
return new Dropdown(props);
case 'geometry':
case 'tui-geometry':
return new Geometry(props);
case 'hotkey':
case 'tui-hotkey':
return new HotKey(props);
case 'keyboard':
case 'tui-keyboard':
return new Keyboard(props);
case 'mouse':
case 'tui-mouse':
return new Mouse(props);
case 'h1':
case 'tui-h1':
return H1(props.text ?? '');
case 'h2':
case 'tui-h2':
return H2(props.text ?? '');
case 'h3':
case 'tui-h3':
return H3(props.text ?? '');
case 'h4':
case 'tui-h4':
return H4(props.text ?? '');
case 'h5':
case 'tui-h5':
return H5(props.text ?? '');
case 'h6':
case 'tui-h6':
return H6(props.text ?? '');
case 'input':
case 'tui-input':
return new Input(props);
case 'legend':
case 'tui-legend':
return new Legend(props);
case 'tui-auto-legend':
return new AutoLegend(props);
case 'separator':
case 'tui-separator':
return new Separator(props);
case 'slider':
case 'tui-slider':
return new Slider(props);
case 'space':
case 'tui-space':
return new Space(props);
case 'progress':
case 'tui-progress':
return new Progress(props);
case 'spinner':
case 'tui-spinner':
return new Spinner(props);
case 'tui-logo':
return new Logo(props);
case 'tui-zstack':
return new ZStack(props);
case 'toggle':
case 'tui-toggle':
return new Toggle(props);
case 'toggle-group':
case 'tui-toggle-group':
return new ToggleGroup(props);
case 'tree':
case 'tui-tree':
return new Tree(props);
// "simple" containers
case 'alert':
case 'tui-alert':
return new Alert(props);
case 'box':
case 'tui-box':
return new Box(props);
case 'callout':
case 'tui-callout':
return new Callout(props);
case 'button':
case 'tui-button':
return new Button(props);
case 'collapsible':
case 'tui-collapsible':
return new Collapsible(props);
case 'modal':
case 'tui-modal':
return new Modal(props);
case 'stack':
case 'tui-stack':
return new Stack(props);
case 'scrollable':
case 'tui-scrollable':
return new Scrollable(props);
case 'style':
case 'tui-style':
return new TextStyle(props);
case 'tui-list':
return new ScrollableList(props);
case 'table':
case 'tui-table':
return new Table(props);
case 'tui-text':
return new TextProvider(props);
// "complex" containers
case 'accordion':
case 'tui-accordion':
return new Accordion(props);
case 'accordion-section':
case 'tui-accordion-section':
return new Accordion.Section(props);
case 'pane':
case 'tui-pane':
return new Pane(props);
case 'drawer':
case 'tui-drawer':
return new Drawer(props);
case 'tabs':
case 'tui-tabs':
return new Tabs(props);
case 'tabs-section':
case 'tui-tabs-section':
return new Tabs.Section(props);
case 'page':
case 'tui-page':
return new Page(props);
case 'page-section':
case 'tui-page-section':
return new Page.Section(props);
default:
throw new Error(`unknown component "${type}"`);
}
}
export function render(screen, window, rootNode) {
function rerender() {
screen.render();
}
function removeFromTextContainer(container, child) {
// find TextContainer with child in it, and remove.
// TextContainer.add() puts TextLiterals/TextStyles into #nodes (accessed
// via .nodes), NOT into .children (which holds generated Text views).
// So we check child.parent === node rather than node.children.includes(child).
for (const node of container.children) {
if (node instanceof TextContainer && child.parent === node) {
node.removeChild(child);
if (node.children.length === 0) {
container.removeChild(node);
}
return;
}
}
}
function removeChild(container, child) {
if (child.parent === container) {
container.removeChild(child);
}
else if (child instanceof TextLiteral || child instanceof TextStyle) {
removeFromTextContainer(container, child);
}
}
function appendChild(parentInstance, child, before) {
if (parentInstance instanceof TextStyle &&
(child instanceof TextLiteral || child instanceof TextStyle)) {
// do not do the TextContainer song and dance
}
else if (child instanceof TextLiteral || child instanceof TextStyle) {
if (before) {
if (before.parent === parentInstance) {
const beforeIndex = parentInstance.children.indexOf(before);
if (~beforeIndex) {
const previousChild = parentInstance.children.at(beforeIndex - 1);
if (previousChild instanceof TextContainer) {
previousChild.add(child);
}
else {
const textContainer = new TextContainer();
parentInstance.add(textContainer, beforeIndex);
textContainer.add(child);
}
return;
}
}
if (before.parent instanceof TextContainer &&
before.parent.parent === parentInstance) {
const textContainer = before.parent;
const beforeIndex = textContainer.nodes.indexOf(before);
if (~beforeIndex) {
textContainer.add(child, beforeIndex);
return;
}
}
}
const lastChild = parentInstance.children.at(-1);
let textContainer;
if (lastChild instanceof TextContainer) {
textContainer = lastChild;
}
else {
textContainer = new TextContainer();
parentInstance.add(textContainer);
}
textContainer.add(child);
return;
}
let index = before
? parentInstance.children.indexOf(before)
: -1;
if (index === -1) {
index = undefined;
}
parentInstance.add(child, index);
}
const reconciler = ReactReconciler({
supportsPersistence: false,
supportsHydration: false,
isPrimaryRenderer: true,
getRootHostContext(rootWindow) {
return { screen, window: rootWindow };
},
getChildHostContext(_parentHostContext, type, _rootWindow) {
return { type };
},
clearContainer(rootWindow) {
rootWindow.removeAllChildren();
},
createInstance(type, props, _rootWindow, _hostContext, _internalInstanceHandle) {
if ('children' in props) {
const { children: _children, ...remainder } = props;
props = remainder;
}
if ('child' in props) {
const { child: _child, ...remainder } = props;
props = remainder;
}
return createInstance(type, props);
},
createTextInstance(text) {
return new TextLiteral(text);
},
appendInitialChild(parentInstance, child) {
appendChild(parentInstance, child, undefined);
},
appendChild(parentInstance, child) {
appendChild(parentInstance, child, undefined);
},
insertBefore(parentInstance, child, beforeChild) {
appendChild(parentInstance, child, beforeChild);
},
appendChildToContainer(rootWindow, child) {
appendChild(rootWindow, child);
},
insertInContainerBefore(rootWindow, child, beforeChild) {
appendChild(rootWindow, child, beforeChild);
},
removeChild(container, child) {
removeChild(container, child);
},
removeChildFromContainer(container, child) {
removeChild(container, child);
},
detachDeletedInstance(_node) { },
finalizeInitialChildren(_instance) {
return false;
},
prepareForCommit() {
return null;
},
resetAfterCommit() {
rerender();
},
commitMount(_instance, _type, _newProps, _internalInstanceHandle) {
// not needed as long as finalizeInitialChildren returns `false`
},
commitTextUpdate(textInstance, _oldText, newText) {
textInstance.text = newText;
},
resetTextContent(instance) {
instance.text = '';
},
shouldSetTextContent(_type, _props) {
return false;
},
prepareUpdate(_instance, _type, oldProps, newProps, _rootContainer, _hostContext) {
for (const prop in oldProps) {
if (!Object.hasOwn(oldProps, prop)) {
continue;
}
if (!isSame(oldProps[prop], newProps[prop])) {
// difference found - we just return a non-null here to indicate "difference"
return [];
}
}
for (const prop in newProps) {
// if we already checked it, or it isn't an own-prop on newProps, continue
if (Object.hasOwn(oldProps, prop) || !Object.hasOwn(newProps, prop)) {
continue;
}
if (!isSame(oldProps[prop], newProps[prop])) {
// difference found - we just return a non-null here to indicate "difference"
return [];
}
}
return null;
},
commitUpdate(node, _updatePayload, _type, _oldProps, newProps, _internalInstanceHandle) {
const { children: _children, ...updates } = newProps;
// if (children !== undefined && node instanceof TextLiteral) {
// updates.text = childrenToText(children)
// }
node.update(updates);
},
supportsMutation: true,
getPublicInstance(instance) {
return instance;
},
preparePortalMount() { },
scheduleTimeout: setTimeout,
cancelTimeout: clearTimeout,
noTimeout: -1,
getCurrentEventPriority() {
return DefaultEventPriority;
},
getInstanceFromNode() {
throw new Error('Function not implemented.');
},
beforeActiveInstanceBlur() {
throw new Error('Function not implemented.');
},
afterActiveInstanceBlur() {
throw new Error('Function not implemented.');
},
prepareScopeUpdate() {
throw new Error('Function not implemented.');
},
getInstanceFromScope() {
throw new Error('Function not implemented.');
},
});
const fiber = reconciler.createContainer(window, 0, null, false, null, '', () => { }, null);
reconciler.updateContainer(rootNode, fiber, null /* parentComponent */, null /* callback */);
return function unmount() {
reconciler.updateContainer(null, fiber, null, null);
};
}
export async function run(component, options) {
const window = new Window();
const [screen, _] = await Screen.start(window, options);
const unmount = render(screen, window, component);
return [screen, window, component, unmount];
}
//# sourceMappingURL=reconciler.js.map