local N=Roact.PureComponent:extend("\069\120\112\108\111\114\101\114")function N.init(p,N)p:setState({Items={},RowHeight=18})p.UpdateQueues={}p.QueueTimers={}p.LastId=0 p.IdMap={}p.PendingParent={}p.ToggleExpand=function(N)return p:setState(function(p)local f=p.Items[N]f.Expanded=not f.Expanded return{Items=p.Items}end)end end function N.didUpdate(f,N,p)if N.Scope~=f.props.Scope then f:UpdateScope(f.props.Scope)end end function N.UpdateScope(p,N)local f=p.props.Core local B=f.Selection if p.ScopeMaid then p.ScopeMaid:Destroy()end p.ScopeMaid=Maid.new()if not N then return end coroutine.resume(coroutine.create(function()p:UpdateTree()if#B.Items>0 then local N=B.IsSelected(B.Focus)and B.Focus or B.Items[1]p:setState({ScrollTo=p.IdMap[N]})else p:setState({ScrollTo=Roact.None})end end))local P=f.Targeting.Scope p.ScopeMaid.Add=P.DescendantAdded:Connect(function(N)p:UpdateTree()end)p.ScopeMaid.Remove=P.DescendantRemoving:Connect(function(N)p:UpdateTree()end)p.ScopeMaid.Select=B.ItemsAdded:Connect(function(N)p:UpdateSelection(N)local f=#N==1 and p.IdMap[N[1]]p:setState(function(N)local B={}local P=N.Items[f]local y=P and p.IdMap[P.Parent]local M=y and N.Items[y]while M do M.Expanded=true B[y]=M y=p.IdMap[M.Parent]M=N.Items[y]end return{Items=Support.Merge(N.Items,B);ScrollTo=f}end)end)p.ScopeMaid.Deselect=B.ItemsRemoved:Connect(function(N)p:UpdateSelection(N)end)end function N.didMount(N)N.Mounted=true N.ItemMaid=Maid.new()N:UpdateScope(N.props.Scope)end function N.willUnmount(N)N.Mounted=false N.ScopeMaid:Destroy()N.ItemMaid:Destroy()end local function p(N)return N:IsA("\077\111\100\101\108")or N:IsA("\066\097\115\101\080\097\114\116")or N:IsA("\084\111\111\108")or N:IsA("\065\099\099\101\115\115\111\114\121")or N:IsA("\065\099\099\111\117\116\114\101\109\101\110\116")end function N.IsItemIndexable(N)return p(N)and N.ClassName~="\084\101\114\114\097\105\110"or N:IsA("\070\111\108\100\101\114")end function N.UpdateTree(N)if not N:ShouldExecuteQueue("\084\114\101\101")then return end local p=1 local f=N.IdMap N:setState(function(B)local P={}local y=N.props.Scope:GetDescendants()local M=Support.FlipTable(y)for y,M in ipairs(y)do local J=f[M]local A=J and B.Items[J]if A then if A.Order~=p then A=Support.CloneTable(A)A.Order=p P[J]=A end p=p+1 local f=N.IdMap[A.Parent]if not B.Items[f]then N:UpdateItemParent(M,P,B)end elseif N:BuildItemState(M,N.props.Scope,p,P,B)then p=p+1 end end for p,y in pairs(B.Items)do local J=y.Instance if not M[J]then P[p]=Support.Blank f[J]=nil N.ItemMaid[p]=nil local M=y.Parent and N.IdMap[y.Parent]local A=N:GetStagedItemState(M,P,B)if A then A.Children[p]=nil A.Unlocked[p]=nil A.IsLocked=next(A.Children)and not next(A.Unlocked)P[M]=A N:PropagateLock(A,P,B)end end end return{Items=Support.MergeWithBlanks(B.Items,P)}end)end function N.UpdateSelection(p,N)local f=p.props.Core.Selection p:QueueUpdate("\083\101\108\101\099\116\105\111\110",N)if not p:ShouldExecuteQueue("\083\101\108\101\099\116\105\111\110")then return end p:setState(function(N)local B={}local P=p:GetUpdateQueue("\083\101\108\101\099\116\105\111\110")for P in pairs(P)do for P,y in ipairs(P)do local M=p.IdMap[y]if M then local p=Support.CloneTable(N.Items[M])p.Selected=f.IsSelected(y)B[M]=p end end end return{Items=Support.Merge(N.Items,B)}end)end function N.BuildItemState(y,N,p,f,B,P)local M=N.Parent local J=y.IdMap[M]local A=J or M==p if not(y.IsItemIndexable(N)and A)then return nil end local Z=y.LastId+1 y.LastId=Z y.IdMap[N]=Z local x=N:IsA("\066\097\115\101\080\097\114\116")local H=Maid.new()y.ItemMaid[Z]=H local Y={Id=Z,Name=N.Name,IsPart=x;IsLocked=x and N.Locked or nil;Class=N.ClassName,Parent=M;Children={};Unlocked={},Order=f,Expanded=nil,Instance=N;Selected=y.props.Core.Selection.IsSelected(N)or nil}B[Z]=Y local g=y:GetStagedItemState(J,B,P)if g then g.Children[Z]=true B[J]=g end for N in pairs(y.PendingParent[N]or{})do local p=y:GetStagedItemState(N,B,P)if p then Y.Children[N]=true y:PropagateLock(p,B,P)end end y:PropagateLock(Y,B,P)H.Name=(N:GetPropertyChangedSignal("\078\097\109\101")):Connect(function()y:QueueUpdate("\078\097\109\101",N)if not y:ShouldExecuteQueue("\078\097\109\101")then return end y:setState(function(N)local p={}local f=y:GetUpdateQueue("\078\097\109\101")for f in pairs(f)do local B=y.IdMap[f]local P=Support.CloneTable(N.Items[B])P.Name=f.Name p[B]=P end return{Items=Support.Merge(N.Items,p)}end)end)H.Parent=(N:GetPropertyChangedSignal("\080\097\114\101\110\116")):Connect(function()if not N.Parent then return end y:QueueUpdate("\080\097\114\101\110\116",N)if not y:ShouldExecuteQueue("\080\097\114\101\110\116")then return end y:setState(function(N)local p={}local f=y:GetUpdateQueue("\080\097\114\101\110\116")for f in pairs(f)do y:UpdateItemParent(f,p,N)end return{Items=Support.MergeWithBlanks(N.Items,p)}end)y:UpdateTree()end)if x then H.Locked=(N:GetPropertyChangedSignal("\076\111\099\107\101\100")):Connect(function()y:QueueUpdate("\076\111\099\107",N)if not y:ShouldExecuteQueue("\076\111\099\107")then return end y:setState(function(N)local p={}local f=y:GetUpdateQueue("\076\111\099\107")for f in pairs(f)do y:UpdateItemLock(f,p,N)end return{Items=Support.MergeWithBlanks(N.Items,p)}end)end)end return true end function N.QueueUpdate(f,N,p)f.UpdateQueues[N]=Support.Merge(f.UpdateQueues[N]or{},{[p]=true})end function N.GetUpdateQueue(p,N)local f=p.UpdateQueues[N]p.UpdateQueues[N]=nil return f end function N.ShouldExecuteQueue(p,N)local f=p.QueueTimers[N]or Support.CreateConsecutiveCallDeferrer(.025)p.QueueTimers[N]=f if f()and p.Mounted then return true end end function N.UpdateItemLock(B,N,p,f)local P=B.IdMap[N]local y=B:GetStagedItemState(P,p,f)if not y then return end y.IsLocked=N.Locked p[P]=y B:PropagateLock(y,p,f)end function N.GetStagedItemState(B,N,p,f)local P=N and p[N]if not N or P==Support.Blank or not(P or f.Items[N])then return end return P or Support.CloneTable(f.Items[N])end function N.UpdateItemParent(B,N,p,f)local P=B.IdMap[N]local y=B:GetStagedItemState(P,p,f)if not y then return end local M=y.Parent and B.IdMap[y.Parent]local J=N.Parent local A=B.IdMap[J]y.Parent=J p[P]=y if not A and not(J==Scope)then if J:IsDescendantOf(Scope)then B.PendingParent[J]=Support.Merge(B.PendingParent[J]or{},{[P]=true})end end local Z=B:GetStagedItemState(M,p,f)if Z then Z.Children[P]=nil Z.Unlocked[P]=nil Z.IsLocked=next(Z.Children)and not next(Z.Unlocked)p[M]=Z end local x=B:GetStagedItemState(A,p,f)if x then x.Children[P]=true p[A]=x B:PropagateLock(y,p,f)end end function N.PropagateLock(B,N,p,f)if not N.Parent then return end local P=N.Id local y=N repeat local N=y.Parent local M=N and B.IdMap[N]local J=B:GetStagedItemState(M,p,f)if J then J.Unlocked[P]=not y.IsLocked and true or nil J.IsLocked=next(J.Children)and not next(J.Unlocked)p[M]=J P=M y=J else P=nil y=nil end until not y end function N.render(N)local p=N.props local f=N.state return new(ImageLabel,{Active=true,Layout="\076\105\115\116";LayoutDirection="\086\101\114\116\105\099\097\108",AnchorPoint=Vector2.new(1,0),Position=UDim2.new(1,-100,.6,-190.0);Width=UDim.new(0,145);Height="\087\082\065\080\095\067\079\078\084\069\078\084",Image="\114\098\120\097\115\115\101\116\105\100\058\047\047\050\050\052\052\050\052\056\051\052\049";ScaleType="\083\108\105\099\101",SliceCenter=Rect.new(4,4,12,12),ImageTransparency=.07;ImageColor="\051\066\051\066\051\066"},{Header=new(TextLabel,{Text="  \069\088\080\076\079\082\069\082",TextSize=9,Height=UDim.new(0,14),TextColor="\070\070\070\070\070\070",TextTransparency=.7},{CloseButton=new(ImageButton,{Image="\114\098\120\097\115\115\101\116\105\100\058\047\047\050\050\052\052\052\053\050\057\055\056",ImageRectOffset=Vector2.new(0,0);ImageRectSize=Vector2.new(14,14)*2,AspectRatio=1,AnchorPoint=Vector2.new(1,.5);Position=UDim2.new(1,0,.5,0),ImageTransparency=.66,[Roact.Event.Activated]=p.Close})}),ItemList=new(ItemList,{Scope=p.Scope,Items=f.Items,ScrollTo=f.ScrollTo;Core=p.Core,IdMap=N.IdMap,RowHeight=f.RowHeight;ToggleExpand=N.ToggleExpand})})end return N

local Explorer = Roact.PureComponent:extend 'Explorer'

function Explorer:init(props)
    self:setState {
        Items = {},
        RowHeight = 18
    }

    -- Update batching data
    self.UpdateQueues = {}
    self.QueueTimers = {}

    -- Item tracking data
    self.LastId = 0
    self.IdMap = {}
    self.PendingParent = {}

    -- Define item expanding function
    self.ToggleExpand = function (ItemId)
        return self:setState(function (State)
            local Item = State.Items[ItemId]
            Item.Expanded = not Item.Expanded
            return { Items = State.Items }
        end)
    end
end

function Explorer:didUpdate(previousProps, previousState)

    -- Trigger a scope change if prop changes
    if previousProps.Scope ~= self.props.Scope then
        self:UpdateScope(self.props.Scope)
    end

end

function Explorer:UpdateScope(Scope)
    local Core = self.props.Core
    local Selection = Core.Selection

    -- Clear previous cleanup maid
    if self.ScopeMaid then
        self.ScopeMaid:Destroy()
    end

    -- Create maid for cleanup
    self.ScopeMaid = Maid.new()

    -- Ensure new scope is defined
    if not Scope then
        return
    end

    -- Build initial tree
    coroutine.resume(coroutine.create(function ()
        self:UpdateTree()

        -- Scroll to first selected item
        if #Selection.Items > 0 then
            local FocusedItem = Selection.IsSelected(Selection.Focus) and Selection.Focus or Selection.Items[1]
            self:setState({
                ScrollTo = self.IdMap[FocusedItem]
            })
        else
            self:setState({
                ScrollTo = Roact.None
            })
        end
    end))

    -- Listen for new and removing items
    local Scope = Core.Targeting.Scope
    self.ScopeMaid.Add = Scope.DescendantAdded:Connect(function (Item)
        self:UpdateTree()
    end)
    self.ScopeMaid.Remove = Scope.DescendantRemoving:Connect(function (Item)
        self:UpdateTree()
    end)

    -- Listen for selected items
    self.ScopeMaid.Select = Selection.ItemsAdded:Connect(function (Items)
        self:UpdateSelection(Items)

        -- If single item selected, get item state
        local ItemId = (#Items == 1) and self.IdMap[Items[1]]

        -- Expand ancestors leading to item
        self:setState(function (State)
            local Changes = {}
            local ItemState = State.Items[ItemId]
            local ParentId = ItemState and self.IdMap[ItemState.Parent]
            local ParentState = ParentId and State.Items[ParentId]

            while ParentState do
                ParentState.Expanded = true
                Changes[ParentId] = ParentState
                ParentId = self.IdMap[ParentState.Parent]
                ParentState = State.Items[ParentId]
            end

            return {
                Items = Support.Merge(State.Items, Changes),
                ScrollTo = ItemId
            }
        end)
    end)
    self.ScopeMaid.Deselect = Selection.ItemsRemoved:Connect(function (Items)
        self:UpdateSelection(Items)
    end)

end

function Explorer:didMount()
    self.Mounted = true

    -- Create maid for cleanup on unmount
    self.ItemMaid = Maid.new()

    -- Set scope
    self:UpdateScope(self.props.Scope)
end

function Explorer:willUnmount()
    self.Mounted = false

    -- Clean up resources
    self.ScopeMaid:Destroy()
    self.ItemMaid:Destroy()
end

local function IsTargetable(Item)
	return Item:IsA 'Model' or
		Item:IsA 'BasePart' or
		Item:IsA 'Tool' or
		Item:IsA 'Accessory' or
		Item:IsA 'Accoutrement'
end

function Explorer.IsItemIndexable(Item)
    return (IsTargetable(Item) and Item.ClassName ~= 'Terrain') or
        Item:IsA 'Folder'
end

function Explorer:UpdateTree()

    -- Check if queue should be executed
    if not self:ShouldExecuteQueue('Tree') then
        return
    end

    -- Track order of each item
    local OrderCounter = 1
    local IdMap = self.IdMap

    -- Perform update to state
    self:setState(function (State)
        local Changes = {}
        local Descendants = self.props.Scope:GetDescendants()
        local DescendantMap = Support.FlipTable(Descendants)

        -- Check all items in scope
        for Index, Item in ipairs(Descendants) do
            local ItemId = IdMap[Item]
            local ItemState = ItemId and State.Items[ItemId]

            -- Update reordered items
            if ItemState then
                if ItemState.Order ~= OrderCounter then
                    ItemState = Support.CloneTable(ItemState)
                    ItemState.Order = OrderCounter
                    Changes[ItemId] = ItemState
                end
                OrderCounter = OrderCounter + 1

                -- Update parents in case scope changed
                local ParentId = self.IdMap[ItemState.Parent]
                if not State.Items[ParentId] then
                    self:UpdateItemParent(Item, Changes, State)
                end

            -- Introduce new items
            elseif self:BuildItemState(Item, self.props.Scope, OrderCounter, Changes, State) then
                OrderCounter = OrderCounter + 1
            end
        end

        -- Remove old items from state
        for ItemId, Item in pairs(State.Items) do
            local Object = Item.Instance
            if not DescendantMap[Object] then

                -- Clear state
                Changes[ItemId] = Support.Blank

                -- Clear ID
                IdMap[Object] = nil

                -- Clean up resources
                self.ItemMaid[ItemId] = nil

                -- Update parent child counter
                local ParentId = Item.Parent and self.IdMap[Item.Parent]
                local ParentState = self:GetStagedItemState(ParentId, Changes, State)
                if ParentState then
                    ParentState.Children[ItemId] = nil
                    ParentState.Unlocked[ItemId] = nil
                    ParentState.IsLocked = next(ParentState.Children) and not next(ParentState.Unlocked)
                    Changes[ParentId] = ParentState
                    self:PropagateLock(ParentState, Changes, State)
                end

            end
        end

        -- Update state
        return { Items = Support.MergeWithBlanks(State.Items, Changes) }
    end)

end

function Explorer:UpdateSelection(Items)
    local Selection = self.props.Core.Selection

    -- Queue changed items
    self:QueueUpdate('Selection', Items)

    -- Check if queue should be executed
    if not self:ShouldExecuteQueue('Selection') then
        return
    end

    -- Perform updates to state
    self:setState(function (State)
        local Changes = {}
        local Queue = self:GetUpdateQueue('Selection')
        for Items in pairs(Queue) do
            for _, Item in ipairs(Items) do
                local ItemId = self.IdMap[Item]
                if ItemId then
                    local ItemState = Support.CloneTable(State.Items[ItemId])
                    ItemState.Selected = Selection.IsSelected(Item)
                    Changes[ItemId] = ItemState
                end
            end
        end
        return { Items = Support.Merge(State.Items, Changes) }
    end)

end

function Explorer:BuildItemState(Item, Scope, Order, Changes, State)
    local Parent = Item.Parent
    local ParentId = self.IdMap[Parent]

    -- Check if indexable and visible in hierarchy
    local InHierarchy = ParentId or (Parent == Scope)
    if not (self.IsItemIndexable(Item) and InHierarchy) then
        return nil
    end

    -- Assign ID
    local ItemId = self.LastId + 1
    self.LastId = ItemId
    self.IdMap[Item] = ItemId

    -- Check if item is a part
    local IsPart = Item:IsA 'BasePart'

    -- Create maid for cleanup when item is removed
    local ItemMaid = Maid.new()
    self.ItemMaid[ItemId] = ItemMaid

    -- Prepare item state
    local ItemState = {
        Id = ItemId,
        Name = Item.Name,
        IsPart = IsPart,
        IsLocked = IsPart and Item.Locked or nil,
        Class = Item.ClassName,
        Parent = Parent,
        Children = {},
        Unlocked = {},
        Order = Order,
        Expanded = nil,
        Instance = Item,
        Selected = self.props.Core.Selection.IsSelected(Item) or nil
    }

    -- Register item state into changes
    Changes[ItemId] = ItemState

    -- Update parent children
    local ParentState = self:GetStagedItemState(ParentId, Changes, State)
    if ParentState then
        ParentState.Children[ItemId] = true
        Changes[ParentId] = ParentState
    end

    -- Update children
    for PendingChildId in pairs(self.PendingParent[Item] or {}) do
        local ChildState = self:GetStagedItemState(PendingChildId, Changes, State)
        if ChildState then
            ItemState.Children[PendingChildId] = true
            self:PropagateLock(ChildState, Changes, State)
        end
    end

    -- Propagate lock to ancestors
    self:PropagateLock(ItemState, Changes, State)

    -- Listen to name changes
    ItemMaid.Name = Item:GetPropertyChangedSignal('Name'):Connect(function ()

        -- Queue change
        self:QueueUpdate('Name', Item)

        -- Check if queue should be executed
        if not self:ShouldExecuteQueue('Name') then
            return
        end

        -- Perform updates to state
        self:setState(function (State)
            local Changes = {}
            local Queue = self:GetUpdateQueue('Name')
            for Item in pairs(Queue) do
                local ItemId = self.IdMap[Item]
                local ItemState = Support.CloneTable(State.Items[ItemId])
                ItemState.Name = Item.Name
                Changes[ItemId] = ItemState
            end
            return { Items = Support.Merge(State.Items, Changes) }
        end)

    end)

    -- Listen to parent changes
    ItemMaid.Parent = Item:GetPropertyChangedSignal('Parent'):Connect(function ()
        if not Item.Parent then
            return
        end

        -- Queue change
        self:QueueUpdate('Parent', Item)

        -- Check if queue should be executed
        if not self:ShouldExecuteQueue('Parent') then
            return
        end

        -- Perform updates to state
        self:setState(function (State)
            local Changes = {}
            local Queue = self:GetUpdateQueue('Parent')
            for Item in pairs(Queue) do
                self:UpdateItemParent(Item, Changes, State)
            end
            return { Items = Support.MergeWithBlanks(State.Items, Changes) }
        end)

        -- Update tree state
        self:UpdateTree()
    end)

    -- Attach part-specific listeners
    if IsPart then
        ItemMaid.Locked = Item:GetPropertyChangedSignal('Locked'):Connect(function ()

            -- Queue change
            self:QueueUpdate('Lock', Item)

            -- Check if queue should be executed
            if not self:ShouldExecuteQueue('Lock') then
                return
            end

            -- Perform updates to state
            self:setState(function (State)
                local Changes = {}
                local Queue = self:GetUpdateQueue('Lock')
                for Item in pairs(Queue) do
                    self:UpdateItemLock(Item, Changes, State)
                end
                return { Items = Support.MergeWithBlanks(State.Items, Changes) }
            end)

        end)
    end

    -- Indicate that item state was created
    return true

end

function Explorer:QueueUpdate(Type, Item)
    self.UpdateQueues[Type] = Support.Merge(self.UpdateQueues[Type] or {}, {
        [Item] = true
    })
end

function Explorer:GetUpdateQueue(Type)

    -- Get queue
    local Queue = self.UpdateQueues[Type]
    self.UpdateQueues[Type] = nil

    -- Return queued items
    return Queue

end

function Explorer:ShouldExecuteQueue(Type)
    local ShouldExecute = self.QueueTimers[Type] or Support.CreateConsecutiveCallDeferrer(0.025)
    self.QueueTimers[Type] = ShouldExecute

    -- Wait until state updatable
    if ShouldExecute() and self.Mounted then
        return true
    end
end

function Explorer:UpdateItemLock(Item, Changes, State)

    -- Get staged item state
    local ItemId = self.IdMap[Item]
    local ItemState = self:GetStagedItemState(ItemId, Changes, State)
    if not ItemState then
        return
    end

    -- Update state
    ItemState.IsLocked = Item.Locked
    Changes[ItemId] = ItemState

    -- Propagate lock state up hierarchy
    self:PropagateLock(ItemState, Changes, State)

end

function Explorer:GetStagedItemState(ItemId, Changes, State)

    -- Check if item staged yet
    local StagedItemState = ItemId and Changes[ItemId]

    -- Ensure item still exists
    if not ItemId or (StagedItemState == Support.Blank) or
        not (StagedItemState or State.Items[ItemId]) then
        return
    end

    -- Return staged item state
    return StagedItemState or Support.CloneTable(State.Items[ItemId])

end

function Explorer:UpdateItemParent(Item, Changes, State)

    -- Get staged item state
    local ItemId = self.IdMap[Item]
    local ItemState = self:GetStagedItemState(ItemId, Changes, State)
    if not ItemState then
        return
    end

    -- Get current parent ID
    local PreviousParentId = ItemState.Parent and self.IdMap[ItemState.Parent]

    -- Set new parent ID
    local Parent = Item.Parent
    local ParentId = self.IdMap[Parent]
    ItemState.Parent = Parent
    Changes[ItemId] = ItemState

    -- Queue parenting if parent item valid, but not yet registered
    if not ParentId and not (Parent == Scope) then
        if Parent:IsDescendantOf(Scope) then
            self.PendingParent[Parent] = Support.Merge(self.PendingParent[Parent] or {}, { [ItemId] = true })
        end
    end

    -- Update previous parent
    local PreviousParentState = self:GetStagedItemState(PreviousParentId, Changes, State)
    if PreviousParentState then
        PreviousParentState.Children[ItemId] = nil
        PreviousParentState.Unlocked[ItemId] = nil
        PreviousParentState.IsLocked = next(PreviousParentState.Children) and not next(PreviousParentState.Unlocked)
        Changes[PreviousParentId] = PreviousParentState
    end

    -- Update new parent
    local ParentState = self:GetStagedItemState(ParentId, Changes, State)
    if ParentState then
        ParentState.Children[ItemId] = true
        Changes[ParentId] = ParentState
        self:PropagateLock(ItemState, Changes, State)
    end

end

function Explorer:PropagateLock(ItemState, Changes, State)

    -- Continue if upward propagation is possible
    if not ItemState.Parent then
        return
    end

    -- Start propagation from changed item
    local ItemId = ItemState.Id
    local ItemState = ItemState

    -- Get item's parent state
    repeat
        local Parent = ItemState.Parent
        local ParentId = Parent and self.IdMap[Parent]
        local ParentState = self:GetStagedItemState(ParentId, Changes, State)
        if ParentState then

            -- Update parent lock state
            ParentState.Unlocked[ItemId] = (not ItemState.IsLocked) and true or nil
            ParentState.IsLocked = next(ParentState.Children) and not next(ParentState.Unlocked)
            Changes[ParentId] = ParentState

            -- Continue propagation upwards in the hierarchy
            ItemId = ParentId
            ItemState = ParentState

        -- Stop propagation if parent being removed
        else
            ItemId = nil
            ItemState = nil
        end


    -- Stop at highest reachable point in hierarchy
    until
        not ItemState
end

function Explorer:render()
    local props = self.props
    local state = self.state

    -- Display window
    return new(ImageLabel, {
        Active = true,
        Layout = 'List',
        LayoutDirection = 'Vertical',
        AnchorPoint = Vector2.new(1, 0),
        Position = UDim2.new(1, -100, 0.6, -380/2),
        Width = UDim.new(0, 145),
        Height = 'WRAP_CONTENT',
        Image = 'rbxassetid://2244248341',
        ScaleType = 'Slice',
        SliceCenter = Rect.new(4, 4, 12, 12),
        ImageTransparency = 1 - 0.93,
        ImageColor = '3B3B3B'
    },
    {
        -- Window header
        Header = new(TextLabel, {
            Text = '  EXPLORER',
            TextSize = 9,
            Height = UDim.new(0, 14),
            TextColor = 'FFFFFF',
            TextTransparency = 1 - 0.6/2
        },
        {
            CloseButton = new(ImageButton, {
                Image = 'rbxassetid://2244452978',
                ImageRectOffset = Vector2.new(0, 0),
                ImageRectSize = Vector2.new(14, 14) * 2,
                AspectRatio = 1,
                AnchorPoint = Vector2.new(1, 0.5),
                Position = UDim2.new(1, 0, 0.5, 0),
                ImageTransparency = 1 - 0.34,
                [Roact.Event.Activated] = props.Close
            })
        }),

        -- Scrollable item list
        ItemList = new(ItemList, {
            Scope = props.Scope,
            Items = state.Items,
            ScrollTo = state.ScrollTo,
            Core = props.Core,
            IdMap = self.IdMap,
            RowHeight = state.RowHeight,
            ToggleExpand = self.ToggleExpand
        })
    })
end

return Explorer
