reblock
Version:
Build interactive Slack surfaces with React
288 lines (287 loc) • 7.98 kB
JavaScript
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
)
}