local Binding = require(script.Parent.Binding)
local Children = require(script.Parent.PropMarkers.Children)
local ElementKind = require(script.Parent.ElementKind)
local SingleEventManager = require(script.Parent.SingleEventManager)
local getDefaultInstanceProperty = require(script.Parent.getDefaultInstanceProperty)
local Ref = require(script.Parent.PropMarkers.Ref)
local Type = require(script.Parent.Type)
local internalAssert = require(script.Parent.internalAssert)
local config = require(script.Parent.GlobalConfig).get()
local applyPropsError = [[
Error applying props:
%s
In element:
%s
]]
local updatePropsError = [[
Error updating props:
%s
In element:
%s
]]
local function identity(...)
return ...
end
local function applyRef(ref, newHostObject)
if ref == nil then
return
end
if typeof(ref) == "function" then
ref(newHostObject)
elseif Type.of(ref) == Type.Binding then
Binding.update(ref, newHostObject)
else
error(("Invalid ref: Expected type Binding but got %s"):format(typeof(ref)))
end
end
local function setRobloxInstanceProperty(hostObject, key, newValue)
if newValue == nil then
local hostClass = hostObject.ClassName
local _, defaultValue = getDefaultInstanceProperty(hostClass, key)
newValue = defaultValue
end
hostObject[key] = newValue
return
end
local function removeBinding(virtualNode, key)
local disconnect = virtualNode.bindings[key]
disconnect()
virtualNode.bindings[key] = nil
end
local function attachBinding(virtualNode, key, newBinding)
local function updateBoundProperty(newValue)
local success, errorMessage = xpcall(function()
setRobloxInstanceProperty(virtualNode.hostObject, key, newValue)
end, identity)
if not success then
local source = virtualNode.currentElement.source
if source == nil then
source = "<enable element tracebacks>"
end
local fullMessage = updatePropsError:format(errorMessage, source)
error(fullMessage, 0)
end
end
if virtualNode.bindings == nil then
virtualNode.bindings = {}
end
virtualNode.bindings[key] = Binding.subscribe(newBinding, updateBoundProperty)
updateBoundProperty(newBinding:getValue())
end
local function detachAllBindings(virtualNode)
if virtualNode.bindings ~= nil then
for _, disconnect in pairs(virtualNode.bindings) do
disconnect()
end
virtualNode.bindings = nil
end
end
local function applyProp(virtualNode, key, newValue, oldValue)
if newValue == oldValue then
return
end
if key == Ref or key == Children then
return
end
local internalKeyType = Type.of(key)
if internalKeyType == Type.HostEvent or internalKeyType == Type.HostChangeEvent then
if virtualNode.eventManager == nil then
virtualNode.eventManager = SingleEventManager.new(virtualNode.hostObject)
end
local eventName = key.name
if internalKeyType == Type.HostChangeEvent then
virtualNode.eventManager:connectPropertyChange(eventName, newValue)
else
virtualNode.eventManager:connectEvent(eventName, newValue)
end
return
end
local newIsBinding = Type.of(newValue) == Type.Binding
local oldIsBinding = Type.of(oldValue) == Type.Binding
if oldIsBinding then
removeBinding(virtualNode, key)
end
if newIsBinding then
attachBinding(virtualNode, key, newValue)
else
setRobloxInstanceProperty(virtualNode.hostObject, key, newValue)
end
end
local function applyProps(virtualNode, props)
for propKey, value in pairs(props) do
applyProp(virtualNode, propKey, value, nil)
end
end
local function updateProps(virtualNode, oldProps, newProps)
for propKey, newValue in pairs(newProps) do
local oldValue = oldProps[propKey]
applyProp(virtualNode, propKey, newValue, oldValue)
end
for propKey, oldValue in pairs(oldProps) do
local newValue = newProps[propKey]
if newValue == nil then
applyProp(virtualNode, propKey, nil, oldValue)
end
end
end
local RobloxRenderer = {}
function RobloxRenderer.isHostObject(target)
return typeof(target) == "Instance"
end
function RobloxRenderer.mountHostNode(reconciler, virtualNode)
local element = virtualNode.currentElement
local hostParent = virtualNode.hostParent
local hostKey = virtualNode.hostKey
if config.internalTypeChecks then
internalAssert(ElementKind.of(element) == ElementKind.Host, "Element at given node is not a host Element")
end
if config.typeChecks then
assert(element.props.Name == nil, "Name can not be specified as a prop to a host component in Roact.")
assert(element.props.Parent == nil, "Parent can not be specified as a prop to a host component in Roact.")
end
local instance = Instance.new(element.component)
virtualNode.hostObject = instance
local success, errorMessage = xpcall(function()
applyProps(virtualNode, element.props)
end, identity)
if not success then
local source = element.source
if source == nil then
source = "<enable element tracebacks>"
end
local fullMessage = applyPropsError:format(errorMessage, source)
error(fullMessage, 0)
end
instance.Name = tostring(hostKey)
local children = element.props[Children]
if children ~= nil then
reconciler.updateVirtualNodeWithChildren(virtualNode, virtualNode.hostObject, children)
end
instance.Parent = hostParent
virtualNode.hostObject = instance
applyRef(element.props[Ref], instance)
if virtualNode.eventManager ~= nil then
virtualNode.eventManager:resume()
end
end
function RobloxRenderer.unmountHostNode(reconciler, virtualNode)
local element = virtualNode.currentElement
applyRef(element.props[Ref], nil)
for _, childNode in pairs(virtualNode.children) do
reconciler.unmountVirtualNode(childNode)
end
detachAllBindings(virtualNode)
virtualNode.hostObject:Destroy()
end
function RobloxRenderer.updateHostNode(reconciler, virtualNode, newElement)
local oldProps = virtualNode.currentElement.props
local newProps = newElement.props
if virtualNode.eventManager ~= nil then
virtualNode.eventManager:suspend()
end
if oldProps[Ref] ~= newProps[Ref] then
applyRef(oldProps[Ref], nil)
applyRef(newProps[Ref], virtualNode.hostObject)
end
local success, errorMessage = xpcall(function()
updateProps(virtualNode, oldProps, newProps)
end, identity)
if not success then
local source = newElement.source
if source == nil then
source = "<enable element tracebacks>"
end
local fullMessage = updatePropsError:format(errorMessage, source)
error(fullMessage, 0)
end
local children = newElement.props[Children]
if children ~= nil or oldProps[Children] ~= nil then
reconciler.updateVirtualNodeWithChildren(virtualNode, virtualNode.hostObject, children)
end
if virtualNode.eventManager ~= nil then
virtualNode.eventManager:resume()
end
return virtualNode
end
return RobloxRenderer