UNPKG

@teaui/react

Version:

React Reconciler and renderer for TeaUI

410 lines 14.9 kB
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