local v=Roact.Component:extend("\073\116\101\109\076\105\115\116")v.defaultProps={MaxHeight=300}function v.init(s,v)s:setState({Min=0,Max=v.MaxHeight;CanvasPosition=Vector2.new()})s.UpdateBoundaries=function(v)if s.Mounted then s:setState({CanvasPosition=v.CanvasPosition,Min=v.CanvasPosition.Y-v.AbsoluteSize.Y;Max=v.CanvasPosition.Y+v.AbsoluteSize.Y})end end end function v.didMount(v)v.Mounted=true end function v.willUnmount(v)v.Mounted=false end function v.didUpdate(C,v,s)local S=C.props.ScrollTo and v.ScrollTo~=C.props.ScrollTo if v.Scope~=C.props.Scope and not S then C:setState({CanvasPosition=Vector2.new(0,0);Min=0,Max=C.props.MaxHeight})end end function v.render(v)local s=v.props local C=v.state local S=0 local F=0 local O local Z={}local N=0 local I=s.RowHeight local t=Support.Values(s.Items)table.sort(t,function(v,s)return v.Order<s.Order end)for I=1,#t,1 do local T=t[I]local f=T.Parent and s.IdMap[T.Parent]local n=f and s.Items[f]local i=true local u=0 if f then local v=n while v do if not v.Expanded then i=nil break else u=u+1 end local C=s.IdMap[v.Parent]v=s.Items[C]end end if T.Id==s.ScrollTo and v.ScrolledTo~=s.ScrollTo then local S=N*s.RowHeight if S<C.CanvasPosition.Y or S>C.CanvasPosition.Y+s.MaxHeight then O=Vector2.new(0,S)v.ScrolledTo=T.Id end end if i then N=N+1 local v=(N-1)*s.RowHeight if v<C.Min then S=S+1 i=nil elseif v>C.Max then F=F+1 i=nil end end Z[T.Id]=i and new(ItemRow,Support.Merge({},T,{Depth=u,Core=s.Core;ToggleExpand=s.ToggleExpand,Height=s.RowHeight}))end return new(ScrollingFrame,{Layout="\076\105\115\116";LayoutDirection="\086\101\114\116\105\099\097\108",CanvasSize=UDim2.new(1,-2,0,N*s.RowHeight);Size=UDim2.new(1,0,0,N*s.RowHeight),CanvasPosition=O or C.CanvasPosition;ScrollBarThickness=4,ScrollBarImageTransparency=.6,VerticalScrollBarInset="\083\099\114\111\108\108\066\097\114",[Roact.Change.CanvasPosition]=v.UpdateBoundaries,[Roact.Children]=Support.Merge(Z,{TopSpacer=new(Frame,{Size=UDim2.new(0,0,0,S*s.RowHeight),LayoutOrder=0}),BottomSpacer=new(Frame,{Size=UDim2.new(0,0,0,F*s.RowHeight),LayoutOrder=#t+1});SizeConstraint=new("\085\073\083\105\122\101\067\111\110\115\116\114\097\105\110\116",{MinSize=Vector2.new(0,20);MaxSize=Vector2.new(math.huge,s.MaxHeight)})})})end return v

local ItemList = Roact.Component:extend 'ItemList'
ItemList.defaultProps = {
    MaxHeight = 300
}

function ItemList:init(props)
    self:setState {
        Min = 0,
        Max = props.MaxHeight,
        CanvasPosition = Vector2.new()
    }

    -- Create callback for updating canvas boundaries
    self.UpdateBoundaries = function (rbx)
        if self.Mounted then
            self:setState {
                CanvasPosition = rbx.CanvasPosition,
                Min = rbx.CanvasPosition.Y - rbx.AbsoluteSize.Y,
                Max = rbx.CanvasPosition.Y + rbx.AbsoluteSize.Y
            }
        end
    end
end

function ItemList:didMount()
    self.Mounted = true
end

function ItemList:willUnmount()
    self.Mounted = false
end

function ItemList:didUpdate(previousProps, previousState)
    local IsScrollTargetSet = self.props.ScrollTo and
        (previousProps.ScrollTo ~= self.props.ScrollTo)

    -- Reset canvas position whenever scope updates (unless a scrolling target is set)
    if (previousProps.Scope ~= self.props.Scope) and (not IsScrollTargetSet) then
        self:setState({
            CanvasPosition = Vector2.new(0, 0);
            Min = 0;
            Max = self.props.MaxHeight;
        })
    end
end

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

    -- Keep track of how many items are out of view
    local SkippedAbove = 0
    local SkippedBelow = 0
    local TargetCanvasPosition

    -- Declare a button for each item
    local ItemList = {}
    local VisibleItemCount = 0
    local ItemHeight = props.RowHeight

    -- Go through each item in order
    local OrderedItems = Support.Values(props.Items)
    table.sort(OrderedItems, function (A, B)
        return A.Order < B.Order
    end)
    for i = 1, #OrderedItems do
        local Item = OrderedItems[i]

        -- Get item parent state
        local ParentId = Item.Parent and props.IdMap[Item.Parent]
        local ParentState = ParentId and props.Items[ParentId]

        -- Determine visibility and depth from ancestors
        local Visible = true
        local Depth = 0
        if ParentId then
            local ParentState = ParentState
            while ParentState do

                -- Stop if ancestor not visible
                if not ParentState.Expanded then
                    Visible = nil
                    break

                -- Count visible ancestors
                else
                    Depth = Depth + 1
                end

                -- Check next ancestor
                local ParentId = props.IdMap[ParentState.Parent]
                ParentState = props.Items[ParentId]

            end
        end

        -- Set canvas position to begin at item if requested and out-of-view
        if (Item.Id == props.ScrollTo) and (self.ScrolledTo ~= props.ScrollTo) then
            local ItemPosition = VisibleItemCount * props.RowHeight
            if ItemPosition < state.CanvasPosition.Y or
               ItemPosition > (state.CanvasPosition.Y + props.MaxHeight) then
                TargetCanvasPosition = Vector2.new(0, ItemPosition)
                self.ScrolledTo = Item.Id
            end
        end

        -- Calculate whether item is in view
        if Visible then
            VisibleItemCount = VisibleItemCount + 1
            local ItemTop = (VisibleItemCount - 1) * props.RowHeight
            if ItemTop < state.Min then
                SkippedAbove = SkippedAbove + 1
                Visible = nil
            elseif ItemTop > state.Max then
                SkippedBelow = SkippedBelow + 1
                Visible = nil
            end
        end

        -- Declare component for item
        ItemList[Item.Id] = Visible and new(ItemRow, Support.Merge({}, Item, {
            Depth = Depth,
            Core = props.Core,
            ToggleExpand = props.ToggleExpand,
            Height = props.RowHeight
        }))
    end

    return new(ScrollingFrame, {
        Layout = 'List',
        LayoutDirection = 'Vertical',
        CanvasSize = UDim2.new(1, -2, 0, VisibleItemCount * props.RowHeight),
        Size = UDim2.new(1, 0, 0, VisibleItemCount * props.RowHeight),
        CanvasPosition = TargetCanvasPosition or state.CanvasPosition,
        ScrollBarThickness = 4,
        ScrollBarImageTransparency = 0.6,
        VerticalScrollBarInset = 'ScrollBar',
        [Roact.Change.CanvasPosition] = self.UpdateBoundaries,
        [Roact.Children] = Support.Merge(ItemList, {
            TopSpacer = new(Frame, {
                Size = UDim2.new(0, 0, 0, SkippedAbove * props.RowHeight),
                LayoutOrder = 0
            }),
            BottomSpacer = new(Frame, {
                Size = UDim2.new(0, 0, 0, SkippedBelow * props.RowHeight),
                LayoutOrder = #OrderedItems + 1
            }),
            SizeConstraint = new('UISizeConstraint', {
                MinSize = Vector2.new(0, 20),
                MaxSize = Vector2.new(math.huge, props.MaxHeight)
            })
        })
    })
end

return ItemList
