--!strict
-- ROBLOX upstream: https://github.com/facebook/react/blob/7516bdfce3f0f8c675494b5c5d0e7ae441bef1d9/packages/react/src/ReactChildren.js
--[[*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
]]
local Packages = script.Parent.Parent
local ReactTypes = require(Packages.Shared)
type ReactNodeList = ReactTypes.ReactNodeList
type React_Node = ReactTypes.React_Node
type ReactElement
= ReactTypes.ReactElement
local invariant = require(Packages.Shared).invariant
local ReactSymbols = require(Packages.Shared).ReactSymbols
local getIteratorFn = ReactSymbols.getIteratorFn
local REACT_ELEMENT_TYPE = ReactSymbols.REACT_ELEMENT_TYPE
local REACT_PORTAL_TYPE = ReactSymbols.REACT_PORTAL_TYPE
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
-- local console = LuauPolyfill.console
type Array = LuauPolyfill.Array
type Object = LuauPolyfill.Object
local ReactElement = require(script.Parent.ReactElement)
local isValidElement = ReactElement.isValidElement
local cloneAndReplaceKey = ReactElement.cloneAndReplaceKey
local SEPARATOR = "."
local SUBSEPARATOR = ":"
-- --[[*
-- * Escape and wrap key so it is safe to use as a reactid
-- *
-- * @param {string} key to be escaped.
-- * @return {string} the escaped key.
-- ]]
--ROBLOX DEVIATION: use gsub instead of RegEx
local function escape(key: string): string
local escapedString = string.gsub(key, "=", "=0")
escapedString = string.gsub(escapedString, ":", "=2")
return "$" .. escapedString
end
-- --[[*
-- * TODO: Test that a single child and an array with one item have the same key
-- * pattern.
-- ]]
-- ROBLOX DEVIATION: There is currently no good way to warn about maps
-- local didWarnAboutMaps = false
-- local userProvidedKeyEscapeRegex = '/\\/+/g'
local function escapeUserProvidedKey(text: string): string
-- ROBLOX DEVIATION: just return the original string
-- return text.replace(userProvidedKeyEscapeRegex, '$&/')
return text
end
-- --[[*
-- * Generate a key string that identifies a element within a set.
-- *
-- * @param {*} element A element that could contain a manual key.
-- * @param {number} index Index that is used if a manual key is not provided.
-- * @return {string}
-- ]]
local function getElementKey(element: any, index: number): string
-- Do some typechecking here since we call this blindly. We want to ensure
-- that we don't block potential future ES APIs.
if typeof(element) == "table" and element ~= nil and element.key ~= nil then
-- Explicit key
return escape(tostring(element.key))
end
-- Implicit key determined by the index in the set
-- ROBLOX DEVIATION: unsupported radix arg in tostring(number)
-- return index.toString(36)
return tostring(index)
end
local function mapIntoArray(
children: ReactNodeList?,
array: Array,
escapedPrefix: string,
nameSoFar: string,
callback: (React_Node?) -> ReactNodeList?
): number
local type = typeof(children)
--[[
ROBLOX DEVIATION: userdata type corresponds to React.None, which is perceived as nil. All
userdata is treated as nil when passed as a child.
]]
if type == "nil" or type == "boolean" or type == "userdata" then
-- All of the above are perceived as nil.
children = nil
end
local invokeCallback = false
if children == nil then
invokeCallback = true
else
if type == "string" or type == "number" then
invokeCallback = true
elseif type == "table" then
local childrenType = (children :: any)["$$typeof"]
if
childrenType == REACT_ELEMENT_TYPE
or childrenType == REACT_PORTAL_TYPE
then
invokeCallback = true
end
end
end
if invokeCallback then
local child = children
local mappedChild = callback(child)
-- If it's the only child, treat the name as if it was wrapped in an array
-- so that it's consistent if the number of children grows:
local childKey = if nameSoFar == ""
then SEPARATOR .. getElementKey(child, 1)
else nameSoFar
if Array.isArray(mappedChild) then
local escapedChildKey = ""
if childKey ~= nil then
escapedChildKey = escapeUserProvidedKey(childKey) .. "/"
end
mapIntoArray(mappedChild, array, escapedChildKey, "", function(c)
return c
end)
elseif mappedChild ~= nil then
if isValidElement(mappedChild :: any) then
local mappedChildKey = (mappedChild :: ReactElement