UNPKG

reblock

Version:

Build interactive Slack surfaces with React

288 lines (287 loc) 7.98 kB
import { nanoid } from 'nanoid' import Reconciler from 'react-reconciler' import { DefaultEventPriority } from 'react-reconciler/constants' export class Root { children getChildren() { if (!this.children) { throw new Error('No children') } function removeHidden(children) { return children.flatMap((child) => { if (child.hidden) { return [] } if (child.type === 'instance') { return [ { ...child, children: removeHidden(child.children), }, ] } return [child] }) } return removeHidden(this.children) } rendering = true timeoutID lastPublishTime = 0 objectTreeModified() { if (!this.rendering) { return } if (this.timeoutID) { clearTimeout(this.timeoutID) } const now = Date.now() const delay = Math.max(50, 1000 - (now - this.lastPublishTime)) this.timeoutID = setTimeout(() => { this.findEventHandlers() this.publish() this.lastPublishTime = now }, delay) } eventHandlers = new Map() findEventHandlers() { this.eventHandlers.clear() const traverseFindHandlers = (children) => { for (const child of children) { if (child.type === 'instance') { if (child.props.onEvent) { const handler = child.props.onEvent this.eventHandlers.set(child.id, handler) } traverseFindHandlers(child.children) } } } traverseFindHandlers(this.getChildren()) } stopRendering() { this.rendering = false if (this.timeoutID) { clearTimeout(this.timeoutID) } } } /** no-op to help with type inference, probably a better way to do this but ¯\_(ツ)_/¯ */ function hostConfigTypeHelper(config) { return config } const makeHostConfig = (root) => hostConfigTypeHelper({ supportsPersistence: true, supportsMutation: true, supportsHydration: false, getRootHostContext: () => undefined, getChildHostContext: () => undefined, createInstance(type, props) { return { type: 'instance', id: nanoid(), hidden: false, element: type, children: [], text: null, props: props, } }, appendInitialChild(parent, child) { parent.children.push(child) root.objectTreeModified() }, createTextInstance(text) { return { type: 'text', id: nanoid(), hidden: false, text, } }, finalizeInitialChildren() { root.objectTreeModified() return false }, shouldSetTextContent: () => false, getPublicInstance: (instance) => instance, prepareForCommit: () => null, resetAfterCommit: () => {}, preparePortalMount: () => null, scheduleTimeout: setTimeout, cancelTimeout: clearTimeout, noTimeout: -1, isPrimaryRenderer: true, getCurrentEventPriority() { return DefaultEventPriority }, ...{ // FOR FUTURE ME: woah, bizarre errors???? // bc I updated react-reconciler, remember the types are very out of date // and you need to manually add the new methods // to see examples, I think https://github.com/facebook/react/blob/main/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js#L41 // has them, maybe the good test was somewhere else tho // good luck trackSchedulerEvent: () => {}, resolveEventType: () => null, resolveEventTimeStamp: () => -1.1, shouldAttemptEagerTransition: () => false, now: Date.now, logRecoverableError: console.error, resolveUpdatePriority() { return DefaultEventPriority }, getCurrentUpdatePriority() { return DefaultEventPriority }, setCurrentUpdatePriority() {}, maySuspendCommit() { return false }, mayResourceSuspendCommit() { throw new Error('Unsupported') }, waitForCommitToBeReady: () => null, }, cloneInstance: ( instance, updatePayload, type, oldProps, newProps, internalInstanceHandle, keepChildren ) => { return { type: 'instance', id: nanoid(), hidden: instance.hidden, element: type, children: keepChildren ? instance.children : [], text: null, props: newProps, } }, getInstanceFromNode: () => { throw new Error('Unsupported') }, beforeActiveInstanceBlur: () => {}, afterActiveInstanceBlur: () => {}, prepareScopeUpdate: () => {}, getInstanceFromScope: () => { throw new Error('Unsupported') }, detachDeletedInstance: () => {}, // eslint-disable-next-line @typescript-eslint/no-unused-vars createContainerChildSet: (_container) => { return [] }, appendChildToContainerChildSet: (childSet, child) => { childSet.push(child) root.objectTreeModified() }, finalizeContainerChildren: (container, newChildren) => { container.children = newChildren root.objectTreeModified() }, replaceContainerChildren: (container, newChildren) => { container.children = newChildren root.objectTreeModified() }, appendChild(parentInstance, child) { parentInstance.children.push(child) root.objectTreeModified() }, appendChildToContainer(container, child) { if (!container.children) { container.children = [] } container.children.push(child) root.objectTreeModified() }, insertBefore(parentInstance, child, beforeChild) { const index = parentInstance.children.indexOf(beforeChild) parentInstance.children.splice(index, 0, child) root.objectTreeModified() }, insertInContainerBefore(container, child, beforeChild) { if (!container.children) { container.children = [] } const index = container.children.indexOf(beforeChild) container.children.splice(index, 0, child) root.objectTreeModified() }, removeChild(parentInstance, child) { const index = parentInstance.children.indexOf(child) parentInstance.children.splice(index, 1) root.objectTreeModified() }, removeChildFromContainer(container, child) { if (!container.children) { container.children = [] } const index = container.children.indexOf(child) container.children.splice(index, 1) root.objectTreeModified() }, resetTextContent(instance) { instance.text = '' root.objectTreeModified() }, commitTextUpdate(textInstance, prevText, nextText) { textInstance.text = nextText root.objectTreeModified() }, commitMount() {}, prepareUpdate: () => { return {} }, commitUpdate(instance, updatePayload, type, prevProps, nextProps) { instance.props = nextProps root.objectTreeModified() }, hideInstance(instance) { instance.hidden = true root.objectTreeModified() }, hideTextInstance(textInstance) { textInstance.hidden = true root.objectTreeModified() }, unhideInstance(instance) { instance.hidden = false root.objectTreeModified() }, unhideTextInstance(textInstance) { textInstance.hidden = false root.objectTreeModified() }, clearContainer(container) { container.children = [] root.objectTreeModified() }, }) export function createContainer(root) { const hostConfig = makeHostConfig(root) const reconciler = Reconciler(hostConfig) const container = reconciler.createContainer( root, 0, null, true, false, '', console.warn, null ) return { container, reconciler } } export function render(element, reactInstance) { reactInstance.reconciler.updateContainer( element, reactInstance.container, null ) }